diff --git a/WinPort/wxWinTranslations.cpp b/WinPort/wxWinTranslations.cpp index d5f4f25e3..2a15cbaa0 100644 --- a/WinPort/wxWinTranslations.cpp +++ b/WinPort/wxWinTranslations.cpp @@ -11,6 +11,10 @@ wxColour ConsoleForeground2wxColorInternal(USHORT attributes) b = (attributes & FOREGROUND_INTENSITY) ? 0xff : 0xa0; if (attributes & FOREGROUND_GREEN) g = (attributes & FOREGROUND_INTENSITY) ? 0xff : 0xa0; + if ((attributes & 0x0f) == FOREGROUND_INTENSITY) + r = g = b = 0x80; + if ((attributes & 0x0f) == (FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED)) + r = g = b = 0xc0; return wxColour(r, g, b); } @@ -18,11 +22,15 @@ wxColour ConsoleBackground2wxColorInternal(USHORT attributes) { wxColour::ChannelType r = 0, g = 0, b = 0; if (attributes & BACKGROUND_RED) - r = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0xa0; + r = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0x80; if (attributes & BACKGROUND_BLUE) - b = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0xa0; + b = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0x80; if (attributes & BACKGROUND_GREEN) - g = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0xa0; + g = (attributes & BACKGROUND_INTENSITY) ? 0xff : 0x80; + if ((attributes & 0xf0) == BACKGROUND_INTENSITY) + r = g = b = 0x80; + if ((attributes & 0xf0) == (BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED)) + r = g = b = 0xc0; return wxColour(r, g, b); } diff --git a/netbox/.gitignore b/netbox/.gitignore new file mode 100644 index 000000000..cbd11b997 --- /dev/null +++ b/netbox/.gitignore @@ -0,0 +1,58 @@ +*.log +build/ +/src/NetBox-build/ +build-CMakeLists.txt-* +/src/build-NetBox-* + +/Far2_x86/ +/Far2_x64/ +/Far3_x86/ +/Far3_x64/ +res.txt +#libs/ +libs/openssl/tmp32/ +libs/openssl/crypto/buildinf.h +libs/openssl/NUL +ipch/ +*.tmp +*.opensdf +*.sdf +*.suo +*.pdb +*.exp +*.dll +*.lib +*.obj +*.bak +*.vcxproj.filters +*.vcxproj.user +*.cache +*.zip +*.7z +*.tgz +*.orig + +*.vsp +*.psess +*.i +*.exe +*.res +*.rbj +*.idb +*.aps +*.idc +*.ctl + +tags +README.html +CMakeLists.txt.user + +*.cbp + +cov-int/ +/src/NetBox/My */ +/src/NetBox/.idea/ + +.vs/ +*.VC.db +*.VC.opendb diff --git a/netbox/ChangeLog b/netbox/ChangeLog new file mode 100644 index 000000000..761434a2f --- /dev/null +++ b/netbox/ChangeLog @@ -0,0 +1,923 @@ +NetBox 2.3.0.436 26.05.2016 +-------------------- + * Update WinSCP sources to 5.8.3 RC + +NetBox 2.2.2.435 24.05.2016 +-------------------- + * Update openssl sources to 1.0.2h + * Update WinSCP sources to 5.8.2 beta + +NetBox 2.2.1.433 05.03.2016 +-------------------- + * Update openssl sources to 1.0.2g + * Bugfix: Issue #193: Crash when trying to enter !^! pattern into local custom command + (https://github.com/michaellukashov/Far-NetBox/issues/193) + * Bugfix: Issue #194: Apply command does nothing when .. is selected + (https://github.com/michaellukashov/Far-NetBox/issues/194) + * Bugfix: Bookmarks are not preserved between FAR sessions + (https://github.com/michaellukashov/Far-NetBox/issues/174) + * SFTP: fix handle leaks + * Fix .lng files + (https://github.com/michaellukashov/Far-NetBox/issues/191) + +NetBox 2.2.0.427 14.02.2016 +-------------------- + * Update WinSCP sources to 5.8.1 beta + +NetBox 2.1.45.426 09.02.2016 +-------------------- + * Allow environment variables in "Private key file" field + (http://forum.farmanager.com/viewtopic.php?p=135588#p135588) + * WebDAV: fix AV in Win2k, WinXPSP2 + * Plugin is functional under Win2k + * Fix VS2015 build + * Bugfix: WebDAV: fix AV + (http://forum.farmanager.com/viewtopic.php?p=135432#p135432) + * Update openssl sources to 1.0.2f + * Fix Issue #175: Symlinks attributes dialog: show linked file name + (https://github.com/michaellukashov/Far-NetBox/issues/175) + * Bugfix: Bookmarks are not preserved between FAR sessions + (https://github.com/michaellukashov/Far-NetBox/issues/174) + * Bugfix: SFTP: remote copy file (Shift-F5) does not work + (http://forum.farmanager.com/viewtopic.php?p=133660#p133660) + * Update putty sources to 0.66 + * Bugfix: Cannot copy file from subst disk + (http://forum.ru-board.com/topic.cgi?forum=5&topic=31718&start=7880#9) + +NetBox 2.1.44.415 04.11.2015 +-------------------- + * Update WinSCP sources to 5.7.6 + * SFTP, SCP: Fix connection to remote server using ecdsa-sha2-nistp521 key exchange algorithm, used by default by dropbear_2015.67 + * Update Russian translation, thanks to DenVdmj + (https://github.com/michaellukashov/Far-NetBox/pull/169) + * SFTP, SCP: Fix remote command execution + (http://forum.ru-board.com:9000/topic.cgi?forum=5&topic=31718&start=7860#7) + * FTP: Fix codepage issues + (http://forum.farmanager.com/viewtopic.php?p=133226#p133226) + (http://bugs.farmanager.com/view.php?id=2516) + * Bugfix: Cannot create directory on remote server if file with the same name already exists + * Fix long path issues + * Bugfix: FTP: AV trying to download file + (http://forum.farmanager.com/viewtopic.php?p=132960#p132960) + * WebDAV, FTP: Fix Unicode-related issues + * Update minimal Far3 version to 3.0.4400 + * Bugfix: SFTP: cannot create symlink + (http://forum.farmanager.com/viewtopic.php?p=132109#p132109) + * Update putty sources + +NetBox 2.1.43.400 16.08.2015 +-------------------- + * Bugfix: Remote file was not saved after editing in external editor + * Update WinSCP sources to 5.7.5 + * Bugfix: Interface setting "Add commands to Plugins menu" was not working + (http://forum.farmanager.com/viewtopic.php?p=131695#p131695) + * Bugfix: SFTP: prevent AV when server unexpectedly closes connection + * Bugfix: AV when remote file is edited in external editor + (http://bugs.farmanager.com/view.php?id=2943) + * Update putty sources to 0.65 + * Bugfix: Message 2106 not found + (http://forum.farmanager.com/viewtopic.php?p=131198#p131198) + * Bugfix: AV trying to open SCP session + (http://forum.farmanager.com/viewtopic.php?p=131130#p131130) + * Bugfix: SCP, SFTP: verified host key is not stored + (http://forum.farmanager.com/viewtopic.php?p=131079#p131079) + * Bugfix: AV when connecting using tunnel + (http://bugs.farmanager.com/view.php?id=3018) + * Update openssl sources to 1.0.2d + +NetBox 2.1.42.385 17.06.2015 +-------------------- + * Update WinSCP sources to 5.7.4 + * Update putty sources to 0.64 + * Update openssl sources to 1.0.2c + * Update apr sources to 1.5.2 + +NetBox 2.1.41.373 28.03.2015 +-------------------- + * Bugfix: Issue #149: Plugin closes if root directory contains symlinks + (http://bugs.farmanager.com/view.php?id=2884) + (https://github.com/michaellukashov/Far-NetBox/issues/149) + * Update WinSCP sources to 5.7.1 + * Update openssl sources to 1.0.1m + * Bugfix: plugin prefixes do not work + (https://github.com/michaellukashov/Far-NetBox/issues/131) + +NetBox 2.1.40.363 23.11.2014 +-------------------- + * Bugfix: Issue #134: Plugin settings not stored + (https://github.com/michaellukashov/Far-NetBox/issues/134) + * Update WinSCP sources to 5.6.3 + * Bugfix: Mantis #0002864: AV trying to connect to WebDAV host + (http://bugs.farmanager.com/view.php?id=2864) + * Bugfix: Issue #130: Check symlink type + (https://github.com/michaellukashov/Far-NetBox/issues/130) + * Bugfix: Issue #132: FTP: date/time of remote files are incorrect + (https://github.com/michaellukashov/Far-NetBox/issues/132) + * Bugfix: Mantis #0002860: Exception occurred: Access violation (GetFilesW) + (http://bugs.farmanager.com/view.php?id=2860) + * Bugfix Mantis #0002859: percent sign in the filename is urlencoded + (http://bugs.farmanager.com/view.php?id=2859) + * Bugfix: plugin crash after autodisconnect + (http://forum.farmanager.com/viewtopic.php?p=125042#p125042) + * Bugfix: cannot save newly created file on remote server + (http://forum.farmanager.com/viewtopic.php?p=124914#p124914) + (http://bugs.farmanager.com/view.php?id=2852) + * Bugfix: SFTP: AV trying to copy files from server + (http://forum.farmanager.com/viewtopic.php?p=124692#p124692) + * Update openssl sources to 1.0.1j + +NetBox 2.1.39.331 11.10.2014 +-------------------- + * Bugfix: cannot edit remote file if filename has symbols not valid for Windows files + (http://forum.farmanager.com/viewtopic.php?p=123869#p123869) + * Bugfix: SCP, SFTP: cannot read/copy files if server uses KOI8-R filesystem encoding + (https://github.com/michaellukashov/Far-NetBox/issues/124) + * Bugfix: WebDAV: do not try to create existing directory on remote server + (http://forum.farmanager.com/viewtopic.php?p=122658#p122658) + * Bugfix: "NetBox commands" -> "Open in Putty": fix open session using Putty 0.63 + (http://forum.farmanager.com/viewtopic.php?p=122618#p122618) + * Bugfix: Issue with files with same name on different folders/servers with SSH/SCP + (http://forum.farmanager.com/viewtopic.php?p=122597#p122597) + (https://github.com/michaellukashov/Far-NetBox/issues/67) + * Improvement: "Apply command": do not clean previous console output + (http://forum.farmanager.com/viewtopic.php?p=121937#p121937) + * Bugfix: WebDAV: login/password asked twice + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=122049#p122049 + * Bugfix: remote command output is scrolled up if Far.exe is started with /w command line switch (Stretch to console window instead of console buffer) + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1680#p121335) + * Update openssl sources to 1.0.1i + * Bugfix: "Apply command": patterns substitution not working + (http://forum.farmanager.com/viewtopic.php?p=121023#p121023) + * Setting "Maximum number of retries" = 0 means unlimited retries + (http://forum.farmanager.com/viewtopic.php?p=121309#p121309) + +NetBox 2.1.38.317 03.08.2014 +-------------------- + * Update WinSCP sources to 5.6.1 + * Bugfix: Filter not working on uploading files on server + Copy only new/changed files not working + (http://forum.farmanager.com/viewtopic.php?p=120480#p120480) + * Bugfix: non-ASCII session name is corrupted + (http://forum.farmanager.com/viewtopic.php?p=120841#p120841) + * fix issue #111: FTP: High CPU load + (https://github.com/michaellukashov/Far-NetBox/issues/111) + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1616#p119451) + * fix issue #113: AV copying file using option "Transfer on background (add to queue)" + (https://github.com/michaellukashov/Far-NetBox/issues/113) + +NetBox 2.1.37.311 13.06.2014 +-------------------- + * Update WinSCP sources to 5.5.4 + * Update openssl sources to 1.0.1h + * Bugfix: plugin hangs sometimes + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=119294#p119294) + * Update apr sources to 1.5.1 + * Bugfix: SFTP: AV after Shutdown -> Sleep + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=118095#p118057) + * Bugfix: FTPS: Far crashes while attempting to connect using FTPS protocol + (http://forum.ru-board.com/topic.cgi?forum=5&topic=31718&start=4960#5) + * Bugfix Mantis #0002632: Moving/renaming (F6) any file on FTP (SFTP) doesn't work properly + (http://bugs.farmanager.com/view.php?id=2632) + +NetBox 2.1.36 23.03.2014 +-------------------- + * Bugfix: WebDAV: cannot upload files + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=117721) + * Update WinSCP sources to 5.5.2 + * fix Mantis #0002625: error while trying to upload file if option "Enable transfer resume/transfer to temporary filename" is set + (http://bugs.farmanager.com/view.php?id=2625) + * Bugfix: SCP: "Set atttributes" dialog: cannot set owner:group if option "Set attributes recursively" is not set + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1545#p117633) + * Bugfix: FTP: timeout while trying to resume upload + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=117590#p117575) + +NetBox 2.1.35 15.03.2014 +-------------------- + * fix Mantis #0002478: FTP: plugin hangs up if file is read-protected + (http://bugs.farmanager.com/view.php?id=2479) + * Bugfix: FTP: plugin hangs up if connection with server is lost while searching on server + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=117453#p117453) + * fix Mantis #0002478: SCP: cannot login if host uses nonstandard directory listing format + (http://bugs.farmanager.com/view.php?id=2478) + * improvement: "Connection failed" dialog: make "Reconnect" button default + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1530#p117083) + * fix Mantis #0002602: WebDAV: Processing of copy file don't break + (http://bugs.farmanager.com/view.php?id=2602) + * Bugfix: FTP: resume download did not work + (http://bugs.farmanager.com/view.php?id=2576) + (http://bugs.farmanager.com/view.php?id=2493) + * fix issue #57: "Could not retrieve directory listing" error when attempting to access FTP + (https://github.com/michaellukashov/Far-NetBox/issues/57) + +NetBox 2.1.34 25.02.2014 +-------------------- + * fix Mantis #0002426: Crash on Access Violation in NetBox.dll, SetDirectoryW + (http://bugs.farmanager.com/view.php?id=2426) + * fix issue #102: AV when trying to syncronize remote and local folders + (https://github.com/michaellukashov/Far-NetBox/issues/102) + * fix Mantis #0002598: SFTP: host key is not cached when SSH tunnel is used + (http://bugs.farmanager.com/view.php?id=2598) + * fix Mantis #0002599: SFTP: AV when long operation is canceled + (http://bugs.farmanager.com/view.php?id=2599) + * fix issue #97: FTP: AV if connection is lost + (https://github.com/michaellukashov/Far-NetBox/issues/97) + * fix lock file after error + (https://github.com/michaellukashov/Far-NetBox/pull/92) + * fix crash in SetDirectory after 'lost connection' + (https://github.com/michaellukashov/Far-NetBox/pull/91) + * fix issue #46: numenter + (https://github.com/michaellukashov/Far-NetBox/pull/90) + +NetBox 2.1.33 02.02.2014 +-------------------- + * Update WinSCP sources to 5.5.1 + * Update openssl sources to 1.0.1f + * Update apr sources to 1.5.0 + * Update tinyxml2 sources to 1.0.14 + * Fix not using LIST -A for UseMlsd=Auto and non-MLSD-supporting servers + (https://github.com/michaellukashov/Far-NetBox/pull/88) + * Fix incorrect dialog sizes when using Far /w switch + (https://github.com/michaellukashov/Far-NetBox/pull/85) + * SessionData: Fix parsing URL paths + (https://github.com/michaellukashov/Far-NetBox/pull/83) + * Bugfix: #79 (Stack Overflow), M0002526 (Crash on login to SSH runing on Debian 7.1 x64) + (https://github.com/michaellukashov/Far-NetBox/issues/79) + (http://bugs.farmanager.com/view.php?id=2526) + +NetBox 2.1.32 04.11.2013 +-------------------- + * Update WinSCP sources to 5.2.5 + * Bugfix: Whenever I try to move (F6) a file from the local HDD to a SFTP server, the file get copied, but the local copy is not deleted. + (http://forum.farmanager.com/viewtopic.php?f=39&t=6638&p=113770#p113770) + +NetBox 2.1.31 31.08.2013 +-------------------- + * Update neon sources to 0.30.0 + * Update WinSCP sources to 5.2.3 + * Update apr sources to 1.4.8 + * Bugfix: Fix FTP proxy settings. + * Mantis #2379: + - Ftp: Add options "Dup FF in commands", "UnDup FF from PWD" + - Alt-Shift-Ins copies full paths, in format: protocol://username:@host:port/path + (http://bugs.farmanager.com/view.php?id=2379) + * Bugfix: Fix button caption in "Confirm overwrite" dialog. + (http://forum.farmanager.com/viewtopic.php?p=110769#p110769) + * Bugfix: Crash on Access Violation in NetBox.dll, SetDirectoryW + (http://bugs.farmanager.com/view.php?id=2426) + * Bugfix: WebDAV: Downloaded files are locked if session is interrupted. + (http://forum.farmanager.com/viewtopic.php?p=110763#p110763) + * Bugfix: AV when option "New and updated file(s) only" is set. + (http://forum.farmanager.com/viewtopic.php?p=110798#p110798) + * Bugfix: "Transfer settings" --> "File mask" not working. + (http://forum.farmanager.com/viewtopic.php?p=110620#p110620) + * Bugfix: List items have incorrect values. + (http://forum.farmanager.com/viewtopic.php?p=110634#p110634) + +NetBox 2.1.30 31.05.2013 +-------------------- + * Bugfix: Reopening remote file in editor wasn't working as expected. + (http://forum.farmanager.com/viewtopic.php?p=110599#p110599) + * Bugfix: Plugin displays summary size of files incorrectly. + (http://forum.farmanager.com/viewtopic.php?p=110385#p110385) + * Bugfix: NetBox commands --> Synchronize fails. + (http://forum.farmanager.com/viewtopic.php?p=110520#p110520) + * Performance improvements. + * Bugfix: Password prompt dialog is showing password. + (http://forum.farmanager.com/viewtopic.php?p=109881#p109881) + * Bugfix: Two instances of plugin share the same panel modes. + (http://forum.farmanager.com/viewtopic.php?p=109737#p109737) + +NetBox 2.1.29 12.05.2013 +-------------------- + * Upgrade WinSCP sources to 5.2.1 + * Bugfix: Issue #58: SCP session to FreeBSD server doesn't work + (https://github.com/michaellukashov/Far-NetBox/issues/58) + * Improvement: Store directory change if option "Remember last used directory" is set. + (http://forum.farmanager.com/viewtopic.php?p=108861#p108861) + * Bugfix: WebDAV: Do not remove local file if file moving was canceled. + (http://forum.farmanager.com/viewtopic.php?p=109224#p109224) + * zlib core upgraded to 1.2.8 + * Bugfix: SFTP: Folder is created in root directory, not in current directory + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=108627#p108585) + * Bugfix: AV if transferring file in background mode + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1065#p108302) + * Far3: Adapt to Far 3.0 build 3331 + * M0002146: Use privilege elevation API if possible + (http://bugs.farmanager.com/view.php?id=2146) + +NetBox 2.1.28 07.04.2013 +-------------------- + * Bugfix: Issue #56: AV when password query is cancelled + (https://github.com/michaellukashov/Far-NetBox/issues/56) + * Bugfix: Far3: Certificate fingerprint was not stored + (http://forum.farmanager.com/viewtopic.php?f=9&t=7866&p=107299#p107299) + * Issue #42 Set empty anonymous password + (https://github.com/michaellukashov/Far-NetBox/issues/42) + * Bugfix: AV when searching files on server + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=107082#p107082) + * Bugfix: Store changes if configuration dialog is called from Plugins configuration + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=106645#p106645) + * Improvement: When editing session, parse username, password and directory, if any + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=975#p106004) + * Improvement: Do not highlight sessions in sessions list + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=990#p106051) + * Bugfix: AV when macro is executed on plugin panel + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=1005#p106185) + * Bugfix: Far3: Cannot rename session if new name contains backslash + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=106157#p106097) + +NetBox 2.1.27 16.03.2013 +-------------------- + * Bugfix: x64: Plugin hangs when trying to delete session + * Improvement: Fix speed issues + * WebDAV: Fix copying large (>4GB) files from/to WebDAV server + +NetBox 2.1.26 22.02.2013 +-------------------- + * Bugfix: WebDAV: Fix connection to Apache/WebDAV server + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=960#p104005) + * WinSCP core upgraded to 5.1.4 + * Bugfix: Wrong protocol type when old .netbox session file is imported + * Bugfix: Issue #34: Problem with editing 2 files with same name over scp + (https://github.com/michaellukashov/Far-NetBox/issues/34) + * Fix minor bugs and memory leaks + * Upgrade openssl to 1.0.1e + +NetBox 2.1.25 10.02.2013 +-------------------- + * Bugfix: Issue #18: FTP: Check type of symlinks + (https://github.com/michaellukashov/Far-NetBox/issues/18#issuecomment-13284640) + * Bugfix: Issue #52: AV when calling plugin via shortcut + (https://github.com/michaellukashov/Far-NetBox/issues/52) + * Upgrade openssl to 1.0.1d + * Bugfix: Issue #51: Uploaded directories are not created with WebDAV + (https://github.com/michaellukashov/Far-NetBox/issues/51) + +NetBox 2.1.24 03.02.2013 +-------------------- + * Improvement: Added command-line parameters -codepage= -username= -password= + Example: + netbox:https://farmanager.com:443/svn/trunk/unicode_far -codepage=65001 -username="anonymous" -password="nopass" + * Bugfix: Protocol settings were not restored correctly + * Far3: Bugfix: List index out of bounds if bookmark is set + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=103143#p103143 + +NetBox 2.1.23 30.01.2013 +-------------------- + * Bugfix: Issue #48: Cannot connect using SCP protocol + (https://github.com/michaellukashov/Far-NetBox/issues/48) + * Bugfix: Issue #49: SSH: "[] Lookup user groups" "[] Enable compression" options was not stored + (https://github.com/michaellukashov/Far-NetBox/issues/49) + +NetBox 2.1.22 27.01.2013 +-------------------- + * Minor bugfixes + * Bug 967: Issuer and Subject swapped on Certificate verification prompt + (http://winscp.net/tracker/show_bug.cgi?id=967) + * Bug 965: Text mode transfers should not be resumed FTP protocol + (http://winscp.net/tracker/show_bug.cgi?id=965) + * Create distributive using InnoSetup 5.5.2 + +NetBox 2.1.21 20.01.2013 +-------------------- + * FTP: By default, treat symlink as file, not a directory + (https://github.com/michaellukashov/Far-NetBox/issues/18#issuecomment-12432092) + * WinSCP core upgraded to 5.1.3 + * Fix some memleaks + * Bugfix: Replace filename chars not valid on windows + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=101701#p101663) + * Bugfix: Cannot use .ppk authentication if path contains non-ASCII characters + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=101701#p101568) + +NetBox 2.1.20 22.12.2012 +-------------------- + * Minor bugfixes + * Bugfix: Netbox Session import/export doesn't restore default values + * WinSCP core upgraded to 5.1.2 + +NetBox 2.1.19 06.12.2012 +-------------------- + * Bugfix: SFTP: cannot copy files to remote server + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=100597#p100515) + * "Edit Session" dialog: Reworked tabs + +NetBox 2.1.18 18.11.2012 +-------------------- + * Fix timestamp issues + (http://forum.farmanager.com/viewtopic.php?f=3&t=7491&start=60#p100002) + * Far3: Adapt to Far 3.0 build 2927 + * Improvement: use tweaked VS2008 MFC sources to reduce plugin size + * WinSCP core upgraded to 5.1.1 + * Fix openssl compilation + +NetBox 2.1.17 03.11.2012 +-------------------- + * Far3: Check OpenShortcutInfo.Flags when plugin is opened from Folders History + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=99392#p99339) + * Fix memory leaks + * Bugfix: Syncronize: wrong 'changed' time in Synchronization checklist + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=99119#p99119) + +NetBox 2.1.16 20.10.2012 +-------------------- + * Bugfix: Bug 927: Use of MLSD FTP command is configurable + (http://winscp.net/tracker/show_bug.cgi?id=927) + * Bugfix: Bug 926: Error retrieving file stats using full path with FTP protocol. + (http://winscp.net/tracker/show_bug.cgi?id=926) + * Bugfix: Bug 923: Treat timestamps in FTP MLSD/MLST results as UTC + (http://winscp.net/tracker/show_bug.cgi?id=923) + * Bugfix: Bug 922: Failure when FTP server returns "parent" directory is MLSD directory listing + (http://winscp.net/tracker/show_bug.cgi?id=922) + * Bugfix: Close file handle if error occurs + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=99024#p99024) + * Bugfix: "Netbox commands" - "Open directory" dialog: fix shortcuts + * Bugfix: "Netbox commands" - "Syncronize" was not working + * Bugfix: Issue #41: Cannot change directory when option "[x] Cache visited remote directories" is on + +NetBox 2.1.15 07.10.2012 +-------------------- + * Fixed x64 issues + * Bugfix: Issue #39: Empty listing for directory instead of permission denied + * Bugfix: Issue #40: Access violation trying to change directory + * Bugfix: Fix dialogs initialization in x64 version + +NetBox 2.1.14 01.10.2012 +-------------------- + * Bugfix: Properly align text in 'Confirmation' dialog + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=98365#p98365) + * Minor bugfixes + * Far3: Adapt to Far 3.0 build 2852 + * WinSCP core upgraded to 5.1.0 + +NetBox 2.1.13 16.09.2012 +-------------------- + * Bugfix: Issue #10: Check buttons width when calculating dialog size + (https://github.com/michaellukashov/Far-NetBox/issues/10) + * Bugfix: Issue #27: Server Updated Key dialog box appearance + (https://github.com/michaellukashov/Far-NetBox/issues/27) + * Bugfix: Issue #37: Fix date format parsing + (https://github.com/michaellukashov/Far-NetBox/issues/37) + * Minor bugfix in "Edit session" dialog + +NetBox 2.1.12 06.09.2012 +-------------------- + * WinSCP core upgraded to 5.0.9 RC + * Bugfix: FTP: Incorrect directory size is shown + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97787#p97787) + * Bugfix: issue #34: Problem with editing 2 files with same name over scp + (https://github.com/michaellukashov/Far-NetBox/issues/34) + * FTP: Add new types of proxy + * Bugfix: FTP: Error connecting over SOCKS proxy with FTP protocol + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97755#p97743, + http://winscp.net/tracker/show_bug.cgi?id=907) + +NetBox 2.1.11 03.09.2012 +-------------------- + * Minor changes in "Edit session" dialog + * WebDAV: try to open session 5 times before throwing exception + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=780#p97663) + * Bugfix: issue #34: Problem with editing 2 files with same name over scp + (https://github.com/michaellukashov/Far-NetBox/issues/34, + http://forum.farmanager.com/viewtopic.php?f=39&p=97665#p97665) + * FTP: Bugfix: Error retrieving file stats, if FTP server returns full file path in MLST response + (http://winscp.net/tracker/show_bug.cgi?id=906) + * FTP: Bugfix: Error when listing file with MLST FTP command fails + (http://winscp.net/tracker/show_bug.cgi?id=905) + +NetBox 2.1.10 27.08.2012 +-------------------- + * Bugfix: WebDAV: SOCKS5 proxy did not work + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97556#p97556) + * SCP, SFTP: add session option "Optimize connection buffer size" + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97556#p97534) + * Bugfix: Plugin configuration: remove option "Disks menu hotkey: Autoassign, Manual" + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97556#p97531) + * Bugfix: "Copy file" dialog: minimize file name + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97556#p97531) + * Bugfix: Issue #33: cursor position not restored + * FTP: Trigger password prompt by the 331 FTP response + (http://winscp.net/tracker/show_bug.cgi?id=653) + +NetBox 2.1.9 20.08.2012 +-------------------- + * Bugfix: export session did not work + * Bugfix: Ctrl-Ins Shift-Ins Ctrl-F work incorrect + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=600#p93306, + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=97522#p97522, + http://forum.farmanager.com/viewtopic.php?t=6317&p=97524#p97524) + * Far3: use privilege elevation, if available + * WebDAV: fix speed issues + +NetBox 2.1.8 15.08.2012 +-------------------- + * Bugfix: WebDAV: do not show password in Password dialog + (https://github.com/michaellukashov/Far-NetBox/issues/21#issuecomment-7600548) + * Bugfix: WebDAV: cannot redirect from HTTP to HTTPS + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=735#p97473) + * Bugfix: WebDAV: cannot copy from server to local directory with non-ASCII name + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=735#p97463) + * Bugfix: Issue #31: [DAV] fails to re-connect if plug-in was closed while visiting a folder with space in its name + * Bugfix: Far3: cannot verify server's rsa2 key fingerprint + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=720#p97354) + +NetBox 2.1.7 04.08.2012 +-------------------- + * WebDAV: rewrite WebDAV protocol support using neon + expat + apr + neon 0.29.6: http://www.webdav.org/neon + expat 2.1.0: http://www.libexpat.org + apr 1.4.6: http://apr.apache.org + * WinSCP core upgraded to 5.0.8 beta + * various minor bugfixes + +NetBox 2.1.6 18.07.2012 +-------------------- + * Bugfix: Far3: SCP: cannot store edited file + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=705#p96759) + * zlib core upgraded to 1.2.7 + +NetBox 2.1.5 09.07.2012 +--------------------- + * Bugfix: Far3: error "Synchronization is supported only against regular panel." + occurs when option "Synchronize browsing" is on. + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=96655#p96441) + * Bugfix: Issue #30 (Far crash when "Confirmation: ... already loaded. + How to open this file?" and you choose "Current" on ssh) + See also http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=96655#p96654 + * Bugfix: expand environment variables before executing Pageant or Puttygen + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=96655#p96655) + +NetBox 2.1.4 20.06.2012 +--------------------- + * Add new type of proxy: "System sesttings". Read actual proxy configuration from IE system settings. + * Far3: bugfix: plugin now works in WinPE environment (tested Hiren Boot CD, MiniPE, WinPE uVS) + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=675#p96040) + * Bugfix: information window didn't show up when connecting to remote server + +NetBox 2.1.3 11.06.2012 +--------------------- + * SCP: bugfix: AV when trying to edit file on remote host + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=690#p96166) + * Far3: bugfix: cannot create session with non-ASCII symbols (Issue #22) + * WebDAV: bugfix: plugin hangs when creating new session + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=690#p96120) + +NetBox 2.1.2 19.05.2012 +--------------------- + * Far2: bugfix: AV when trying to copy from plugin panel to protected local directory + * SFTP: bugfix: error when trying to execute command on remote host + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=95922#p95890) + * various bugfixes + +NetBox 2.1.1 16.05.2012 +--------------------- + * WebDAV: Bugfix: cannot create directory + * Issue #22: Incorrect initialization of sessions with non-ASCII names + * SFTP, SCP: Bugfix: do not truncate user password if it's longer than 100 bytes + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=660#p95847) + * FTP: Use CodePage setting to setup UTF8 mode + * SFTP: Bugfix: error when trying to edit remote file + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=660#p95836) + * WinSCP core upgraded to 5.0.7 + +NetBox 2.1.0 15.05.2012 +--------------------- + * WebDAV: bugfix: files not copied when trying to copy directory tree to remote server + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=645#p94910) + * libssh2 upgraded to 1.4.1 + * libcurl upgraded to 7.25.0 + * openssl upgraded to 1.0.1c + * WinSCP core upgraded to 5.0.6 + +NetBox 2.0.19 23.04.2012 +--------------------- + * WebDAV: bugfixes + * Bugfix: Ask for user password if current password is empty or not accepted + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=645#p94910) + +NetBox 2.0.18 08.04.2012 +--------------------- + * Bugfix: Cannot edit file on another plugin panel, if two plugin panels opened + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=615#p93944) + * FTP: Ask for user password only if the server asks + * FTP: Use MLSD/MLST to list file in directory with FTP protocol + - When copying files from/to server, seconds are preserved + - Use MLST to get file properties + +NetBox 2.0.17 27.03.2012 +--------------------- + * FTP(S): Problem with space in file/directory names in FTPS session (Issue #11) + * Far3: Restore history in edit dialog items + * Far3: Adapt to Far 3.0 build 2574 + +NetBox 2.0.16 24.03.2012 +--------------------- + * Improvement: open session by pressing Enter on .netbox file + * Far3: Bugfix: NumEnter opens .netbox file instead of opening session + * Far3: adapted to Far 3.0 build 2551: + - fixed confirmations before delete file or session + - fixed open session when plugin is called from Folders history + * FTP: Use MLSD/MLST to list file in directory with FTP protocol + (http://winscp.net/tracker/show_bug.cgi?id=568) + +NetBox 2.0.15 17.03.2012 +--------------------- + * Far3: Bugfix: Cannot open session when plugin is called from Folders history + * Bugfix: Folder shortcuts to plugin sessions did not work + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=93033#p93033) + * Improvement: Show progress indication on taskbar under Win7 if background transfer is active + * Bugfix: Far3: Cannot run command on remote server (Issue #9) + * Bugfix: FTPS: Failure on SSL session resuming (http://winscp.net/tracker/show_bug.cgi?id=668). + Some FTPS servers, e.g. vsftpd, require reuse of SSL session for FTPS data connections + (see require_ssl_reuse option in vsftpd.conf man page). + Add new FTPS session option "Session ID re-use". + +NetBox 2.0.14 09.03.2012 +--------------------- + * FTP: Query reopen connection if error occurs in background transfer + * SFTP: Add button "Resume" in "Confirm overwrite" dialog + (http://forum.farmanager.com/viewtopic.php?f=39&t=6638#p90521) + * Rename option "Automatically reconnect session, if it breaks during transfer" --> "Reconnect after [ ] seconds"; + add option "Maximum number of retries" (F11 --> NetBox commands --> Configure --> NetBox --> Endurance settings --> Maximum number of retries: [ ]) + (http://forum.farmanager.com/viewtopic.php?f=39&p=92250#p92214) + * Bugfix: Session options --> Proxy --> Proxy type --> Telnet causes crash + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=92140#p92140) + * Various minor bugfixes + +NetBox 2.0.13 03.03.2012 +--------------------- + * SFTP: AV if a connection breaks; AV after reconnect + (http://forum.farmanager.com/viewtopic.php?f=39&t=6638&p=91993#p91993) + * FTP: Fixed bug causing a "string too long" error + * FTPS: Added option "Explicit FTP over TLS" (issue #7) + * SCP: Bugfix: Ctrl-G ("Apply command") did not work; fixed scroll terminal screen + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=570#p91648) + +NetBox 2.0.12 26.02.2012 +--------------------- + * FTP: Bugfix: cannot set session option "Keepalives: Off" + * Far3: Bugfix: Editor/viewer settings: "[x] Allow more than one edited file", "[x] Upload after every save" + did not work + * Various minor bugfixes + * Session properties: It is now possible to select charset encoding for filenames + (Session --> Environment --> Charset encoding for filenames) + Use local charset as default codepage. + +NetBox 2.0.11 21.02.2012 +--------------------- + * Bugfix: FTPS is not listed in list of protocols + * WinSCP core upgraded to 4.3.7 + +NetBox 2.0.10 17.02.2012 +--------------------- + * Far3: adapted to Far 3.0.2462 + * FTP: Session options --> Connection --> Server response timeout: 0 means no timeout + * Interface settings: Enable option "Use custom settings for "Detailed" panel mode" + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=540#p90652) + * Session properies: Enable autocorrect port number + * Bugfix: Set default port number when plugin is called from command-line + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=525#p90619) + * Various minor bugfixes + * libssh2 core upgraded to 1.4.0 + * zlib core upgraded to 1.2.6 + +NetBox 2.0.9 11.02.2012 +--------------------- + * FTPS: Allow to configure type of connection: explicit/implicit + (Session options --> FTP(S) --> Encryption: Require explicit/implicit FTP) + * Bugfix: FTP: Option "Seconds between keepalives" did not change + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=525#p90266) + * Bugfix: SFTP: resume transfer did not work + (http://forum.farmanager.com/viewtopic.php?f=39&t=6638#p90373) + * Get rid of error "View from find dialog is not supported" when "Find file" dialog is called. + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=510#p90041) + +NetBox 2.0.8 06.02.2012 +--------------------- + * WebDAV: Fixed progress indication + * Fixed a number of x64-related issues + * Bugfix: Fixed change directory on remote server when option "Cache directory changes" is set + * Bugfix: Do not set "C:\" as current directory when plugin panel is closed + * Far3: Fixed hotkeys handling + * CURL upgraded to version 7.24.0 + * SCP: Fixed UTF-8 file names encoding/decoding; allow to change option "UTF-8 encoding for filenames" + * Bugfix: Dialog "Change file attributes" did not work + +NetBox 2.0.7 30.01.2012 +--------------------- + * FAR3: Fixed incompatibility with Far3bis and future Far3 versions + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=89491&sid=b111a60c6c3ab17656fb03eba951cdf9#p89457) + * Session properties: UTF-8 encoding for filenames is "On" by default + * SSL core upgraded to OpenSSL 1.0.0g + * Bugfix: command-line prefix "netbox:" was not handled properly + * Update documentation + * Bugfix: Queue dialog: crash when "Suspend" is pressed + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=89460#p89460) + * Sessions list: Do not set custom panel mode; Also disable option "Use custom settings for "Detailed" panel mode" + * Bugfix: Sessions list: F3 on session name in subfolder did not work + * Bugfix: Remove file attributes check before copying to remote server + * Bugfix: FAR3: AV when trying to connect using command line + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=89214#p89181) + * Bugfix: AV when trying to show background operations list + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=450#p89148) + * Bugfix: FTPS: certificate info was not verified + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=435#p88880) + +NetBox 2.0.6 21.01.2012 +--------------------- + * Bugfix: FTPS: Far crashes when attempting to connect to FTPS server + * Session properties: Add new option "Login type: Anonymous/Normal" + * Bugfix: AppVerifier reported error: "Critical section is already initialized". + * Bugfix: FTP: error while trying to remove directory from root directory + * Bugfix: dialog "Reconnect" did not work properly + * Bugfix: option "Synchronize browsing" did not work properly + * Bugfix: option "Transfer on background" did not work properly + +NetBox 2.0.5 14.01.2012 +--------------------- + * Ported to Far 3.0.2379 + * Implemented import/export session from/to .xml file + * Added command "Import sessions" (F11 --> NetBox Commands --> Import Sessions) + * SSL core upgraded to OpenSSL 1.0.0f + +NetBox 2.0.4 04.01.2012 +--------------------- + * SFTP: Add session options "Min packet size", "Max packet size" + * FTP: Add session option "Allow empty password" + (http://forum.farmanager.com/viewtopic.php?f=5&t=6317&p=87981#p87238) + To use this, you need to create FTP session, say "username@server"; + edit session properties; + go to "FTP" tab; + check "Allow empty password" option + * Fix a number of x64-related issues + * Update WinSCP sources to version 4.3.6 + * Update openssl sources to version 1.0.0e + * Update tinyxml sources to version 2.6.2 + +NetBox 2.0.3 26.12.2011 +--------------------- + * Bugfix: Ошибка подключения по протоколу SCP, воспроизводилось для 64-битной версии плагина + * Bugfix: SFTP: Исправлено отображение имен файлов, содержащих не-ASCII символы + * Bugfix: Ошибка подключения по протоколу SFTP, воспроизводилось на некоторых версиях SFTP серверов + +NetBox 2.0.2 11.12.2011 +--------------------- + * Bugfix: Ошибка при создании новой сессии + * В диалоге создания сессии секция Protocol поставлена выше секции Session + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=360#p87030 + +NetBox 2.0.1 11.12.2011 +--------------------- + * Bugfix: SFTP: Исправлена ошибка подключения к некоторым версиям SSH серверов + * Bugfix: SFTP: Исправлены утечки памяти при копировании на сервер + * Bugfix: AV при попытке переименования сессии + * Bugfix: Если на панели плагина выделено несколько файлов, удаляется только первый выделенный файл + * Bugfix: SFTP: При попытке выполнить команду на удаленном сервере - Access Violation + * Список сессий: После создания каталога фокус перемещается на вновь созданный каталог + * Bugfix: выделили обе папки, пытаемся удалить, фар падает. + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=345#p86968 + +NetBox 2.0.0 01.12.2011 +--------------------- + * Плагин полностью переписан на основе кода WinSCP + WinSCP: SFTP/FTP/SCP client for FAR version 1.6.2 Copyright (c) 2000-2009 Martin Prikryl + Based on WinSCP version 4.1.9.0 Copyright (c) 2000-2009 Martin Prikryl + SSH and SCP code based on PuTTY 0.61 Copyright (c) 1997-2011 Simon Tatham + FTP code based on FileZilla 2.2.32 Copyright (c) 2001-2007 Tim Kosse + +NetBox 1.19 12.08.2011 +--------------------- + * WebDAV: Исправлены найденные ошибки создания/удаления файлов и каталогов + Исправлено создание/удаление файлов и каталогов содержащих символ # + * Настройки -> Основные настройки: Поле ввода "Каталог сохранённых сессий" + может содержать имена переменных окружения, заключённые в %. Переменные будут заменены их значениями. + * Fixed: ShiftF6 до сих пор ведёт себя не так, как в файловых панелях -- отображается путь без имени файла, а не само имя. + http://forum.farmanager.com/viewtopic.php?t=6317&p=84282#p84282 + +NetBox 1.18 09.08.2011 +--------------------- + * Fixed: ShiftF6 ведёт себя немного не так, как в файловых панелях. Делает вид, что хочет скопировать на противоположную панель, а не переименовать. + * Fixed: F6 на хостинге себя ведёт как ShiftF6, поэтому сделать F6 с хостинга не получается, приходится делать F5, а потом F8. + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=285#p83819 + * При сохранении сессии с хостом вида: ftp://user:pass@host.com/start/dir + парсить его и сохранять имя пользователя и пароль в своих полях + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=285#p83769 + Если имя сессии не указано, именем сессии становится имя сервера + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&sid=fa84db92de59c9e9c268aa5bf54fbfda&start=15#p79275 + * Пофиксена обработка отмены длительных операций + * FTP: Если невозможно определить тип симлинка - не показываем симлинк + +NetBox 1.17 29.07.2011 +--------------------- + * SFTP: Сделано корректное отображение симлинков на каталоги + * SFTP: Сделано подробное логирование при уровне логирования Debug 2 + * Добавлен протокол FTPS + +NetBox 1.16 27.07.2011 +--------------------- + * WebDAV: исправлена обработка ответа сервера 301 Moved Permanently + * Сделано подробное логирование работы с WebDAV сервером при уровне логирования Debug 2 + * В настройках плагина, если зайти в любой раздел (Main, Proxy, Logging), а потом выйти из него, то попадаем к списку плагинов, хотя более ожидаемо попасть снова к списку разделов (как это сделано в плагинах AltHistory, Background copy, EdtFind, MultiArc). + http://forum.farmanager.com/viewtopic.php?f=5&t=6317&start=270#p83571 + +NetBox 1.15 20.07.2011 +--------------------- + * Сделана настройка прокси для каждой сессии + * FTP: исправлено удаление папок с '#' в имени + +NetBox 1.14 10.07.2011 +--------------------- + * Добавлена настройка прокси + * Добавлено логирование + +NetBox 1.13 03-07-2011 +--------------------- + * SFTP: Пофиксен баг: невозможно прервать коннекшн по Esc на стадии "establish connection". + * x64: В архив плагина добавлены libeay32.dll ssleay32.dll + +NetBox 1.12 19-05-2011 +--------------------- + * Пофиксен баг: Не обрабатывались имя пользователя/пароль при вызове через префиксы + * Пофиксен баг: Отвалилась обработка префикса netbox + * FTP: Пофиксен баг: Обработка символа # в путях + +NetBox 1.11 10-05-2011 +--------------------- + + FTP/WebDAV: Возможность отменить долговременную операцию установки соединения по Esc + * FTP/WebDAV: Пофиксен баг: Операции получения/отправки больших файлов завершались неудачно (таймаут) + * WebDAV: Работа с серверами, не поддерживающими запросы всех аттрибутов (alwaysdata.com etc) + * Пофиксен баг: Редактирование существующей сессии с одновременным изменением имени приводило к созданию новой ссылки + * Пофиксен баг: В настройках плагина путь к папке сессий не удалялся + * Пофиксен баг: Неправильное отображение кодовой страницы в настройках соединения + +NetBox 1.10 06-05-2011 +--------------------- + + Хранение сессий в виде файлов + + Папки с набором сессий + * FTP/WebDAV: Пофиксен баг: Плагин падал при копировании файлов нулевой длины (деление на 0) + * FTP: Робкая попытка поддержать ссылки + * FTP: Пофиксен баг: Неправильно считался общий размер файлов в директории (учитывался размер каталогов) + * FTP: Пофиксен баг: Неправильно указывался год некоторых файлов + * Большой багфиксинг 1.9 + +NetBox 1.9 08-04-2011 +--------------------- + + Обработка префиксов ftp/sftp/http/https + + Возможность указания ключа шифрования для хранимых паролей + + Импорт сессий из стандартного плагина FTP + * FTP: Пофиксен баг: неправильное определение размера файла (если файл > 2G) + * Пофиксен баг: падение плагина при попытке переименования файла + * Пофиксен баг: при копировании в буфер обмена имя файла писалось слитно с именем каталога + +NetBox 1.8 07-04-2011 +--------------------- + + Переименование и перемещение файлов/папок внутри сервера + + Копирование URL в буфер обмена (Ctrl+Alt+Ins, Shift+Alt+Ins) + + SFTP: Возможность указать кодировку при работе с сервером + * WebDAV: парсер xml переехал на tinyxml (проблемы с пространствами имён в msxml) + +NetBox 1.7 06-04-2011 +--------------------- + + Возможность указания таймаута ожидания ответа от сервера + * Пофиксен баг: Отвалилась обработка командной строки + * Пофиксен баг: При закрытии плагина не сбрасывалось соединение + +NetBox 1.6 05-04-2011 +--------------------- + * FTP: Ошибки в парсере + +NetBox 1.5 05-04-2011 +--------------------- + * FTP: Парсер листинга теперь в плагине, а не в libcurl + * FTP/WebDAV: Прогресс бар не работал при отправке файлов на сервер. + * UI: если имя файла длинное, то в диалоге по F5 надпись вылазила за рамку + +NetBox 1.4 04-04-2011 +--------------------- + * Пофиксен баг: Ключи ssh не загружались + * Пофиксен баг: Если URL заканчивается на слеш то при входе в любой подкаталог слеш дублируется и появляются side effects, например еще один trunk в trunk'e и т.д. + +NetBox 1.3 04-04-2011 +--------------------- + * Переход на использование библиотеки libcurl для FTP и WebDAV протоколов + * Прогресс бар с возможностью отмены копирования файлов + * Небольшие изменения в UI + +NetBox 1.2 24-03-2011 +--------------------- + + FTP: Возможность указать кодировку при работе с сервером + * WebDAV: Пофиксен (?) баг: все URL в запросах передаются в utf-8 с %XX + * Пофиксен баг: при переименовании сессии имя на панели не менялось + +NetBox 1.1 23-03-2011 +--------------------- + + Обработка префикса командной строки + + Возможность запроса пароля непосредственно при подключении + + Русская локализация + + WebDAV: Добавлена поддержка редиректа (Экспериментально, не проверена - не на чем) + * WebDAV: Имена файлов преобразуются в нормальный вид (%XX) + * Если имя сесии не указано, оно формируется автоматически из имени хоста + * Исправления в UI + +NetBox 1.0 22-03-2011 +--------------------- + * Первая версия diff --git a/netbox/LICENSE.txt b/netbox/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/netbox/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/netbox/README.RU.md b/netbox/README.RU.md new file mode 100644 index 000000000..aac1c5339 --- /dev/null +++ b/netbox/README.RU.md @@ -0,0 +1,71 @@ +NetBox: SFTP/FTP(S)/SCP/WebDAV клиент для Far Manager 2.0/3.0 +=============== + +1. Общие сведения о плагине + + Плагин реализует клиентскую часть протоколов SFTP, FTP, SCP, FTPS и WebDAV. + SFTP, FTP, SCP, FTPS протоколы реализованы на основе плагина WinSCP [http://winscp.net/eng/download.php/](http://winscp.net/eng/download.php/) + Поддержка протокола WebDAV реализована на основе библиотеки neon [http://www.webdav.org/neon/](http://www.webdav.org/neon/). + Парсер xml работает c помощью библиотеки TinyXML [http://sourceforge.net/projects/tinyxml/] (http://sourceforge.net/projects/tinyxml/). + +2. Использование префикса командной строки + + Доступ к удалённому серверу возможен как через собственное хранилище сессий, так и через префикс. + + Возможны два варианта использования префикса: + a. NetBox:Protocol://[[User]:[Password]@]HostName[:Port][/Path] + + где Protocol - имя протокола (ftp/ftps/sftp/http/https) + User - имя пользователя + Password - пароль пользователя + HostName - имя хоста + Port - номер порта + Path - путь + + b. (sftp|ftp|scp|ftps|http|https)://[[User]:[Password]@]HostName[:Port][/Path] + где (sftp|ftp|scp|ftps|http|https) - имя протокола + User - имя пользователя + Password - пароль пользователя + HostName - имя хоста + Port - номер порта + Path - путь + + Особенности работы с FTP серверами допускающими анонимный логин. + + ВНИМАНИЕ! + + Если Вы подключаетесь к FTP серверу допускающему анонимный логин, + то у Вас возможно сообщение сервера о неудачном логине с + использованием пустого пароля. В таком случае вместо анонимного + логина попробуйте нормальный логин с использованием пары + "логин - пароль" такого вида: + + Логин: Anonymous или для некоторых серверов anonimous (это зависит от настройки данного FTP сервера!) + Пароль: user@server.com + + Данная комбинация "имя-пароль" является стандартной формой + анонимного логина определённой в спецификациях протокола FTP и + допускается на всех разрешающих анонимный логин FTP серверах. + + + Например, следующие команды в Far'e позволят просматривать хранилище + svn с исходными кодами Far: + a. NetBox: http://farmanager.com/svn/trunk + b. http://farmanager.com/svn/trunk + +3. Ключи при использовании протоколов SFTP, SCP + + Файл с ключами (публичным и приватным) должен быть в формате Putty (.ppk). + +4. Фичи + + Комбинация клавиш в панели: + Ctrl+Alt+Ins: Копирование текущего URL в буфер обмена (вместе с паролем). + Shift+Alt+Ins: Копирование текущего URL в буфер обмена (без пароля). + +5. Установка + + Распакуйте содержимое архива в каталог плагинов Far (...Far\Plugins). + +Данный плагин предоставляется "as is" ("как есть"). Автор не несет +ответственности за последствия использования данного плагина. diff --git a/netbox/README.md b/netbox/README.md new file mode 100644 index 000000000..7565d0a27 --- /dev/null +++ b/netbox/README.md @@ -0,0 +1,70 @@ +NetBox: SFTP/FTP/FTP(S)/SCP/WebDAV client for Far Manager 2.0/3.0 x86/x64 +============== + +Based on [WinSCP](http://winscp.net/eng/index.php) version 5.8.3 Copyright (c) 2000-2016 Martin Prikryl +Based on [WinSCP as FAR Plugin: SFTP/FTP/SCP client for FAR version 1.6.2](http://winscp.net/download/winscpfar162setup.exe) Copyright (c) 2000-2009 Martin Prikryl +SSH and SCP code based on PuTTY 0.67 Copyright (c) 1997-2016 Simon Tatham +FTP code based on FileZilla 2.2.32 Copyright (c) 2001-2007 Tim Kosse + +How to install +============== + +You can either download an appropriate binary package for your +platform or build from source. Binaries can be obtained from [here](http://plugring.farmanager.com/plugin.php?pid=859&l=en). + +Unpack the archive in the plugin directory Far (... Far \ Plugins). + +How to build from source +======================== + +To build plugin from source, you will need: + + + * Visual Studio 2010 SP1 + * Microsoft Platform SDK, available for download at [http://www.microsoft.com/msdownload/platformsdk/sdkupdate/](http://www.microsoft.com/msdownload/platformsdk/sdkupdate/). + * Perl 5 (to compile openssl), available at [http://www.activestate.com/ActivePerl/](http://www.activestate.com/ActivePerl/) + * UnxUtils [http://unxutils.sourceforge.net/](http://unxutils.sourceforge.net/) + * nasm [http://www.nasm.us/pub/nasm/releasebuilds/2.09.10/win32/](http://www.nasm.us/pub/nasm/releasebuilds/2.09.10/win32/) + + + +Download the source: + +You can either download a release source zip ball from [tags page](https://github.com/michaellukashov/Far-NetBox/tags) +and unpack it in your source directory, say C:/src, +or from git repository: + + cd C:/src + git clone git://github.com/michaellukashov/Far-NetBox.git + +From now on, we assume that your source tree is C:/src/Far-NetBox + + +Compile openssl: + + cd libs/openssl + call ../../src/NetBox/scripts/build_openssl.bat x86 + call ../../src/NetBox/scripts/build_openssl.bat x64 + +Either open src/NetBox/NetBox.sln in Visual Studio, or compile NetBox plugin on the command line as follows: + + cmd /c "%VS100COMNTOOLS%\..\..\VC\vcvarsall.bat" x86 && devenv NetBox.sln /Build "Release|Win32" /USEENV /Project "NetBox" + cmd /c "%VS100COMNTOOLS%\..\..\VC\vcvarsall.bat" x86_amd64 && devenv NetBox.sln /Build "Release|x64" /USEENV /Project "NetBox" + +Replace Release with Debug to build in Debug mode. The built binaries will be in Far2_x86/Plugins/NetBox/ (or Far2_x64/Plugins/NetBox/) directory. + + +Links +======================== + +* Project main page: [https://github.com/michaellukashov/Far-NetBox/](https://github.com/michaellukashov/Far-NetBox/) +* Download page: [http://plugring.farmanager.com/plugin.php?pid=859&l=en](http://plugring.farmanager.com/plugin.php?pid=859&l=en) +* Far Manager forum: [http://forum.farmanager.com/](http://forum.farmanager.com/) +* NetBox discussions (in Russian): [http://forum.farmanager.com/viewtopic.php?f=5&t=6317](http://forum.farmanager.com/viewtopic.php?f=5&t=6317) +* NetBox discussions (in English): [http://forum.farmanager.com/viewtopic.php?f=39&t=6638](http://forum.farmanager.com/viewtopic.php?f=39&t=6638) + +License +======================== + +NetBox is [free](http://www.gnu.org/philosophy/free-sw.html) software: you can use it, redistribute it and/or modify it under the terms of the [GNU General Public License](http://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +NetBox is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the [GNU General Public License](http://www.gnu.org/licenses/gpl.html) for more details. diff --git a/netbox/libs/Putty/be_all.c b/netbox/libs/Putty/be_all.c new file mode 100644 index 000000000..c58903cc5 --- /dev/null +++ b/netbox/libs/Putty/be_all.c @@ -0,0 +1,31 @@ +/* + * Linking module for PuTTY proper: list the available backends + * including ssh. + */ + +#include +#include "putty.h" + +/* + * This appname is not strictly in the right place, since Plink + * also uses this module. However, Plink doesn't currently use any + * of the dialog-box sorts of things that make use of appname, so + * it shouldn't do any harm here. I'm trying to avoid having to + * have tiny little source modules containing nothing but + * declarations of appname, for as long as I can... + */ +const char *const appname = "PuTTY"; + +#ifdef TELNET_DEFAULT +const int be_default_protocol = PROT_TELNET; +#else +const int be_default_protocol = PROT_SSH; +#endif + +Backend *backends[] = { + &ssh_backend, + &telnet_backend, + &rlogin_backend, + &raw_backend, + NULL +}; diff --git a/netbox/libs/Putty/be_all_s.c b/netbox/libs/Putty/be_all_s.c new file mode 100644 index 000000000..0ffd07376 --- /dev/null +++ b/netbox/libs/Putty/be_all_s.c @@ -0,0 +1,32 @@ +/* + * Linking module for PuTTY proper: list the available backends + * including ssh, plus the serial backend. + */ + +#include +#include "putty.h" + +/* + * This appname is not strictly in the right place, since Plink + * also uses this module. However, Plink doesn't currently use any + * of the dialog-box sorts of things that make use of appname, so + * it shouldn't do any harm here. I'm trying to avoid having to + * have tiny little source modules containing nothing but + * declarations of appname, for as long as I can... + */ +const char *const appname = "PuTTY"; + +#ifdef TELNET_DEFAULT +const int be_default_protocol = PROT_TELNET; +#else +const int be_default_protocol = PROT_SSH; +#endif + +Backend *backends[] = { + &ssh_backend, + &telnet_backend, + &rlogin_backend, + &raw_backend, + &serial_backend, + NULL +}; diff --git a/netbox/libs/Putty/be_none.c b/netbox/libs/Putty/be_none.c new file mode 100644 index 000000000..688b8dafe --- /dev/null +++ b/netbox/libs/Putty/be_none.c @@ -0,0 +1,11 @@ +/* + * Linking module for programs that do not support selection of backend + * (such as pterm). + */ + +#include +#include "putty.h" + +Backend *backends[] = { + NULL +}; diff --git a/netbox/libs/Putty/be_nos_s.c b/netbox/libs/Putty/be_nos_s.c new file mode 100644 index 000000000..a574ead91 --- /dev/null +++ b/netbox/libs/Putty/be_nos_s.c @@ -0,0 +1,34 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +Backend *backends[] = { + &telnet_backend, + &rlogin_backend, + &raw_backend, + &serial_backend, + NULL +}; + +/* + * Stub implementations of functions not used in non-ssh versions. + */ +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(unsigned long data) +{ +} diff --git a/netbox/libs/Putty/be_nossh.c b/netbox/libs/Putty/be_nossh.c new file mode 100644 index 000000000..33d783a84 --- /dev/null +++ b/netbox/libs/Putty/be_nossh.c @@ -0,0 +1,33 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +Backend *backends[] = { + &telnet_backend, + &rlogin_backend, + &raw_backend, + NULL +}; + +/* + * Stub implementations of functions not used in non-ssh versions. + */ +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(unsigned long data) +{ +} diff --git a/netbox/libs/Putty/be_ssh.c b/netbox/libs/Putty/be_ssh.c new file mode 100644 index 000000000..57d241c2e --- /dev/null +++ b/netbox/libs/Putty/be_ssh.c @@ -0,0 +1,16 @@ +/* + * Linking module for programs that are restricted to only using SSH + * (pscp and psftp). These do not support selection of backend, but + * must still have a backends[] array mentioning SSH because + * settings.c will want to consult it during session load. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_SSH; + +Backend *backends[] = { + &ssh_backend, + NULL +}; diff --git a/netbox/libs/Putty/callback.c b/netbox/libs/Putty/callback.c new file mode 100644 index 000000000..c70dc53fb --- /dev/null +++ b/netbox/libs/Putty/callback.c @@ -0,0 +1,74 @@ +/* + * Facility for queueing callback functions to be run from the + * top-level event loop after the current top-level activity finishes. + */ + +#include + +#include "putty.h" + +struct callback { + struct callback *next; + + toplevel_callback_fn_t fn; + void *ctx; +}; + +struct callback *cbhead = NULL, *cbtail = NULL; + +toplevel_callback_notify_fn_t notify_frontend = NULL; +void *frontend = NULL; + +void request_callback_notifications(toplevel_callback_notify_fn_t fn, + void *fr) +{ + notify_frontend = fn; + frontend = fr; +} + +void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) +{ + struct callback *cb; + + cb = snew(struct callback); + cb->fn = fn; + cb->ctx = ctx; + + /* If the front end has requested notification of pending + * callbacks, and we didn't already have one queued, let it know + * we do have one now. */ + if (notify_frontend && !cbhead) + notify_frontend(frontend); + + if (cbtail) + cbtail->next = cb; + else + cbhead = cb; + cbtail = cb; + cb->next = NULL; +} + +void run_toplevel_callbacks(void) +{ + if (cbhead) { + struct callback *cb = cbhead; + /* + * Careful ordering here. We call the function _before_ + * advancing cbhead (though, of course, we must free cb + * _after_ advancing it). This means that if the very last + * callback schedules another callback, cbhead does not become + * NULL at any point, and so the frontend notification + * function won't be needlessly pestered. + */ + cb->fn(cb->ctx); + cbhead = cb->next; + sfree(cb); + if (!cbhead) + cbtail = NULL; + } +} + +int toplevel_callback_pending(void) +{ + return cbhead != NULL; +} diff --git a/netbox/libs/Putty/charset/charset.h b/netbox/libs/Putty/charset/charset.h new file mode 100644 index 000000000..bc5ae3ac6 --- /dev/null +++ b/netbox/libs/Putty/charset/charset.h @@ -0,0 +1,157 @@ +/* + * charset.h - header file for general character set conversion + * routines. + */ + +#ifndef charset_charset_h +#define charset_charset_h + +#include + +/* + * Enumeration that lists all the multibyte or single-byte + * character sets known to this library. + */ +typedef enum { + CS_NONE, /* used for reporting errors, etc */ + CS_ISO8859_1, + CS_ISO8859_1_X11, /* X font encoding with VT100 glyphs */ + CS_ISO8859_2, + CS_ISO8859_3, + CS_ISO8859_4, + CS_ISO8859_5, + CS_ISO8859_6, + CS_ISO8859_7, + CS_ISO8859_8, + CS_ISO8859_9, + CS_ISO8859_10, + CS_ISO8859_11, + CS_ISO8859_13, + CS_ISO8859_14, + CS_ISO8859_15, + CS_ISO8859_16, + CS_CP437, + CS_CP850, + CS_CP852, + CS_CP866, + CS_CP1250, + CS_CP1251, + CS_CP1252, + CS_CP1253, + CS_CP1254, + CS_CP1255, + CS_CP1256, + CS_CP1257, + CS_CP1258, + CS_KOI8_R, + CS_KOI8_U, + CS_MAC_ROMAN, + CS_MAC_TURKISH, + CS_MAC_CROATIAN, + CS_MAC_ICELAND, + CS_MAC_ROMANIAN, + CS_MAC_GREEK, + CS_MAC_CYRILLIC, + CS_MAC_THAI, + CS_MAC_CENTEURO, + CS_MAC_SYMBOL, + CS_MAC_DINGBATS, + CS_MAC_ROMAN_OLD, + CS_MAC_CROATIAN_OLD, + CS_MAC_ICELAND_OLD, + CS_MAC_ROMANIAN_OLD, + CS_MAC_GREEK_OLD, + CS_MAC_CYRILLIC_OLD, + CS_MAC_UKRAINE, + CS_MAC_VT100, + CS_MAC_VT100_OLD, + CS_VISCII, + CS_HP_ROMAN8, + CS_DEC_MCS, + CS_UTF8 +} charset_t; + +typedef struct { + unsigned long s0; +} charset_state; + +/* + * Routine to convert a MB/SB character set to Unicode. + * + * This routine accepts some number of bytes, updates a state + * variable, and outputs some number of Unicode characters. There + * are no guarantees. You can't even guarantee that at most one + * Unicode character will be output per byte you feed in; for + * example, suppose you're reading UTF-8, you've seen E1 80, and + * then you suddenly see FE. Now you need to output _two_ error + * characters - one for the incomplete sequence E1 80, and one for + * the completely invalid UTF-8 byte FE. + * + * Returns the number of wide characters output; will never output + * more than the size of the buffer (as specified on input). + * Advances the `input' pointer and decrements `inlen', to indicate + * how far along the input string it got. + * + * The sequence of `errlen' wide characters pointed to by `errstr' + * will be used to indicate a conversion error. If `errstr' is + * NULL, `errlen' will be ignored, and the library will choose + * something sensible to do on its own. For Unicode, this will be + * U+FFFD (REPLACEMENT CHARACTER). + */ + +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen); + +/* + * Routine to convert Unicode to an MB/SB character set. + * + * This routine accepts some number of Unicode characters, updates + * a state variable, and outputs some number of bytes. + * + * Returns the number of bytes characters output; will never output + * more than the size of the buffer (as specified on input), and + * will never output a partial MB character. Advances the `input' + * pointer and decrements `inlen', to indicate how far along the + * input string it got. + * + * The sequence of `errlen' characters pointed to by `errstr' will + * be used to indicate a conversion error. If `errstr' is NULL, + * `errlen' will be ignored, and the library will choose something + * sensible to do on its own (which will vary depending on the + * output charset). + */ + +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen); + +/* + * Convert X11 encoding names to and from our charset identifiers. + */ +const char *charset_to_xenc(int charset); +int charset_from_xenc(const char *name); + +/* + * Convert MIME encoding names to and from our charset identifiers. + */ +const char *charset_to_mimeenc(int charset); +int charset_from_mimeenc(const char *name); + +/* + * Convert our own encoding names to and from our charset + * identifiers. + */ +const char *charset_to_localenc(int charset); +int charset_from_localenc(const char *name); +int charset_localenc_nth(int n); + +/* + * Convert Mac OS script/region/font to our charset identifiers. + */ +int charset_from_macenc(int script, int region, int sysvers, + const char *fontname); + +#endif /* charset_charset_h */ diff --git a/netbox/libs/Putty/charset/enum.c b/netbox/libs/Putty/charset/enum.c new file mode 100644 index 000000000..347647d16 --- /dev/null +++ b/netbox/libs/Putty/charset/enum.c @@ -0,0 +1,19 @@ +/* + * enum.c - enumerate all charsets defined by the library. + * + * This file maintains a list of every other source file which + * contains ENUM_CHARSET definitions. It #includes each one with + * ENUM_CHARSETS defined, which causes those source files to do + * nothing at all except call the ENUM_CHARSET macro on each + * charset they define. + * + * This file in turn is included from various other places, with + * the ENUM_CHARSET macro defined to various different things. This + * allows us to have multiple implementations of the master charset + * lookup table (a static one and a dynamic one). + */ + +#define ENUM_CHARSETS +#include "sbcsdat.c" +#include "utf8.c" +#undef ENUM_CHARSETS diff --git a/netbox/libs/Putty/charset/fromucs.c b/netbox/libs/Putty/charset/fromucs.c new file mode 100644 index 000000000..6ccdb864f --- /dev/null +++ b/netbox/libs/Putty/charset/fromucs.c @@ -0,0 +1,92 @@ +/* + * fromucs.c - convert Unicode to other character sets. + */ + +#include "charset.h" +#include "internal.h" + +struct charset_emit_param { + char *output; + int outlen; + const char *errstr; + int errlen; + int stopped; +}; + +static void charset_emit(void *ctx, long int output) +{ + struct charset_emit_param *param = (struct charset_emit_param *)ctx; + char outval; + char const *p; + int outlen; + + if (output == ERROR) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct charset_emit_param param; + + param.output = output; + param.outlen = outlen; + param.stopped = 0; + + /* + * charset_emit will expect a valid errstr. + */ + if (!errstr) { + /* *shrug* this is good enough, and consistent across all SBCS... */ + param.errstr = "."; + param.errlen = 1; + } + param.errstr = errstr; + param.errlen = errlen; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + state = &localstate; + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->write(spec, **input, &localstate, charset_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + return param.output - output; +} diff --git a/netbox/libs/Putty/charset/internal.h b/netbox/libs/Putty/charset/internal.h new file mode 100644 index 000000000..38df2eaad --- /dev/null +++ b/netbox/libs/Putty/charset/internal.h @@ -0,0 +1,89 @@ +/* + * internal.h - internal header stuff for the charset library. + */ + +#ifndef charset_internal_h +#define charset_internal_h + +/* This invariably comes in handy */ +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +/* This is an invalid Unicode value used to indicate an error. */ +#define ERROR 0xFFFFL /* Unicode value representing error */ + +typedef struct charset_spec charset_spec; +typedef struct sbcs_data sbcs_data; + +struct charset_spec { + int charset; /* numeric identifier */ + + /* + * A function to read the character set and output Unicode + * characters. The `emit' function expects to get Unicode chars + * passed to it; it should be sent ERROR for any encoding error + * on the input. + */ + void (*read)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + /* + * A function to read Unicode characters and output in this + * character set. The `emit' function expects to get byte + * values passed to it; it should be sent ERROR for any + * non-representable characters on the input. + */ + void (*write)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + void const *data; +}; + +/* + * This is the format of `data' used by the SBCS read and write + * functions; so it's the format used in all SBCS definitions. + */ +struct sbcs_data { + /* + * This is a simple mapping table converting each SBCS position + * to a Unicode code point. Some positions may contain ERROR, + * indicating that that byte value is not defined in the SBCS + * in question and its occurrence in input is an error. + */ + unsigned long sbcs2ucs[256]; + + /* + * This lookup table is used to convert Unicode back to the + * SBCS. It consists of the valid byte values in the SBCS, + * sorted in order of their Unicode translation. So given a + * Unicode value U, you can do a binary search on this table + * using the above table as a lookup: when testing the Xth + * position in this table, you branch according to whether + * sbcs2ucs[ucs2sbcs[X]] is less than, greater than, or equal + * to U. + * + * Note that since there may be fewer than 256 valid byte + * values in a particular SBCS, we must supply the length of + * this table as well as the contents. + */ + unsigned char ucs2sbcs[256]; + int nvalid; +}; + +/* + * Prototypes for internal library functions. + */ +charset_spec const *charset_find_spec(int charset); +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + +/* + * Placate compiler warning about unused parameters, of which we + * expect to have some in this library. + */ +#define UNUSEDARG(x) ( (x) = (x) ) + +#endif /* charset_internal_h */ diff --git a/netbox/libs/Putty/charset/localenc.c b/netbox/libs/Putty/charset/localenc.c new file mode 100644 index 000000000..a24126d3e --- /dev/null +++ b/netbox/libs/Putty/charset/localenc.c @@ -0,0 +1,126 @@ +/* + * local.c - translate our internal character set codes to and from + * our own set of plausibly legible character-set names. Also + * provides a canonical name for each encoding (useful for software + * announcing what character set it will be using), and a set of + * enumeration functions which return a list of supported + * encodings one by one. + * + * charset_from_localenc will attempt all other text translations + * as well as this table, to maximise the number of different ways + * you can select a supported charset. + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; + int return_in_enum; /* enumeration misses some charsets */ +} localencs[] = { + { "", CS_NONE, 0 }, + { "UTF-8", CS_UTF8, 1 }, + { "ISO-8859-1", CS_ISO8859_1, 1 }, + { "ISO-8859-1 with X11 line drawing", CS_ISO8859_1_X11, 0 }, + { "ISO-8859-2", CS_ISO8859_2, 1 }, + { "ISO-8859-3", CS_ISO8859_3, 1 }, + { "ISO-8859-4", CS_ISO8859_4, 1 }, + { "ISO-8859-5", CS_ISO8859_5, 1 }, + { "ISO-8859-6", CS_ISO8859_6, 1 }, + { "ISO-8859-7", CS_ISO8859_7, 1 }, + { "ISO-8859-8", CS_ISO8859_8, 1 }, + { "ISO-8859-9", CS_ISO8859_9, 1 }, + { "ISO-8859-10", CS_ISO8859_10, 1 }, + { "ISO-8859-11", CS_ISO8859_11, 1 }, + { "ISO-8859-13", CS_ISO8859_13, 1 }, + { "ISO-8859-14", CS_ISO8859_14, 1 }, + { "ISO-8859-15", CS_ISO8859_15, 1 }, + { "ISO-8859-16", CS_ISO8859_16, 1 }, + { "CP437", CS_CP437, 1 }, + { "CP850", CS_CP850, 1 }, + { "CP852", CS_CP852, 1 }, + { "CP866", CS_CP866, 1 }, + { "CP1250", CS_CP1250, 1 }, + { "CP1251", CS_CP1251, 1 }, + { "CP1252", CS_CP1252, 1 }, + { "CP1253", CS_CP1253, 1 }, + { "CP1254", CS_CP1254, 1 }, + { "CP1255", CS_CP1255, 1 }, + { "CP1256", CS_CP1256, 1 }, + { "CP1257", CS_CP1257, 1 }, + { "CP1258", CS_CP1258, 1 }, + { "KOI8-R", CS_KOI8_R, 1 }, + { "KOI8-U", CS_KOI8_U, 1 }, + { "Mac Roman", CS_MAC_ROMAN, 1 }, + { "Mac Turkish", CS_MAC_TURKISH, 1 }, + { "Mac Croatian", CS_MAC_CROATIAN, 1 }, + { "Mac Iceland", CS_MAC_ICELAND, 1 }, + { "Mac Romanian", CS_MAC_ROMANIAN, 1 }, + { "Mac Greek", CS_MAC_GREEK, 1 }, + { "Mac Cyrillic", CS_MAC_CYRILLIC, 1 }, + { "Mac Thai", CS_MAC_THAI, 1 }, + { "Mac Centeuro", CS_MAC_CENTEURO, 1 }, + { "Mac Symbol", CS_MAC_SYMBOL, 1 }, + { "Mac Dingbats", CS_MAC_DINGBATS, 1 }, + { "Mac Roman (old)", CS_MAC_ROMAN_OLD, 0 }, + { "Mac Croatian (old)", CS_MAC_CROATIAN_OLD, 0 }, + { "Mac Iceland (old)", CS_MAC_ICELAND_OLD, 0 }, + { "Mac Romanian (old)", CS_MAC_ROMANIAN_OLD, 0 }, + { "Mac Greek (old)", CS_MAC_GREEK_OLD, 0 }, + { "Mac Cyrillic (old)", CS_MAC_CYRILLIC_OLD, 0 }, + { "Mac Ukraine", CS_MAC_UKRAINE, 1 }, + { "Mac VT100", CS_MAC_VT100, 1 }, + { "Mac VT100 (old)", CS_MAC_VT100_OLD, 0 }, + { "VISCII", CS_VISCII, 1 }, + { "HP ROMAN8", CS_HP_ROMAN8, 1 }, + { "DEC MCS", CS_DEC_MCS, 1 }, +}; + +const char *charset_to_localenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (charset == localencs[i].charset) + return localencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_localenc(const char *name) +{ + int i; + + if ( (i = charset_from_mimeenc(name)) != CS_NONE) + return i; + if ( (i = charset_from_xenc(name)) != CS_NONE) + return i; + + for (i = 0; i < (int)lenof(localencs); i++) { + const char *p, *q; + p = name; + q = localencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return localencs[i].charset; + } + + return CS_NONE; /* not found */ +} + +int charset_localenc_nth(int n) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (localencs[i].return_in_enum && !n--) + return localencs[i].charset; + + return CS_NONE; /* end of list */ +} diff --git a/netbox/libs/Putty/charset/macenc.c b/netbox/libs/Putty/charset/macenc.c new file mode 100644 index 000000000..bf70e9c25 --- /dev/null +++ b/netbox/libs/Putty/charset/macenc.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2003 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/* + * macenc.c -- Convert a Mac OS script/region/font combination to our + * internal charset code. + */ + +#include + +#include "charset.h" +#include "internal.h" + +/* + * These are defined by Mac OS's , but we'd like to be + * independent of that. + */ + +#define smRoman 0 +#define smJapanese 1 +#define smTradChinese 2 +#define smKorean 3 +#define smArabic 4 +#define smHebrew 5 +#define smCyrillic 7 +#define smDevenagari 9 +#define smGurmukhi 10 +#define smGujurati 11 +#define smThai 21 +#define smSimpChinese 25 +#define smTibetan 26 +#define smEthiopic 28 +#define smCentralEuroRoman 29 + +#define verGreece 20 +#define verIceland 21 +#define verTurkey 24 +#define verYugoCroatian 25 +#define verRomania 39 +#define verFaroeIsl 47 +#define verIran 48 +#define verRussia 49 +#define verSlovenian 66 +#define verCroatia 68 +#define verBulgaria 72 +#define verScottishGaelic 75 +#define verManxGaelic 76 +#define verBreton 77 +#define verNunavut 78 +#define verWelsh 79 +#define verIrishGaelicScript 81 + +static const struct { + int script; + int region; + int sysvermin; + char const *fontname; + int charset; +} macencs[] = { + { smRoman, -1, 0x850, "VT100", CS_MAC_VT100 }, + { smRoman, -1, 0, "VT100", CS_MAC_VT100_OLD }, + /* + * From here on, this table is largely derived from + * , + * with _OLD version added based on the comments in individual + * mapping files. + */ + { smRoman, -1, 0, "Symbol", CS_MAC_SYMBOL }, + { smRoman, -1, 0, "Zapf Dingbats", CS_MAC_DINGBATS }, + { smRoman, verTurkey, 0, NULL, CS_MAC_TURKISH }, + { smRoman, verYugoCroatian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verYugoCroatian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verSlovenian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verSlovenian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verCroatia, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verCroatia, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verIceland, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verIceland, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verFaroeIsl, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verFaroeIsl, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verRomania, 0x850, NULL, CS_MAC_ROMANIAN }, + { smRoman, verRomania, 0, NULL, CS_MAC_ROMANIAN_OLD }, +#if 0 /* No mapping table on ftp.unicode.org */ + { smRoman, verIreland, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verIreland, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verScottishGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verScottishGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verManxGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verManxGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verBreton, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verBreton, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verWelsh, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verWelsh, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verIrishGaelicScript, 0x850, NULL, CS_MAC_GAELIC }, + { smRoman, verIrishGaelicScript, 0, NULL, CS_MAC_GAELIC_OLD }, +#endif + { smRoman, verGreece, 0x922, NULL, CS_MAC_GREEK }, + { smRoman, verGreece, 0, NULL, CS_MAC_GREEK_OLD }, + { smRoman, -1, 0x850, NULL, CS_MAC_ROMAN }, + { smRoman, -1, 0, NULL, CS_MAC_ROMAN_OLD }, +#if 0 /* Multi-byte encodings, not yet supported */ + { smJapanese, -1, 0, NULL, CS_MAC_JAPANESE }, + { smTradChinese, -1, 0, NULL, CS_MAC_CHINTRAD }, + { smKorean, -1, 0, NULL, CS_MAC_KOREAN }, +#endif +#if 0 /* Bidirectional encodings, not yet supported */ + { smArabic, verIran, 0, NULL, CS_MAC_FARSI }, + { smArabic, -1, 0, NULL, CS_MAC_ARABIC }, + { smHebrew, -1, 0, NULL, CS_MAC_HEBREW }, +#endif + { smCyrillic, -1, 0x900, NULL, CS_MAC_CYRILLIC }, + { smCyrillic, verRussia, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, verBulgaria, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, -1, 0, NULL, CS_MAC_UKRAINE }, +#if 0 /* Complex Indic scripts, not yet supported */ + { smDevanagari, -1, 0, NULL, CS_MAC_DEVENAGA }, + { smGurmukhi, -1, 0, NULL, CS_MAC_GURMUKHI }, + { smGujurati, -1, 0, NULL, CS_MAC_GUJURATI }, +#endif + { smThai, -1, 0, NULL, CS_MAC_THAI }, +#if 0 /* Multi-byte encoding, not yet supported */ + { smSimpChinese, -1, 0, NULL, CS_MAC_CHINSIMP }, +#endif +#if 0 /* No mapping table on ftp.unicode.org */ + { smTibetan, -1, 0, NULL, CS_MAC_TIBETAN }, + { smEthiopic, -1, 0, NULL, CS_MAC_ETHIOPIC }, + { smEthiopic, verNanavut, 0, NULL, CS_MAC_INUIT }, +#endif + { smCentralEuroRoman, -1, 0, NULL, CS_MAC_CENTEURO }, +}; + +int charset_from_macenc(int script, int region, int sysvers, + char const *fontname) +{ + int i; + + for (i = 0; i < (int)lenof(macencs); i++) + if ((macencs[i].script == script) && + (macencs[i].region < 0 || macencs[i].region == region) && + (macencs[i].sysvermin <= sysvers) && + (macencs[i].fontname == NULL || + (fontname != NULL && strcmp(macencs[i].fontname, fontname) == 0))) + return macencs[i].charset; + + return CS_NONE; +} diff --git a/netbox/libs/Putty/charset/mimeenc.c b/netbox/libs/Putty/charset/mimeenc.c new file mode 100644 index 000000000..fb9243c68 --- /dev/null +++ b/netbox/libs/Putty/charset/mimeenc.c @@ -0,0 +1,219 @@ +/* + * mimeenc.c - translate our internal character set codes to and + * from MIME standard character-set names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} mimeencs[] = { + /* + * These names are taken from + * + * http://www.iana.org/assignments/character-sets + * + * Where multiple encoding names map to the same encoding id + * (such as the variety of aliases for ISO-8859-1), the first + * is considered canonical and will be returned when + * translating the id to a string. + */ + { "ISO-8859-1", CS_ISO8859_1 }, + { "iso-ir-100", CS_ISO8859_1 }, + { "ISO_8859-1", CS_ISO8859_1 }, + { "ISO_8859-1:1987", CS_ISO8859_1 }, + { "latin1", CS_ISO8859_1 }, + { "l1", CS_ISO8859_1 }, + { "IBM819", CS_ISO8859_1 }, + { "CP819", CS_ISO8859_1 }, + { "csISOLatin1", CS_ISO8859_1 }, + + { "ISO-8859-2", CS_ISO8859_2 }, + { "ISO_8859-2:1987", CS_ISO8859_2 }, + { "iso-ir-101", CS_ISO8859_2 }, + { "ISO_8859-2", CS_ISO8859_2 }, + { "latin2", CS_ISO8859_2 }, + { "l2", CS_ISO8859_2 }, + { "csISOLatin2", CS_ISO8859_2 }, + + { "ISO-8859-3", CS_ISO8859_3 }, + { "ISO_8859-3:1988", CS_ISO8859_3 }, + { "iso-ir-109", CS_ISO8859_3 }, + { "ISO_8859-3", CS_ISO8859_3 }, + { "latin3", CS_ISO8859_3 }, + { "l3", CS_ISO8859_3 }, + { "csISOLatin3", CS_ISO8859_3 }, + + { "ISO-8859-4", CS_ISO8859_4 }, + { "ISO_8859-4:1988", CS_ISO8859_4 }, + { "iso-ir-110", CS_ISO8859_4 }, + { "ISO_8859-4", CS_ISO8859_4 }, + { "latin4", CS_ISO8859_4 }, + { "l4", CS_ISO8859_4 }, + { "csISOLatin4", CS_ISO8859_4 }, + + { "ISO-8859-5", CS_ISO8859_5 }, + { "ISO_8859-5:1988", CS_ISO8859_5 }, + { "iso-ir-144", CS_ISO8859_5 }, + { "ISO_8859-5", CS_ISO8859_5 }, + { "cyrillic", CS_ISO8859_5 }, + { "csISOLatinCyrillic", CS_ISO8859_5 }, + + { "ISO-8859-6", CS_ISO8859_6 }, + { "ISO_8859-6:1987", CS_ISO8859_6 }, + { "iso-ir-127", CS_ISO8859_6 }, + { "ISO_8859-6", CS_ISO8859_6 }, + { "ECMA-114", CS_ISO8859_6 }, + { "ASMO-708", CS_ISO8859_6 }, + { "arabic", CS_ISO8859_6 }, + { "csISOLatinArabic", CS_ISO8859_6 }, + + { "ISO-8859-7", CS_ISO8859_7 }, + { "ISO_8859-7:1987", CS_ISO8859_7 }, + { "iso-ir-126", CS_ISO8859_7 }, + { "ISO_8859-7", CS_ISO8859_7 }, + { "ELOT_928", CS_ISO8859_7 }, + { "ECMA-118", CS_ISO8859_7 }, + { "greek", CS_ISO8859_7 }, + { "greek8", CS_ISO8859_7 }, + { "csISOLatinGreek", CS_ISO8859_7 }, + + { "ISO-8859-8", CS_ISO8859_8 }, + { "ISO_8859-8:1988", CS_ISO8859_8 }, + { "iso-ir-138", CS_ISO8859_8 }, + { "ISO_8859-8", CS_ISO8859_8 }, + { "hebrew", CS_ISO8859_8 }, + { "csISOLatinHebrew", CS_ISO8859_8 }, + + { "ISO-8859-9", CS_ISO8859_9 }, + { "ISO_8859-9:1989", CS_ISO8859_9 }, + { "iso-ir-148", CS_ISO8859_9 }, + { "ISO_8859-9", CS_ISO8859_9 }, + { "latin5", CS_ISO8859_9 }, + { "l5", CS_ISO8859_9 }, + { "csISOLatin5", CS_ISO8859_9 }, + + { "ISO-8859-10", CS_ISO8859_10 }, + { "iso-ir-157", CS_ISO8859_10 }, + { "l6", CS_ISO8859_10 }, + { "ISO_8859-10:1992", CS_ISO8859_10 }, + { "csISOLatin6", CS_ISO8859_10 }, + { "latin6", CS_ISO8859_10 }, + + { "ISO-8859-13", CS_ISO8859_13 }, + + { "ISO-8859-14", CS_ISO8859_14 }, + { "iso-ir-199", CS_ISO8859_14 }, + { "ISO_8859-14:1998", CS_ISO8859_14 }, + { "ISO_8859-14", CS_ISO8859_14 }, + { "latin8", CS_ISO8859_14 }, + { "iso-celtic", CS_ISO8859_14 }, + { "l8", CS_ISO8859_14 }, + + { "ISO-8859-15", CS_ISO8859_15 }, + { "ISO_8859-15", CS_ISO8859_15 }, + { "Latin-9", CS_ISO8859_15 }, + + { "ISO-8859-16", CS_ISO8859_16 }, + { "iso-ir-226", CS_ISO8859_16 }, + { "ISO_8859-16", CS_ISO8859_16 }, + { "ISO_8859-16:2001", CS_ISO8859_16 }, + { "latin10", CS_ISO8859_16 }, + { "l10", CS_ISO8859_16 }, + + { "IBM437", CS_CP437 }, + { "cp437", CS_CP437 }, + { "437", CS_CP437 }, + { "csPC8CodePage437", CS_CP437 }, + + { "IBM850", CS_CP850 }, + { "cp850", CS_CP850 }, + { "850", CS_CP850 }, + { "csPC850Multilingual", CS_CP850 }, + + { "IBM852", CS_CP852 }, + { "cp852", CS_CP852 }, + { "852", CS_CP852 }, + { "csIBM852", CS_CP852 }, + + { "IBM866", CS_CP866 }, + { "cp866", CS_CP866 }, + { "866", CS_CP866 }, + { "csIBM866", CS_CP866 }, + + { "windows-1250", CS_CP1250 }, + + { "windows-1251", CS_CP1251 }, + + { "windows-1252", CS_CP1252 }, + + { "windows-1253", CS_CP1253 }, + + { "windows-1254", CS_CP1254 }, + + { "windows-1255", CS_CP1255 }, + + { "windows-1256", CS_CP1256 }, + + { "windows-1257", CS_CP1257 }, + + { "windows-1258", CS_CP1258 }, + + { "KOI8-R", CS_KOI8_R }, + { "csKOI8R", CS_KOI8_R }, + + { "KOI8-U", CS_KOI8_U }, + + { "macintosh", CS_MAC_ROMAN_OLD }, + { "mac", CS_MAC_ROMAN_OLD }, + { "csMacintosh", CS_MAC_ROMAN_OLD }, + + { "VISCII", CS_VISCII }, + { "csVISCII", CS_VISCII }, + + { "hp-roman8", CS_HP_ROMAN8 }, + { "roman8", CS_HP_ROMAN8 }, + { "r8", CS_HP_ROMAN8 }, + { "csHPRoman8", CS_HP_ROMAN8 }, + + { "DEC-MCS", CS_DEC_MCS }, + { "dec", CS_DEC_MCS }, + { "csDECMCS", CS_DEC_MCS }, + + { "UTF-8", CS_UTF8 }, +}; + +const char *charset_to_mimeenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) + if (charset == mimeencs[i].charset) + return mimeencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_mimeenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) { + const char *p, *q; + p = name; + q = mimeencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return mimeencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/netbox/libs/Putty/charset/sbcs.c b/netbox/libs/Putty/charset/sbcs.c new file mode 100644 index 000000000..8e2a22747 --- /dev/null +++ b/netbox/libs/Putty/charset/sbcs.c @@ -0,0 +1,53 @@ +/* + * sbcs.c - routines to handle single-byte character sets. + */ + +#include "charset.h" +#include "internal.h" + +/* + * The charset_spec for any single-byte character set should + * provide read_sbcs() as its read function, and its `data' field + * should be a wchar_t string constant containing the 256 entries + * of the translation table. + */ + +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + + UNUSEDARG(state); + + emit(emitctx, sd->sbcs2ucs[input_chr]); +} + +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + int i, j, k, c; + + UNUSEDARG(state); + + /* + * Binary-search in the ucs2sbcs table. + */ + i = -1; + j = sd->nvalid; + while (i+1 < j) { + k = (i+j)/2; + c = sd->ucs2sbcs[k]; + if (input_chr < sd->sbcs2ucs[c]) + j = k; + else if (input_chr > sd->sbcs2ucs[c]) + i = k; + else { + emit(emitctx, c); + return; + } + } + emit(emitctx, ERROR); +} diff --git a/netbox/libs/Putty/charset/sbcsdat.c b/netbox/libs/Putty/charset/sbcsdat.c new file mode 100644 index 000000000..7a31bf110 --- /dev/null +++ b/netbox/libs/Putty/charset/sbcsdat.c @@ -0,0 +1,4094 @@ +/* + * sbcsdat.c - data definitions for single-byte character sets. + * + * Generated by sbcsgen.pl from sbcs.dat. + * You should edit those files rather than editing this one. + */ + +#ifndef ENUM_CHARSETS + +#include "charset.h" +#include "internal.h" + +static const sbcs_data data_CS_ISO8859_1 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }, + 256 +}; +const charset_spec charset_CS_ISO8859_1 = { + CS_ISO8859_1, read_sbcs, write_sbcs, &data_CS_ISO8859_1 +}; + +static const sbcs_data data_CS_ISO8859_2 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x02d8, 0x0141, 0x00a4, 0x013d, 0x015a, 0x00a7, + 0x00a8, 0x0160, 0x015e, 0x0164, 0x0179, 0x00ad, 0x017d, 0x017b, + 0x00b0, 0x0105, 0x02db, 0x0142, 0x00b4, 0x013e, 0x015b, 0x02c7, + 0x00b8, 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb4, 0xb8, + 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, 0xcd, 0xce, + 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, 0xdd, 0xdf, + 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, 0xed, 0xee, + 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, 0xfd, 0xc3, + 0xe3, 0xa1, 0xb1, 0xc6, 0xe6, 0xc8, 0xe8, 0xcf, + 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, 0xec, 0xc5, + 0xe5, 0xa5, 0xb5, 0xa3, 0xb3, 0xd1, 0xf1, 0xd2, + 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, 0xf8, 0xa6, + 0xb6, 0xaa, 0xba, 0xa9, 0xb9, 0xde, 0xfe, 0xab, + 0xbb, 0xd9, 0xf9, 0xdb, 0xfb, 0xac, 0xbc, 0xaf, + 0xbf, 0xae, 0xbe, 0xb7, 0xa2, 0xff, 0xb2, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_2 = { + CS_ISO8859_2, read_sbcs, write_sbcs, &data_CS_ISO8859_2 +}; + +static const sbcs_data data_CS_ISO8859_3 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0126, 0x02d8, 0x00a3, 0x00a4, ERROR , 0x0124, 0x00a7, + 0x00a8, 0x0130, 0x015e, 0x011e, 0x0134, 0x00ad, ERROR , 0x017b, + 0x00b0, 0x0127, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x0125, 0x00b7, + 0x00b8, 0x0131, 0x015f, 0x011f, 0x0135, 0x00bd, ERROR , 0x017c, + 0x00c0, 0x00c1, 0x00c2, ERROR , 0x00c4, 0x010a, 0x0108, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x0120, 0x00d6, 0x00d7, + 0x011c, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x016c, 0x015c, 0x00df, + 0x00e0, 0x00e1, 0x00e2, ERROR , 0x00e4, 0x010b, 0x0109, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x0121, 0x00f6, 0x00f7, + 0x011d, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x016d, 0x015d, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb7, 0xb8, 0xbd, 0xc0, 0xc1, + 0xc2, 0xc4, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, + 0xcd, 0xce, 0xcf, 0xd1, 0xd2, 0xd3, 0xd4, 0xd6, + 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe4, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xc6, 0xe6, 0xc5, + 0xe5, 0xd8, 0xf8, 0xab, 0xbb, 0xd5, 0xf5, 0xa6, + 0xb6, 0xa1, 0xb1, 0xa9, 0xb9, 0xac, 0xbc, 0xde, + 0xfe, 0xaa, 0xba, 0xdd, 0xfd, 0xaf, 0xbf, 0xa2, + 0xff + }, + 249 +}; +const charset_spec charset_CS_ISO8859_3 = { + CS_ISO8859_3, read_sbcs, write_sbcs, &data_CS_ISO8859_3 +}; + +static const sbcs_data data_CS_ISO8859_4 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0138, 0x0156, 0x00a4, 0x0128, 0x013b, 0x00a7, + 0x00a8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00ad, 0x017d, 0x00af, + 0x00b0, 0x0105, 0x02db, 0x0157, 0x00b4, 0x0129, 0x013c, 0x02c7, + 0x00b8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014a, 0x017e, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x012a, + 0x0110, 0x0145, 0x014c, 0x0136, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x0168, 0x016a, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x012b, + 0x0111, 0x0146, 0x014d, 0x0137, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x0169, 0x016b, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xaf, 0xb0, 0xb4, + 0xb8, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc9, + 0xcb, 0xcd, 0xce, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xda, 0xdb, 0xdc, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xc0, 0xe0, + 0xa1, 0xb1, 0xc8, 0xe8, 0xd0, 0xf0, 0xaa, 0xba, + 0xcc, 0xec, 0xca, 0xea, 0xab, 0xbb, 0xa5, 0xb5, + 0xcf, 0xef, 0xc7, 0xe7, 0xd3, 0xf3, 0xa2, 0xa6, + 0xb6, 0xd1, 0xf1, 0xbd, 0xbf, 0xd2, 0xf2, 0xa3, + 0xb3, 0xa9, 0xb9, 0xac, 0xbc, 0xdd, 0xfd, 0xde, + 0xfe, 0xd9, 0xf9, 0xae, 0xbe, 0xb7, 0xff, 0xb2 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_4 = { + CS_ISO8859_4, read_sbcs, write_sbcs, &data_CS_ISO8859_4 +}; + +static const sbcs_data data_CS_ISO8859_5 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x00ad, 0x040e, 0x040f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x00a7, 0x045e, 0x045f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xfd, 0xad, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfe, 0xff, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_5 = { + CS_ISO8859_5, read_sbcs, write_sbcs, &data_CS_ISO8859_5 +}; + +static const sbcs_data data_CS_ISO8859_6 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , ERROR , ERROR , 0x00a4, ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , 0x060c, 0x00ad, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , 0x061b, ERROR , ERROR , ERROR , 0x061f, + ERROR , 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, + 0x0638, 0x0639, 0x063a, ERROR , ERROR , ERROR , ERROR , ERROR , + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, + 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, + 0x0650, 0x0651, 0x0652, ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xad, 0xac, 0xbb, 0xbf, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2 + }, + 211 +}; +const charset_spec charset_CS_ISO8859_6 = { + CS_ISO8859_6, read_sbcs, write_sbcs, &data_CS_ISO8859_6 +}; + +static const sbcs_data data_CS_ISO8859_7 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x2018, 0x2019, 0x00a3, ERROR , ERROR , 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, ERROR , 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x0385, 0x0386, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xb0, 0xb1, 0xb2, 0xb3, 0xb7, 0xbb, 0xbd, + 0xb4, 0xb5, 0xb6, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaf, + 0xa1, 0xa2 + }, + 250 +}; +const charset_spec charset_CS_ISO8859_7 = { + CS_ISO8859_7, read_sbcs, write_sbcs, &data_CS_ISO8859_7 +}; + +static const sbcs_data data_CS_ISO8859_8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , 0x2017, + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xaa, 0xba, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfd, 0xfe, 0xdf + }, + 220 +}; +const charset_spec charset_CS_ISO8859_8 = { + CS_ISO8859_8, read_sbcs, write_sbcs, &data_CS_ISO8859_8 +}; + +static const sbcs_data data_CS_ISO8859_9 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0xde, 0xfe + }, + 256 +}; +const charset_spec charset_CS_ISO8859_9 = { + CS_ISO8859_9, read_sbcs, write_sbcs, &data_CS_ISO8859_9 +}; + +static const sbcs_data data_CS_ISO8859_10 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0112, 0x0122, 0x012a, 0x0128, 0x0136, 0x00a7, + 0x013b, 0x0110, 0x0160, 0x0166, 0x017d, 0x00ad, 0x016a, 0x014a, + 0x00b0, 0x0105, 0x0113, 0x0123, 0x012b, 0x0129, 0x0137, 0x00b7, + 0x013c, 0x0111, 0x0161, 0x0167, 0x017e, 0x2015, 0x016b, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x0145, 0x014c, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0168, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x0146, 0x014d, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0169, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x0138 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xad, 0xb0, 0xb7, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc9, 0xcb, 0xcd, 0xce, 0xcf, + 0xd0, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xef, 0xf0, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xfa, 0xfb, 0xfc, + 0xfd, 0xfe, 0xc0, 0xe0, 0xa1, 0xb1, 0xc8, 0xe8, + 0xa9, 0xb9, 0xa2, 0xb2, 0xcc, 0xec, 0xca, 0xea, + 0xa3, 0xb3, 0xa5, 0xb5, 0xa4, 0xb4, 0xc7, 0xe7, + 0xa6, 0xb6, 0xff, 0xa8, 0xb8, 0xd1, 0xf1, 0xaf, + 0xbf, 0xd2, 0xf2, 0xaa, 0xba, 0xab, 0xbb, 0xd7, + 0xf7, 0xae, 0xbe, 0xd9, 0xf9, 0xac, 0xbc, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_10 = { + CS_ISO8859_10, read_sbcs, write_sbcs, &data_CS_ISO8859_10 +}; + +static const sbcs_data data_CS_ISO8859_11 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, ERROR , ERROR , ERROR , ERROR , 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0e4e, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x0e5a, 0x0e5b, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb + }, + 248 +}; +const charset_spec charset_CS_ISO8859_11 = { + CS_ISO8859_11, read_sbcs, write_sbcs, &data_CS_ISO8859_11 +}; + +static const sbcs_data data_CS_ISO8859_13 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x201d, 0x00a2, 0x00a3, 0x00a4, 0x201e, 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x201c, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x2019 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, + 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, + 0xb6, 0xb7, 0xb9, 0xbb, 0xbc, 0xbd, 0xbe, 0xc4, + 0xc5, 0xaf, 0xc9, 0xd3, 0xd5, 0xd6, 0xd7, 0xa8, + 0xdc, 0xdf, 0xe4, 0xe5, 0xbf, 0xe9, 0xf3, 0xf5, + 0xf6, 0xf7, 0xb8, 0xfc, 0xc2, 0xe2, 0xc0, 0xe0, + 0xc3, 0xe3, 0xc8, 0xe8, 0xc7, 0xe7, 0xcb, 0xeb, + 0xc6, 0xe6, 0xcc, 0xec, 0xce, 0xee, 0xc1, 0xe1, + 0xcd, 0xed, 0xcf, 0xef, 0xd9, 0xf9, 0xd1, 0xf1, + 0xd2, 0xf2, 0xd4, 0xf4, 0xaa, 0xba, 0xda, 0xfa, + 0xd0, 0xf0, 0xdb, 0xfb, 0xd8, 0xf8, 0xca, 0xea, + 0xdd, 0xfd, 0xde, 0xfe, 0xff, 0xb4, 0xa1, 0xa5 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_13 = { + CS_ISO8859_13, read_sbcs, write_sbcs, &data_CS_ISO8859_13 +}; + +static const sbcs_data data_CS_ISO8859_14 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x1e02, 0x1e03, 0x00a3, 0x010a, 0x010b, 0x1e0a, 0x00a7, + 0x1e80, 0x00a9, 0x1e82, 0x1e0b, 0x1ef2, 0x00ad, 0x00ae, 0x0178, + 0x1e1e, 0x1e1f, 0x0120, 0x0121, 0x1e40, 0x1e41, 0x00b6, 0x1e56, + 0x1e81, 0x1e57, 0x1e83, 0x1e60, 0x1ef3, 0x1e84, 0x1e85, 0x1e61, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0174, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x1e6a, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x0176, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0175, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x1e6b, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x0177, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa7, 0xa9, 0xad, 0xae, 0xb6, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, + 0xff, 0xa4, 0xa5, 0xb2, 0xb3, 0xd0, 0xf0, 0xde, + 0xfe, 0xaf, 0xa1, 0xa2, 0xa6, 0xab, 0xb0, 0xb1, + 0xb4, 0xb5, 0xb7, 0xb9, 0xbb, 0xbf, 0xd7, 0xf7, + 0xa8, 0xb8, 0xaa, 0xba, 0xbd, 0xbe, 0xac, 0xbc + }, + 256 +}; +const charset_spec charset_CS_ISO8859_14 = { + CS_ISO8859_14, read_sbcs, write_sbcs, &data_CS_ISO8859_14 +}; + +static const sbcs_data data_CS_ISO8859_15 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20ac, 0x00a5, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x017d, 0x00b5, 0x00b6, 0x00b7, + 0x017e, 0x00b9, 0x00ba, 0x00bb, 0x0152, 0x0153, 0x0178, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0xbc, 0xbd, 0xa6, 0xa8, 0xbe, 0xb4, 0xb8, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_15 = { + CS_ISO8859_15, read_sbcs, write_sbcs, &data_CS_ISO8859_15 +}; + +static const sbcs_data data_CS_ISO8859_16 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0105, 0x0141, 0x20ac, 0x201e, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x0218, 0x00ab, 0x0179, 0x00ad, 0x017a, 0x017b, + 0x00b0, 0x00b1, 0x010c, 0x0142, 0x017d, 0x201d, 0x00b6, 0x00b7, + 0x017e, 0x010d, 0x0219, 0x00bb, 0x0152, 0x0153, 0x0178, 0x017c, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0106, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x0143, 0x00d2, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x015a, + 0x0170, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0118, 0x021a, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x0107, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x0144, 0x00f2, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x015b, + 0x0171, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0119, 0x021b, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xa9, 0xab, 0xad, 0xb0, 0xb1, 0xb6, + 0xb7, 0xbb, 0xc0, 0xc1, 0xc2, 0xc4, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd2, 0xd3, 0xd4, 0xd6, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf2, + 0xf3, 0xf4, 0xf6, 0xf9, 0xfa, 0xfb, 0xfc, 0xff, + 0xc3, 0xe3, 0xa1, 0xa2, 0xc5, 0xe5, 0xb2, 0xb9, + 0xd0, 0xf0, 0xdd, 0xfd, 0xa3, 0xb3, 0xd1, 0xf1, + 0xd5, 0xf5, 0xbc, 0xbd, 0xd7, 0xf7, 0xa6, 0xa8, + 0xd8, 0xf8, 0xbe, 0xac, 0xae, 0xaf, 0xbf, 0xb4, + 0xb8, 0xaa, 0xba, 0xde, 0xfe, 0xb5, 0xa5, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_16 = { + CS_ISO8859_16, read_sbcs, write_sbcs, &data_CS_ISO8859_16 +}; + +static const sbcs_data data_CS_ISO8859_1_X11 = { + { + 0x0020, 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0x23ba, 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x1c, 0x1d, 0x1a, 0x1b, 0x10, 0x11, 0x13, 0x14, + 0x03, 0x06, 0x0a, 0x04, 0x05, 0x09, 0x12, 0x19, + 0x0d, 0x0c, 0x0e, 0x0b, 0x15, 0x16, 0x18, 0x17, + 0x0f, 0x02, 0x01 + }, + 251 +}; +const charset_spec charset_CS_ISO8859_1_X11 = { + CS_ISO8859_1_X11, read_sbcs, write_sbcs, &data_CS_ISO8859_1_X11 +}; + +static const sbcs_data data_CS_CP437 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, + 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, + 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0x9b, 0x9c, 0x9d, 0xa6, 0xae, 0xaa, + 0xf8, 0xf1, 0xfd, 0xe6, 0xfa, 0xa7, 0xaf, 0xac, + 0xab, 0xa8, 0x8e, 0x8f, 0x92, 0x80, 0x90, 0xa5, + 0x99, 0x9a, 0xe1, 0x85, 0xa0, 0x83, 0x84, 0x86, + 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, + 0x8c, 0x8b, 0xa4, 0x95, 0xa2, 0x93, 0x94, 0xf6, + 0x97, 0xa3, 0x96, 0x81, 0x98, 0x9f, 0xe2, 0xe9, + 0xe4, 0xe8, 0xea, 0xe0, 0xeb, 0xee, 0xe3, 0xe5, + 0xe7, 0xed, 0xfc, 0x9e, 0xf9, 0xfb, 0xec, 0xef, + 0xf7, 0xf0, 0xf3, 0xf2, 0xa9, 0xf4, 0xf5, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP437 = { + CS_CP437, read_sbcs, write_sbcs, &data_CS_CP437 +}; + +static const sbcs_data data_CS_CP850 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, + 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, + 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, + 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, + 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, + 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, + 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee, + 0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, + 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8, + 0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, + 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8, + 0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, + 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1, + 0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, + 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, + 0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, + 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98, + 0xd5, 0x9f, 0xf2, 0xc4, 0xb3, 0xda, 0xbf, 0xc0, + 0xd9, 0xc3, 0xb4, 0xc2, 0xc1, 0xc5, 0xcd, 0xba, + 0xc9, 0xbb, 0xc8, 0xbc, 0xcc, 0xb9, 0xcb, 0xca, + 0xce, 0xdf, 0xdc, 0xdb, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP850 = { + CS_CP850, read_sbcs, write_sbcs, &data_CS_CP850 +}; + +static const sbcs_data data_CS_CP866 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040e, 0x045e, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x2116, 0x00a4, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xfd, 0xf8, 0xfa, 0xf0, 0xf2, 0xf4, 0xf6, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf1, 0xf3, 0xf5, 0xf7, 0xfc, 0xf9, 0xfb, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP866 = { + CS_CP866, read_sbcs, write_sbcs, &data_CS_CP866 +}; + +static const sbcs_data data_CS_CP852 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x016f, 0x0107, 0x00e7, + 0x0142, 0x00eb, 0x0150, 0x0151, 0x00ee, 0x0179, 0x00c4, 0x0106, + 0x00c9, 0x0139, 0x013a, 0x00f4, 0x00f6, 0x013d, 0x013e, 0x015a, + 0x015b, 0x00d6, 0x00dc, 0x0164, 0x0165, 0x0141, 0x00d7, 0x010d, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x0104, 0x0105, 0x017d, 0x017e, + 0x0118, 0x0119, 0x00ac, 0x017a, 0x010c, 0x015f, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x011a, + 0x015e, 0x2563, 0x2551, 0x2557, 0x255d, 0x017b, 0x017c, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x0102, 0x0103, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, + 0x0111, 0x0110, 0x010e, 0x00cb, 0x010f, 0x0147, 0x00cd, 0x00ce, + 0x011b, 0x2518, 0x250c, 0x2588, 0x2584, 0x0162, 0x016e, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, + 0x0154, 0x00da, 0x0155, 0x0170, 0x00fd, 0x00dd, 0x0163, 0x00b4, + 0x00ad, 0x02dd, 0x02db, 0x02c7, 0x02d8, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x02d9, 0x0171, 0x0158, 0x0159, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xcf, 0xf5, 0xf9, 0xae, 0xaa, 0xf0, 0xf8, + 0xef, 0xf7, 0xaf, 0xb5, 0xb6, 0x8e, 0x80, 0x90, + 0xd3, 0xd6, 0xd7, 0xe0, 0xe2, 0x99, 0x9e, 0xe9, + 0x9a, 0xed, 0xe1, 0xa0, 0x83, 0x84, 0x87, 0x82, + 0x89, 0xa1, 0x8c, 0xa2, 0x93, 0x94, 0xf6, 0xa3, + 0x81, 0xec, 0xc6, 0xc7, 0xa4, 0xa5, 0x8f, 0x86, + 0xac, 0x9f, 0xd2, 0xd4, 0xd1, 0xd0, 0xa8, 0xa9, + 0xb7, 0xd8, 0x91, 0x92, 0x95, 0x96, 0x9d, 0x88, + 0xe3, 0xe4, 0xd5, 0xe5, 0x8a, 0x8b, 0xe8, 0xea, + 0xfc, 0xfd, 0x97, 0x98, 0xb8, 0xad, 0xe6, 0xe7, + 0xdd, 0xee, 0x9b, 0x9c, 0xde, 0x85, 0xeb, 0xfb, + 0x8d, 0xab, 0xbd, 0xbe, 0xa6, 0xa7, 0xf3, 0xf4, + 0xfa, 0xf2, 0xf1, 0xc4, 0xb3, 0xda, 0xbf, 0xc0, + 0xd9, 0xc3, 0xb4, 0xc2, 0xc1, 0xc5, 0xcd, 0xba, + 0xc9, 0xbb, 0xc8, 0xbc, 0xcc, 0xb9, 0xcb, 0xca, + 0xce, 0xdf, 0xdc, 0xdb, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP852 = { + CS_CP852, read_sbcs, write_sbcs, &data_CS_CP852 +}; + +static const sbcs_data data_CS_CP1250 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xae, 0xb0, 0xb1, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xbb, 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, + 0xcd, 0xce, 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, + 0xdd, 0xdf, 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, + 0xed, 0xee, 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, + 0xfd, 0xc3, 0xe3, 0xa5, 0xb9, 0xc6, 0xe6, 0xc8, + 0xe8, 0xcf, 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, + 0xec, 0xc5, 0xe5, 0xbc, 0xbe, 0xa3, 0xb3, 0xd1, + 0xf1, 0xd2, 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, + 0xf8, 0x8c, 0x9c, 0xaa, 0xba, 0x8a, 0x9a, 0xde, + 0xfe, 0x8d, 0x9d, 0xd9, 0xf9, 0xdb, 0xfb, 0x8f, + 0x9f, 0xaf, 0xbf, 0x8e, 0x9e, 0xa1, 0xa2, 0xff, + 0xb2, 0xbd, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1250 = { + CS_CP1250, read_sbcs, write_sbcs, &data_CS_CP1250 +}; + +static const sbcs_data data_CS_CP1251 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020, 0x2021, + 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f, + 0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, + 0x00a0, 0x040e, 0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, + 0x0401, 0x00a9, 0x0404, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, + 0x00b0, 0x00b1, 0x0406, 0x0456, 0x0491, 0x00b5, 0x00b6, 0x00b7, + 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, 0xac, 0xad, + 0xae, 0xb0, 0xb1, 0xb5, 0xb6, 0xb7, 0xbb, 0xa8, + 0x80, 0x81, 0xaa, 0xbd, 0xb2, 0xaf, 0xa3, 0x8a, + 0x8c, 0x8e, 0x8d, 0xa1, 0x8f, 0xc0, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xb8, 0x90, 0x83, + 0xba, 0xbe, 0xb3, 0xbf, 0xbc, 0x9a, 0x9c, 0x9e, + 0x9d, 0xa2, 0x9f, 0xa5, 0xb4, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0x88, 0xb9, 0x99 + }, + 255 +}; +const charset_spec charset_CS_CP1251 = { + CS_CP1251, read_sbcs, write_sbcs, &data_CS_CP1251 +}; + +static const sbcs_data data_CS_CP1252 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , 0x017d, ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , 0x017e, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x8c, 0x9c, 0x8a, 0x9a, 0x9f, 0x8e, 0x9e, 0x83, + 0x88, 0x98, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1252 = { + CS_CP1252, read_sbcs, write_sbcs, &data_CS_CP1252 +}; + +static const sbcs_data data_CS_CP1253 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x0385, 0x0386, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x00b5, 0x00b6, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb5, 0xb6, 0xb7, 0xbb, 0xbd, 0x83, 0xb4, 0xa1, + 0xa2, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, 0xbf, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0x96, 0x97, 0xaf, + 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, + 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 239 +}; +const charset_spec charset_CS_CP1253 = { + CS_CP1253, read_sbcs, write_sbcs, &data_CS_CP1253 +}; + +static const sbcs_data data_CS_CP1254 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0x8c, 0x9c, + 0xde, 0xfe, 0x8a, 0x9a, 0x9f, 0x83, 0x88, 0x98, + 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, + 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, + 0x99 + }, + 249 +}; +const charset_spec charset_CS_CP1254 = { + CS_CP1254, read_sbcs, write_sbcs, &data_CS_CP1254 +}; + +static const sbcs_data data_CS_CP1255 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20aa, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, + 0x05b8, 0x05b9, ERROR , 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf, + 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f0, 0x05f1, 0x05f2, 0x05f3, + 0x05f4, ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xaa, 0xba, 0x83, + 0x88, 0x98, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0xa4, 0x80, + 0x99 + }, + 233 +}; +const charset_spec charset_CS_CP1255 = { + CS_CP1255, read_sbcs, write_sbcs, &data_CS_CP1255 +}; + +static const sbcs_data data_CS_CP1256 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x067e, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06af, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x06a9, 0x2122, 0x0691, 0x203a, 0x0153, 0x200c, 0x200d, 0x06ba, + 0x00a0, 0x060c, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x06be, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x061b, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x061f, + 0x06c1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00d7, + 0x0637, 0x0638, 0x0639, 0x063a, 0x0640, 0x0641, 0x0642, 0x0643, + 0x00e0, 0x0644, 0x00e2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0649, 0x064a, 0x00ee, 0x00ef, + 0x064b, 0x064c, 0x064d, 0x064e, 0x00f4, 0x064f, 0x0650, 0x00f7, + 0x0651, 0x00f9, 0x0652, 0x00fb, 0x00fc, 0x200e, 0x200f, 0x06d2 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xd7, 0xe0, 0xe2, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf7, + 0xf9, 0xfb, 0xfc, 0x8c, 0x9c, 0x83, 0x88, 0xa1, + 0xba, 0xbf, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe1, 0xe3, 0xe4, 0xe5, 0xe6, 0xec, 0xed, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf5, 0xf6, 0xf8, 0xfa, 0x8a, + 0x81, 0x8d, 0x8f, 0x9a, 0x8e, 0x98, 0x90, 0x9f, + 0xaa, 0xc0, 0xff, 0x9d, 0x9e, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 256 +}; +const charset_spec charset_CS_CP1256 = { + CS_CP1256, read_sbcs, write_sbcs, &data_CS_CP1256 +}; + +static const sbcs_data data_CS_CP1257 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , 0x00a8, 0x02c7, 0x00b8, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , 0x00af, 0x02db, ERROR , + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, ERROR , 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0x8d, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0x9d, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0x8f, 0xb9, 0xbb, + 0xbc, 0xbd, 0xbe, 0xc4, 0xc5, 0xaf, 0xc9, 0xd3, + 0xd5, 0xd6, 0xd7, 0xa8, 0xdc, 0xdf, 0xe4, 0xe5, + 0xbf, 0xe9, 0xf3, 0xf5, 0xf6, 0xf7, 0xb8, 0xfc, + 0xc2, 0xe2, 0xc0, 0xe0, 0xc3, 0xe3, 0xc8, 0xe8, + 0xc7, 0xe7, 0xcb, 0xeb, 0xc6, 0xe6, 0xcc, 0xec, + 0xce, 0xee, 0xc1, 0xe1, 0xcd, 0xed, 0xcf, 0xef, + 0xd9, 0xf9, 0xd1, 0xf1, 0xd2, 0xf2, 0xd4, 0xf4, + 0xaa, 0xba, 0xda, 0xfa, 0xd0, 0xf0, 0xdb, 0xfb, + 0xd8, 0xf8, 0xca, 0xea, 0xdd, 0xfd, 0xde, 0xfe, + 0x8e, 0xff, 0x9e, 0x96, 0x97, 0x91, 0x92, 0x82, + 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, + 0x8b, 0x9b, 0x80, 0x99 + }, + 244 +}; +const charset_spec charset_CS_CP1257 = { + CS_CP1257, read_sbcs, write_sbcs, &data_CS_CP1257 +}; + +static const sbcs_data data_CS_CP1258 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x0300, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x00d1, 0x0309, 0x00d3, 0x00d4, 0x01a0, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x01af, 0x0303, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0301, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x00f1, 0x0323, 0x00f3, 0x00f4, 0x01a1, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x01b0, 0x20ab, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xd1, 0xd3, + 0xd4, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xed, 0xee, 0xef, 0xf1, + 0xf3, 0xf4, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xc3, 0xe3, 0xd0, 0xf0, 0x8c, 0x9c, + 0x9f, 0x83, 0xd5, 0xf5, 0xdd, 0xfd, 0x88, 0x98, + 0xcc, 0xec, 0xde, 0xd2, 0xf2, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0xfe, 0x80, 0x99 + }, + 247 +}; +const charset_spec charset_CS_CP1258 = { + CS_CP1258, read_sbcs, write_sbcs, &data_CS_CP1258 +}; + +static const sbcs_data data_CS_KOI8_R = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xe1, + 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, 0xf6, 0xfa, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf2, + 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, 0xe3, 0xfe, 0xfb, + 0xfd, 0xff, 0xf9, 0xf8, 0xfc, 0xe0, 0xf1, 0xc1, + 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, 0xd6, 0xda, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd2, + 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, 0xc3, 0xde, 0xdb, + 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, 0xc0, 0xd1, 0xa3, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_R = { + CS_KOI8_R, read_sbcs, write_sbcs, &data_CS_KOI8_R +}; + +static const sbcs_data data_CS_KOI8_U = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x0491, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x0490, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xb4, + 0xb6, 0xb7, 0xe1, 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, + 0xf6, 0xfa, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf0, 0xf2, 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, + 0xe3, 0xfe, 0xfb, 0xfd, 0xff, 0xf9, 0xf8, 0xfc, + 0xe0, 0xf1, 0xc1, 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, + 0xd6, 0xda, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd2, 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, + 0xc3, 0xde, 0xdb, 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, + 0xc0, 0xd1, 0xa3, 0xa4, 0xa6, 0xa7, 0xbd, 0xad, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa5, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb5, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_U = { + CS_KOI8_U, read_sbcs, write_sbcs, &data_CS_KOI8_U +}; + +static const sbcs_data data_CS_MAC_ROMAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, + 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, + 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, + 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, 0xdb, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN = { + CS_MAC_ROMAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN +}; + +static const sbcs_data data_CS_MAC_TURKISH = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x011e, 0x011f, 0x0130, 0x0131, 0x015e, 0x015f, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, ERROR , 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xda, 0xdb, 0xdc, 0xdd, 0xce, 0xcf, 0xde, 0xdf, + 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, + 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, + 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, + 0xe4, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, + 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 255 +}; +const charset_spec charset_CS_MAC_TURKISH = { + CS_MAC_TURKISH, read_sbcs, write_sbcs, &data_CS_MAC_TURKISH +}; + +static const sbcs_data data_CS_MAC_CROATIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x20ac, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xa4, 0xac, 0xd9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, 0xb5, + 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, 0xe7, + 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, 0x83, + 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, + 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, + 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, 0xe6, + 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, 0xa9, + 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, 0xf7, + 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN = { + CS_MAC_CROATIAN, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN +}; + +static const sbcs_data data_CS_MAC_ICELAND = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0xdc, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, 0x87, + 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, + 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, + 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, + 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, + 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, + 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, 0xe4, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND = { + CS_MAC_ICELAND, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND +}; + +static const sbcs_data data_CS_MAC_ROMANIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, 0x83, + 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, 0x86, + 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0x8d, + 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0x9d, + 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, 0xf6, + 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, + 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, + 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN = { + CS_MAC_ROMANIAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN +}; + +static const sbcs_data data_CS_MAC_GREEK = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x20ac, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, 0x00ad + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0xff, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x9c, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_GREEK = { + CS_MAC_GREEK, read_sbcs, write_sbcs, &data_CS_MAC_GREEK +}; + +static const sbcs_data data_CS_MAC_CYRILLIC = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x20ac + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, 0xa1, + 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, 0xab, + 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, 0xbe, + 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, 0xb9, + 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, 0xce, + 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xff, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_CYRILLIC = { + CS_MAC_CYRILLIC, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC +}; + +static const sbcs_data data_CS_MAC_THAI = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00ab, 0x00bb, 0x2026, 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x201c, 0x201d, 0x0e4d, + ERROR , 0x2022, 0x0e31, 0x0e47, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x2018, 0x2019, ERROR , + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, 0x2060, 0x200b, 0x2013, 0x2014, 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x2122, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x00ae, 0x00a9, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xfb, 0x80, 0xfa, 0x81, 0xa1, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, + 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xdc, 0xdd, 0xde, 0x9d, 0x9e, 0x8d, 0x8e, + 0x91, 0x82, 0xdb, 0xee + }, + 228 +}; +const charset_spec charset_CS_MAC_THAI = { + CS_MAC_THAI, read_sbcs, write_sbcs, &data_CS_MAC_THAI +}; + +static const sbcs_data data_CS_MAC_CENTEURO = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x0100, 0x0101, 0x00c9, 0x0104, 0x00d6, 0x00dc, 0x00e1, + 0x0105, 0x010c, 0x00e4, 0x010d, 0x0106, 0x0107, 0x00e9, 0x0179, + 0x017a, 0x010e, 0x00ed, 0x010f, 0x0112, 0x0113, 0x0116, 0x00f3, + 0x0117, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x011a, 0x011b, 0x00fc, + 0x2020, 0x00b0, 0x0118, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x0119, 0x00a8, 0x2260, 0x0123, 0x012e, + 0x012f, 0x012a, 0x2264, 0x2265, 0x012b, 0x0136, 0x2202, 0x2211, + 0x0142, 0x013b, 0x013c, 0x013d, 0x013e, 0x0139, 0x013a, 0x0145, + 0x0146, 0x0143, 0x00ac, 0x221a, 0x0144, 0x0147, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x0148, 0x0150, 0x00d5, 0x0151, 0x014c, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x014d, 0x0154, 0x0155, 0x0158, 0x2039, 0x203a, 0x0159, 0x0156, + 0x0157, 0x0160, 0x201a, 0x201e, 0x0161, 0x015a, 0x015b, 0x00c1, + 0x0164, 0x0165, 0x00cd, 0x017d, 0x017e, 0x016a, 0x00d3, 0x00d4, + 0x016b, 0x016e, 0x00da, 0x016f, 0x0170, 0x0171, 0x0172, 0x0173, + 0x00dd, 0x00fd, 0x0137, 0x017b, 0x0141, 0x017c, 0x0122, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xac, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xa6, 0xc8, 0xe7, 0x80, 0x83, 0xea, 0xee, + 0xef, 0xcd, 0x85, 0xf2, 0x86, 0xf8, 0xa7, 0x87, + 0x8a, 0x8e, 0x92, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9c, 0x9f, 0xf9, 0x81, 0x82, 0x84, 0x88, 0x8c, + 0x8d, 0x89, 0x8b, 0x91, 0x93, 0x94, 0x95, 0x96, + 0x98, 0xa2, 0xab, 0x9d, 0x9e, 0xfe, 0xae, 0xb1, + 0xb4, 0xaf, 0xb0, 0xb5, 0xfa, 0xbd, 0xbe, 0xb9, + 0xba, 0xbb, 0xbc, 0xfc, 0xb8, 0xc1, 0xc4, 0xbf, + 0xc0, 0xc5, 0xcb, 0xcf, 0xd8, 0xcc, 0xce, 0xd9, + 0xda, 0xdf, 0xe0, 0xdb, 0xde, 0xe5, 0xe6, 0xe1, + 0xe4, 0xe8, 0xe9, 0xed, 0xf0, 0xf1, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0x8f, 0x90, 0xfb, 0xfd, 0xeb, + 0xec, 0xff, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xdc, 0xdd, 0xaa, + 0xb6, 0xc6, 0xb7, 0xc3, 0xad, 0xb2, 0xb3, 0xd7 + }, + 256 +}; +const charset_spec charset_CS_MAC_CENTEURO = { + CS_MAC_CENTEURO, read_sbcs, write_sbcs, &data_CS_MAC_CENTEURO +}; + +static const sbcs_data data_CS_MAC_SYMBOL = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x2200, 0x0023, 0x2203, 0x0025, 0x0026, 0x220d, + 0x0028, 0x0029, 0x2217, 0x002b, 0x002c, 0x2212, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x2245, 0x0391, 0x0392, 0x03a7, 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, 0x039b, 0x039c, 0x039d, 0x039f, + 0x03a0, 0x0398, 0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0x005b, 0x2234, 0x005d, 0x22a5, 0x005f, + 0xf8e5, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03d5, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03b8, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0x007b, 0x007c, 0x007d, 0x223c, 0x007f, + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x20ac, 0x03d2, 0x2032, 0x2264, 0x2044, 0x221e, 0x0192, 0x2663, + 0x2666, 0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, + 0x00b0, 0x00b1, 0x2033, 0x2265, 0x00d7, 0x221d, 0x2202, 0x2022, + 0x00f7, 0x2260, 0x2261, 0x2248, 0x2026, 0xf8e6, 0x23af, 0x21b5, + 0x2135, 0x2111, 0x211c, 0x2118, 0x2297, 0x2295, 0x2205, 0x2229, + 0x222a, 0x2283, 0x2287, 0x2284, 0x2282, 0x2286, 0x2208, 0x2209, + 0x2220, 0x2207, 0x00ae, 0x00a9, 0x2122, 0x220f, 0x221a, 0x22c5, + 0x00ac, 0x2227, 0x2228, 0x21d4, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x22c4, 0x3008, 0x00ae, 0x00a9, 0x2122, 0x2211, 0x239b, 0x239c, + 0x239d, 0x23a1, 0x23a2, 0x23a3, 0x23a7, 0x23a8, 0x23a9, 0x23aa, + 0xf8ff, 0x3009, 0x222b, 0x2320, 0x23ae, 0x2321, 0x239e, 0x239f, + 0x23a0, 0x23a4, 0x23a5, 0x23a6, 0x23ab, 0x23ac, 0x23ad, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x23, 0x25, 0x26, 0x28, 0x29, 0x2b, + 0x2c, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, + 0x3d, 0x3e, 0x3f, 0x5b, 0x5d, 0x5f, 0x7b, 0x7c, + 0x7d, 0x7f, 0xd3, 0xd8, 0xd2, 0xb0, 0xb1, 0xb4, + 0xb8, 0xa6, 0x41, 0x42, 0x47, 0x44, 0x45, 0x5a, + 0x48, 0x51, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x58, + 0x4f, 0x50, 0x52, 0x53, 0x54, 0x55, 0x46, 0x43, + 0x59, 0x57, 0x61, 0x62, 0x67, 0x64, 0x65, 0x7a, + 0x68, 0x71, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x78, + 0x6f, 0x70, 0x72, 0x56, 0x73, 0x74, 0x75, 0x66, + 0x63, 0x79, 0x77, 0x4a, 0xa1, 0x6a, 0x76, 0xb7, + 0xbc, 0xa2, 0xb2, 0xa4, 0xa0, 0xc1, 0xc3, 0xc2, + 0xd4, 0xc0, 0xac, 0xad, 0xae, 0xaf, 0xab, 0xbf, + 0xdc, 0xdd, 0xde, 0xdf, 0xdb, 0x22, 0xb6, 0x24, + 0xc6, 0xd1, 0xce, 0xcf, 0x27, 0xd5, 0xe5, 0x2d, + 0x2a, 0xd6, 0xb5, 0xa5, 0xd0, 0xd9, 0xda, 0xc7, + 0xc8, 0xf2, 0x5c, 0x7e, 0x40, 0xbb, 0xb9, 0xba, + 0xa3, 0xb3, 0xcc, 0xc9, 0xcb, 0xcd, 0xca, 0xc5, + 0xc4, 0x5e, 0xe0, 0xd7, 0xf3, 0xf5, 0xe6, 0xe7, + 0xe8, 0xf6, 0xf7, 0xf8, 0xe9, 0xea, 0xeb, 0xf9, + 0xfa, 0xfb, 0xec, 0xed, 0xee, 0xef, 0xfc, 0xfd, + 0xfe, 0xf4, 0xbe, 0xaa, 0xa7, 0xa9, 0xa8, 0xe1, + 0xf1, 0x60, 0xbd, 0xf0 + }, + 220 +}; +const charset_spec charset_CS_MAC_SYMBOL = { + CS_MAC_SYMBOL, read_sbcs, write_sbcs, &data_CS_MAC_SYMBOL +}; + +static const sbcs_data data_CS_MAC_DINGBATS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x2701, 0x2702, 0x2703, 0x2704, 0x260e, 0x2706, 0x2707, + 0x2708, 0x2709, 0x261b, 0x261e, 0x270c, 0x270d, 0x270e, 0x270f, + 0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, + 0x2718, 0x2719, 0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, + 0x2720, 0x2721, 0x2722, 0x2723, 0x2724, 0x2725, 0x2726, 0x2727, + 0x2605, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e, 0x272f, + 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, + 0x2738, 0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, + 0x2740, 0x2741, 0x2742, 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, + 0x2748, 0x2749, 0x274a, 0x274b, 0x25cf, 0x274d, 0x25a0, 0x274f, + 0x2750, 0x2751, 0x2752, 0x25b2, 0x25bc, 0x25c6, 0x2756, 0x25d7, + 0x2758, 0x2759, 0x275a, 0x275b, 0x275c, 0x275d, 0x275e, 0x007f, + 0x2768, 0x2769, 0x276a, 0x276b, 0x276c, 0x276d, 0x276e, 0x276f, + 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, 0x2775, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, + 0x2663, 0x2666, 0x2665, 0x2660, 0x2460, 0x2461, 0x2462, 0x2463, + 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x2776, 0x2777, + 0x2778, 0x2779, 0x277a, 0x277b, 0x277c, 0x277d, 0x277e, 0x277f, + 0x2780, 0x2781, 0x2782, 0x2783, 0x2784, 0x2785, 0x2786, 0x2787, + 0x2788, 0x2789, 0x278a, 0x278b, 0x278c, 0x278d, 0x278e, 0x278f, + 0x2790, 0x2791, 0x2792, 0x2793, 0x2794, 0x2192, 0x2194, 0x2195, + 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e, 0x279f, + 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, + 0x27a8, 0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, + ERROR , 0x27b1, 0x27b2, 0x27b3, 0x27b4, 0x27b5, 0x27b6, 0x27b7, + 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd, 0x27be, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x7f, 0xd5, 0xd6, 0xd7, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0x6e, + 0x73, 0x74, 0x75, 0x6c, 0x77, 0x48, 0x25, 0x2a, + 0x2b, 0xab, 0xa8, 0xaa, 0xa9, 0x21, 0x22, 0x23, + 0x24, 0x26, 0x27, 0x28, 0x29, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6f, 0x70, 0x71, + 0x72, 0x76, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe + }, + 235 +}; +const charset_spec charset_CS_MAC_DINGBATS = { + CS_MAC_DINGBATS, read_sbcs, write_sbcs, &data_CS_MAC_DINGBATS +}; + +static const sbcs_data data_CS_MAC_ROMAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xd8, 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, + 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, + 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, + 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN_OLD = { + CS_MAC_ROMAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN_OLD +}; + +static const sbcs_data data_CS_MAC_CROATIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x00a4, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xa4, 0xac, 0xd9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, + 0x83, 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, + 0xe6, 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, + 0xa9, 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, + 0xf7, 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, + 0xd2, 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN_OLD = { + CS_MAC_CROATIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN_OLD +}; + +static const sbcs_data data_CS_MAC_ICELAND_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0xdc, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, + 0xf4, 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, + 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, + 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, + 0x9d, 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, + 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, + 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, + 0xe4, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND_OLD = { + CS_MAC_ICELAND_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND_OLD +}; + +static const sbcs_data data_CS_MAC_ROMANIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, + 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, + 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, + 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN_OLD = { + CS_MAC_ROMANIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN_OLD +}; + +static const sbcs_data data_CS_MAC_GREEK_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x00ad, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0x9c, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_GREEK_OLD = { + CS_MAC_GREEK_OLD, read_sbcs, write_sbcs, &data_CS_MAC_GREEK_OLD +}; + +static const sbcs_data data_CS_MAC_CYRILLIC_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x2022, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa2, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, + 0xa8, 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, + 0xdd, 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, + 0xbc, 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, + 0xaf, 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, + 0xcc, 0xce, 0xd9, 0xdb, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, 0xaa, + 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_CYRILLIC_OLD = { + CS_MAC_CYRILLIC_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC_OLD +}; + +static const sbcs_data data_CS_MAC_UKRAINE = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, + 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, + 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, + 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, + 0xce, 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, + 0xd5, 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_UKRAINE = { + CS_MAC_UKRAINE, read_sbcs, write_sbcs, &data_CS_MAC_UKRAINE +}; + +static const sbcs_data data_CS_MAC_VT100 = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x20ac, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xb4, 0xba, 0xa4, 0xac, 0xa9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, 0xab, + 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, 0xc5, + 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, 0x80, + 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, 0xed, + 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, 0xef, + 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, 0x86, + 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, 0xb9, + 0xd0, 0xd1, 0xd7, 0xc9, 0xdb, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100 = { + CS_MAC_VT100, read_sbcs, write_sbcs, &data_CS_MAC_VT100 +}; + +static const sbcs_data data_CS_MAC_VT100_OLD = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x00a4, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xdb, 0xb4, 0xba, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, + 0xab, 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, + 0xc5, 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, + 0x80, 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, + 0xed, 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, + 0xef, 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, + 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, + 0x9f, 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, + 0xb9, 0xd0, 0xd1, 0xd7, 0xc9, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100_OLD = { + CS_MAC_VT100_OLD, read_sbcs, write_sbcs, &data_CS_MAC_VT100_OLD +}; + +static const sbcs_data data_CS_VISCII = { + { + 0x0000, 0x0001, 0x1eb2, 0x0003, 0x0004, 0x1eb4, 0x1eaa, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x1ef6, 0x0015, 0x0016, 0x0017, + 0x0018, 0x1ef8, 0x001a, 0x001b, 0x001c, 0x001d, 0x1ef4, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x1ea0, 0x1eae, 0x1eb0, 0x1eb6, 0x1ea4, 0x1ea6, 0x1ea8, 0x1eac, + 0x1ebc, 0x1eb8, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, 0x1ed0, + 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, 0x1ee2, 0x1eda, 0x1edc, 0x1ede, + 0x1eca, 0x1ece, 0x1ecc, 0x1ec8, 0x1ee6, 0x0168, 0x1ee4, 0x1ef2, + 0x00d5, 0x1eaf, 0x1eb1, 0x1eb7, 0x1ea5, 0x1ea7, 0x1ea8, 0x1ead, + 0x1ebd, 0x1eb9, 0x1ebf, 0x1ec1, 0x1ec3, 0x1ec5, 0x1ec7, 0x1ed1, + 0x1ed3, 0x1ed5, 0x1ed7, 0x1ee0, 0x01a0, 0x1ed9, 0x1edd, 0x1edf, + 0x1ecb, 0x1ef0, 0x1ee8, 0x1eea, 0x1eec, 0x01a1, 0x1edb, 0x01af, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x1ea2, 0x0102, 0x1eb3, 0x1eb5, + 0x00c8, 0x00c9, 0x00ca, 0x1eba, 0x00cc, 0x00cd, 0x0128, 0x1ef3, + 0x0110, 0x1ee9, 0x00d2, 0x00d3, 0x00d4, 0x1ea1, 0x1ef7, 0x1eeb, + 0x1eed, 0x00d9, 0x00da, 0x1ef9, 0x1ef5, 0x00dd, 0x1ee1, 0x01b0, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x1ea3, 0x0103, 0x1eef, 0x1eab, + 0x00e8, 0x00e9, 0x00ea, 0x1ebb, 0x00ec, 0x00ed, 0x0129, 0x1ec9, + 0x0111, 0x1ef1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x1ecf, 0x1ecd, + 0x1ee5, 0x00f9, 0x00fa, 0x0169, 0x1ee7, 0x00fd, 0x1ee3, 0x1eee + }, + { + 0x00, 0x01, 0x03, 0x04, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0xc0, 0xc1, 0xc2, 0xc3, 0xc8, 0xc9, + 0xca, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xa0, 0xd9, + 0xda, 0xdd, 0xe0, 0xe1, 0xe2, 0xe3, 0xe8, 0xe9, + 0xea, 0xec, 0xed, 0xf2, 0xf3, 0xf4, 0xf5, 0xf9, + 0xfa, 0xfd, 0xc5, 0xe5, 0xd0, 0xf0, 0xce, 0xee, + 0x9d, 0xfb, 0xb4, 0xbd, 0xbf, 0xdf, 0x80, 0xd5, + 0xc4, 0xe4, 0x84, 0xa4, 0x85, 0xa5, 0x86, 0x06, + 0xe7, 0x87, 0xa7, 0x81, 0xa1, 0x82, 0xa2, 0x02, + 0xc6, 0x05, 0xc7, 0x83, 0xa3, 0x89, 0xa9, 0xcb, + 0xeb, 0x88, 0xa8, 0x8a, 0xaa, 0x8b, 0xab, 0x8c, + 0xac, 0x8d, 0xad, 0x8e, 0xae, 0x9b, 0xef, 0x98, + 0xb8, 0x9a, 0xf7, 0x99, 0xf6, 0x8f, 0xaf, 0x90, + 0xb0, 0x91, 0xb1, 0x92, 0xb2, 0x93, 0xb5, 0x95, + 0xbe, 0x96, 0xb6, 0x97, 0xb7, 0xb3, 0xde, 0x94, + 0xfe, 0x9e, 0xf8, 0x9c, 0xfc, 0xba, 0xd1, 0xbb, + 0xd7, 0xbc, 0xd8, 0xff, 0xe6, 0xb9, 0xf1, 0x9f, + 0xcf, 0x1e, 0xdc, 0x14, 0xd6, 0x19, 0xdb + }, + 255 +}; +const charset_spec charset_CS_VISCII = { + CS_VISCII, read_sbcs, write_sbcs, &data_CS_VISCII +}; + +static const sbcs_data data_CS_HP_ROMAN8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00c0, 0x00c2, 0x00c8, 0x00ca, 0x00cb, 0x00ce, 0x00cf, + 0x00b4, 0x02cb, 0x02c6, 0x00a8, 0x02dc, 0x00d9, 0x00db, 0x20a4, + 0x00af, 0x00dd, 0x00fd, 0x00b0, 0x00c7, 0x00e7, 0x00d1, 0x00f1, + 0x00a1, 0x00bf, 0x00a4, 0x00a3, 0x00a5, 0x00a7, 0x0192, 0x00a2, + 0x00e2, 0x00ea, 0x00f4, 0x00fb, 0x00e1, 0x00e9, 0x00f3, 0x00fa, + 0x00e0, 0x00e8, 0x00f2, 0x00f9, 0x00e4, 0x00eb, 0x00f6, 0x00fc, + 0x00c5, 0x00ee, 0x00d8, 0x00c6, 0x00e5, 0x00ed, 0x00f8, 0x00e6, + 0x00c4, 0x00ec, 0x00d6, 0x00dc, 0x00c9, 0x00ef, 0x00df, 0x00d4, + 0x00c1, 0x00c3, 0x00e3, 0x00d0, 0x00f0, 0x00cd, 0x00cc, 0x00d3, + 0x00d2, 0x00d5, 0x00f5, 0x0160, 0x0161, 0x00da, 0x0178, 0x00ff, + 0x00de, 0x00fe, 0x00b7, 0x00b5, 0x00b6, 0x00be, 0x2014, 0x00bc, + 0x00bd, 0x00aa, 0x00ba, 0x00ab, 0x25a0, 0x00bb, 0x00b1, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xb8, 0xbf, 0xbb, 0xba, 0xbc, 0xbd, 0xab, + 0xf9, 0xfb, 0xb0, 0xb3, 0xfe, 0xa8, 0xf3, 0xf4, + 0xf2, 0xfa, 0xfd, 0xf7, 0xf8, 0xf5, 0xb9, 0xa1, + 0xe0, 0xa2, 0xe1, 0xd8, 0xd0, 0xd3, 0xb4, 0xa3, + 0xdc, 0xa4, 0xa5, 0xe6, 0xe5, 0xa6, 0xa7, 0xe3, + 0xb6, 0xe8, 0xe7, 0xdf, 0xe9, 0xda, 0xd2, 0xad, + 0xed, 0xae, 0xdb, 0xb1, 0xf0, 0xde, 0xc8, 0xc4, + 0xc0, 0xe2, 0xcc, 0xd4, 0xd7, 0xb5, 0xc9, 0xc5, + 0xc1, 0xcd, 0xd9, 0xd5, 0xd1, 0xdd, 0xe4, 0xb7, + 0xca, 0xc6, 0xc2, 0xea, 0xce, 0xd6, 0xcb, 0xc7, + 0xc3, 0xcf, 0xb2, 0xf1, 0xef, 0xeb, 0xec, 0xee, + 0xbe, 0xaa, 0xa9, 0xac, 0xf6, 0xaf, 0xfc + }, + 255 +}; +const charset_spec charset_CS_HP_ROMAN8 = { + CS_HP_ROMAN8, read_sbcs, write_sbcs, &data_CS_HP_ROMAN8 +}; + +static const sbcs_data data_CS_DEC_MCS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + ERROR , 0x00a1, 0x00a2, 0x00a3, ERROR , 0x00a5, ERROR , 0x00a7, + 0x00a4, 0x00a9, 0x00aa, 0x00ab, ERROR , ERROR , ERROR , ERROR , + 0x00b0, 0x00b1, 0x00b2, 0x00b3, ERROR , 0x00b5, 0x00b6, 0x00b7, + ERROR , 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, ERROR , 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0152, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0178, ERROR , 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0153, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00ff, ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa1, 0xa2, 0xa3, 0xa8, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, 0xb6, 0xb7, + 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbf, 0xc0, 0xc1, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xd7, 0xf7, + 0xdd + }, + 241 +}; +const charset_spec charset_CS_DEC_MCS = { + CS_DEC_MCS, read_sbcs, write_sbcs, &data_CS_DEC_MCS +}; + +#else /* ENUM_CHARSETS */ + +ENUM_CHARSET(CS_ISO8859_1) +ENUM_CHARSET(CS_ISO8859_2) +ENUM_CHARSET(CS_ISO8859_3) +ENUM_CHARSET(CS_ISO8859_4) +ENUM_CHARSET(CS_ISO8859_5) +ENUM_CHARSET(CS_ISO8859_6) +ENUM_CHARSET(CS_ISO8859_7) +ENUM_CHARSET(CS_ISO8859_8) +ENUM_CHARSET(CS_ISO8859_9) +ENUM_CHARSET(CS_ISO8859_10) +ENUM_CHARSET(CS_ISO8859_11) +ENUM_CHARSET(CS_ISO8859_13) +ENUM_CHARSET(CS_ISO8859_14) +ENUM_CHARSET(CS_ISO8859_15) +ENUM_CHARSET(CS_ISO8859_16) +ENUM_CHARSET(CS_ISO8859_1_X11) +ENUM_CHARSET(CS_CP437) +ENUM_CHARSET(CS_CP850) +ENUM_CHARSET(CS_CP866) +ENUM_CHARSET(CS_CP852) +ENUM_CHARSET(CS_CP1250) +ENUM_CHARSET(CS_CP1251) +ENUM_CHARSET(CS_CP1252) +ENUM_CHARSET(CS_CP1253) +ENUM_CHARSET(CS_CP1254) +ENUM_CHARSET(CS_CP1255) +ENUM_CHARSET(CS_CP1256) +ENUM_CHARSET(CS_CP1257) +ENUM_CHARSET(CS_CP1258) +ENUM_CHARSET(CS_KOI8_R) +ENUM_CHARSET(CS_KOI8_U) +ENUM_CHARSET(CS_MAC_ROMAN) +ENUM_CHARSET(CS_MAC_TURKISH) +ENUM_CHARSET(CS_MAC_CROATIAN) +ENUM_CHARSET(CS_MAC_ICELAND) +ENUM_CHARSET(CS_MAC_ROMANIAN) +ENUM_CHARSET(CS_MAC_GREEK) +ENUM_CHARSET(CS_MAC_CYRILLIC) +ENUM_CHARSET(CS_MAC_THAI) +ENUM_CHARSET(CS_MAC_CENTEURO) +ENUM_CHARSET(CS_MAC_SYMBOL) +ENUM_CHARSET(CS_MAC_DINGBATS) +ENUM_CHARSET(CS_MAC_ROMAN_OLD) +ENUM_CHARSET(CS_MAC_CROATIAN_OLD) +ENUM_CHARSET(CS_MAC_ICELAND_OLD) +ENUM_CHARSET(CS_MAC_ROMANIAN_OLD) +ENUM_CHARSET(CS_MAC_GREEK_OLD) +ENUM_CHARSET(CS_MAC_CYRILLIC_OLD) +ENUM_CHARSET(CS_MAC_UKRAINE) +ENUM_CHARSET(CS_MAC_VT100) +ENUM_CHARSET(CS_MAC_VT100_OLD) +ENUM_CHARSET(CS_VISCII) +ENUM_CHARSET(CS_HP_ROMAN8) +ENUM_CHARSET(CS_DEC_MCS) + +#endif /* ENUM_CHARSETS */ diff --git a/netbox/libs/Putty/charset/slookup.c b/netbox/libs/Putty/charset/slookup.c new file mode 100644 index 000000000..7a4d7c28a --- /dev/null +++ b/netbox/libs/Putty/charset/slookup.c @@ -0,0 +1,29 @@ +/* + * slookup.c - static lookup of character sets. + */ + +#include "charset.h" +#include "internal.h" + +#define ENUM_CHARSET(x) extern charset_spec const charset_##x; +#include "enum.c" +#undef ENUM_CHARSET + +static charset_spec const *const cs_table[] = { + +#define ENUM_CHARSET(x) &charset_##x, +#include "enum.c" +#undef ENUM_CHARSET + +}; + +charset_spec const *charset_find_spec(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(cs_table); i++) + if (cs_table[i]->charset == charset) + return cs_table[i]; + + return NULL; +} diff --git a/netbox/libs/Putty/charset/toucs.c b/netbox/libs/Putty/charset/toucs.c new file mode 100644 index 000000000..1df788a64 --- /dev/null +++ b/netbox/libs/Putty/charset/toucs.c @@ -0,0 +1,90 @@ +/* + * toucs.c - convert charsets to Unicode. + */ + +#include "charset.h" +#include "internal.h" + +struct unicode_emit_param { + wchar_t *output; + int outlen; + const wchar_t *errstr; + int errlen; + int stopped; +}; + +static void unicode_emit(void *ctx, long int output) +{ + struct unicode_emit_param *param = (struct unicode_emit_param *)ctx; + wchar_t outval; + wchar_t const *p; + int outlen; + + if (output == ERROR) { + if (param->errstr) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ + p = &outval; + outlen = 1; + } + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct unicode_emit_param param; + + param.output = output; + param.outlen = outlen; + param.errstr = errstr; + param.errlen = errlen; + param.stopped = 0; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->read(spec, (unsigned char)**input, &localstate, + unicode_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + + return param.output - output; +} diff --git a/netbox/libs/Putty/charset/utf8.c b/netbox/libs/Putty/charset/utf8.c new file mode 100644 index 000000000..3c777292d --- /dev/null +++ b/netbox/libs/Putty/charset/utf8.c @@ -0,0 +1,882 @@ +/* + * utf8.c - routines to handle UTF-8. + */ + +#ifndef ENUM_CHARSETS + +#include "charset.h" +#include "internal.h" + +void read_utf8(charset_spec const *, long int, charset_state *, + void (*)(void *, long int), void *); +void write_utf8(charset_spec const *, long int, + charset_state *, void (*)(void *, long int), void *); + +/* + * UTF-8 has no associated data, so `charset' may be ignored. + */ + +void read_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + UNUSEDARG(charset); + + /* + * For reading UTF-8, the `state' word contains: + * + * - in bits 29-31, the number of bytes expected to be in the + * current multibyte character (which we can tell instantly + * from the first byte, of course). + * + * - in bits 26-28, the number of bytes _seen so far_ in the + * current multibyte character. + * + * - in the remainder of the word, the current value of the + * character, which is shifted upwards by 6 bits to + * accommodate each new byte. + * + * As required, the state is zero when we are not in the middle + * of a multibyte character at all. + * + * For example, when reading E9 8D 8B, starting at state=0: + * + * - after E9, the state is 0x64000009 + * - after 8D, the state is 0x6800024d + * - after 8B, the state conceptually becomes 0x6c00934b, at + * which point we notice we've got as many characters as we + * were expecting, output U+934B, and reset the state to + * zero. + * + * Note that the maximum number of bits we might need to store + * in the character value field is 25 (U+7FFFFFFF contains 31 + * bits, but we will never actually store its full value + * because when we receive the last 6 bits in the final + * continuation byte we will output it and revert the state to + * zero). Hence the character value field never collides with + * the byte counts. + */ + + if (input_chr < 0x80) { + /* + * Single-byte character. If the state is nonzero before + * coming here, output an error for an incomplete sequence. + * Then output the character. + */ + if (state->s0 != 0) { + emit(emitctx, ERROR); + state->s0 = 0; + } + emit(emitctx, input_chr); + } else if (input_chr == 0xFE || input_chr == 0xFF) { + /* + * FE and FF bytes should _never_ occur in UTF-8. They are + * automatic errors; if the state was nonzero to start + * with, output a further error for an incomplete sequence. + */ + if (state->s0 != 0) { + emit(emitctx, ERROR); + state->s0 = 0; + } + emit(emitctx, ERROR); + } else if (input_chr >= 0x80 && input_chr < 0xC0) { + /* + * Continuation byte. Output an error for an unexpected + * continuation byte, if the state is zero. + */ + if (state->s0 == 0) { + emit(emitctx, ERROR); + } else { + unsigned long charval; + unsigned long topstuff; + int bytes; + + /* + * Otherwise, accumulate more of the character value. + */ + charval = state->s0 & 0x03ffffffL; + charval = (charval << 6) | (input_chr & 0x3F); + + /* + * Check the byte counts; if we have not reached the + * end of the character, update the state and return. + */ + topstuff = state->s0 & 0xfc000000L; + topstuff += 0x04000000L; /* add one to the byte count */ + if (((topstuff << 3) ^ topstuff) & 0xe0000000L) { + state->s0 = topstuff | charval; + return; + } + + /* + * Now we know we've reached the end of the character. + * `charval' is the Unicode value. We should check for + * various invalid things, and then either output + * charval or an error. In all cases we reset the state + * to zero. + */ + bytes = topstuff >> 29; + state->s0 = 0; + + if (charval >= 0xD800 && charval < 0xE000) { + /* + * Surrogates (0xD800-0xDFFF) may never be encoded + * in UTF-8. A surrogate pair in Unicode should + * have been encoded as a single UTF-8 character + * occupying more than three bytes. + */ + emit(emitctx, ERROR); + } else if (charval == 0xFFFE || charval == 0xFFFF) { + /* + * U+FFFE and U+FFFF are invalid Unicode characters + * and may never be encoded in UTF-8. (This is one + * reason why U+FFFF is our way of signalling an + * error to our `emit' function :-) + */ + emit(emitctx, ERROR); + } else if ((charval <= 0x7FL /* && bytes > 1 */) || + (charval <= 0x7FFL && bytes > 2) || + (charval <= 0xFFFFL && bytes > 3) || + (charval <= 0x1FFFFFL && bytes > 4) || + (charval <= 0x3FFFFFFL && bytes > 5)) { + /* + * Overlong sequences are not to be tolerated, + * under any circumstances. + */ + emit(emitctx, ERROR); + } else { + /* + * Oh, all right. We'll let this one off. + */ + emit(emitctx, charval); + } + } + + } else { + /* + * Lead byte. First output an error for an incomplete + * sequence, if the state is nonzero. + */ + if (state->s0 != 0) + emit(emitctx, ERROR); + + /* + * Now deal with the lead byte: work out the number of + * bytes we expect to see in this character, and extract + * the initial bits of it too. + */ + if (input_chr >= 0xC0 && input_chr < 0xE0) { + state->s0 = 0x44000000L | (input_chr & 0x1F); + } else if (input_chr >= 0xE0 && input_chr < 0xF0) { + state->s0 = 0x64000000L | (input_chr & 0x0F); + } else if (input_chr >= 0xF0 && input_chr < 0xF8) { + state->s0 = 0x84000000L | (input_chr & 0x07); + } else if (input_chr >= 0xF8 && input_chr < 0xFC) { + state->s0 = 0xa4000000L | (input_chr & 0x03); + } else if (input_chr >= 0xFC && input_chr < 0xFE) { + state->s0 = 0xc4000000L | (input_chr & 0x01); + } + } +} + +/* + * UTF-8 is a stateless multi-byte encoding (in the sense that just + * after any character has been completed, the state is always the + * same); hence when writing it, there is no need to use the + * charset_state. + */ + +void write_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + UNUSEDARG(charset); + UNUSEDARG(state); + + /* + * Refuse to output any illegal code points. + */ + if (input_chr == 0xFFFE || input_chr == 0xFFFF || + (input_chr >= 0xD800 && input_chr < 0xE000)) { + emit(emitctx, ERROR); + } else if (input_chr < 0x80) { /* one-byte character */ + emit(emitctx, input_chr); + } else if (input_chr < 0x800) { /* two-byte character */ + emit(emitctx, 0xC0 | (0x1F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x10000) { /* three-byte character */ + emit(emitctx, 0xE0 | (0x0F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x200000) { /* four-byte character */ + emit(emitctx, 0xF0 | (0x07 & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x4000000) {/* five-byte character */ + emit(emitctx, 0xF8 | (0x03 & (input_chr >> 24))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else { /* six-byte character */ + emit(emitctx, 0xFC | (0x01 & (input_chr >> 30))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 24))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } +} + +#ifdef TESTMODE + +#include +#include + +int total_errs = 0; + +void utf8_emit(void *ctx, long output) +{ + wchar_t **p = (wchar_t **)ctx; + *(*p)++ = output; +} + +void utf8_read_test(int line, char *input, int inlen, ...) +{ + va_list ap; + wchar_t *p, str[512]; + int i; + charset_state state; + unsigned long l; + + state.s0 = 0; + p = str; + + for (i = 0; i < inlen; i++) + read_utf8(NULL, input[i] & 0xFF, &state, utf8_emit, &p); + + va_start(ap, inlen); + l = 0; + for (i = 0; i < p - str; i++) { + l = va_arg(ap, long int); + if (l == -1) { + printf("%d: correct string shorter than output\n", line); + total_errs++; + break; + } + if (l != str[i]) { + printf("%d: char %d came out as %08x, should be %08x\n", + line, i, str[i], l); + total_errs++; + } + } + if (l != -1) { + l = va_arg(ap, long int); + if (l != -1) { + printf("%d: correct string longer than output\n", line); + total_errs++; + } + } + va_end(ap); +} + +void utf8_write_test(int line, const long *input, int inlen, ...) +{ + va_list ap; + wchar_t *p, str[512]; + int i; + charset_state state; + unsigned long l; + + state.s0 = 0; + p = str; + + for (i = 0; i < inlen; i++) + write_utf8(NULL, input[i], &state, utf8_emit, &p); + + va_start(ap, inlen); + l = 0; + for (i = 0; i < p - str; i++) { + l = va_arg(ap, long int); + if (l == -1) { + printf("%d: correct string shorter than output\n", line); + total_errs++; + break; + } + if (l != str[i]) { + printf("%d: char %d came out as %08x, should be %08x\n", + line, i, str[i], l); + total_errs++; + } + } + if (l != -1) { + l = va_arg(ap, long int); + if (l != -1) { + printf("%d: correct string longer than output\n", line); + total_errs++; + } + } + va_end(ap); +} + +/* Macro to concoct the first three parameters of utf8_read_test. */ +#define TESTSTR(x) __LINE__, x, lenof(x) + +int main(void) +{ + printf("read tests beginning\n"); + utf8_read_test(TESTSTR("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5"), + 0x000003BA, /* GREEK SMALL LETTER KAPPA */ + 0x00001F79, /* GREEK SMALL LETTER OMICRON WITH OXIA */ + 0x000003C3, /* GREEK SMALL LETTER SIGMA */ + 0x000003BC, /* GREEK SMALL LETTER MU */ + 0x000003B5, /* GREEK SMALL LETTER EPSILON */ + 0, -1); + utf8_read_test(TESTSTR("\x00"), + 0x00000000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xC2\x80"), + 0x00000080, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\xA0\x80"), + 0x00000800, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x90\x80\x80"), + 0x00010000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x88\x80\x80\x80"), + 0x00200000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x84\x80\x80\x80\x80"), + 0x04000000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\x7F"), + 0x0000007F, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xDF\xBF"), + 0x000007FF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBD"), + 0x0000FFFD, /* REPLACEMENT CHARACTER */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBF"), + ERROR, /* (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xF7\xBF\xBF\xBF"), + 0x001FFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF\xBF"), + 0x03FFFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF\xBF"), + 0x7FFFFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xED\x9F\xBF"), + 0x0000D7FF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEE\x80\x80"), + 0x0000E000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBD"), + 0x0000FFFD, /* REPLACEMENT CHARACTER */ + 0, -1); + utf8_read_test(TESTSTR("\xF4\x8F\xBF\xBF"), + 0x0010FFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF4\x90\x80\x80"), + 0x00110000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\x80"), + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xBF"), + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\x20\xC1\x20\xC2\x20\xC3\x20\xC4\x20\xC5\x20\xC6\x20\xC7\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x20\xE1\x20\xE2\x20\xE3\x20\xE4\x20\xE5\x20\xE6\x20\xE7\x20\xE8\x20\xE9\x20\xEA\x20\xEB\x20\xEC\x20\xED\x20\xEE\x20\xEF\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x20\xF1\x20\xF2\x20\xF3\x20\xF4\x20\xF5\x20\xF6\x20\xF7\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x20\xF9\x20\xFA\x20\xFB\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x20\xFD\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xC0"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xDF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF7\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\xE0\x80\xF0\x80\x80\xF8\x80\x80\x80\xFC\x80\x80\x80\x80\xDF\xEF\xBF\xF7\xBF\xBF\xFB\xBF\xBF\xBF\xFD\xBF\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFE"), + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xFF"), + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xFE\xFE\xFF\xFF"), + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xC1\xBF"), + ERROR, /* (overlong form of 7F) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x9F\xBF"), + ERROR, /* (overlong form of DF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x8F\xBF\xBF"), + ERROR, /* (overlong form of EF BF BF) (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x87\xBF\xBF\xBF"), + ERROR, /* (overlong form of F7 BF BF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x83\xBF\xBF\xBF\xBF"), + ERROR, /* (overlong form of FB BF BF BF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xB0\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xBE\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBE"), + ERROR, /* (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBF"), + ERROR, /* (invalid char) */ + 0, -1); + printf("read tests completed\n"); + printf("write tests beginning\n"); + { + const static long str[] = + {0x03BAL, 0x1F79L, 0x03C3L, 0x03BCL, 0x03B5L, 0}; + utf8_write_test(TESTSTR(str), + 0xCE, 0xBA, + 0xE1, 0xBD, 0xB9, + 0xCF, 0x83, + 0xCE, 0xBC, + 0xCE, 0xB5, + 0, -1); + } + { + const static long str[] = {0x0000L, 0}; + utf8_write_test(TESTSTR(str), + 0x00, + 0, -1); + } + { + const static long str[] = {0x0080L, 0}; + utf8_write_test(TESTSTR(str), + 0xC2, 0x80, + 0, -1); + } + { + const static long str[] = {0x0800L, 0}; + utf8_write_test(TESTSTR(str), + 0xE0, 0xA0, 0x80, + 0, -1); + } + { + const static long str[] = {0x00010000L, 0}; + utf8_write_test(TESTSTR(str), + 0xF0, 0x90, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x00200000L, 0}; + utf8_write_test(TESTSTR(str), + 0xF8, 0x88, 0x80, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x04000000L, 0}; + utf8_write_test(TESTSTR(str), + 0xFC, 0x84, 0x80, 0x80, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x007FL, 0}; + utf8_write_test(TESTSTR(str), + 0x7F, + 0, -1); + } + { + const static long str[] = {0x07FFL, 0}; + utf8_write_test(TESTSTR(str), + 0xDF, 0xBF, + 0, -1); + } + { + const static long str[] = {0xFFFDL, 0}; + utf8_write_test(TESTSTR(str), + 0xEF, 0xBF, 0xBD, + 0, -1); + } + { + const static long str[] = {0xFFFFL, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0x001FFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xF7, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0x03FFFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xFB, 0xBF, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0x7FFFFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xFD, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0xD7FFL, 0}; + utf8_write_test(TESTSTR(str), + 0xED, 0x9F, 0xBF, + 0, -1); + } + { + const static long str[] = {0xD800L, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0xD800L, 0xDC00L, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + ERROR, + 0, -1); + } + { + const static long str[] = {0xDFFFL, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0xE000L, 0}; + utf8_write_test(TESTSTR(str), + 0xEE, 0x80, 0x80, + 0, -1); + } + printf("write tests completed\n"); + + printf("total: %d errors\n", total_errs); + return (total_errs != 0); +} +#endif /* TESTMODE */ + +const charset_spec charset_CS_UTF8 = { + CS_UTF8, read_utf8, write_utf8, NULL +}; + +#else /* ENUM_CHARSETS */ + +ENUM_CHARSET(CS_UTF8) + +#endif /* ENUM_CHARSETS */ diff --git a/netbox/libs/Putty/charset/xenc.c b/netbox/libs/Putty/charset/xenc.c new file mode 100644 index 000000000..444af7048 --- /dev/null +++ b/netbox/libs/Putty/charset/xenc.c @@ -0,0 +1,94 @@ +/* + * xenc.c - translate our internal character set codes to and from + * X11 character encoding names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} xencs[] = { + /* + * Officially registered encoding names. This list is derived + * from the font encodings section of + * + * http://ftp.x.org/pub/DOCS/registry + * + * Where multiple encoding names map to the same encoding id + * (such as iso8859-15 and fcd8859-15), the first is considered + * canonical and will be returned when translating the id to a + * string. + */ + { "iso8859-1", CS_ISO8859_1 }, + { "iso8859-2", CS_ISO8859_2 }, + { "iso8859-3", CS_ISO8859_3 }, + { "iso8859-4", CS_ISO8859_4 }, + { "iso8859-5", CS_ISO8859_5 }, + { "iso8859-6", CS_ISO8859_6 }, + { "iso8859-7", CS_ISO8859_7 }, + { "iso8859-8", CS_ISO8859_8 }, + { "iso8859-9", CS_ISO8859_9 }, + { "iso8859-10", CS_ISO8859_10 }, + { "iso8859-13", CS_ISO8859_13 }, + { "iso8859-14", CS_ISO8859_14 }, + { "iso8859-15", CS_ISO8859_15 }, + { "fcd8859-15", CS_ISO8859_15 }, + { "hp-roman8", CS_HP_ROMAN8 }, + { "koi8-r", CS_KOI8_R }, + /* + * Unofficial encoding names found in the wild. + */ + { "iso8859-16", CS_ISO8859_16 }, + { "koi8-u", CS_KOI8_U }, + { "ibm-cp437", CS_CP437 }, + { "ibm-cp850", CS_CP850 }, + { "ibm-cp852", CS_CP852 }, + { "ibm-cp866", CS_CP866 }, + { "microsoft-cp1250", CS_CP1250 }, + { "microsoft-cp1251", CS_CP1251 }, + { "microsoft-cp1252", CS_CP1252 }, + { "microsoft-cp1253", CS_CP1253 }, + { "microsoft-cp1254", CS_CP1254 }, + { "microsoft-cp1255", CS_CP1255 }, + { "microsoft-cp1256", CS_CP1256 }, + { "microsoft-cp1257", CS_CP1257 }, + { "microsoft-cp1258", CS_CP1258 }, + { "mac-roman", CS_MAC_ROMAN }, + { "viscii1.1-1", CS_VISCII }, + { "viscii1-1", CS_VISCII }, +}; + +const char *charset_to_xenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) + if (charset == xencs[i].charset) + return xencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_xenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) { + const char *p, *q; + p = name; + q = xencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return xencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/netbox/libs/Putty/cmdgen.c b/netbox/libs/Putty/cmdgen.c new file mode 100644 index 000000000..c15c01dd3 --- /dev/null +++ b/netbox/libs/Putty/cmdgen.c @@ -0,0 +1,1611 @@ +/* + * cmdgen.c - command-line form of PuTTYgen + */ + +#define PUTTY_DO_GLOBALS + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +#ifdef TEST_CMDGEN +/* + * This section overrides some definitions below for test purposes. + * When compiled with -DTEST_CMDGEN: + * + * - Calls to get_random_data() are replaced with the diagnostic + * function below (I #define the name so that I can still link + * with the original set of modules without symbol clash), in + * order to avoid depleting the test system's /dev/random + * unnecessarily. + * + * - Calls to console_get_userpass_input() are replaced with the + * diagnostic function below, so that I can run tests in an + * automated manner and provide their interactive passphrase + * inputs. + * + * - main() is renamed to cmdgen_main(); at the bottom of the file + * I define another main() which calls the former repeatedly to + * run tests. + */ +#define get_random_data get_random_data_diagnostic +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + memset(buf, 'x', len); + return buf; +} +#define console_get_userpass_input console_get_userpass_input_diagnostic +int nprompts, promptsgot; +const char *prompts[3]; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t i; + int ret = 1; + for (i = 0; i < p->n_prompts; i++) { + if (promptsgot < nprompts) { + assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len); + strcpy(p->prompts[i]->result, prompts[promptsgot++]); + } else { + promptsgot++; /* track number of requests anyway */ + ret = 0; + } + } + return ret; +} +#define main cmdgen_main +#endif + +struct progress { + int phase, current; +}; + +static void progress_update(void *param, int action, int phase, int iprogress) +{ + struct progress *p = (struct progress *)param; + if (action != PROGFN_PROGRESS) + return; + if (phase > p->phase) { + if (p->phase >= 0) + fputc('\n', stderr); + p->phase = phase; + if (iprogress >= 0) + p->current = iprogress - 1; + else + p->current = iprogress; + } + while (p->current < iprogress) { + fputc('+', stdout); + p->current++; + } + fflush(stdout); +} + +static void no_progress(void *param, int action, int phase, int iprogress) +{ +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + cleanup_exit(1); +} + +void nonfatal(char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); +} + +/* + * Stubs to let everything else link sensibly. + */ +void log_eventlog(void *handle, const char *event) +{ +} +char *x_get_default(const char *key) +{ + return NULL; +} +void sk_cleanup(void) +{ +} + +void showversion(void) +{ + printf("puttygen: %s\n", ver); +} + +void usage(int standalone) +{ + fprintf(stderr, + "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" + " [ -C comment ] [ -P ] [ -q ]\n" + " [ -o output-keyfile ] [ -O type | -l | -L" + " | -p ]\n"); + if (standalone) + fprintf(stderr, + "Use \"puttygen --help\" for more detail.\n"); +} + +void help(void) +{ + /* + * Help message is an extended version of the usage message. So + * start with that, plus a version heading. + */ + showversion(); + usage(FALSE); + fprintf(stderr, + " -t specify key type when generating (rsa, dsa, rsa1)\n" + " -b specify number of bits when generating key\n" + " -C change or specify key comment\n" + " -P change key passphrase\n" + " -q quiet: do not display progress bar\n" + " -O specify output type:\n" + " private output PuTTY private key format\n" + " private-openssh export OpenSSH private key\n" + " private-sshcom export ssh.com private key\n" + " public standard / ssh.com public key\n" + " public-openssh OpenSSH public key\n" + " fingerprint output the key fingerprint\n" + " -o specify output file\n" + " -l equivalent to `-O fingerprint'\n" + " -L equivalent to `-O public-openssh'\n" + " -p equivalent to `-O public'\n" + ); +} + +static int save_ssh2_pubkey(char *filename, char *comment, + void *v_pub_blob, int pub_len) +{ + unsigned char *pub_blob = (unsigned char *)v_pub_blob; + char *p; + int i, column; + FILE *fp; + + if (filename) { + fp = fopen(filename, "wb"); + if (!fp) + return 0; + } else + fp = stdout; + + fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n"); + + if (comment) { + fprintf(fp, "Comment: \""); + for (p = comment; *p; p++) { + if (*p == '\\' || *p == '\"') + fputc('\\', fp); + fputc(*p, fp); + } + fprintf(fp, "\"\n"); + } + + i = 0; + column = 0; + while (i < pub_len) { + char buf[5]; + int n = (pub_len - i < 3 ? pub_len - i : 3); + base64_encode_atom(pub_blob + i, n, buf); + i += n; + buf[4] = '\0'; + fputs(buf, fp); + if (++column >= 16) { + fputc('\n', fp); + column = 0; + } + } + if (column > 0) + fputc('\n', fp); + + fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n"); + if (filename) + fclose(fp); + return 1; +} + +static int move(char *from, char *to) +{ + int ret; + + ret = rename(from, to); + if (ret) { + /* + * This OS may require us to remove the original file first. + */ + remove(to); + ret = rename(from, to); + } + if (ret) { + perror("puttygen: cannot move new file on to old one"); + return FALSE; + } + return TRUE; +} + +static char *blobfp(char *alg, int bits, unsigned char *blob, int bloblen) +{ + char buffer[128]; + unsigned char digest[16]; + struct MD5Context md5c; + int i; + + MD5Init(&md5c); + MD5Update(&md5c, blob, bloblen); + MD5Final(digest, &md5c); + + sprintf(buffer, "%s ", alg); + if (bits > 0) + sprintf(buffer + strlen(buffer), "%d ", bits); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + + return dupstr(buffer); +} + +int main(int argc, char **argv) +{ + char *infile = NULL; + Filename *infilename = NULL, *outfilename = NULL; + enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; + char *outfile = NULL, *outfiletmp = NULL; + enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; + int bits = 2048; + char *comment = NULL, *origcomment = NULL; + int change_passphrase = FALSE; + int errs = FALSE, nogo = FALSE; + int intype = SSH_KEYTYPE_UNOPENABLE; + int sshver = 0; + struct ssh2_userkey *ssh2key = NULL; + struct RSAKey *ssh1key = NULL; + unsigned char *ssh2blob = NULL; + char *ssh2alg = NULL; + const struct ssh_signkey *ssh2algf = NULL; + int ssh2bloblen; + char *passphrase = NULL; + int load_encrypted; + progfn_t progressfn = is_interactive() ? progress_update : no_progress; + + /* ------------------------------------------------------------------ + * Parse the command line to figure out what we've been asked to do. + */ + + /* + * If run with no arguments at all, print the usage message and + * return success. + */ + if (argc <= 1) { + usage(TRUE); + return 0; + } + + /* + * Parse command line arguments. + */ + while (--argc) { + char *p = *++argv; + if (*p == '-') { + /* + * An option. + */ + while (p && *++p) { + char c = *p; + switch (c) { + case '-': + /* + * Long option. + */ + { + char *opt, *val; + opt = p++; /* opt will have _one_ leading - */ + while (*p && *p != '=') + p++; /* find end of option */ + if (*p == '=') { + *p++ = '\0'; + val = p; + } else + val = NULL; + + if (!strcmp(opt, "-help")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + help(); + nogo = TRUE; + } + } else if (!strcmp(opt, "-version")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + showversion(); + nogo = TRUE; + } + } else if (!strcmp(opt, "-pgpfp")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + /* support --pgpfp for consistency */ + pgp_fingerprints(); + nogo = TRUE; + } + } + /* + * For long options requiring an argument, add + * code along the lines of + * + * else if (!strcmp(opt, "-output")) { + * if (!val) { + * errs = TRUE; + * fprintf(stderr, "puttygen: option `-%s'" + * " expects an argument\n", opt); + * } else + * ofile = val; + * } + */ + else { + errs = TRUE; + fprintf(stderr, + "puttygen: no such option `-%s'\n", opt); + } + } + p = NULL; + break; + case 'h': + case 'V': + case 'P': + case 'l': + case 'L': + case 'p': + case 'q': + /* + * Option requiring no parameter. + */ + switch (c) { + case 'h': + help(); + nogo = TRUE; + break; + case 'V': + showversion(); + nogo = TRUE; + break; + case 'P': + change_passphrase = TRUE; + break; + case 'l': + outtype = FP; + break; + case 'L': + outtype = PUBLICO; + break; + case 'p': + outtype = PUBLIC; + break; + case 'q': + progressfn = no_progress; + break; + } + break; + case 't': + case 'b': + case 'C': + case 'O': + case 'o': + /* + * Option requiring parameter. + */ + p++; + if (!*p && argc > 1) + --argc, p = *++argv; + else if (!*p) { + fprintf(stderr, "puttygen: option `-%c' expects a" + " parameter\n", c); + errs = TRUE; + } + /* + * Now c is the option and p is the parameter. + */ + switch (c) { + case 't': + if (!strcmp(p, "rsa") || !strcmp(p, "rsa2")) + keytype = RSA2, sshver = 2; + else if (!strcmp(p, "rsa1")) + keytype = RSA1, sshver = 1; + else if (!strcmp(p, "dsa") || !strcmp(p, "dss")) + keytype = DSA, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown key type `%s'\n", p); + errs = TRUE; + } + break; + case 'b': + bits = atoi(p); + break; + case 'C': + comment = p; + break; + case 'O': + if (!strcmp(p, "public")) + outtype = PUBLIC; + else if (!strcmp(p, "public-openssh")) + outtype = PUBLICO; + else if (!strcmp(p, "private")) + outtype = PRIVATE; + else if (!strcmp(p, "fingerprint")) + outtype = FP; + else if (!strcmp(p, "private-openssh")) + outtype = OPENSSH, sshver = 2; + else if (!strcmp(p, "private-sshcom")) + outtype = SSHCOM, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown output type `%s'\n", p); + errs = TRUE; + } + break; + case 'o': + outfile = p; + break; + } + p = NULL; /* prevent continued processing */ + break; + default: + /* + * Unrecognised option. + */ + errs = TRUE; + fprintf(stderr, "puttygen: no such option `-%c'\n", c); + break; + } + } + } else { + /* + * A non-option argument. + */ + if (!infile) + infile = p; + else { + errs = TRUE; + fprintf(stderr, "puttygen: cannot handle more than one" + " input file\n"); + } + } + } + + if (errs) + return 1; + + if (nogo) + return 0; + + /* + * If run with at least one argument _but_ not the required + * ones, print the usage message and return failure. + */ + if (!infile && keytype == NOKEYGEN) { + usage(TRUE); + return 1; + } + + /* ------------------------------------------------------------------ + * Figure out further details of exactly what we're going to do. + */ + + /* + * Bomb out if we've been asked to both load and generate a + * key. + */ + if (keytype != NOKEYGEN && infile) { + fprintf(stderr, "puttygen: cannot both load and generate a key\n"); + return 1; + } + + /* + * We must save the private part when generating a new key. + */ + if (keytype != NOKEYGEN && + (outtype != PRIVATE && outtype != OPENSSH && outtype != SSHCOM)) { + fprintf(stderr, "puttygen: this would generate a new key but " + "discard the private part\n"); + return 1; + } + + /* + * Analyse the type of the input file, in case this affects our + * course of action. + */ + if (infile) { + infilename = filename_from_str(infile); + + intype = key_type(infilename); + + switch (intype) { + /* + * It would be nice here to be able to load _public_ + * key files, in any of a number of forms, and (a) + * convert them to other public key types, (b) print + * out their fingerprints. Or, I suppose, for real + * orthogonality, (c) change their comment! + * + * In fact this opens some interesting possibilities. + * Suppose ssh2_userkey_loadpub() were able to load + * public key files as well as extracting the public + * key from private ones. And suppose I did the thing + * I've been wanting to do, where specifying a + * particular private key file for authentication + * causes any _other_ key in the agent to be discarded. + * Then, if you had an agent forwarded to the machine + * you were running Unix PuTTY or Plink on, and you + * needed to specify which of the keys in the agent it + * should use, you could do that by supplying a + * _public_ key file, thus not needing to trust even + * your encrypted private key file to the network. Ooh! + */ + + case SSH_KEYTYPE_UNOPENABLE: + case SSH_KEYTYPE_UNKNOWN: + fprintf(stderr, "puttygen: unable to load file `%s': %s\n", + infile, key_type_to_str(intype)); + return 1; + + case SSH_KEYTYPE_SSH1: + if (sshver == 2) { + fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys" + " not supported\n"); + return 1; + } + sshver = 1; + break; + + case SSH_KEYTYPE_SSH2: + case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSHCOM: + if (sshver == 1) { + fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys" + " not supported\n"); + return 1; + } + sshver = 2; + break; + } + } + + /* + * Determine the default output file, if none is provided. + * + * This will usually be equal to stdout, except that if the + * input and output file formats are the same then the default + * output is to overwrite the input. + * + * Also in this code, we bomb out if the input and output file + * formats are the same and no other action is performed. + */ + if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) || + (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { + if (!outfile) { + outfile = infile; + outfiletmp = dupcat(outfile, ".tmp", NULL); + } + + if (!change_passphrase && !comment) { + fprintf(stderr, "puttygen: this command would perform no useful" + " action\n"); + return 1; + } + } else { + if (!outfile) { + /* + * Bomb out rather than automatically choosing to write + * a private key file to stdout. + */ + if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) { + fprintf(stderr, "puttygen: need to specify an output file\n"); + return 1; + } + } + } + + /* + * Figure out whether we need to load the encrypted part of the + * key. This will be the case if either (a) we need to write + * out a private key format, or (b) the entire input key file + * is encrypted. + */ + if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM || + intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM) + load_encrypted = TRUE; + else + load_encrypted = FALSE; + + /* ------------------------------------------------------------------ + * Now we're ready to actually do some stuff. + */ + + /* + * Either load or generate a key. + */ + if (keytype != NOKEYGEN) { + char *entropy; + char default_comment[80]; + struct tm tm; + struct progress prog; + + prog.phase = -1; + prog.current = -1; + + tm = ltime(); + if (keytype == DSA) + strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm); + else + strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm); + + random_ref(); + entropy = get_random_data(bits / 8); + if (!entropy) { + fprintf(stderr, "puttygen: failed to collect entropy, " + "could not generate key\n"); + return 1; + } + random_add_heavynoise(entropy, bits / 8); + smemclr(entropy, bits/8); + sfree(entropy); + + if (keytype == DSA) { + struct dss_key *dsskey = snew(struct dss_key); + dsa_generate(dsskey, bits, progressfn, &prog); + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = dsskey; + ssh2key->alg = &ssh_dss; + ssh1key = NULL; + } else { + struct RSAKey *rsakey = snew(struct RSAKey); + rsa_generate(rsakey, bits, progressfn, &prog); + rsakey->comment = NULL; + if (keytype == RSA1) { + ssh1key = rsakey; + } else { + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = rsakey; + ssh2key->alg = &ssh_rsa; + } + } + progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); + + if (ssh2key) + ssh2key->comment = dupstr(default_comment); + if (ssh1key) + ssh1key->comment = dupstr(default_comment); + + } else { + const char *error = NULL; + int encrypted; + + assert(infile != NULL); + + /* + * Find out whether the input key is encrypted. + */ + if (intype == SSH_KEYTYPE_SSH1) + encrypted = rsakey_encrypted(infilename, &origcomment); + else if (intype == SSH_KEYTYPE_SSH2) + encrypted = ssh2_userkey_encrypted(infilename, &origcomment); + else + encrypted = import_encrypted(infilename, intype, &origcomment); + + /* + * If so, ask for a passphrase. + */ + if (encrypted && load_encrypted) { + prompts_t *p = new_prompts(NULL); + int ret; + p->to_server = FALSE; + p->name = dupstr("SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read passphrase"); + return 1; + } else { + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + } + } else { + passphrase = NULL; + } + + switch (intype) { + int ret; + + case SSH_KEYTYPE_SSH1: + ssh1key = snew(struct RSAKey); + if (!load_encrypted) { + void *vblob; + unsigned char *blob; + int n, l, bloblen; + + ret = rsakey_pubblob(infilename, &vblob, &bloblen, + &origcomment, &error); + blob = (unsigned char *)vblob; + + n = 4; /* skip modulus bits */ + + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->exponent); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else { + n += l; + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->modulus); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else + n += l; + } + ssh1key->comment = dupstr(origcomment); + ssh1key->private_exponent = NULL; + ssh1key->p = NULL; + ssh1key->q = NULL; + ssh1key->iqmp = NULL; + } else { + ret = loadrsakey(infilename, ssh1key, passphrase, &error); + } + if (ret > 0) + error = NULL; + else if (!error) + error = "unknown error"; + break; + + case SSH_KEYTYPE_SSH2: + if (!load_encrypted) { + ssh2blob = ssh2_userkey_loadpub(infilename, &ssh2alg, + &ssh2bloblen, NULL, &error); + if (ssh2blob) { + ssh2algf = find_pubkey_alg(ssh2alg); + if (ssh2algf) + bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); + else + bits = -1; + } + } else { + ssh2key = ssh2_load_userkey(infilename, passphrase, &error); + } + if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob) + error = NULL; + else if (!error) { + if (ssh2key == SSH2_WRONG_PASSPHRASE) + error = "wrong passphrase"; + else + error = "unknown error"; + } + break; + + case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSHCOM: + ssh2key = import_ssh2(infilename, intype, passphrase, &error); + if (ssh2key) { + if (ssh2key != SSH2_WRONG_PASSPHRASE) + error = NULL; + else + error = "wrong passphrase"; + } else if (!error) + error = "unknown error"; + break; + + default: + assert(0); + } + + if (error) { + fprintf(stderr, "puttygen: error loading `%s': %s\n", + infile, error); + return 1; + } + } + + /* + * Change the comment if asked to. + */ + if (comment) { + if (sshver == 1) { + assert(ssh1key); + sfree(ssh1key->comment); + ssh1key->comment = dupstr(comment); + } else { + assert(ssh2key); + sfree(ssh2key->comment); + ssh2key->comment = dupstr(comment); + } + } + + /* + * Prompt for a new passphrase if we have been asked to, or if + * we have just generated a key. + */ + if (change_passphrase || keytype != NOKEYGEN) { + prompts_t *p = new_prompts(NULL); + int ret; + + p->to_server = FALSE; + p->name = dupstr("New SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE); + add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read new passphrase"); + return 1; + } else { + if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { + free_prompts(p); + fprintf(stderr, "puttygen: passphrases do not match\n"); + return 1; + } + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + if (!*passphrase) { + sfree(passphrase); + passphrase = NULL; + } + } + } + + /* + * Write output. + * + * (In the case where outfile and outfiletmp are both NULL, + * there is no semantic reason to initialise outfilename at + * all; but we have to write _something_ to it or some compiler + * will probably complain that it might be used uninitialised.) + */ + if (outfiletmp) + outfilename = filename_from_str(outfiletmp); + else + outfilename = filename_from_str(outfile ? outfile : ""); + + switch (outtype) { + int ret; + + case PRIVATE: + if (sshver == 1) { + assert(ssh1key); + ret = saversakey(outfilename, ssh1key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); + return 1; + } + } else { + assert(ssh2key); + ret = ssh2_save_userkey(outfilename, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH-2 private key\n"); + return 1; + } + } + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } + break; + + case PUBLIC: + case PUBLICO: + if (sshver == 1) { + FILE *fp; + char *dec1, *dec2; + + assert(ssh1key); + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + dec1 = bignum_decimal(ssh1key->exponent); + dec2 = bignum_decimal(ssh1key->modulus); + fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus), + dec1, dec2, ssh1key->comment); + sfree(dec1); + sfree(dec2); + if (outfile) + fclose(fp); + } else if (outtype == PUBLIC) { + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = ssh2key->alg->public_blob(ssh2key->data, + &ssh2bloblen); + } + save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment, + ssh2blob, ssh2bloblen); + } else if (outtype == PUBLICO) { + char *buffer, *p; + int i; + FILE *fp; + + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = ssh2key->alg->public_blob(ssh2key->data, + &ssh2bloblen); + } + if (!ssh2alg) { + assert(ssh2key); + ssh2alg = ssh2key->alg->name; + } + if (ssh2key) + comment = ssh2key->comment; + else + comment = origcomment; + + buffer = snewn(strlen(ssh2alg) + + 4 * ((ssh2bloblen+2) / 3) + + strlen(comment) + 3, char); + strcpy(buffer, ssh2alg); + p = buffer + strlen(buffer); + *p++ = ' '; + i = 0; + while (i < ssh2bloblen) { + int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3); + base64_encode_atom(ssh2blob + i, n, p); + i += n; + p += 4; + } + if (*comment) { + *p++ = ' '; + strcpy(p, comment); + } else + *p++ = '\0'; + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + fprintf(fp, "%s\n", buffer); + if (outfile) + fclose(fp); + + sfree(buffer); + } + break; + + case FP: + { + FILE *fp; + char *fingerprint; + + if (sshver == 1) { + assert(ssh1key); + fingerprint = snewn(128, char); + rsa_fingerprint(fingerprint, 128, ssh1key); + } else { + if (ssh2key) { + fingerprint = ssh2key->alg->fingerprint(ssh2key->data); + } else { + assert(ssh2blob); + fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen); + } + } + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + fprintf(fp, "%s\n", fingerprint); + if (outfile) + fclose(fp); + + sfree(fingerprint); + } + break; + + case OPENSSH: + case SSHCOM: + assert(sshver == 2); + assert(ssh2key); + random_ref(); /* both foreign key types require randomness, + * for IV or padding */ + ret = export_ssh2(outfilename, outtype, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to export key\n"); + return 1; + } + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } + break; + } + + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + + if (ssh1key) + freersakey(ssh1key); + if (ssh2key) { + ssh2key->alg->freekey(ssh2key->data); + sfree(ssh2key); + } + + return 0; +} + +#ifdef TEST_CMDGEN + +#undef main + +#include + +int passes, fails; + +void setup_passphrases(char *first, ...) +{ + va_list ap; + char *next; + + nprompts = 0; + if (first) { + prompts[nprompts++] = first; + va_start(ap, first); + while ((next = va_arg(ap, char *)) != NULL) { + assert(nprompts < lenof(prompts)); + prompts[nprompts++] = next; + } + va_end(ap); + } +} + +void test(int retval, ...) +{ + va_list ap; + int i, argc, ret; + char **argv; + + argc = 0; + va_start(ap, retval); + while (va_arg(ap, char *) != NULL) + argc++; + va_end(ap); + + argv = snewn(argc+1, char *); + va_start(ap, retval); + for (i = 0; i <= argc; i++) + argv[i] = va_arg(ap, char *); + va_end(ap); + + promptsgot = 0; + ret = cmdgen_main(argc, argv); + + if (ret != retval) { + printf("FAILED retval (exp %d got %d):", retval, ret); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else if (promptsgot != nprompts) { + printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else { + passes++; + } +} + +void filecmp(char *file1, char *file2, char *fmt, ...) +{ + /* + * Ideally I should do file comparison myself, to maximise the + * portability of this test suite once this application begins + * running on non-Unix platforms. For the moment, though, + * calling Unix diff is perfectly adequate. + */ + char *buf; + int ret; + + buf = dupprintf("diff -q '%s' '%s'", file1, file2); + ret = system(buf); + sfree(buf); + + if (ret) { + va_list ap; + + printf("FAILED diff (ret=%d): ", ret); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } else + passes++; +} + +char *cleanup_fp(char *s) +{ + char *p; + + if (!strncmp(s, "ssh-", 4)) { + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + } + + p = s; + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + s += strcspn(s, " \n\t"); + + return dupprintf("%.*s", s - p, p); +} + +char *get_fp(char *filename) +{ + FILE *fp; + char buf[256], *ret; + + fp = fopen(filename, "r"); + if (!fp) + return NULL; + ret = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (!ret) + return NULL; + return cleanup_fp(buf); +} + +void check_fp(char *filename, char *fp, char *fmt, ...) +{ + char *newfp; + + if (!fp) + return; + + newfp = get_fp(filename); + + if (!strcmp(fp, newfp)) { + passes++; + } else { + va_list ap; + + printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } + + sfree(newfp); +} + +int main(int argc, char **argv) +{ + int i; + static char *const keytypes[] = { "rsa1", "dsa", "rsa" }; + + /* + * Even when this thing is compiled for automatic test mode, + * it's helpful to be able to invoke it with command-line + * options for _manual_ tests. + */ + if (argc > 1) + return cmdgen_main(argc, argv); + + passes = fails = 0; + + for (i = 0; i < lenof(keytypes); i++) { + char filename[128], osfilename[128], scfilename[128]; + char pubfilename[128], tmpfilename1[128], tmpfilename2[128]; + char *fp; + + sprintf(filename, "test-%s.ppk", keytypes[i]); + sprintf(pubfilename, "test-%s.pub", keytypes[i]); + sprintf(osfilename, "test-%s.os", keytypes[i]); + sprintf(scfilename, "test-%s.sc", keytypes[i]); + sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]); + sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]); + + /* + * Create an encrypted key. + */ + setup_passphrases("sponge", "sponge", NULL); + test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL); + + /* + * List the public key in OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL); + { + char cmdbuf[256]; + fp = NULL; + sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'", + pubfilename, tmpfilename1); + if (system(cmdbuf) || + (fp = get_fp(tmpfilename1)) == NULL) { + printf("UNABLE to test fingerprint matching against OpenSSH"); + } + } + + /* + * List the public key in IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", filename, NULL); + + /* + * List the fingerprint of the key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL); + if (!fp) { + /* + * If we can't test fingerprints against OpenSSH, we + * can at the very least test equality of all the + * fingerprints we generate of this key throughout + * testing. + */ + fp = get_fp(tmpfilename1); + } else { + check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]); + } + + /* + * Change the comment of the key; this _does_ require a + * passphrase owing to the tamperproofing. + * + * NOTE: In SSH-1, this only requires a passphrase because + * of inadequacies of the loading and saving mechanisms. In + * _principle_, it should be perfectly possible to modify + * the comment on an SSH-1 key without requiring a + * passphrase; the only reason I can't do it is because my + * loading and saving mechanisms don't include a method of + * loading all the key data without also trying to decrypt + * the private section. + * + * I don't consider this to be a problem worth solving, + * because (a) to fix it would probably end up bloating + * PuTTY proper, and (b) SSH-1 is on the way out anyway so + * it shouldn't be highly significant. If it seriously + * bothers anyone then perhaps I _might_ be persuadable. + */ + setup_passphrases("sponge", NULL); + test(0, "puttygen", "-C", "new-comment", filename, NULL); + + /* + * Change the passphrase to nothing. + */ + setup_passphrases("sponge", "", "", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Change the comment of the key again; this time we expect no + * passphrase to be required. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-C", "new-comment-2", filename, NULL); + + /* + * Export the private key into OpenSSH format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases(NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p clear %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases(NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p clear %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p clear %s", keytypes[i]); + } + + /* + * Check that mismatched passphrases cause an error. + */ + setup_passphrases("sponge2", "sponge3", NULL); + test(1, "puttygen", "-P", filename, NULL); + + /* + * Put a passphrase back on. + */ + setup_passphrases("sponge2", "sponge2", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Export the private key into OpenSSH format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p encrypted %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p encrypted %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p encrypted %s", keytypes[i]); + } + + /* + * Load with the wrong passphrase. + */ + setup_passphrases("sponge8", NULL); + test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL); + + /* + * Load a totally bogus file. + */ + setup_passphrases(NULL); + test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL); + } + printf("%d passes, %d fails\n", passes, fails); + return 0; +} + +#endif diff --git a/netbox/libs/Putty/cmdline.c b/netbox/libs/Putty/cmdline.c new file mode 100644 index 000000000..9f3360b2b --- /dev/null +++ b/netbox/libs/Putty/cmdline.c @@ -0,0 +1,612 @@ +/* + * cmdline.c - command-line parsing shared between many of the + * PuTTY applications + */ + +#include +#include +#include +#include "putty.h" + +/* + * Some command-line parameters need to be saved up until after + * we've loaded the saved session which will form the basis of our + * eventual running configuration. For this we use the macro + * SAVEABLE, which notices if the `need_save' parameter is set and + * saves the parameter and value on a list. + * + * We also assign priorities to saved parameters, just to slightly + * ameliorate silly ordering problems. For example, if you specify + * a saved session to load, it will be loaded _before_ all your + * local modifications such as -L are evaluated; and if you specify + * a protocol and a port, the protocol is set up first so that the + * port can override its choice of port number. + * + * (In fact -load is not saved at all, since in at least Plink the + * processing of further command-line options depends on whether or + * not the loaded session contained a hostname. So it must be + * executed immediately.) + */ + +#define NPRIORITIES 2 + +struct cmdline_saved_param { + char *p, *value; +}; +struct cmdline_saved_param_set { + struct cmdline_saved_param *params; + int nsaved, savesize; +}; + +/* + * C guarantees this structure will be initialised to all zero at + * program start, which is exactly what we want. + */ +static struct cmdline_saved_param_set saves[NPRIORITIES]; + +static void cmdline_save_param(char *p, char *value, int pri) +{ + if (saves[pri].nsaved >= saves[pri].savesize) { + saves[pri].savesize = saves[pri].nsaved + 32; + saves[pri].params = sresize(saves[pri].params, saves[pri].savesize, + struct cmdline_saved_param); + } + saves[pri].params[saves[pri].nsaved].p = p; + saves[pri].params[saves[pri].nsaved].value = value; + saves[pri].nsaved++; +} + +static char *cmdline_password = NULL; + +void cmdline_cleanup(void) +{ + int pri; + + if (cmdline_password) { + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + } + + for (pri = 0; pri < NPRIORITIES; pri++) { + sfree(saves[pri].params); + saves[pri].params = NULL; + saves[pri].savesize = 0; + saves[pri].nsaved = 0; + } +} + +#define SAVEABLE(pri) do { \ + if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ +} while (0) + +/* + * Similar interface to get_userpass_input(), except that here a -1 + * return means that we aren't capable of processing the prompt and + * someone else should do it. + */ +int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) { + + static int tried_once = 0; + + /* + * We only handle prompts which don't echo (which we assume to be + * passwords), and (currently) we only cope with a password prompt + * that comes in a prompt-set on its own. + */ + if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) { + return -1; + } + + /* + * If we've tried once, return utter failure (no more passwords left + * to try). + */ + if (tried_once) + return 0; + + prompt_set_result(p->prompts[0], cmdline_password); + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + tried_once = 1; + return 1; +} + +/* + * Here we have a flags word which describes the capabilities of + * the particular tool on whose behalf we're running. We will + * refuse certain command-line options if a particular tool + * inherently can't do anything sensible. For example, the file + * transfer tools (psftp, pscp) can't do a great deal with protocol + * selections (ever tried running scp over telnet?) or with port + * forwarding (even if it wasn't a hideously bad idea, they don't + * have the select() infrastructure to make them work). + */ +int cmdline_tooltype = 0; + +static int cmdline_check_unavailable(int flag, char *p) +{ + if (cmdline_tooltype & flag) { + cmdline_error("option \"%s\" not available in this tool", p); + return 1; + } + return 0; +} + +#define UNAVAILABLE_IN(flag) do { \ + if (cmdline_check_unavailable(flag, p)) return ret; \ +} while (0) + +/* + * Process a standard command-line parameter. `p' is the parameter + * in question; `value' is the subsequent element of argv, which + * may or may not be required as an operand to the parameter. + * If `need_save' is 1, arguments which need to be saved as + * described at this top of this file are, for later execution; + * if 0, they are processed normally. (-1 is a special value used + * by pterm to count arguments for a preliminary pass through the + * argument list; it causes immediate return with an appropriate + * value with no action taken.) + * Return value is 2 if both arguments were used; 1 if only p was + * used; 0 if the parameter wasn't one we recognised; -2 if it + * should have been 2 but value was NULL. + */ + +#define RETURN(x) do { \ + if ((x) == 2 && !value) return -2; \ + ret = x; \ + if (need_save < 0) return x; \ +} while (0) + +int cmdline_process_param(char *p, char *value, int need_save, Conf *conf) +{ + int ret = 0; + + if (!strcmp(p, "-load")) { + RETURN(2); + /* This parameter must be processed immediately rather than being + * saved. */ + do_defaults(value, conf); + loaded_session = TRUE; + cmdline_session_name = dupstr(value); + return 2; + } + if (!strcmp(p, "-ssh")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = PROT_SSH; + default_port = 22; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); + return 1; + } + if (!strcmp(p, "-telnet")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = PROT_TELNET; + default_port = 23; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); + return 1; + } + if (!strcmp(p, "-rlogin")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = PROT_RLOGIN; + default_port = 513; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); + return 1; + } + if (!strcmp(p, "-raw")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = PROT_RAW; + conf_set_int(conf, CONF_protocol, default_protocol); + } + if (!strcmp(p, "-serial")) { + RETURN(1); + /* Serial is not NONNETWORK in an odd sense of the word */ + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = PROT_SERIAL; + conf_set_int(conf, CONF_protocol, default_protocol); + /* The host parameter will already be loaded into CONF_host, + * so copy it across */ + conf_set_str(conf, CONF_serline, conf_get_str(conf, CONF_host)); + } + if (!strcmp(p, "-v")) { + RETURN(1); + flags |= FLAG_VERBOSE; + } + if (!strcmp(p, "-l")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_str(conf, CONF_username, value); + } + if (!strcmp(p, "-loghost")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_str(conf, CONF_loghost, value); + } + if (!strcmp(p, "-hostkey")) { + char *dup; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + dup = dupstr(value); + if (!validate_manual_hostkey(dup)) { + cmdline_error("'%s' is not a valid format for a manual host " + "key specification", value); + sfree(dup); + return ret; + } + conf_set_str_str(conf, CONF_ssh_manual_hostkeys, dup, ""); + sfree(dup); + } + if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) { + char type, *q, *qq, *key, *val; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + if (strcmp(p, "-D")) { + /* + * For -L or -R forwarding types: + * + * We expect _at least_ two colons in this string. The + * possible formats are `sourceport:desthost:destport', + * or `sourceip:sourceport:desthost:destport' if you're + * specifying a particular loopback address. We need to + * replace the one between source and dest with a \t; + * this means we must find the second-to-last colon in + * the string. + * + * (This looks like a foolish way of doing it given the + * existence of strrchr, but it's more efficient than + * two strrchrs - not to mention that the second strrchr + * would require us to modify the input string!) + */ + + type = p[1]; /* 'L' or 'R' */ + + q = qq = host_strchr(value, ':'); + while (qq) { + char *qqq = host_strchr(qq+1, ':'); + if (qqq) + q = qq; + qq = qqq; + } + + if (!q) { + cmdline_error("-%c expects at least two colons in its" + " argument", type); + return ret; + } + + key = dupprintf("%c%.*s", type, (int)(q - value), value); + val = dupstr(q+1); + } else { + /* + * Dynamic port forwardings are entered under the same key + * as if they were local (because they occupy the same + * port space - a local and a dynamic forwarding on the + * same local port are mutually exclusive), with the + * special value "D" (which can be distinguished from + * anything in the ordinary -L case by containing no + * colon). + */ + key = dupprintf("L%s", value); + val = dupstr("D"); + } + conf_set_str_str(conf, CONF_portfwd, key, val); + sfree(key); + sfree(val); + } + if ((!strcmp(p, "-nc"))) { + char *host, *portp; + + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + + portp = host_strchr(value, ':'); + if (!portp) { + cmdline_error("-nc expects argument of form 'host:port'"); + return ret; + } + + host = dupprintf("%.*s", (int)(portp - value), value); + conf_set_str(conf, CONF_ssh_nc_host, host); + conf_set_int(conf, CONF_ssh_nc_port, atoi(portp + 1)); + sfree(host); + } + if (!strcmp(p, "-m")) { + char *filename, *command; + int cmdlen, cmdsize; + FILE *fp; + int c, d; + + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + + filename = value; + + cmdlen = cmdsize = 0; + command = NULL; + fp = fopen(filename, "r"); + if (!fp) { + cmdline_error("unable to open command file \"%s\"", filename); + return ret; + } + do { + c = fgetc(fp); + d = c; + if (c == EOF) + d = 0; + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++] = d; + } while (c != EOF); + fclose(fp); + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_int(conf, CONF_nopty, TRUE); /* command => no terminal */ + sfree(command); + } + if (!strcmp(p, "-P")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(1); /* lower priority than -ssh,-telnet */ + conf_set_int(conf, CONF_port, atoi(value)); + } + if (!strcmp(p, "-pw")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(1); + /* We delay evaluating this until after the protocol is decided, + * so that we can warn if it's of no use with the selected protocol */ + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) + cmdline_error("the -pw option can only be used with the " + "SSH protocol"); + else { + cmdline_password = dupstr(value); + /* Assuming that `value' is directly from argv, make a good faith + * attempt to trample it, to stop it showing up in `ps' output + * on Unix-like systems. Not guaranteed, of course. */ + smemclr(value, strlen(value)); + } + } + + if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || + !strcmp(p, "-pageant")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_tryagent, TRUE); + } + if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") || + !strcmp(p, "-nopageant")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_tryagent, FALSE); + } + + if (!strcmp(p, "-A")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_agentfwd, 1); + } + if (!strcmp(p, "-a")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_agentfwd, 0); + } + + if (!strcmp(p, "-X")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_x11_forward, 1); + } + if (!strcmp(p, "-x")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_x11_forward, 0); + } + + if (!strcmp(p, "-t")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); /* lower priority than -m */ + conf_set_int(conf, CONF_nopty, 0); + } + if (!strcmp(p, "-T")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); + conf_set_int(conf, CONF_nopty, 1); + } + + if (!strcmp(p, "-N")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_ssh_no_shell, 1); + } + + if (!strcmp(p, "-C")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_compression, 1); + } + + if (!strcmp(p, "-1")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_sshprot, 0); /* ssh protocol 1 only */ + } + if (!strcmp(p, "-2")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_sshprot, 3); /* ssh protocol 2 only */ + } + + if (!strcmp(p, "-i")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_keyfile, fn); + filename_free(fn); + } + + if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { + RETURN(1); + SAVEABLE(1); + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4); + } + if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { + RETURN(1); + SAVEABLE(1); + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); + } + if (!strcmp(p, "-sercfg")) { + char* nextitem; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); + if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL) + cmdline_error("the -sercfg option can only be used with the " + "serial protocol"); + /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */ + nextitem = value; + while (nextitem[0] != '\0') { + int length, skip; + char *end = strchr(nextitem, ','); + if (!end) { + length = strlen(nextitem); + skip = 0; + } else { + length = end - nextitem; + nextitem[length] = '\0'; + skip = 1; + } + if (length == 1) { + switch (*nextitem) { + case '1': + case '2': + conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0')); + break; + + case '5': + case '6': + case '7': + case '8': + case '9': + conf_set_int(conf, CONF_serdatabits, *nextitem-'0'); + break; + + case 'n': + conf_set_int(conf, CONF_serparity, SER_PAR_NONE); + break; + case 'o': + conf_set_int(conf, CONF_serparity, SER_PAR_ODD); + break; + case 'e': + conf_set_int(conf, CONF_serparity, SER_PAR_EVEN); + break; + case 'm': + conf_set_int(conf, CONF_serparity, SER_PAR_MARK); + break; + case 's': + conf_set_int(conf, CONF_serparity, SER_PAR_SPACE); + break; + + case 'N': + conf_set_int(conf, CONF_serflow, SER_FLOW_NONE); + break; + case 'X': + conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF); + break; + case 'R': + conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS); + break; + case 'D': + conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR); + break; + + default: + cmdline_error("Unrecognised suboption \"-sercfg %c\"", + *nextitem); + } + } else if (length == 3 && !strncmp(nextitem,"1.5",3)) { + /* Messy special case */ + conf_set_int(conf, CONF_serstopbits, 3); + } else { + int serspeed = atoi(nextitem); + if (serspeed != 0) { + conf_set_int(conf, CONF_serspeed, serspeed); + } else { + cmdline_error("Unrecognised suboption \"-sercfg %s\"", + nextitem); + } + } + nextitem += length + skip; + } + } + + if (!strcmp(p, "-sessionlog")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + /* but available even in TOOLTYPE_NONNETWORK, cf pterm "-log" */ + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); + filename_free(fn); + } + + if (!strcmp(p, "-sshlog") || + !strcmp(p, "-sshrawlog")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, + !strcmp(p, "-sshlog") ? LGTYP_PACKETS : + /* !strcmp(p, "-sshrawlog") ? */ LGTYP_SSHRAW); + filename_free(fn); + } + + return ret; /* unrecognised */ +} + +void cmdline_run_saved(Conf *conf) +{ + int pri, i; + for (pri = 0; pri < NPRIORITIES; pri++) + for (i = 0; i < saves[pri].nsaved; i++) + cmdline_process_param(saves[pri].params[i].p, + saves[pri].params[i].value, 0, conf); +} diff --git a/netbox/libs/Putty/conf.c b/netbox/libs/Putty/conf.c new file mode 100644 index 000000000..e80f5853a --- /dev/null +++ b/netbox/libs/Putty/conf.c @@ -0,0 +1,613 @@ +/* + * conf.c: implementation of the internal storage format used for + * the configuration of a PuTTY session. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" + +/* + * Enumeration of types used in keys and values. + */ +typedef enum { TYPE_NONE, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT } Type; + +/* + * Arrays which allow us to look up the subkey and value types for a + * given primary key id. + */ +#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, +static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; +#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, +static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; + +/* + * Configuration keys are primarily integers (big enum of all the + * different configurable options); some keys have string-designated + * subkeys, such as the list of environment variables (subkeys + * defined by the variable names); some have integer-designated + * subkeys (wordness, colours, preference lists). + */ +struct key { + int primary; + union { + int i; + char *s; + } secondary; +}; + +struct value { + union { + int intval; + char *stringval; + Filename *fileval; + FontSpec *fontval; + } u; +}; + +struct conf_entry { + struct key key; + struct value value; +}; + +struct conf_tag { + tree234 *tree; +}; + +/* + * Because 'struct key' is the first element in 'struct conf_entry', + * it's safe (guaranteed by the C standard) to cast arbitrarily back + * and forth between the two types. Therefore, we only need one + * comparison function, which can double as a main sort function for + * the tree (comparing two conf_entry structures with each other) + * and a search function (looking up an externally supplied key). + */ +static int conf_cmp(void *av, void *bv) +{ + struct key *a = (struct key *)av; + struct key *b = (struct key *)bv; + + if (a->primary < b->primary) + return -1; + else if (a->primary > b->primary) + return +1; + switch (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct key'. We + * don't free the structure itself, since it's probably part of a + * larger allocated block. + */ +static void free_key(struct key *key) +{ + if (subkeytypes[key->primary] == TYPE_STR) + sfree(key->secondary.s); +} + +/* + * Copy a 'struct key' into another one, copying its dynamic data + * if necessary. + */ +static void copy_key(struct key *to, struct key *from) +{ + to->primary = from->primary; + switch (subkeytypes[to->primary]) { + case TYPE_INT: + to->secondary.i = from->secondary.i; + break; + case TYPE_STR: + to->secondary.s = dupstr(from->secondary.s); + break; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct value'. We + * don't free the value itself, since it's probably part of a larger + * allocated block. + */ +static void free_value(struct value *val, int type) +{ + if (type == TYPE_STR) + sfree(val->u.stringval); + else if (type == TYPE_FILENAME) + filename_free(val->u.fileval); + else if (type == TYPE_FONT) + fontspec_free(val->u.fontval); +} + +/* + * Copy a 'struct value' into another one, copying its dynamic data + * if necessary. + */ +static void copy_value(struct value *to, struct value *from, int type) +{ + switch (type) { + case TYPE_INT: + to->u.intval = from->u.intval; + break; + case TYPE_STR: + to->u.stringval = dupstr(from->u.stringval); + break; + case TYPE_FILENAME: + to->u.fileval = filename_copy(from->u.fileval); + break; + case TYPE_FONT: + to->u.fontval = fontspec_copy(from->u.fontval); + break; + } +} + +/* + * Free an entire 'struct conf_entry' and its dynamic data. + */ +static void free_entry(struct conf_entry *entry) +{ + free_key(&entry->key); + free_value(&entry->value, valuetypes[entry->key.primary]); + sfree(entry); +} + +Conf *conf_new(void) +{ + Conf *conf = snew(struct conf_tag); + + conf->tree = newtree234(conf_cmp); + + return conf; +} + +static void conf_clear(Conf *conf) +{ + struct conf_entry *entry; + + while ((entry = delpos234(conf->tree, 0)) != NULL) + free_entry(entry); +} + +void conf_free(Conf *conf) +{ + conf_clear(conf); + freetree234(conf->tree); + sfree(conf); +} + +static void conf_insert(Conf *conf, struct conf_entry *entry) +{ + struct conf_entry *oldentry = add234(conf->tree, entry); + if (oldentry && oldentry != entry) { + del234(conf->tree, oldentry); + free_entry(oldentry); + oldentry = add234(conf->tree, entry); + assert(oldentry == entry); + } +} + +void conf_copy_into(Conf *newconf, Conf *oldconf) +{ + struct conf_entry *entry, *entry2; + int i; + + conf_clear(newconf); + + for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { + entry2 = snew(struct conf_entry); + copy_key(&entry2->key, &entry->key); + copy_value(&entry2->value, &entry->value, + valuetypes[entry->key.primary]); + add234(newconf->tree, entry2); + } +} + +Conf *conf_copy(Conf *oldconf) +{ + Conf *newconf = conf_new(); + + conf_copy_into(newconf, oldconf); + + return newconf; +} + +int conf_get_int(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +int conf_get_int_int(Conf *conf, int primary, int secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + key.secondary.i = secondary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +char *conf_get_str(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval; +} + +char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + return entry ? entry->value.u.stringval : NULL; +} + +char *conf_get_str_str(Conf *conf, int primary, const char *secondary) +{ + char *ret = conf_get_str_str_opt(conf, primary, secondary); + assert(ret); + return ret; +} + +char *conf_get_str_strs(Conf *conf, int primary, + char *subkeyin, char **subkeyout) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + if (subkeyin) { + key.secondary.s = subkeyin; + entry = findrel234(conf->tree, &key, NULL, REL234_GT); + } else { + key.secondary.s = ""; + entry = findrel234(conf->tree, &key, NULL, REL234_GE); + } + if (!entry || entry->key.primary != primary) + return NULL; + *subkeyout = entry->key.secondary.s; + return entry->value.u.stringval; +} + +char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) +{ + struct key key; + struct conf_entry *entry; + int index; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = ""; + entry = findrelpos234(conf->tree, &key, NULL, REL234_GE, &index); + if (!entry || entry->key.primary != primary) + return NULL; + entry = index234(conf->tree, index + n); + if (!entry || entry->key.primary != primary) + return NULL; + return entry->key.secondary.s; +} + +Filename *conf_get_filename(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fileval; +} + +FontSpec *conf_get_fontspec(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fontval; +} + +void conf_set_int(Conf *conf, int primary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_int_int(Conf *conf, int primary, int secondary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->key.secondary.i = secondary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_str(Conf *conf, int primary, const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_set_str_str(Conf *conf, int primary, const char *secondary, + const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->key.secondary.s = dupstr(secondary); + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_del_str_str(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + if (entry) { + del234(conf->tree, entry); + free_entry(entry); + } + } + +void conf_set_filename(Conf *conf, int primary, const Filename *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + entry->key.primary = primary; + entry->value.u.fileval = filename_copy(value); + conf_insert(conf, entry); +} + +void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + entry->key.primary = primary; + entry->value.u.fontval = fontspec_copy(value); + conf_insert(conf, entry); +} + +int conf_serialised_size(Conf *conf) +{ + int i; + struct conf_entry *entry; + int size = 0; + + for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { + size += 4; /* primary key */ + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + size += 4; + break; + case TYPE_STR: + size += 1 + strlen(entry->key.secondary.s); + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + size += 4; + break; + case TYPE_STR: + size += 1 + strlen(entry->value.u.stringval); + break; + case TYPE_FILENAME: + size += filename_serialise(entry->value.u.fileval, NULL); + break; + case TYPE_FONT: + size += fontspec_serialise(entry->value.u.fontval, NULL); + break; + } + } + + size += 4; /* terminator value */ + + return size; +} + +void conf_serialise(Conf *conf, void *vdata) +{ + unsigned char *data = (unsigned char *)vdata; + int i, len; + struct conf_entry *entry; + + for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { + PUT_32BIT_MSB_FIRST(data, entry->key.primary); + data += 4; + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + PUT_32BIT_MSB_FIRST(data, entry->key.secondary.i); + data += 4; + break; + case TYPE_STR: + len = strlen(entry->key.secondary.s); + memcpy(data, entry->key.secondary.s, len); + data += len; + *data++ = 0; + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + PUT_32BIT_MSB_FIRST(data, entry->value.u.intval); + data += 4; + break; + case TYPE_STR: + len = strlen(entry->value.u.stringval); + memcpy(data, entry->value.u.stringval, len); + data += len; + *data++ = 0; + break; + case TYPE_FILENAME: + data += filename_serialise(entry->value.u.fileval, data); + break; + case TYPE_FONT: + data += fontspec_serialise(entry->value.u.fontval, data); + break; + } + } + + PUT_32BIT_MSB_FIRST(data, 0xFFFFFFFFU); +} + +int conf_deserialise(Conf *conf, void *vdata, int maxsize) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned char *start = data; + struct conf_entry *entry; + unsigned primary; + int used; + unsigned char *zero; + + while (maxsize >= 4) { + primary = GET_32BIT_MSB_FIRST(data); + data += 4, maxsize -= 4; + + if (primary >= N_CONFIG_OPTIONS) + break; + + entry = snew(struct conf_entry); + entry->key.primary = primary; + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + if (maxsize < 4) { + sfree(entry); + goto done; + } + entry->key.secondary.i = toint(GET_32BIT_MSB_FIRST(data)); + data += 4, maxsize -= 4; + break; + case TYPE_STR: + zero = memchr(data, 0, maxsize); + if (!zero) { + sfree(entry); + goto done; + } + entry->key.secondary.s = dupstr((char *)data); + maxsize -= (zero + 1 - data); + data = zero + 1; + break; + } + + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + if (maxsize < 4) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + entry->value.u.intval = toint(GET_32BIT_MSB_FIRST(data)); + data += 4, maxsize -= 4; + break; + case TYPE_STR: + zero = memchr(data, 0, maxsize); + if (!zero) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + entry->value.u.stringval = dupstr((char *)data); + maxsize -= (zero + 1 - data); + data = zero + 1; + break; + case TYPE_FILENAME: + entry->value.u.fileval = + filename_deserialise(data, maxsize, &used); + if (!entry->value.u.fileval) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + data += used; + maxsize -= used; + break; + case TYPE_FONT: + entry->value.u.fontval = + fontspec_deserialise(data, maxsize, &used); + if (!entry->value.u.fontval) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + data += used; + maxsize -= used; + break; + } + conf_insert(conf, entry); + } + + done: + return (int)(data - start); +} diff --git a/netbox/libs/Putty/config.c b/netbox/libs/Putty/config.c new file mode 100644 index 000000000..086956fc9 --- /dev/null +++ b/netbox/libs/Putty/config.c @@ -0,0 +1,2621 @@ +/* + * config.c - the platform-independent parts of the PuTTY + * configuration box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +#define PRINTER_DISABLED_STRING "None (printing disabled)" + +#define HOST_BOX_TITLE "Host Name (or IP address)" +#define PORT_BOX_TITLE "Port" + +void conf_radiobutton_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + + /* + * For a standard radio button set, the context parameter gives + * the primary key (CONF_foo), and the extra data per button + * gives the value the target field should take if that button + * is the one selected. + */ + if (event == EVENT_REFRESH) { + int val = conf_get_int(conf, ctrl->radio.context.i); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (val == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_int(conf, ctrl->radio.context.i, + ctrl->radio.buttondata[button].i); + } +} + +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key, invert; + Conf *conf = (Conf *)data; + + /* + * For a standard checkbox, the context parameter gives the + * primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT. + */ + key = ctrl->checkbox.context.i; + if (key & CHECKBOX_INVERT) { + key &= ~CHECKBOX_INVERT; + invert = 1; + } else + invert = 0; + + /* + * C lacks a logical XOR, so the following code uses the idiom + * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1 + * iff exactly one of a and b is nonzero, otherwise 0.) + */ + + if (event == EVENT_REFRESH) { + int val = conf_get_int(conf, key); + dlg_checkbox_set(ctrl, dlg, (!val ^ !invert)); + } else if (event == EVENT_VALCHANGE) { + conf_set_int(conf, key, !dlg_checkbox_get(ctrl,dlg) ^ !invert); + } +} + +void conf_editbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + /* + * The standard edit-box handler expects the main `context' + * field to contain the primary key. The secondary `context2' + * field indicates the type of this field: + * + * - if context2 > 0, the field is a string. + * - if context2 == -1, the field is an int and the edit box + * is numeric. + * - if context2 < -1, the field is an int and the edit box is + * _floating_, and (-context2) gives the scale. (E.g. if + * context2 == -1000, then typing 1.2 into the box will set + * the field to 1200.) + */ + int key = ctrl->editbox.context.i; + int length = ctrl->editbox.context2.i; + Conf *conf = (Conf *)data; + + if (length > 0) { + if (event == EVENT_REFRESH) { + char *field = conf_get_str(conf, key); + dlg_editbox_set(ctrl, dlg, field); + } else if (event == EVENT_VALCHANGE) { + char *field = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, key, field); + sfree(field); + } + } else if (length < 0) { + if (event == EVENT_REFRESH) { + char str[80]; + int value = conf_get_int(conf, key); + if (length == -1) + sprintf(str, "%d", value); + else + sprintf(str, "%g", (double)value / (double)(-length)); + dlg_editbox_set(ctrl, dlg, str); + } else if (event == EVENT_VALCHANGE) { + char *str = dlg_editbox_get(ctrl, dlg); + if (length == -1) + conf_set_int(conf, key, atoi(str)); + else + conf_set_int(conf, key, (int)((-length) * atof(str))); + sfree(str); + } + } +} + +void conf_filesel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key = ctrl->fileselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_filesel_set(ctrl, dlg, conf_get_filename(conf, key)); + } else if (event == EVENT_VALCHANGE) { + Filename *filename = dlg_filesel_get(ctrl, dlg); + conf_set_filename(conf, key, filename); + filename_free(filename); + } +} + +void conf_fontsel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key = ctrl->fontselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_fontsel_set(ctrl, dlg, conf_get_fontspec(conf, key)); + } else if (event == EVENT_VALCHANGE) { + FontSpec *fontspec = dlg_fontsel_get(ctrl, dlg); + conf_set_fontspec(conf, key, fontspec); + fontspec_free(fontspec); + } +} + +static void config_host_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + + /* + * This function works just like the standard edit box handler, + * only it has to choose the control's label and text from two + * different places depending on the protocol. + */ + if (event == EVENT_REFRESH) { + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) { + /* + * This label text is carefully chosen to contain an n, + * since that's the shortcut for the host name control. + */ + dlg_label_change(ctrl, dlg, "Serial line"); + dlg_editbox_set(ctrl, dlg, conf_get_str(conf, CONF_serline)); + } else { + dlg_label_change(ctrl, dlg, HOST_BOX_TITLE); + dlg_editbox_set(ctrl, dlg, conf_get_str(conf, CONF_host)); + } + } else if (event == EVENT_VALCHANGE) { + char *s = dlg_editbox_get(ctrl, dlg); + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + conf_set_str(conf, CONF_serline, s); + else + conf_set_str(conf, CONF_host, s); + sfree(s); + } +} + +static void config_port_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + char buf[80]; + + /* + * This function works similarly to the standard edit box handler, + * only it has to choose the control's label and text from two + * different places depending on the protocol. + */ + if (event == EVENT_REFRESH) { + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) { + /* + * This label text is carefully chosen to contain a p, + * since that's the shortcut for the port control. + */ + dlg_label_change(ctrl, dlg, "Speed"); + sprintf(buf, "%d", conf_get_int(conf, CONF_serspeed)); + } else { + dlg_label_change(ctrl, dlg, PORT_BOX_TITLE); + if (conf_get_int(conf, CONF_port) != 0) + sprintf(buf, "%d", conf_get_int(conf, CONF_port)); + else + /* Display an (invalid) port of 0 as blank */ + buf[0] = '\0'; + } + dlg_editbox_set(ctrl, dlg, buf); + } else if (event == EVENT_VALCHANGE) { + char *s = dlg_editbox_get(ctrl, dlg); + int i = atoi(s); + sfree(s); + + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + conf_set_int(conf, CONF_serspeed, i); + else + conf_set_int(conf, CONF_port, i); + } +} + +struct hostport { + union control *host, *port; +}; + +/* + * We export this function so that platform-specific config + * routines can use it to conveniently identify the protocol radio + * buttons in order to add to them. + */ +void config_protocolbuttons_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + struct hostport *hp = (struct hostport *)ctrl->radio.context.p; + + /* + * This function works just like the standard radio-button + * handler, except that it also has to change the setting of + * the port box, and refresh both host and port boxes when. We + * expect the context parameter to point at a hostport + * structure giving the `union control's for both. + */ + if (event == EVENT_REFRESH) { + int protocol = conf_get_int(conf, CONF_protocol); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (protocol == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + int oldproto = conf_get_int(conf, CONF_protocol); + int newproto, port; + + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + newproto = ctrl->radio.buttondata[button].i; + conf_set_int(conf, CONF_protocol, newproto); + + if (oldproto != newproto) { + Backend *ob = backend_from_proto(oldproto); + Backend *nb = backend_from_proto(newproto); + assert(ob); + assert(nb); + /* Iff the user hasn't changed the port from the old protocol's + * default, update it with the new protocol's default. + * (This includes a "default" of 0, implying that there is no + * sensible default for that protocol; in this case it's + * displayed as a blank.) + * This helps with the common case of tabbing through the + * controls in order and setting a non-default port before + * getting to the protocol; we want that non-default port + * to be preserved. */ + port = conf_get_int(conf, CONF_port); + if (port == ob->default_port) + conf_set_int(conf, CONF_port, nb->default_port); + } + dlg_refresh(hp->host, dlg); + dlg_refresh(hp->port, dlg); + } +} + +static void loggingbuttons_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + /* This function works just like the standard radio-button handler, + * but it has to fall back to "no logging" in situations where the + * configured logging type isn't applicable. + */ + if (event == EVENT_REFRESH) { + int logtype = conf_get_int(conf, CONF_logtype); + + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (logtype == ctrl->radio.buttondata[button].i) + break; + + /* We fell off the end, so we lack the configured logging type */ + if (button == ctrl->radio.nbuttons) { + button = 0; + conf_set_int(conf, CONF_logtype, LGTYP_NONE); + } + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_int(conf, CONF_logtype, ctrl->radio.buttondata[button].i); + } +} + +static void numeric_keypad_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + /* + * This function works much like the standard radio button + * handler, but it has to handle two fields in Conf. + */ + if (event == EVENT_REFRESH) { + if (conf_get_int(conf, CONF_nethack_keypad)) + button = 2; + else if (conf_get_int(conf, CONF_app_keypad)) + button = 1; + else + button = 0; + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + if (button == 2) { + conf_set_int(conf, CONF_app_keypad, FALSE); + conf_set_int(conf, CONF_nethack_keypad, TRUE); + } else { + conf_set_int(conf, CONF_app_keypad, (button != 0)); + conf_set_int(conf, CONF_nethack_keypad, FALSE); + } + } +} + +static void cipherlist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int i; + + static const struct { char *s; int c; } ciphers[] = { + { "3DES", CIPHER_3DES }, + { "Blowfish", CIPHER_BLOWFISH }, + { "DES", CIPHER_DES }, + { "AES (SSH-2 only)", CIPHER_AES }, + { "Arcfour (SSH-2 only)", CIPHER_ARCFOUR }, + { "-- warn below here --", CIPHER_WARN } + }; + + /* Set up the "selected ciphers" box. */ + /* (cipherlist assumed to contain all ciphers) */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < CIPHER_MAX; i++) { + int c = conf_get_int_int(conf, CONF_ssh_cipherlist, i); + int j; + char *cstr = NULL; + for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) { + if (ciphers[j].c == c) { + cstr = ciphers[j].s; + break; + } + } + dlg_listbox_addwithid(ctrl, dlg, cstr, c); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < CIPHER_MAX; i++) + conf_set_int_int(conf, CONF_ssh_cipherlist, i, + dlg_listbox_getid(ctrl, dlg, i)); + } +} + +#ifndef NO_GSSAPI +static void gsslist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int i; + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < ngsslibs; i++) { + int id = conf_get_int_int(conf, CONF_ssh_gsslist, i); + assert(id >= 0 && id < ngsslibs); + dlg_listbox_addwithid(ctrl, dlg, gsslibnames[id], id); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < ngsslibs; i++) + conf_set_int_int(conf, CONF_ssh_gsslist, i, + dlg_listbox_getid(ctrl, dlg, i)); + } +} +#endif + +static void kexlist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int i; + + static const struct { char *s; int k; } kexes[] = { + { "Diffie-Hellman group 1", KEX_DHGROUP1 }, + { "Diffie-Hellman group 14", KEX_DHGROUP14 }, + { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, + { "-- warn below here --", KEX_WARN } + }; + + /* Set up the "kex preference" box. */ + /* (kexlist assumed to contain all algorithms) */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < KEX_MAX; i++) { + int k = conf_get_int_int(conf, CONF_ssh_kexlist, i); + int j; + char *kstr = NULL; + for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) { + if (kexes[j].k == k) { + kstr = kexes[j].s; + break; + } + } + dlg_listbox_addwithid(ctrl, dlg, kstr, k); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < KEX_MAX; i++) + conf_set_int_int(conf, CONF_ssh_kexlist, i, + dlg_listbox_getid(ctrl, dlg, i)); + } +} + +static void printerbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int nprinters, i; + printer_enum *pe; + char *printer; + + dlg_update_start(ctrl, dlg); + /* + * Some backends may wish to disable the drop-down list on + * this edit box. Be prepared for this. + */ + if (ctrl->editbox.has_list) { + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_add(ctrl, dlg, PRINTER_DISABLED_STRING); + pe = printer_start_enum(&nprinters); + for (i = 0; i < nprinters; i++) + dlg_listbox_add(ctrl, dlg, printer_get_name(pe, i)); + printer_finish_enum(pe); + } + printer = conf_get_str(conf, CONF_printer); + if (!printer) + printer = PRINTER_DISABLED_STRING; + dlg_editbox_set(ctrl, dlg, printer); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) { + char *printer = dlg_editbox_get(ctrl, dlg); + if (!strcmp(printer, PRINTER_DISABLED_STRING)) + printer[0] = '\0'; + conf_set_str(conf, CONF_printer, printer); + sfree(printer); + } +} + +static void codepage_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int i; + const char *cp, *thiscp; + dlg_update_start(ctrl, dlg); + thiscp = cp_name(decode_codepage(conf_get_str(conf, + CONF_line_codepage))); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; (cp = cp_enumerate(i)) != NULL; i++) + dlg_listbox_add(ctrl, dlg, cp); + dlg_editbox_set(ctrl, dlg, thiscp); + conf_set_str(conf, CONF_line_codepage, thiscp); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) { + char *codepage = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, CONF_line_codepage, + cp_name(decode_codepage(codepage))); + sfree(codepage); + } +} + +static void sshbug_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + /* + * We must fetch the previously configured value from the Conf + * before we start modifying the drop-down list, otherwise the + * spurious SELCHANGE we trigger in the process will overwrite + * the value we wanted to keep. + */ + int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); + dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); + dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); + switch (oldconf) { + case AUTO: dlg_listbox_select(ctrl, dlg, 0); break; + case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 1); break; + case FORCE_ON: dlg_listbox_select(ctrl, dlg, 2); break; + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = AUTO; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, ctrl->listbox.context.i, i); + } +} + +struct sessionsaver_data { + union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; + union control *okbutton, *cancelbutton; + struct sesslist sesslist; + int midsession; + char *savedsession; /* the current contents of ssd->editbox */ +}; + +static void sessionsaver_data_free(void *ssdv) +{ + struct sessionsaver_data *ssd = (struct sessionsaver_data *)ssdv; + get_sesslist(&ssd->sesslist, FALSE); + sfree(ssd->savedsession); + sfree(ssd); +} + +/* + * Helper function to load the session selected in the list box, if + * any, as this is done in more than one place below. Returns 0 for + * failure. + */ +static int load_selected_session(struct sessionsaver_data *ssd, + void *dlg, Conf *conf, int *maybe_launch) +{ + int i = dlg_listbox_index(ssd->listbox, dlg); + int isdef; + if (i < 0) { + dlg_beep(dlg); + return 0; + } + isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); + load_settings(ssd->sesslist.sessions[i], conf); + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : ssd->sesslist.sessions[i]); + if (maybe_launch) + *maybe_launch = !isdef; + dlg_refresh(NULL, dlg); + /* Restore the selection, which might have been clobbered by + * changing the value of the edit box. */ + dlg_listbox_select(ssd->listbox, dlg, i); + return 1; +} + +static void sessionsaver_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct sessionsaver_data *ssd = + (struct sessionsaver_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ssd->editbox) { + dlg_editbox_set(ctrl, dlg, ssd->savedsession); + } else if (ctrl == ssd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < ssd->sesslist.nsessions; i++) + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]); + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_VALCHANGE) { + int top, bottom, halfway, i; + if (ctrl == ssd->editbox) { + sfree(ssd->savedsession); + ssd->savedsession = dlg_editbox_get(ctrl, dlg); + top = ssd->sesslist.nsessions; + bottom = -1; + while (top-bottom > 1) { + halfway = (top+bottom)/2; + i = strcmp(ssd->savedsession, ssd->sesslist.sessions[halfway]); + if (i <= 0 ) { + top = halfway; + } else { + bottom = halfway; + } + } + if (top == ssd->sesslist.nsessions) { + top -= 1; + } + dlg_listbox_select(ssd->listbox, dlg, top); + } + } else if (event == EVENT_ACTION) { + int mbl = FALSE; + if (!ssd->midsession && + (ctrl == ssd->listbox || + (ssd->loadbutton && ctrl == ssd->loadbutton))) { + /* + * The user has double-clicked a session, or hit Load. + * We must load the selected session, and then + * terminate the configuration dialog _if_ there was a + * double-click on the list box _and_ that session + * contains a hostname. + */ + if (load_selected_session(ssd, dlg, conf, &mbl) && + (mbl && ctrl == ssd->listbox && conf_launchable(conf))) { + dlg_end(dlg, 1); /* it's all over, and succeeded */ + } + } else if (ctrl == ssd->savebutton) { + int isdef = !strcmp(ssd->savedsession, "Default Settings"); + if (!ssd->savedsession[0]) { + int i = dlg_listbox_index(ssd->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + return; + } + isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : + ssd->sesslist.sessions[i]); + } + { + char *errmsg = save_settings(ssd->savedsession, conf); + if (errmsg) { + dlg_error_msg(dlg, errmsg); + sfree(errmsg); + } + } + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->editbox, dlg); + dlg_refresh(ssd->listbox, dlg); + } else if (!ssd->midsession && + ssd->delbutton && ctrl == ssd->delbutton) { + int i = dlg_listbox_index(ssd->listbox, dlg); + if (i <= 0) { + dlg_beep(dlg); + } else { + del_settings(ssd->sesslist.sessions[i]); + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->listbox, dlg); + } + } else if (ctrl == ssd->okbutton) { + if (ssd->midsession) { + /* In a mid-session Change Settings, Apply is always OK. */ + dlg_end(dlg, 1); + return; + } + /* + * Annoying special case. If the `Open' button is + * pressed while no host name is currently set, _and_ + * the session list previously had the focus, _and_ + * there was a session selected in that which had a + * valid host name in it, then load it and go. + */ + if (dlg_last_focused(ctrl, dlg) == ssd->listbox && + !conf_launchable(conf)) { + Conf *conf2 = conf_new(); + int mbl = FALSE; + if (!load_selected_session(ssd, dlg, conf2, &mbl)) { + dlg_beep(dlg); + conf_free(conf2); + return; + } + /* If at this point we have a valid session, go! */ + if (mbl && conf_launchable(conf2)) { + conf_copy_into(conf, conf2); + dlg_end(dlg, 1); + } else + dlg_beep(dlg); + + conf_free(conf2); + return; + } + + /* + * Otherwise, do the normal thing: if we have a valid + * session, get going. + */ + if (conf_launchable(conf)) { + dlg_end(dlg, 1); + } else + dlg_beep(dlg); + } else if (ctrl == ssd->cancelbutton) { + dlg_end(dlg, 0); + } + } +} + +struct charclass_data { + union control *listbox, *editbox, *button; +}; + +static void charclass_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct charclass_data *ccd = + (struct charclass_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ccd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < 128; i++) { + char str[100]; + sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i, + (i >= 0x21 && i != 0x7F) ? i : ' ', + conf_get_int_int(conf, CONF_wordness, i)); + dlg_listbox_add(ctrl, dlg, str); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ccd->button) { + char *str; + int i, n; + str = dlg_editbox_get(ccd->editbox, dlg); + n = atoi(str); + sfree(str); + for (i = 0; i < 128; i++) { + if (dlg_listbox_issel(ccd->listbox, dlg, i)) + conf_set_int_int(conf, CONF_wordness, i, n); + } + dlg_refresh(ccd->listbox, dlg); + } + } +} + +struct colour_data { + union control *listbox, *redit, *gedit, *bedit, *button; +}; + +static const char *const colours[] = { + "Default Foreground", "Default Bold Foreground", + "Default Background", "Default Bold Background", + "Cursor Text", "Cursor Colour", + "ANSI Black", "ANSI Black Bold", + "ANSI Red", "ANSI Red Bold", + "ANSI Green", "ANSI Green Bold", + "ANSI Yellow", "ANSI Yellow Bold", + "ANSI Blue", "ANSI Blue Bold", + "ANSI Magenta", "ANSI Magenta Bold", + "ANSI Cyan", "ANSI Cyan Bold", + "ANSI White", "ANSI White Bold" +}; + +static void colour_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct colour_data *cd = + (struct colour_data *)ctrl->generic.context.p; + int update = FALSE, clear = FALSE, r, g, b; + + if (event == EVENT_REFRESH) { + if (ctrl == cd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(colours); i++) + dlg_listbox_add(ctrl, dlg, colours[i]); + dlg_update_done(ctrl, dlg); + clear = TRUE; + update = TRUE; + } + } else if (event == EVENT_SELCHANGE) { + if (ctrl == cd->listbox) { + /* The user has selected a colour. Update the RGB text. */ + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) { + clear = TRUE; + } else { + clear = FALSE; + r = conf_get_int_int(conf, CONF_colours, i*3+0); + g = conf_get_int_int(conf, CONF_colours, i*3+1); + b = conf_get_int_int(conf, CONF_colours, i*3+2); + } + update = TRUE; + } + } else if (event == EVENT_VALCHANGE) { + if (ctrl == cd->redit || ctrl == cd->gedit || ctrl == cd->bedit) { + /* The user has changed the colour using the edit boxes. */ + char *str; + int i, cval; + + str = dlg_editbox_get(ctrl, dlg); + cval = atoi(str); + sfree(str); + if (cval > 255) cval = 255; + if (cval < 0) cval = 0; + + i = dlg_listbox_index(cd->listbox, dlg); + if (i >= 0) { + if (ctrl == cd->redit) + conf_set_int_int(conf, CONF_colours, i*3+0, cval); + else if (ctrl == cd->gedit) + conf_set_int_int(conf, CONF_colours, i*3+1, cval); + else if (ctrl == cd->bedit) + conf_set_int_int(conf, CONF_colours, i*3+2, cval); + } + } + } else if (event == EVENT_ACTION) { + if (ctrl == cd->button) { + int i = dlg_listbox_index(cd->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + return; + } + /* + * Start a colour selector, which will send us an + * EVENT_CALLBACK when it's finished and allow us to + * pick up the results. + */ + dlg_coloursel_start(ctrl, dlg, + conf_get_int_int(conf, CONF_colours, i*3+0), + conf_get_int_int(conf, CONF_colours, i*3+1), + conf_get_int_int(conf, CONF_colours, i*3+2)); + } + } else if (event == EVENT_CALLBACK) { + if (ctrl == cd->button) { + int i = dlg_listbox_index(cd->listbox, dlg); + /* + * Collect the results of the colour selector. Will + * return nonzero on success, or zero if the colour + * selector did nothing (user hit Cancel, for example). + */ + if (dlg_coloursel_results(ctrl, dlg, &r, &g, &b)) { + conf_set_int_int(conf, CONF_colours, i*3+0, r); + conf_set_int_int(conf, CONF_colours, i*3+1, g); + conf_set_int_int(conf, CONF_colours, i*3+2, b); + clear = FALSE; + update = TRUE; + } + } + } + + if (update) { + if (clear) { + dlg_editbox_set(cd->redit, dlg, ""); + dlg_editbox_set(cd->gedit, dlg, ""); + dlg_editbox_set(cd->bedit, dlg, ""); + } else { + char buf[40]; + sprintf(buf, "%d", r); dlg_editbox_set(cd->redit, dlg, buf); + sprintf(buf, "%d", g); dlg_editbox_set(cd->gedit, dlg, buf); + sprintf(buf, "%d", b); dlg_editbox_set(cd->bedit, dlg, buf); + } + } +} + +struct ttymodes_data { + union control *modelist, *valradio, *valbox; + union control *addbutton, *rembutton, *listbox; +}; + +static void ttymodes_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct ttymodes_data *td = + (struct ttymodes_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == td->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) { + char *disp = dupprintf("%s\t%s", key, + (val[0] == 'A') ? "(auto)" : val+1); + dlg_listbox_add(ctrl, dlg, disp); + sfree(disp); + } + dlg_update_done(ctrl, dlg); + } else if (ctrl == td->modelist) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; ttymodes[i]; i++) + dlg_listbox_add(ctrl, dlg, ttymodes[i]); + dlg_listbox_select(ctrl, dlg, 0); /* *shrug* */ + dlg_update_done(ctrl, dlg); + } else if (ctrl == td->valradio) { + dlg_radiobutton_set(ctrl, dlg, 0); + } + } else if (event == EVENT_ACTION) { + if (ctrl == td->addbutton) { + int ind = dlg_listbox_index(td->modelist, dlg); + if (ind >= 0) { + char type = dlg_radiobutton_get(td->valradio, dlg) ? 'V' : 'A'; + const char *key; + char *str, *val; + /* Construct new entry */ + key = ttymodes[ind]; + str = dlg_editbox_get(td->valbox, dlg); + val = dupprintf("%c%s", type, str); + sfree(str); + conf_set_str_str(conf, CONF_ttymodes, key, val); + sfree(val); + dlg_refresh(td->listbox, dlg); + } else + dlg_beep(dlg); + } else if (ctrl == td->rembutton) { + int i = 0; + char *key, *val; + int multisel = dlg_listbox_index(td->listbox, dlg) < 0; + for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) { + if (dlg_listbox_issel(td->listbox, dlg, i)) { + if (!multisel) { + /* Populate controls with entry we're about to + * delete, for ease of editing. + * (If multiple entries were selected, don't + * touch the controls.) */ + int ind = 0; + val++; + while (ttymodes[ind]) { + if (!strcmp(ttymodes[ind], key)) + break; + ind++; + } + dlg_listbox_select(td->modelist, dlg, ind); + dlg_radiobutton_set(td->valradio, dlg, + (*val == 'V')); + dlg_editbox_set(td->valbox, dlg, val+1); + } + conf_del_str_str(conf, CONF_ttymodes, key); + } + i++; + } + dlg_refresh(td->listbox, dlg); + } + } +} + +struct environ_data { + union control *varbox, *valbox, *addbutton, *rembutton, *listbox; +}; + +static void environ_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct environ_data *ed = + (struct environ_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ed->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *p = dupprintf("%s\t%s", key, val); + dlg_listbox_add(ctrl, dlg, p); + sfree(p); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ed->addbutton) { + char *key, *val, *str; + key = dlg_editbox_get(ed->varbox, dlg); + if (!*key) { + sfree(key); + dlg_beep(dlg); + return; + } + val = dlg_editbox_get(ed->valbox, dlg); + if (!*val) { + sfree(key); + sfree(val); + dlg_beep(dlg); + return; + } + conf_set_str_str(conf, CONF_environmt, key, val); + str = dupcat(key, "\t", val, NULL); + dlg_editbox_set(ed->varbox, dlg, ""); + dlg_editbox_set(ed->valbox, dlg, ""); + sfree(str); + sfree(key); + sfree(val); + dlg_refresh(ed->listbox, dlg); + } else if (ctrl == ed->rembutton) { + int i = dlg_listbox_index(ed->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *key, *val; + + key = conf_get_str_nthstrkey(conf, CONF_environmt, i); + if (key) { + /* Populate controls with the entry we're about to delete + * for ease of editing */ + val = conf_get_str_str(conf, CONF_environmt, key); + dlg_editbox_set(ed->varbox, dlg, key); + dlg_editbox_set(ed->valbox, dlg, val); + /* And delete it */ + conf_del_str_str(conf, CONF_environmt, key); + } + } + dlg_refresh(ed->listbox, dlg); + } + } +} + +struct portfwd_data { + union control *addbutton, *rembutton, *listbox; + union control *sourcebox, *destbox, *direction; +#ifndef NO_IPV6 + union control *addressfamily; +#endif +}; + +static void portfwd_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct portfwd_data *pfd = + (struct portfwd_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == pfd->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *p; + if (!strcmp(val, "D")) { + char *L; + /* + * A dynamic forwarding is stored as L12345=D or + * 6L12345=D (since it's mutually exclusive with + * L12345=anything else), but displayed as D12345 + * to match the fiction that 'Local', 'Remote' and + * 'Dynamic' are three distinct modes and also to + * align with OpenSSH's command line option syntax + * that people will already be used to. So, for + * display purposes, find the L in the key string + * and turn it into a D. + */ + p = dupprintf("%s\t", key); + L = strchr(p, 'L'); + if (L) *L = 'D'; + } else + p = dupprintf("%s\t%s", key, val); + dlg_listbox_add(ctrl, dlg, p); + sfree(p); + } + dlg_update_done(ctrl, dlg); + } else if (ctrl == pfd->direction) { + /* + * Default is Local. + */ + dlg_radiobutton_set(ctrl, dlg, 0); +#ifndef NO_IPV6 + } else if (ctrl == pfd->addressfamily) { + dlg_radiobutton_set(ctrl, dlg, 0); +#endif + } + } else if (event == EVENT_ACTION) { + if (ctrl == pfd->addbutton) { + char *family, *type, *src, *key, *val; + int whichbutton; + +#ifndef NO_IPV6 + whichbutton = dlg_radiobutton_get(pfd->addressfamily, dlg); + if (whichbutton == 1) + family = "4"; + else if (whichbutton == 2) + family = "6"; + else +#endif + family = ""; + + whichbutton = dlg_radiobutton_get(pfd->direction, dlg); + if (whichbutton == 0) + type = "L"; + else if (whichbutton == 1) + type = "R"; + else + type = "D"; + + src = dlg_editbox_get(pfd->sourcebox, dlg); + if (!*src) { + dlg_error_msg(dlg, "You need to specify a source port number"); + sfree(src); + return; + } + if (*type != 'D') { + val = dlg_editbox_get(pfd->destbox, dlg); + if (!*val || !host_strchr(val, ':')) { + dlg_error_msg(dlg, + "You need to specify a destination address\n" + "in the form \"host.name:port\""); + sfree(src); + sfree(val); + return; + } + } else { + type = "L"; + val = dupstr("D"); /* special case */ + } + + key = dupcat(family, type, src, NULL); + sfree(src); + + if (conf_get_str_str_opt(conf, CONF_portfwd, key)) { + dlg_error_msg(dlg, "Specified forwarding already exists"); + } else { + conf_set_str_str(conf, CONF_portfwd, key, val); + } + + sfree(key); + sfree(val); + dlg_refresh(pfd->listbox, dlg); + } else if (ctrl == pfd->rembutton) { + int i = dlg_listbox_index(pfd->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *key, *val, *p; + + key = conf_get_str_nthstrkey(conf, CONF_portfwd, i); + if (key) { + static const char *const afs = "A46"; + static const char *const dirs = "LRD"; + char *afp; + int dir; +#ifndef NO_IPV6 + int idx; +#endif + + /* Populate controls with the entry we're about to delete + * for ease of editing */ + p = key; + + afp = strchr(afs, *p); +#ifndef NO_IPV6 + idx = afp ? afp-afs : 0; +#endif + if (afp) + p++; +#ifndef NO_IPV6 + dlg_radiobutton_set(pfd->addressfamily, dlg, idx); +#endif + + dir = *p; + + val = conf_get_str_str(conf, CONF_portfwd, key); + if (!strcmp(val, "D")) { + dir = 'D'; + val = ""; + } + + dlg_radiobutton_set(pfd->direction, dlg, + strchr(dirs, dir) - dirs); + p++; + + dlg_editbox_set(pfd->sourcebox, dlg, p); + dlg_editbox_set(pfd->destbox, dlg, val); + /* And delete it */ + conf_del_str_str(conf, CONF_portfwd, key); + } + } + dlg_refresh(pfd->listbox, dlg); + } + } +} + +struct manual_hostkey_data { + union control *addbutton, *rembutton, *listbox, *keybox; +}; + +static void manual_hostkey_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct manual_hostkey_data *mh = + (struct manual_hostkey_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == mh->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys, + NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys, + key, &key)) { + dlg_listbox_add(ctrl, dlg, key); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == mh->addbutton) { + char *key; + + key = dlg_editbox_get(mh->keybox, dlg); + if (!*key) { + dlg_error_msg(dlg, "You need to specify a host key or " + "fingerprint"); + sfree(key); + return; + } + + if (!validate_manual_hostkey(key)) { + dlg_error_msg(dlg, "Host key is not in a valid format"); + } else if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, + key)) { + dlg_error_msg(dlg, "Specified host key is already listed"); + } else { + conf_set_str_str(conf, CONF_ssh_manual_hostkeys, key, ""); + } + + sfree(key); + dlg_refresh(mh->listbox, dlg); + } else if (ctrl == mh->rembutton) { + int i = dlg_listbox_index(mh->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *key; + + key = conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, i); + if (key) { + dlg_editbox_set(mh->keybox, dlg, key); + /* And delete it */ + conf_del_str_str(conf, CONF_ssh_manual_hostkeys, key); + } + } + dlg_refresh(mh->listbox, dlg); + } + } +} + +void setup_config_box(struct controlbox *b, int midsession, + int protocol, int protcfginfo) +{ + struct controlset *s; + struct sessionsaver_data *ssd; + struct charclass_data *ccd; + struct colour_data *cd; + struct ttymodes_data *td; + struct environ_data *ed; + struct portfwd_data *pfd; + struct manual_hostkey_data *mh; + union control *c; + char *str; + + ssd = (struct sessionsaver_data *) + ctrl_alloc_with_free(b, sizeof(struct sessionsaver_data), + sessionsaver_data_free); + memset(ssd, 0, sizeof(*ssd)); + ssd->savedsession = dupstr(""); + ssd->midsession = midsession; + + /* + * The standard panel that appears at the bottom of all panels: + * Open, Cancel, Apply etc. + */ + s = ctrl_getset(b, "", "", ""); + ctrl_columns(s, 5, 20, 20, 20, 20, 20); + ssd->okbutton = ctrl_pushbutton(s, + (midsession ? "Apply" : "Open"), + (char)(midsession ? 'a' : 'o'), + HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->okbutton->button.isdefault = TRUE; + ssd->okbutton->generic.column = 3; + ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->cancelbutton->button.iscancel = TRUE; + ssd->cancelbutton->generic.column = 4; + /* We carefully don't close the 5-column part, so that platform- + * specific add-ons can put extra buttons alongside Open and Cancel. */ + + /* + * The Session panel. + */ + str = dupprintf("Basic options for your %s session", appname); + ctrl_settitle(b, "Session", str); + sfree(str); + + if (!midsession) { + struct hostport *hp = (struct hostport *) + ctrl_alloc(b, sizeof(struct hostport)); + + s = ctrl_getset(b, "Session", "hostport", + "Specify the destination you want to connect to"); + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100, + HELPCTX(session_hostname), + config_host_handler, I(0), I(0)); + c->generic.column = 0; + hp->host = c; + c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100, + HELPCTX(session_hostname), + config_port_handler, I(0), I(0)); + c->generic.column = 1; + hp->port = c; + ctrl_columns(s, 1, 100); + + if (!backend_from_proto(PROT_SSH)) { + ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3, + HELPCTX(session_hostname), + config_protocolbuttons_handler, P(hp), + "Raw", 'w', I(PROT_RAW), + "Telnet", 't', I(PROT_TELNET), + "Rlogin", 'i', I(PROT_RLOGIN), + NULL); + } else { + ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 4, + HELPCTX(session_hostname), + config_protocolbuttons_handler, P(hp), + "Raw", 'w', I(PROT_RAW), + "Telnet", 't', I(PROT_TELNET), + "Rlogin", 'i', I(PROT_RLOGIN), + "SSH", 's', I(PROT_SSH), + NULL); + } + } + + /* + * The Load/Save panel is available even in mid-session. + */ + s = ctrl_getset(b, "Session", "savedsessions", + midsession ? "Save the current session settings" : + "Load, save or delete a stored session"); + ctrl_columns(s, 2, 75, 25); + get_sesslist(&ssd->sesslist, TRUE); + ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd), P(NULL)); + ssd->editbox->generic.column = 0; + /* Reset columns so that the buttons are alongside the list, rather + * than alongside that edit box. */ + ctrl_columns(s, 1, 100); + ctrl_columns(s, 2, 75, 25); + ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->listbox->generic.column = 0; + ssd->listbox->listbox.height = 7; + if (!midsession) { + ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->loadbutton->generic.column = 1; + } else { + /* We can't offer the Load button mid-session, as it would allow the + * user to load and subsequently save settings they can't see. (And + * also change otherwise immutable settings underfoot; that probably + * shouldn't be a problem, but.) */ + ssd->loadbutton = NULL; + } + /* "Save" button is permitted mid-session. */ + ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->savebutton->generic.column = 1; + if (!midsession) { + ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->delbutton->generic.column = 1; + } else { + /* Disable the Delete button mid-session too, for UI consistency. */ + ssd->delbutton = NULL; + } + ctrl_columns(s, 1, 100); + + s = ctrl_getset(b, "Session", "otheropts", NULL); + ctrl_radiobuttons(s, "Close window on exit:", 'x', 4, + HELPCTX(session_coe), + conf_radiobutton_handler, + I(CONF_close_on_exit), + "Always", I(FORCE_ON), + "Never", I(FORCE_OFF), + "Only on clean exit", I(AUTO), NULL); + + /* + * The Session/Logging panel. + */ + ctrl_settitle(b, "Session/Logging", "Options controlling session logging"); + + s = ctrl_getset(b, "Session/Logging", "main", NULL); + /* + * The logging buttons change depending on whether SSH packet + * logging can sensibly be available. + */ + { + char *sshlogname, *sshrawlogname; + if ((midsession && protocol == PROT_SSH) || + (!midsession && backend_from_proto(PROT_SSH))) { + sshlogname = "SSH packets"; + sshrawlogname = "SSH packets and raw data"; + } else { + sshlogname = NULL; /* this will disable both buttons */ + sshrawlogname = NULL; /* this will just placate optimisers */ + } + ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 2, + HELPCTX(logging_main), + loggingbuttons_handler, + I(CONF_logtype), + "None", 't', I(LGTYP_NONE), + "Printable output", 'p', I(LGTYP_ASCII), + "All session output", 'l', I(LGTYP_DEBUG), + sshlogname, 's', I(LGTYP_PACKETS), + sshrawlogname, 'r', I(LGTYP_SSHRAW), + NULL); + } + ctrl_filesel(s, "Log file name:", 'f', + NULL, TRUE, "Select session log file name", + HELPCTX(logging_filename), + conf_filesel_handler, I(CONF_logfilename)); + ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," + " &T for time, &H for host name, and &P for port number)", + HELPCTX(logging_filename)); + ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1, + HELPCTX(logging_exists), + conf_radiobutton_handler, I(CONF_logxfovr), + "Always overwrite it", I(LGXF_OVR), + "Always append to the end of it", I(LGXF_APN), + "Ask the user every time", I(LGXF_ASK), NULL); + ctrl_checkbox(s, "Flush log file frequently", 'u', + HELPCTX(logging_flush), + conf_checkbox_handler, I(CONF_logflush)); + + if ((midsession && protocol == PROT_SSH) || + (!midsession && backend_from_proto(PROT_SSH))) { + s = ctrl_getset(b, "Session/Logging", "ssh", + "Options specific to SSH packet logging"); + ctrl_checkbox(s, "Omit known password fields", 'k', + HELPCTX(logging_ssh_omit_password), + conf_checkbox_handler, I(CONF_logomitpass)); + ctrl_checkbox(s, "Omit session data", 'd', + HELPCTX(logging_ssh_omit_data), + conf_checkbox_handler, I(CONF_logomitdata)); + } + + /* + * The Terminal panel. + */ + ctrl_settitle(b, "Terminal", "Options controlling the terminal emulation"); + + s = ctrl_getset(b, "Terminal", "general", "Set various terminal options"); + ctrl_checkbox(s, "Auto wrap mode initially on", 'w', + HELPCTX(terminal_autowrap), + conf_checkbox_handler, I(CONF_wrap_mode)); + ctrl_checkbox(s, "DEC Origin Mode initially on", 'd', + HELPCTX(terminal_decom), + conf_checkbox_handler, I(CONF_dec_om)); + ctrl_checkbox(s, "Implicit CR in every LF", 'r', + HELPCTX(terminal_lfhascr), + conf_checkbox_handler, I(CONF_lfhascr)); + ctrl_checkbox(s, "Implicit LF in every CR", 'f', + HELPCTX(terminal_crhaslf), + conf_checkbox_handler, I(CONF_crhaslf)); + ctrl_checkbox(s, "Use background colour to erase screen", 'e', + HELPCTX(terminal_bce), + conf_checkbox_handler, I(CONF_bce)); + ctrl_checkbox(s, "Enable blinking text", 'n', + HELPCTX(terminal_blink), + conf_checkbox_handler, I(CONF_blinktext)); + ctrl_editbox(s, "Answerback to ^E:", 's', 100, + HELPCTX(terminal_answerback), + conf_editbox_handler, I(CONF_answerback), I(1)); + + s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); + ctrl_radiobuttons(s, "Local echo:", 'l', 3, + HELPCTX(terminal_localecho), + conf_radiobutton_handler,I(CONF_localecho), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + ctrl_radiobuttons(s, "Local line editing:", 't', 3, + HELPCTX(terminal_localedit), + conf_radiobutton_handler,I(CONF_localedit), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + + s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); + ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100, + HELPCTX(terminal_printing), + printerbox_handler, P(NULL), P(NULL)); + + /* + * The Terminal/Keyboard panel. + */ + ctrl_settitle(b, "Terminal/Keyboard", + "Options controlling the effects of keys"); + + s = ctrl_getset(b, "Terminal/Keyboard", "mappings", + "Change the sequences sent by:"); + ctrl_radiobuttons(s, "The Backspace key", 'b', 2, + HELPCTX(keyboard_backspace), + conf_radiobutton_handler, + I(CONF_bksp_is_delete), + "Control-H", I(0), "Control-? (127)", I(1), NULL); + ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, + HELPCTX(keyboard_homeend), + conf_radiobutton_handler, + I(CONF_rxvt_homeend), + "Standard", I(0), "rxvt", I(1), NULL); + ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, + HELPCTX(keyboard_funkeys), + conf_radiobutton_handler, + I(CONF_funky_type), + "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), + "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL); + + s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", + "Application keypad settings:"); + ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3, + HELPCTX(keyboard_appcursor), + conf_radiobutton_handler, + I(CONF_app_cursor), + "Normal", I(0), "Application", I(1), NULL); + ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, + HELPCTX(keyboard_appkeypad), + numeric_keypad_handler, P(NULL), + "Normal", I(0), "Application", I(1), "NetHack", I(2), + NULL); + + /* + * The Terminal/Bell panel. + */ + ctrl_settitle(b, "Terminal/Bell", + "Options controlling the terminal bell"); + + s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell"); + ctrl_radiobuttons(s, "Action to happen when a bell occurs:", 'b', 1, + HELPCTX(bell_style), + conf_radiobutton_handler, I(CONF_beep), + "None (bell disabled)", I(BELL_DISABLED), + "Make default system alert sound", I(BELL_DEFAULT), + "Visual bell (flash window)", I(BELL_VISUAL), NULL); + + s = ctrl_getset(b, "Terminal/Bell", "overload", + "Control the bell overload behaviour"); + ctrl_checkbox(s, "Bell is temporarily disabled when over-used", 'd', + HELPCTX(bell_overload), + conf_checkbox_handler, I(CONF_bellovl)); + ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_n), I(-1)); + ctrl_editbox(s, "... in this many seconds", 't', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_t), + I(-TICKSPERSEC)); + ctrl_text(s, "The bell is re-enabled after a few seconds of silence.", + HELPCTX(bell_overload)); + ctrl_editbox(s, "Seconds of silence required", 's', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_s), + I(-TICKSPERSEC)); + + /* + * The Terminal/Features panel. + */ + ctrl_settitle(b, "Terminal/Features", + "Enabling and disabling advanced terminal features"); + + s = ctrl_getset(b, "Terminal/Features", "main", NULL); + ctrl_checkbox(s, "Disable application cursor keys mode", 'u', + HELPCTX(features_application), + conf_checkbox_handler, I(CONF_no_applic_c)); + ctrl_checkbox(s, "Disable application keypad mode", 'k', + HELPCTX(features_application), + conf_checkbox_handler, I(CONF_no_applic_k)); + ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x', + HELPCTX(features_mouse), + conf_checkbox_handler, I(CONF_no_mouse_rep)); + ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's', + HELPCTX(features_resize), + conf_checkbox_handler, + I(CONF_no_remote_resize)); + ctrl_checkbox(s, "Disable switching to alternate terminal screen", 'w', + HELPCTX(features_altscreen), + conf_checkbox_handler, I(CONF_no_alt_screen)); + ctrl_checkbox(s, "Disable remote-controlled window title changing", 't', + HELPCTX(features_retitle), + conf_checkbox_handler, + I(CONF_no_remote_wintitle)); + ctrl_radiobuttons(s, "Response to remote title query (SECURITY):", 'q', 3, + HELPCTX(features_qtitle), + conf_radiobutton_handler, + I(CONF_remote_qtitle_action), + "None", I(TITLE_NONE), + "Empty string", I(TITLE_EMPTY), + "Window title", I(TITLE_REAL), NULL); + ctrl_checkbox(s, "Disable destructive backspace on server sending ^?",'b', + HELPCTX(features_dbackspace), + conf_checkbox_handler, I(CONF_no_dbackspace)); + ctrl_checkbox(s, "Disable remote-controlled character set configuration", + 'r', HELPCTX(features_charset), conf_checkbox_handler, + I(CONF_no_remote_charset)); + ctrl_checkbox(s, "Disable Arabic text shaping", + 'l', HELPCTX(features_arabicshaping), conf_checkbox_handler, + I(CONF_arabicshaping)); + ctrl_checkbox(s, "Disable bidirectional text display", + 'd', HELPCTX(features_bidi), conf_checkbox_handler, + I(CONF_bidi)); + + /* + * The Window panel. + */ + str = dupprintf("Options controlling %s's window", appname); + ctrl_settitle(b, "Window", str); + sfree(str); + + s = ctrl_getset(b, "Window", "size", "Set the size of the window"); + ctrl_columns(s, 2, 50, 50); + c = ctrl_editbox(s, "Columns", 'm', 100, + HELPCTX(window_size), + conf_editbox_handler, I(CONF_width), I(-1)); + c->generic.column = 0; + c = ctrl_editbox(s, "Rows", 'r', 100, + HELPCTX(window_size), + conf_editbox_handler, I(CONF_height),I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + + s = ctrl_getset(b, "Window", "scrollback", + "Control the scrollback in the window"); + ctrl_editbox(s, "Lines of scrollback", 's', 50, + HELPCTX(window_scrollback), + conf_editbox_handler, I(CONF_savelines), I(-1)); + ctrl_checkbox(s, "Display scrollbar", 'd', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scrollbar)); + ctrl_checkbox(s, "Reset scrollback on keypress", 'k', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scroll_on_key)); + ctrl_checkbox(s, "Reset scrollback on display activity", 'p', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scroll_on_disp)); + ctrl_checkbox(s, "Push erased text into scrollback", 'e', + HELPCTX(window_erased), + conf_checkbox_handler, + I(CONF_erase_to_scrollback)); + + /* + * The Window/Appearance panel. + */ + str = dupprintf("Configure the appearance of %s's window", appname); + ctrl_settitle(b, "Window/Appearance", str); + sfree(str); + + s = ctrl_getset(b, "Window/Appearance", "cursor", + "Adjust the use of the cursor"); + ctrl_radiobuttons(s, "Cursor appearance:", NO_SHORTCUT, 3, + HELPCTX(appearance_cursor), + conf_radiobutton_handler, + I(CONF_cursor_type), + "Block", 'l', I(0), + "Underline", 'u', I(1), + "Vertical line", 'v', I(2), NULL); + ctrl_checkbox(s, "Cursor blinks", 'b', + HELPCTX(appearance_cursor), + conf_checkbox_handler, I(CONF_blink_cur)); + + s = ctrl_getset(b, "Window/Appearance", "font", + "Font settings"); + ctrl_fontsel(s, "Font used in the terminal window", 'n', + HELPCTX(appearance_font), + conf_fontsel_handler, I(CONF_font)); + + s = ctrl_getset(b, "Window/Appearance", "mouse", + "Adjust the use of the mouse pointer"); + ctrl_checkbox(s, "Hide mouse pointer when typing in window", 'p', + HELPCTX(appearance_hidemouse), + conf_checkbox_handler, I(CONF_hide_mouseptr)); + + s = ctrl_getset(b, "Window/Appearance", "border", + "Adjust the window border"); + ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, + HELPCTX(appearance_border), + conf_editbox_handler, + I(CONF_window_border), I(-1)); + + /* + * The Window/Behaviour panel. + */ + str = dupprintf("Configure the behaviour of %s's window", appname); + ctrl_settitle(b, "Window/Behaviour", str); + sfree(str); + + s = ctrl_getset(b, "Window/Behaviour", "title", + "Adjust the behaviour of the window title"); + ctrl_editbox(s, "Window title:", 't', 100, + HELPCTX(appearance_title), + conf_editbox_handler, I(CONF_wintitle), I(1)); + ctrl_checkbox(s, "Separate window and icon titles", 'i', + HELPCTX(appearance_title), + conf_checkbox_handler, + I(CHECKBOX_INVERT | CONF_win_name_always)); + + s = ctrl_getset(b, "Window/Behaviour", "main", NULL); + ctrl_checkbox(s, "Warn before closing window", 'w', + HELPCTX(behaviour_closewarn), + conf_checkbox_handler, I(CONF_warn_on_close)); + + /* + * The Window/Translation panel. + */ + ctrl_settitle(b, "Window/Translation", + "Options controlling character set translation"); + + s = ctrl_getset(b, "Window/Translation", "trans", + "Character set translation"); + ctrl_combobox(s, "Remote character set:", + 'r', 100, HELPCTX(translation_codepage), + codepage_handler, P(NULL), P(NULL)); + + s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); + ctrl_checkbox(s, "Treat CJK ambiguous characters as wide", 'w', + HELPCTX(translation_cjk_ambig_wide), + conf_checkbox_handler, I(CONF_cjk_ambig_wide)); + + str = dupprintf("Adjust how %s handles line drawing characters", appname); + s = ctrl_getset(b, "Window/Translation", "linedraw", str); + sfree(str); + ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1, + HELPCTX(translation_linedraw), + conf_radiobutton_handler, + I(CONF_vtmode), + "Use Unicode line drawing code points",'u',I(VT_UNICODE), + "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), + NULL); + ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', + HELPCTX(selection_linedraw), + conf_checkbox_handler, I(CONF_rawcnp)); + + /* + * The Window/Selection panel. + */ + ctrl_settitle(b, "Window/Selection", "Options controlling copy and paste"); + + s = ctrl_getset(b, "Window/Selection", "mouse", + "Control use of mouse"); + ctrl_checkbox(s, "Shift overrides application's use of mouse", 'p', + HELPCTX(selection_shiftdrag), + conf_checkbox_handler, I(CONF_mouse_override)); + ctrl_radiobuttons(s, + "Default selection mode (Alt+drag does the other one):", + NO_SHORTCUT, 2, + HELPCTX(selection_rect), + conf_radiobutton_handler, + I(CONF_rect_select), + "Normal", 'n', I(0), + "Rectangular block", 'r', I(1), NULL); + + s = ctrl_getset(b, "Window/Selection", "charclass", + "Control the select-one-word-at-a-time mode"); + ccd = (struct charclass_data *) + ctrl_alloc(b, sizeof(struct charclass_data)); + ccd->listbox = ctrl_listbox(s, "Character classes:", 'e', + HELPCTX(selection_charclasses), + charclass_handler, P(ccd)); + ccd->listbox->listbox.multisel = 1; + ccd->listbox->listbox.ncols = 4; + ccd->listbox->listbox.percentages = snewn(4, int); + ccd->listbox->listbox.percentages[0] = 15; + ccd->listbox->listbox.percentages[1] = 25; + ccd->listbox->listbox.percentages[2] = 20; + ccd->listbox->listbox.percentages[3] = 40; + ctrl_columns(s, 2, 67, 33); + ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50, + HELPCTX(selection_charclasses), + charclass_handler, P(ccd), P(NULL)); + ccd->editbox->generic.column = 0; + ccd->button = ctrl_pushbutton(s, "Set", 's', + HELPCTX(selection_charclasses), + charclass_handler, P(ccd)); + ccd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Window/Colours panel. + */ + ctrl_settitle(b, "Window/Colours", "Options controlling use of colours"); + + s = ctrl_getset(b, "Window/Colours", "general", + "General options for colour usage"); + ctrl_checkbox(s, "Allow terminal to specify ANSI colours", 'i', + HELPCTX(colours_ansi), + conf_checkbox_handler, I(CONF_ansi_colour)); + ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', + HELPCTX(colours_xterm256), conf_checkbox_handler, + I(CONF_xterm_256_colour)); + ctrl_radiobuttons(s, "Indicate bolded text by changing:", 'b', 3, + HELPCTX(colours_bold), + conf_radiobutton_handler, I(CONF_bold_style), + "The font", I(1), + "The colour", I(2), + "Both", I(3), + NULL); + + str = dupprintf("Adjust the precise colours %s displays", appname); + s = ctrl_getset(b, "Window/Colours", "adjust", str); + sfree(str); + ctrl_text(s, "Select a colour from the list, and then click the" + " Modify button to change its appearance.", + HELPCTX(colours_config)); + ctrl_columns(s, 2, 67, 33); + cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data)); + cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u', + HELPCTX(colours_config), colour_handler, P(cd)); + cd->listbox->generic.column = 0; + cd->listbox->listbox.height = 7; + c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); + c->generic.column = 1; + cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->redit->generic.column = 1; + cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->gedit->generic.column = 1; + cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->bedit->generic.column = 1; + cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), + colour_handler, P(cd)); + cd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Connection panel. This doesn't show up if we're in a + * non-network utility such as pterm. We tell this by being + * passed a protocol < 0. + */ + if (protocol >= 0) { + ctrl_settitle(b, "Connection", "Options controlling the connection"); + + s = ctrl_getset(b, "Connection", "keepalive", + "Sending of null packets to keep session active"); + ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, + HELPCTX(connection_keepalive), + conf_editbox_handler, I(CONF_ping_interval), + I(-1)); + + if (!midsession) { + s = ctrl_getset(b, "Connection", "tcp", + "Low-level TCP connection options"); + ctrl_checkbox(s, "Disable Nagle's algorithm (TCP_NODELAY option)", + 'n', HELPCTX(connection_nodelay), + conf_checkbox_handler, + I(CONF_tcp_nodelay)); + ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)", + 'p', HELPCTX(connection_tcpkeepalive), + conf_checkbox_handler, + I(CONF_tcp_keepalives)); +#ifndef NO_IPV6 + s = ctrl_getset(b, "Connection", "ipversion", + "Internet protocol version"); + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(connection_ipversion), + conf_radiobutton_handler, + I(CONF_addressfamily), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + + { + char *label = backend_from_proto(PROT_SSH) ? + "Logical name of remote host (e.g. for SSH key lookup):" : + "Logical name of remote host:"; + s = ctrl_getset(b, "Connection", "identity", + "Logical name of remote host"); + ctrl_editbox(s, label, 'm', 100, + HELPCTX(connection_loghost), + conf_editbox_handler, I(CONF_loghost), I(1)); + } + } + + /* + * A sub-panel Connection/Data, containing options that + * decide on data to send to the server. + */ + if (!midsession) { + ctrl_settitle(b, "Connection/Data", "Data to send to the server"); + + s = ctrl_getset(b, "Connection/Data", "login", + "Login details"); + ctrl_editbox(s, "Auto-login username", 'u', 50, + HELPCTX(connection_username), + conf_editbox_handler, I(CONF_username), I(1)); + { + /* We assume the local username is sufficiently stable + * to include on the dialog box. */ + char *user = get_username(); + char *userlabel = dupprintf("Use system username (%s)", + user ? user : ""); + sfree(user); + ctrl_radiobuttons(s, "When username is not specified:", 'n', 4, + HELPCTX(connection_username_from_env), + conf_radiobutton_handler, + I(CONF_username_from_env), + "Prompt", I(FALSE), + userlabel, I(TRUE), + NULL); + sfree(userlabel); + } + + s = ctrl_getset(b, "Connection/Data", "term", + "Terminal details"); + ctrl_editbox(s, "Terminal-type string", 't', 50, + HELPCTX(connection_termtype), + conf_editbox_handler, I(CONF_termtype), I(1)); + ctrl_editbox(s, "Terminal speeds", 's', 50, + HELPCTX(connection_termspeed), + conf_editbox_handler, I(CONF_termspeed), I(1)); + + s = ctrl_getset(b, "Connection/Data", "env", + "Environment variables"); + ctrl_columns(s, 2, 80, 20); + ed = (struct environ_data *) + ctrl_alloc(b, sizeof(struct environ_data)); + ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->varbox->generic.column = 0; + ed->valbox = ctrl_editbox(s, "Value", 'l', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->valbox->generic.column = 0; + ed->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->addbutton->generic.column = 1; + ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->rembutton->generic.column = 1; + ctrl_columns(s, 1, 100); + ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->listbox->listbox.height = 3; + ed->listbox->listbox.ncols = 2; + ed->listbox->listbox.percentages = snewn(2, int); + ed->listbox->listbox.percentages[0] = 30; + ed->listbox->listbox.percentages[1] = 70; + } + + } + + if (!midsession) { + /* + * The Connection/Proxy panel. + */ + ctrl_settitle(b, "Connection/Proxy", + "Options controlling proxy usage"); + + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + ctrl_radiobuttons(s, "Proxy type:", 't', 3, + HELPCTX(proxy_type), + conf_radiobutton_handler, + I(CONF_proxy_type), + "None", I(PROXY_NONE), + "SOCKS 4", I(PROXY_SOCKS4), + "SOCKS 5", I(PROXY_SOCKS5), + "HTTP", I(PROXY_HTTP), + "Telnet", I(PROXY_TELNET), + NULL); + ctrl_columns(s, 2, 80, 20); + c = ctrl_editbox(s, "Proxy hostname", 'y', 100, + HELPCTX(proxy_main), + conf_editbox_handler, + I(CONF_proxy_host), I(1)); + c->generic.column = 0; + c = ctrl_editbox(s, "Port", 'p', 100, + HELPCTX(proxy_main), + conf_editbox_handler, + I(CONF_proxy_port), + I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, + HELPCTX(proxy_exclude), + conf_editbox_handler, + I(CONF_proxy_exclude_list), I(1)); + ctrl_checkbox(s, "Consider proxying local host connections", 'x', + HELPCTX(proxy_exclude), + conf_checkbox_handler, + I(CONF_even_proxy_localhost)); + ctrl_radiobuttons(s, "Do DNS name lookup at proxy end:", 'd', 3, + HELPCTX(proxy_dns), + conf_radiobutton_handler, + I(CONF_proxy_dns), + "No", I(FORCE_OFF), + "Auto", I(AUTO), + "Yes", I(FORCE_ON), NULL); + ctrl_editbox(s, "Username", 'u', 60, + HELPCTX(proxy_auth), + conf_editbox_handler, + I(CONF_proxy_username), I(1)); + c = ctrl_editbox(s, "Password", 'w', 60, + HELPCTX(proxy_auth), + conf_editbox_handler, + I(CONF_proxy_password), I(1)); + c->editbox.password = 1; + ctrl_editbox(s, "Telnet command", 'm', 100, + HELPCTX(proxy_command), + conf_editbox_handler, + I(CONF_proxy_telnet_command), I(1)); + } + + /* + * The Telnet panel exists in the base config box, and in a + * mid-session reconfig box _if_ we're using Telnet. + */ + if (!midsession || protocol == PROT_TELNET) { + /* + * The Connection/Telnet panel. + */ + ctrl_settitle(b, "Connection/Telnet", + "Options controlling Telnet connections"); + + s = ctrl_getset(b, "Connection/Telnet", "protocol", + "Telnet protocol adjustments"); + + if (!midsession) { + ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:", + NO_SHORTCUT, 2, + HELPCTX(telnet_oldenviron), + conf_radiobutton_handler, + I(CONF_rfc_environ), + "BSD (commonplace)", 'b', I(0), + "RFC 1408 (unusual)", 'f', I(1), NULL); + ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, + HELPCTX(telnet_passive), + conf_radiobutton_handler, + I(CONF_passive_telnet), + "Passive", I(1), "Active", I(0), NULL); + } + ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', + HELPCTX(telnet_specialkeys), + conf_checkbox_handler, + I(CONF_telnet_keyboard)); + ctrl_checkbox(s, "Return key sends Telnet New Line instead of ^M", + 'm', HELPCTX(telnet_newline), + conf_checkbox_handler, + I(CONF_telnet_newline)); + } + + if (!midsession) { + + /* + * The Connection/Rlogin panel. + */ + ctrl_settitle(b, "Connection/Rlogin", + "Options controlling Rlogin connections"); + + s = ctrl_getset(b, "Connection/Rlogin", "data", + "Data to send to the server"); + ctrl_editbox(s, "Local username:", 'l', 50, + HELPCTX(rlogin_localuser), + conf_editbox_handler, I(CONF_localusername), I(1)); + + } + + /* + * All the SSH stuff is omitted in PuTTYtel, or in a reconfig + * when we're not doing SSH. + */ + + if (backend_from_proto(PROT_SSH) && (!midsession || protocol == PROT_SSH)) { + + /* + * The Connection/SSH panel. + */ + ctrl_settitle(b, "Connection/SSH", + "Options controlling SSH connections"); + + /* SSH-1 or connection-sharing downstream */ + if (midsession && (protcfginfo == 1 || protcfginfo == -1)) { + s = ctrl_getset(b, "Connection/SSH", "disclaimer", NULL); + ctrl_text(s, "Nothing on this panel may be reconfigured in mid-" + "session; it is only here so that sub-panels of it can " + "exist without looking strange.", HELPCTX(no_help)); + } + + if (!midsession) { + + s = ctrl_getset(b, "Connection/SSH", "data", + "Data to send to the server"); + ctrl_editbox(s, "Remote command:", 'r', 100, + HELPCTX(ssh_command), + conf_editbox_handler, I(CONF_remote_cmd), I(1)); + + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + ctrl_checkbox(s, "Don't start a shell or command at all", 'n', + HELPCTX(ssh_noshell), + conf_checkbox_handler, + I(CONF_ssh_no_shell)); + } + + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_checkbox(s, "Enable compression", 'e', + HELPCTX(ssh_compress), + conf_checkbox_handler, + I(CONF_compression)); + } + + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools"); + + ctrl_checkbox(s, "Share SSH connections if possible", 's', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing)); + + ctrl_text(s, "Permitted roles in a shared connection:", + HELPCTX(ssh_share)); + ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_upstream)); + ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_downstream)); + } + + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4, + HELPCTX(ssh_protocol), + conf_radiobutton_handler, + I(CONF_sshprot), + "1 only", 'l', I(0), + "1", '1', I(1), + "2", '2', I(2), + "2 only", 'y', I(3), NULL); + } + + /* + * The Connection/SSH/Kex panel. (Owing to repeat key + * exchange, much of this is meaningful in mid-session _if_ + * we're using SSH-2 and are not a connection-sharing + * downstream, or haven't decided yet.) + */ + if (protcfginfo != 1 && protcfginfo != -1) { + ctrl_settitle(b, "Connection/SSH/Kex", + "Options controlling SSH key exchange"); + + s = ctrl_getset(b, "Connection/SSH/Kex", "main", + "Key exchange algorithm options"); + c = ctrl_draglist(s, "Algorithm selection policy:", 's', + HELPCTX(ssh_kexlist), + kexlist_handler, P(NULL)); + c->listbox.height = 5; + + s = ctrl_getset(b, "Connection/SSH/Kex", "repeat", + "Options controlling key re-exchange"); + + ctrl_editbox(s, "Max minutes before rekey (0 for no limit)", 't', 20, + HELPCTX(ssh_kex_repeat), + conf_editbox_handler, + I(CONF_ssh_rekey_time), + I(-1)); + ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, + HELPCTX(ssh_kex_repeat), + conf_editbox_handler, + I(CONF_ssh_rekey_data), + I(16)); + ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", + HELPCTX(ssh_kex_repeat)); + } + + /* + * Manual host key configuration is irrelevant mid-session, + * as we enforce that the host key for rekeys is the + * same as that used at the start of the session. + */ + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH/Kex", "hostkeys", + "Manually configure host keys for this connection"); + + ctrl_columns(s, 2, 75, 25); + c = ctrl_text(s, "Host keys or fingerprints to accept:", + HELPCTX(ssh_kex_manual_hostkeys)); + c->generic.column = 0; + /* You want to select from the list, _then_ hit Remove. So + * tab order should be that way round. */ + mh = (struct manual_hostkey_data *) + ctrl_alloc(b,sizeof(struct manual_hostkey_data)); + mh->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh)); + mh->rembutton->generic.column = 1; + mh->rembutton->generic.tabdelay = 1; + mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh)); + /* This list box can't be very tall, because there's not + * much room in the pane on Windows at least. This makes + * it become really unhelpful if a horizontal scrollbar + * appears, so we suppress that. */ + mh->listbox->listbox.height = 2; + mh->listbox->listbox.hscroll = FALSE; + ctrl_tabdelay(s, mh->rembutton); + mh->keybox = ctrl_editbox(s, "Key", 'k', 80, + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh), P(NULL)); + mh->keybox->generic.column = 0; + mh->addbutton = ctrl_pushbutton(s, "Add key", 'y', + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh)); + mh->addbutton->generic.column = 1; + ctrl_columns(s, 1, 100); + } + + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { + /* + * The Connection/SSH/Cipher panel. + */ + ctrl_settitle(b, "Connection/SSH/Cipher", + "Options controlling SSH encryption"); + + s = ctrl_getset(b, "Connection/SSH/Cipher", + "encryption", "Encryption options"); + c = ctrl_draglist(s, "Encryption cipher selection policy:", 's', + HELPCTX(ssh_ciphers), + cipherlist_handler, P(NULL)); + c->listbox.height = 6; + + ctrl_checkbox(s, "Enable legacy use of single-DES in SSH-2", 'i', + HELPCTX(ssh_ciphers), + conf_checkbox_handler, + I(CONF_ssh2_des_cbc)); + } + + if (!midsession) { + + /* + * The Connection/SSH/Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth", + "Options controlling SSH authentication"); + + s = ctrl_getset(b, "Connection/SSH/Auth", "main", NULL); + ctrl_checkbox(s, "Bypass authentication entirely (SSH-2 only)", 'b', + HELPCTX(ssh_auth_bypass), + conf_checkbox_handler, + I(CONF_ssh_no_userauth)); + ctrl_checkbox(s, "Display pre-authentication banner (SSH-2 only)", + 'd', HELPCTX(ssh_auth_banner), + conf_checkbox_handler, + I(CONF_ssh_show_banner)); + + s = ctrl_getset(b, "Connection/SSH/Auth", "methods", + "Authentication methods"); + ctrl_checkbox(s, "Attempt authentication using Pageant", 'p', + HELPCTX(ssh_auth_pageant), + conf_checkbox_handler, + I(CONF_tryagent)); + ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH-1)", 'm', + HELPCTX(ssh_auth_tis), + conf_checkbox_handler, + I(CONF_try_tis_auth)); + ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH-2)", + 'i', HELPCTX(ssh_auth_ki), + conf_checkbox_handler, + I(CONF_try_ki_auth)); + + s = ctrl_getset(b, "Connection/SSH/Auth", "params", + "Authentication parameters"); + ctrl_checkbox(s, "Allow agent forwarding", 'f', + HELPCTX(ssh_auth_agentfwd), + conf_checkbox_handler, I(CONF_agentfwd)); + ctrl_checkbox(s, "Allow attempted changes of username in SSH-2", NO_SHORTCUT, + HELPCTX(ssh_auth_changeuser), + conf_checkbox_handler, + I(CONF_change_username)); + ctrl_filesel(s, "Private key file for authentication:", 'k', + FILTER_KEY_FILES, FALSE, "Select private key file", + HELPCTX(ssh_auth_privkey), + conf_filesel_handler, I(CONF_keyfile)); + +#ifndef NO_GSSAPI + /* + * Connection/SSH/Auth/GSSAPI, which sadly won't fit on + * the main Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth/GSSAPI", + "Options controlling GSSAPI authentication"); + s = ctrl_getset(b, "Connection/SSH/Auth/GSSAPI", "gssapi", NULL); + + ctrl_checkbox(s, "Attempt GSSAPI authentication (SSH-2 only)", + 't', HELPCTX(ssh_gssapi), + conf_checkbox_handler, + I(CONF_try_gssapi_auth)); + + ctrl_checkbox(s, "Allow GSSAPI credential delegation", 'l', + HELPCTX(ssh_gssapi_delegation), + conf_checkbox_handler, + I(CONF_gssapifwd)); + + /* + * GSSAPI library selection. + */ + if (ngsslibs > 1) { + c = ctrl_draglist(s, "Preference order for GSSAPI libraries:", + 'p', HELPCTX(ssh_gssapi_libraries), + gsslist_handler, P(NULL)); + c->listbox.height = ngsslibs; + + /* + * I currently assume that if more than one GSS + * library option is available, then one of them is + * 'user-supplied' and so we should present the + * following file selector. This is at least half- + * reasonable, because if we're using statically + * linked GSSAPI then there will only be one option + * and no way to load from a user-supplied library, + * whereas if we're using dynamic libraries then + * there will almost certainly be some default + * option in addition to a user-supplied path. If + * anyone ever ports PuTTY to a system on which + * dynamic-library GSSAPI is available but there is + * absolutely no consensus on where to keep the + * libraries, there'll need to be a flag alongside + * ngsslibs to control whether the file selector is + * displayed. + */ + + ctrl_filesel(s, "User-supplied GSSAPI library path:", 's', + FILTER_DYNLIB_FILES, FALSE, "Select library file", + HELPCTX(ssh_gssapi_libraries), + conf_filesel_handler, + I(CONF_ssh_gss_custom)); + } +#endif + } + + if (!midsession) { + /* + * The Connection/SSH/TTY panel. + */ + ctrl_settitle(b, "Connection/SSH/TTY", "Remote terminal settings"); + + s = ctrl_getset(b, "Connection/SSH/TTY", "sshtty", NULL); + ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p', + HELPCTX(ssh_nopty), + conf_checkbox_handler, + I(CONF_nopty)); + + s = ctrl_getset(b, "Connection/SSH/TTY", "ttymodes", + "Terminal modes"); + td = (struct ttymodes_data *) + ctrl_alloc(b, sizeof(struct ttymodes_data)); + ctrl_columns(s, 2, 75, 25); + c = ctrl_text(s, "Terminal modes to send:", HELPCTX(ssh_ttymodes)); + c->generic.column = 0; + td->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->rembutton->generic.column = 1; + td->rembutton->generic.tabdelay = 1; + ctrl_columns(s, 1, 100); + td->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->listbox->listbox.multisel = 1; + td->listbox->listbox.height = 4; + td->listbox->listbox.ncols = 2; + td->listbox->listbox.percentages = snewn(2, int); + td->listbox->listbox.percentages[0] = 40; + td->listbox->listbox.percentages[1] = 60; + ctrl_tabdelay(s, td->rembutton); + ctrl_columns(s, 2, 75, 25); + td->modelist = ctrl_droplist(s, "Mode:", 'm', 67, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->modelist->generic.column = 0; + td->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->addbutton->generic.column = 1; + td->addbutton->generic.tabdelay = 1; + ctrl_columns(s, 1, 100); /* column break */ + /* Bit of a hack to get the value radio buttons and + * edit-box on the same row. */ + ctrl_columns(s, 3, 25, 50, 25); + c = ctrl_text(s, "Value:", HELPCTX(ssh_ttymodes)); + c->generic.column = 0; + td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td), + "Auto", NO_SHORTCUT, P(NULL), + "This:", NO_SHORTCUT, P(NULL), + NULL); + td->valradio->generic.column = 1; + td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td), P(NULL)); + td->valbox->generic.column = 2; + ctrl_tabdelay(s, td->addbutton); + + } + + if (!midsession) { + /* + * The Connection/SSH/X11 panel. + */ + ctrl_settitle(b, "Connection/SSH/X11", + "Options controlling SSH X11 forwarding"); + + s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); + ctrl_checkbox(s, "Enable X11 forwarding", 'e', + HELPCTX(ssh_tunnels_x11), + conf_checkbox_handler,I(CONF_x11_forward)); + ctrl_editbox(s, "X display location", 'x', 50, + HELPCTX(ssh_tunnels_x11), + conf_editbox_handler, I(CONF_x11_display), I(1)); + ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, + HELPCTX(ssh_tunnels_x11auth), + conf_radiobutton_handler, + I(CONF_x11_auth), + "MIT-Magic-Cookie-1", I(X11_MIT), + "XDM-Authorization-1", I(X11_XDM), NULL); + } + + /* + * The Tunnels panel _is_ still available in mid-session. + */ + ctrl_settitle(b, "Connection/SSH/Tunnels", + "Options controlling SSH port forwarding"); + + s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd", + "Port forwarding"); + ctrl_checkbox(s, "Local ports accept connections from other hosts",'t', + HELPCTX(ssh_tunnels_portfwd_localhost), + conf_checkbox_handler, + I(CONF_lport_acceptall)); + ctrl_checkbox(s, "Remote ports do the same (SSH-2 only)", 'p', + HELPCTX(ssh_tunnels_portfwd_localhost), + conf_checkbox_handler, + I(CONF_rport_acceptall)); + + ctrl_columns(s, 3, 55, 20, 25); + c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); + c->generic.column = COLUMN_FIELD(0,2); + /* You want to select from the list, _then_ hit Remove. So tab order + * should be that way round. */ + pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data)); + pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->rembutton->generic.column = 2; + pfd->rembutton->generic.tabdelay = 1; + pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->listbox->listbox.height = 3; + pfd->listbox->listbox.ncols = 2; + pfd->listbox->listbox.percentages = snewn(2, int); + pfd->listbox->listbox.percentages[0] = 20; + pfd->listbox->listbox.percentages[1] = 80; + ctrl_tabdelay(s, pfd->rembutton); + ctrl_text(s, "Add new forwarded port:", HELPCTX(ssh_tunnels_portfwd)); + /* You want to enter source, destination and type, _then_ hit Add. + * Again, we adjust the tab order to reflect this. */ + pfd->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->addbutton->generic.column = 2; + pfd->addbutton->generic.tabdelay = 1; + pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->sourcebox->generic.column = 0; + pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), + "Local", 'l', P(NULL), + "Remote", 'm', P(NULL), + "Dynamic", 'y', P(NULL), + NULL); +#ifndef NO_IPV6 + pfd->addressfamily = + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd_ipversion), + portfwd_handler, P(pfd), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + ctrl_tabdelay(s, pfd->addbutton); + ctrl_columns(s, 1, 100); + + if (!midsession) { + /* + * The Connection/SSH/Bugs panels. + */ + ctrl_settitle(b, "Connection/SSH/Bugs", + "Workarounds for SSH server bugs"); + + s = ctrl_getset(b, "Connection/SSH/Bugs", "main", + "Detection of known bugs in SSH servers"); + ctrl_droplist(s, "Chokes on SSH-1 ignore messages", 'i', 20, + HELPCTX(ssh_bugs_ignore1), + sshbug_handler, I(CONF_sshbug_ignore1)); + ctrl_droplist(s, "Refuses all SSH-1 password camouflage", 's', 20, + HELPCTX(ssh_bugs_plainpw1), + sshbug_handler, I(CONF_sshbug_plainpw1)); + ctrl_droplist(s, "Chokes on SSH-1 RSA authentication", 'r', 20, + HELPCTX(ssh_bugs_rsa1), + sshbug_handler, I(CONF_sshbug_rsa1)); + ctrl_droplist(s, "Chokes on SSH-2 ignore messages", '2', 20, + HELPCTX(ssh_bugs_ignore2), + sshbug_handler, I(CONF_sshbug_ignore2)); + ctrl_droplist(s, "Chokes on PuTTY's SSH-2 'winadj' requests", 'j', + 20, HELPCTX(ssh_bugs_winadj), + sshbug_handler, I(CONF_sshbug_winadj)); + ctrl_droplist(s, "Miscomputes SSH-2 HMAC keys", 'm', 20, + HELPCTX(ssh_bugs_hmac2), + sshbug_handler, I(CONF_sshbug_hmac2)); + ctrl_droplist(s, "Miscomputes SSH-2 encryption keys", 'e', 20, + HELPCTX(ssh_bugs_derivekey2), + sshbug_handler, I(CONF_sshbug_derivekey2)); + + ctrl_settitle(b, "Connection/SSH/More bugs", + "Further workarounds for SSH server bugs"); + + s = ctrl_getset(b, "Connection/SSH/More bugs", "main", + "Detection of known bugs in SSH servers"); + ctrl_droplist(s, "Requires padding on SSH-2 RSA signatures", 'p', 20, + HELPCTX(ssh_bugs_rsapad2), + sshbug_handler, I(CONF_sshbug_rsapad2)); + ctrl_droplist(s, "Misuses the session ID in SSH-2 PK auth", 'n', 20, + HELPCTX(ssh_bugs_pksessid2), + sshbug_handler, I(CONF_sshbug_pksessid2)); + ctrl_droplist(s, "Handles SSH-2 key re-exchange badly", 'k', 20, + HELPCTX(ssh_bugs_rekey2), + sshbug_handler, I(CONF_sshbug_rekey2)); + ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20, + HELPCTX(ssh_bugs_maxpkt2), + sshbug_handler, I(CONF_sshbug_maxpkt2)); + ctrl_droplist(s, "Only supports pre-RFC4419 SSH-2 DH GEX", 'd', 20, + HELPCTX(ssh_bugs_oldgex2), + sshbug_handler, I(CONF_sshbug_oldgex2)); + ctrl_droplist(s, "Replies to requests on closed channels", 'q', 20, + HELPCTX(ssh_bugs_chanreq), + sshbug_handler, I(CONF_sshbug_chanreq)); + } + } +} diff --git a/netbox/libs/Putty/cproxy.c b/netbox/libs/Putty/cproxy.c new file mode 100644 index 000000000..8c8a50cb5 --- /dev/null +++ b/netbox/libs/Putty/cproxy.c @@ -0,0 +1,194 @@ +/* + * Routines to do cryptographic interaction with proxies in PuTTY. + * This is in a separate module from proxy.c, so that it can be + * conveniently removed in PuTTYtel by replacing this module with + * the stub version nocproxy.c. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "ssh.h" /* For MD5 support */ +#include "network.h" +#include "proxy.h" + +static void hmacmd5_chap(const unsigned char *challenge, int challen, + const char *passwd, unsigned char *response) +{ + void *hmacmd5_ctx; + int pwlen; + + hmacmd5_ctx = hmacmd5_make_context(); + + pwlen = strlen(passwd); + if (pwlen>64) { + unsigned char md5buf[16]; + MD5Simple(passwd, pwlen, md5buf); + hmacmd5_key(hmacmd5_ctx, md5buf, 16); + } else { + hmacmd5_key(hmacmd5_ctx, passwd, pwlen); + } + + hmacmd5_do_hmac(hmacmd5_ctx, challenge, challen, response); + hmacmd5_free_context(hmacmd5_ctx); +} + +void proxy_socks5_offerencryptedauth(char *command, int *len) +{ + command[*len] = 0x03; /* CHAP */ + (*len)++; +} + +int proxy_socks5_handlechap (Proxy_Socket p) +{ + + /* CHAP authentication reply format: + * version number (1 bytes) = 1 + * number of commands (1 byte) + * + * For each command: + * command identifier (1 byte) + * data length (1 byte) + */ + unsigned char data[260]; + unsigned char outbuf[20]; + + while(p->chap_num_attributes == 0 || + p->chap_num_attributes_processed < p->chap_num_attributes) { + if (p->chap_num_attributes == 0 || + p->chap_current_attribute == -1) { + /* CHAP normally reads in two bytes, either at the + * beginning or for each attribute/value pair. But if + * we're waiting for the value's data, we might not want + * to read 2 bytes. + */ + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->chap_num_attributes == 0) { + /* If there are no attributes, this is our first msg + * with the server, where we negotiate version and + * number of attributes + */ + if (data[0] != 0x01) { + plug_closing(p->plug, "Proxy error: SOCKS proxy wants" + " a different CHAP version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (data[1] == 0x00) { + plug_closing(p->plug, "Proxy error: SOCKS proxy won't" + " negotiate CHAP with us", + PROXY_ERROR_GENERAL, 0); + return 1; + } + p->chap_num_attributes = data[1]; + } else { + if (p->chap_current_attribute == -1) { + /* We have to read in each attribute/value pair - + * those we don't understand can be ignored, but + * there are a few we'll need to handle. + */ + p->chap_current_attribute = data[0]; + p->chap_current_datalen = data[1]; + } + if (bufchain_size(&p->pending_input_data) < + p->chap_current_datalen) + return 1; /* not got everything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, + p->chap_current_datalen); + + bufchain_consume(&p->pending_input_data, + p->chap_current_datalen); + + switch (p->chap_current_attribute) { + case 0x00: + /* Successful authentication */ + if (data[0] == 0x00) + p->state = 2; + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy" + " refused CHAP authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + case 0x03: + outbuf[0] = 0x01; /* Version */ + outbuf[1] = 0x01; /* One attribute */ + outbuf[2] = 0x04; /* Response */ + outbuf[3] = 0x10; /* Length */ + hmacmd5_chap(data, p->chap_current_datalen, + conf_get_str(p->conf, CONF_proxy_password), + &outbuf[4]); + sk_write(p->sub_socket, (char *)outbuf, 20); + break; + case 0x11: + /* Chose a protocol */ + if (data[0] != 0x85) { + plug_closing(p->plug, "Proxy error: Server chose " + "CHAP of other than HMAC-MD5 but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + } + p->chap_current_attribute = -1; + p->chap_num_attributes_processed++; + } + if (p->state == 8 && + p->chap_num_attributes_processed >= p->chap_num_attributes) { + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_datalen = 0; + } + } + return 0; +} + +int proxy_socks5_selectchap(Proxy_Socket p) +{ + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char chapbuf[514]; + int ulen; + chapbuf[0] = '\x01'; /* Version */ + chapbuf[1] = '\x02'; /* Number of attributes sent */ + chapbuf[2] = '\x11'; /* First attribute - algorithms list */ + chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ + chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ + chapbuf[5] = '\x02'; /* Second attribute - username */ + + ulen = strlen(username); + if (ulen > 255) ulen = 255; + if (ulen < 1) ulen = 1; + + chapbuf[6] = ulen; + memcpy(chapbuf+7, username, ulen); + + sk_write(p->sub_socket, chapbuf, ulen + 7); + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_attribute = -1; + p->chap_current_datalen = 0; + + p->state = 8; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "CHAP authentication but we didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; +} diff --git a/netbox/libs/Putty/dialog.c b/netbox/libs/Putty/dialog.c new file mode 100644 index 000000000..cc1987751 --- /dev/null +++ b/netbox/libs/Putty/dialog.c @@ -0,0 +1,477 @@ +/* + * dialog.c - a reasonably platform-independent mechanism for + * describing dialog boxes. + */ + +#include +#include +#include +#include + +#define DEFINE_INTORPTR_FNS + +#include "putty.h" +#include "dialog.h" + +int ctrl_path_elements(char *path) +{ + int i = 1; + while (*path) { + if (*path == '/') i++; + path++; + } + return i; +} + +/* Return the number of matching path elements at the starts of p1 and p2, + * or INT_MAX if the paths are identical. */ +int ctrl_path_compare(char *p1, char *p2) +{ + int i = 0; + while (*p1 || *p2) { + if ((*p1 == '/' || *p1 == '\0') && + (*p2 == '/' || *p2 == '\0')) + i++; /* a whole element matches, ooh */ + if (*p1 != *p2) + return i; /* mismatch */ + p1++, p2++; + } + return INT_MAX; /* exact match */ +} + +struct controlbox *ctrl_new_box(void) +{ + struct controlbox *ret = snew(struct controlbox); + + ret->nctrlsets = ret->ctrlsetsize = 0; + ret->ctrlsets = NULL; + ret->nfrees = ret->freesize = 0; + ret->frees = NULL; + ret->freefuncs = NULL; + + return ret; +} + +void ctrl_free_box(struct controlbox *b) +{ + int i; + + for (i = 0; i < b->nctrlsets; i++) { + ctrl_free_set(b->ctrlsets[i]); + } + for (i = 0; i < b->nfrees; i++) + b->freefuncs[i](b->frees[i]); + sfree(b->ctrlsets); + sfree(b->frees); + sfree(b->freefuncs); + sfree(b); +} + +void ctrl_free_set(struct controlset *s) +{ + int i; + + sfree(s->pathname); + sfree(s->boxname); + sfree(s->boxtitle); + for (i = 0; i < s->ncontrols; i++) { + ctrl_free(s->ctrls[i]); + } + sfree(s->ctrls); + sfree(s); +} + +/* + * Find the index of first controlset in a controlbox for a given + * path. If that path doesn't exist, return the index where it + * should be inserted. + */ +static int ctrl_find_set(struct controlbox *b, char *path, int start) +{ + int i, last, thisone; + + last = 0; + for (i = 0; i < b->nctrlsets; i++) { + thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname); + /* + * If `start' is true and there exists a controlset with + * exactly the path we've been given, we should return the + * index of the first such controlset we find. Otherwise, + * we should return the index of the first entry in which + * _fewer_ path elements match than they did last time. + */ + if ((start && thisone == INT_MAX) || thisone < last) + return i; + last = thisone; + } + return b->nctrlsets; /* insert at end */ +} + +/* + * Find the index of next controlset in a controlbox for a given + * path, or -1 if no such controlset exists. If -1 is passed as + * input, finds the first. + */ +int ctrl_find_path(struct controlbox *b, char *path, int index) +{ + if (index < 0) + index = ctrl_find_set(b, path, 1); + else + index++; + + if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname)) + return index; + else + return -1; +} + +/* Set up a panel title. */ +struct controlset *ctrl_settitle(struct controlbox *b, + char *path, char *title) +{ + + struct controlset *s = snew(struct controlset); + int index = ctrl_find_set(b, path, 1); + s->pathname = dupstr(path); + s->boxname = NULL; + s->boxtitle = dupstr(title); + s->ncontrols = s->ctrlsize = 0; + s->ncolumns = 0; /* this is a title! */ + s->ctrls = NULL; + if (b->nctrlsets >= b->ctrlsetsize) { + b->ctrlsetsize = b->nctrlsets + 32; + b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *); + } + if (index < b->nctrlsets) + memmove(&b->ctrlsets[index+1], &b->ctrlsets[index], + (b->nctrlsets-index) * sizeof(*b->ctrlsets)); + b->ctrlsets[index] = s; + b->nctrlsets++; + return s; +} + +/* Retrieve a pointer to a controlset, creating it if absent. */ +struct controlset *ctrl_getset(struct controlbox *b, + char *path, char *name, char *boxtitle) +{ + struct controlset *s; + int index = ctrl_find_set(b, path, 1); + while (index < b->nctrlsets && + !strcmp(b->ctrlsets[index]->pathname, path)) { + if (b->ctrlsets[index]->boxname && + !strcmp(b->ctrlsets[index]->boxname, name)) + return b->ctrlsets[index]; + index++; + } + s = snew(struct controlset); + s->pathname = dupstr(path); + s->boxname = dupstr(name); + s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL; + s->ncolumns = 1; + s->ncontrols = s->ctrlsize = 0; + s->ctrls = NULL; + if (b->nctrlsets >= b->ctrlsetsize) { + b->ctrlsetsize = b->nctrlsets + 32; + b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *); + } + if (index < b->nctrlsets) + memmove(&b->ctrlsets[index+1], &b->ctrlsets[index], + (b->nctrlsets-index) * sizeof(*b->ctrlsets)); + b->ctrlsets[index] = s; + b->nctrlsets++; + return s; +} + +/* Allocate some private data in a controlbox. */ +void *ctrl_alloc_with_free(struct controlbox *b, size_t size, + ctrl_freefn_t freefunc) +{ + void *p; + /* + * This is an internal allocation routine, so it's allowed to + * use smalloc directly. + */ + p = smalloc(size); + if (b->nfrees >= b->freesize) { + b->freesize = b->nfrees + 32; + b->frees = sresize(b->frees, b->freesize, void *); + b->freefuncs = sresize(b->freefuncs, b->freesize, ctrl_freefn_t); + } + b->frees[b->nfrees] = p; + b->freefuncs[b->nfrees] = freefunc; + b->nfrees++; + return p; +} + +static void ctrl_default_free(void *p) +{ + sfree(p); +} + +void *ctrl_alloc(struct controlbox *b, size_t size) +{ + return ctrl_alloc_with_free(b, size, ctrl_default_free); +} + +static union control *ctrl_new(struct controlset *s, int type, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = snew(union control); + if (s->ncontrols >= s->ctrlsize) { + s->ctrlsize = s->ncontrols + 32; + s->ctrls = sresize(s->ctrls, s->ctrlsize, union control *); + } + s->ctrls[s->ncontrols++] = c; + /* + * Fill in the standard fields. + */ + c->generic.type = type; + c->generic.tabdelay = 0; + c->generic.column = COLUMN_FIELD(0, s->ncolumns); + c->generic.helpctx = helpctx; + c->generic.handler = handler; + c->generic.context = context; + c->generic.label = NULL; + return c; +} + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *s, int ncolumns, ...) +{ + union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + assert(s->ncolumns == 1 || ncolumns == 1); + c->columns.ncols = ncolumns; + s->ncolumns = ncolumns; + if (ncolumns == 1) { + c->columns.percentages = NULL; + } else { + va_list ap; + int i; + c->columns.percentages = snewn(ncolumns, int); + va_start(ap, ncolumns); + for (i = 0; i < ncolumns; i++) + c->columns.percentages[i] = va_arg(ap, int); + va_end(ap); + } + return c; +} + +union control *ctrl_editbox(struct controlset *s, char *label, char shortcut, + int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = 0; + c->editbox.has_list = 0; + c->editbox.context2 = context2; + return c; +} + +union control *ctrl_combobox(struct controlset *s, char *label, char shortcut, + int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = 0; + c->editbox.has_list = 1; + c->editbox.context2 = context2; + return c; +} + +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *s, char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...) +{ + va_list ap; + int i; + union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); + c->radio.label = label ? dupstr(label) : NULL; + c->radio.shortcut = shortcut; + c->radio.ncolumns = ncolumns; + /* + * Initial pass along variable argument list to count the + * buttons. + */ + va_start(ap, context); + i = 0; + while (va_arg(ap, char *) != NULL) { + i++; + if (c->radio.shortcut == NO_SHORTCUT) + (void)va_arg(ap, int); /* char promotes to int in arg lists */ + (void)va_arg(ap, intorptr); + } + va_end(ap); + c->radio.nbuttons = i; + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts = snewn(c->radio.nbuttons, char); + else + c->radio.shortcuts = NULL; + c->radio.buttons = snewn(c->radio.nbuttons, char *); + c->radio.buttondata = snewn(c->radio.nbuttons, intorptr); + /* + * Second pass along variable argument list to actually fill in + * the structure. + */ + va_start(ap, context); + for (i = 0; i < c->radio.nbuttons; i++) { + c->radio.buttons[i] = dupstr(va_arg(ap, char *)); + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts[i] = va_arg(ap, int); + /* char promotes to int in arg lists */ + c->radio.buttondata[i] = va_arg(ap, intorptr); + } + va_end(ap); + return c; +} + +union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); + c->button.label = label ? dupstr(label) : NULL; + c->button.shortcut = shortcut; + c->button.isdefault = 0; + c->button.iscancel = 0; + return c; +} + +union control *ctrl_listbox(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = 0; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + c->listbox.hscroll = TRUE; + return c; +} + +union control *ctrl_droplist(struct controlset *s, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 0; /* means it's a drop-down list */ + c->listbox.draglist = 0; + c->listbox.multisel = 0; + c->listbox.percentwidth = percentage; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + c->listbox.hscroll = FALSE; + return c; +} + +union control *ctrl_draglist(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = 1; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + c->listbox.hscroll = FALSE; + return c; +} + +union control *ctrl_filesel(struct controlset *s,char *label,char shortcut, + char const *filter, int write, char *title, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); + c->fileselect.label = label ? dupstr(label) : NULL; + c->fileselect.shortcut = shortcut; + c->fileselect.filter = filter; + c->fileselect.for_writing = write; + c->fileselect.title = dupstr(title); + return c; +} + +union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); + c->fontselect.label = label ? dupstr(label) : NULL; + c->fontselect.shortcut = shortcut; + return c; +} + +union control *ctrl_tabdelay(struct controlset *s, union control *ctrl) +{ + union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + c->tabdelay.ctrl = ctrl; + return c; +} + +union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx) +{ + union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); + c->text.label = dupstr(text); + return c; +} + +union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); + c->checkbox.label = label ? dupstr(label) : NULL; + c->checkbox.shortcut = shortcut; + return c; +} + +void ctrl_free(union control *ctrl) +{ + int i; + + sfree(ctrl->generic.label); + switch (ctrl->generic.type) { + case CTRL_RADIO: + for (i = 0; i < ctrl->radio.nbuttons; i++) + sfree(ctrl->radio.buttons[i]); + sfree(ctrl->radio.buttons); + sfree(ctrl->radio.shortcuts); + sfree(ctrl->radio.buttondata); + break; + case CTRL_COLUMNS: + sfree(ctrl->columns.percentages); + break; + case CTRL_LISTBOX: + sfree(ctrl->listbox.percentages); + break; + case CTRL_FILESELECT: + sfree(ctrl->fileselect.title); + break; + } + sfree(ctrl); +} diff --git a/netbox/libs/Putty/dialog.h b/netbox/libs/Putty/dialog.h new file mode 100644 index 000000000..677516701 --- /dev/null +++ b/netbox/libs/Putty/dialog.h @@ -0,0 +1,654 @@ +/* + * Exports and types from dialog.c. + */ + +/* + * This will come in handy for generic control handlers. Anyone + * knows how to make this more portable, let me know :-) + */ +#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) ) + +/* + * This is the big union which defines a single control, of any + * type. + * + * General principles: + * - _All_ pointers in this structure are expected to point to + * dynamically allocated things, unless otherwise indicated. + * - `char' fields giving keyboard shortcuts are expected to be + * NO_SHORTCUT if no shortcut is desired for a particular control. + * - The `label' field can often be NULL, which will cause the + * control to not have a label at all. This doesn't apply to + * checkboxes and push buttons, in which the label is not + * separate from the control. + */ + +#define NO_SHORTCUT '\0' + +enum { + CTRL_TEXT, /* just a static line of text */ + CTRL_EDITBOX, /* label plus edit box */ + CTRL_RADIO, /* label plus radio buttons */ + CTRL_CHECKBOX, /* checkbox (contains own label) */ + CTRL_BUTTON, /* simple push button (no label) */ + CTRL_LISTBOX, /* label plus list box */ + CTRL_COLUMNS, /* divide window into columns */ + CTRL_FILESELECT, /* label plus filename selector */ + CTRL_FONTSELECT, /* label plus font selector */ + CTRL_TABDELAY /* see `tabdelay' below */ +}; + +/* + * Many controls have `intorptr' unions for storing user data, + * since the user might reasonably want to store either an integer + * or a void * pointer. Here I define a union, and two convenience + * functions to create that union from actual integers or pointers. + * + * The convenience functions are declared as inline if possible. + * Otherwise, they're declared here and defined when this header is + * included with DEFINE_INTORPTR_FNS defined. This is a total pain, + * but such is life. + */ +typedef union { void *p; int i; } intorptr; + +#ifndef INLINE +intorptr I(int i); +intorptr P(void *p); +#endif + +#if defined DEFINE_INTORPTR_FNS || defined INLINE +#ifdef INLINE +#define PREFIX INLINE +#else +#define PREFIX +#endif +PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; } +PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } +#undef PREFIX +#endif + +/* + * Each control has an `int' field specifying which columns it + * occupies in a multi-column part of the dialog box. These macros + * pack and unpack that field. + * + * If a control belongs in exactly one column, just specifying the + * column number is perfectly adequate. + */ +#define COLUMN_FIELD(start, span) ( (((span)-1) << 16) + (start) ) +#define COLUMN_START(field) ( (field) & 0xFFFF ) +#define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 ) + +union control; + +/* + * The number of event types is being deliberately kept small, on + * the grounds that not all platforms might be able to report a + * large number of subtle events. We have: + * - the special REFRESH event, called when a control's value + * needs setting + * - the ACTION event, called when the user does something that + * positively requests action (double-clicking a list box item, + * or pushing a push-button) + * - the VALCHANGE event, called when the user alters the setting + * of the control in a way that is usually considered to alter + * the underlying data (toggling a checkbox or radio button, + * moving the items around in a drag-list, editing an edit + * control) + * - the SELCHANGE event, called when the user alters the setting + * of the control in a more minor way (changing the selected + * item in a list box). + * - the CALLBACK event, which happens after the handler routine + * has requested a subdialog (file selector, font selector, + * colour selector) and it has come back with information. + */ +enum { + EVENT_REFRESH, + EVENT_ACTION, + EVENT_VALCHANGE, + EVENT_SELCHANGE, + EVENT_CALLBACK +}; +typedef void (*handler_fn)(union control *ctrl, void *dlg, + void *data, int event); + +#define STANDARD_PREFIX \ + int type; \ + char *label; \ + int tabdelay; \ + int column; \ + handler_fn handler; \ + intorptr context; \ + intorptr helpctx + +union control { + /* + * The first possibility in this union is the generic header + * shared by all the structures, which we are therefore allowed + * to access through any one of them. + */ + struct { + int type; + /* + * Every control except CTRL_COLUMNS has _some_ sort of + * label. By putting it in the `generic' union as well as + * everywhere else, we avoid having to have an irritating + * switch statement when we go through and deallocate all + * the memory in a config-box structure. + * + * Yes, this does mean that any non-NULL value in this + * field is expected to be dynamically allocated and + * freeable. + * + * For CTRL_COLUMNS, this field MUST be NULL. + */ + char *label; + /* + * If `tabdelay' is non-zero, it indicates that this + * particular control should not yet appear in the tab + * order. A subsequent CTRL_TABDELAY entry will place it. + */ + int tabdelay; + /* + * Indicate which column(s) this control occupies. This can + * be unpacked into starting column and column span by the + * COLUMN macros above. + */ + int column; + /* + * Most controls need to provide a function which gets + * called when that control's setting is changed, or when + * the control's setting needs initialising. + * + * The `data' parameter points to the writable data being + * modified as a result of the configuration activity; for + * example, the PuTTY `Conf' structure, although not + * necessarily. + * + * The `dlg' parameter is passed back to the platform- + * specific routines to read and write the actual control + * state. + */ + handler_fn handler; + /* + * Almost all of the above functions will find it useful to + * be able to store a piece of `void *' or `int' data. + */ + intorptr context; + /* + * For any control, we also allow the storage of a piece of + * data for use by context-sensitive help. For example, on + * Windows you can click the magic question mark and then + * click a control, and help for that control should spring + * up. Hence, here is a slot in which to store per-control + * data that a particular platform-specific driver can use + * to ensure it brings up the right piece of help text. + */ + intorptr helpctx; + } generic; + struct { + STANDARD_PREFIX; + union control *ctrl; + } tabdelay; + struct { + STANDARD_PREFIX; + } text; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Percentage of the dialog-box width used by the edit box. + * If this is set to 100, the label is on its own line; + * otherwise the label is on the same line as the box + * itself. + */ + int percentwidth; + int password; /* details of input are hidden */ + /* + * A special case of the edit box is the combo box, which + * has a drop-down list built in. (Note that a _non_- + * editable drop-down list is done as a special case of a + * list box.) + * + * Don't try setting has_list and password on the same + * control; front ends are not required to support that + * combination. + */ + int has_list; + /* + * Edit boxes tend to need two items of context, so here's + * a spare. + */ + intorptr context2; + } editbox; + struct { + STANDARD_PREFIX; + /* + * `shortcut' here is a single keyboard shortcut which is + * expected to select the whole group of radio buttons. It + * can be NO_SHORTCUT if required, and there is also a way + * to place individual shortcuts on each button; see below. + */ + char shortcut; + /* + * There are separate fields for `ncolumns' and `nbuttons' + * for several reasons. + * + * Firstly, we sometimes want the last of a set of buttons + * to have a longer label than the rest; we achieve this by + * setting `ncolumns' higher than `nbuttons', and the + * layout code is expected to understand that the final + * button should be given all the remaining space on the + * line. This sounds like a ludicrously specific special + * case (if we're doing this sort of thing, why not have + * the general ability to have a particular button span + * more than one column whether it's the last one or not?) + * but actually it's reasonably common for the sort of + * three-way control you get a lot of in PuTTY: `yes' + * versus `no' versus `some more complex way to decide'. + * + * Secondly, setting `nbuttons' higher than `ncolumns' lets + * us have more than one line of radio buttons for a single + * setting. A very important special case of this is + * setting `ncolumns' to 1, so that each button is on its + * own line. + */ + int ncolumns; + int nbuttons; + /* + * This points to a dynamically allocated array of `char *' + * pointers, each of which points to a dynamically + * allocated string. + */ + char **buttons; /* `nbuttons' button labels */ + /* + * This points to a dynamically allocated array of `char' + * giving the individual keyboard shortcuts for each radio + * button. The array may be NULL if none are required. + */ + char *shortcuts; /* `nbuttons' shortcuts; may be NULL */ + /* + * This points to a dynamically allocated array of + * intorptr, giving helpful data for each button. + */ + intorptr *buttondata; /* `nbuttons' entries; may be NULL */ + } radio; + struct { + STANDARD_PREFIX; + char shortcut; + } checkbox; + struct { + STANDARD_PREFIX; + char shortcut; + /* + * At least Windows has the concept of a `default push + * button', which gets implicitly pressed when you hit + * Return even if it doesn't have the input focus. + */ + int isdefault; + /* + * Also, the reverse of this: a default cancel-type button, + * which is implicitly pressed when you hit Escape. + */ + int iscancel; + } button; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Height of the list box, in approximate number of lines. + * If this is zero, the list is a drop-down list. + */ + int height; /* height in lines */ + /* + * If this is set, the list elements can be reordered by + * the user (by drag-and-drop or by Up and Down buttons, + * whatever the per-platform implementation feels + * comfortable with). This is not guaranteed to work on a + * drop-down list, so don't try it! + */ + int draglist; + /* + * If this is non-zero, the list can have more than one + * element selected at a time. This is not guaranteed to + * work on a drop-down list, so don't try it! + * + * Different non-zero values request slightly different + * types of multi-selection (this may well be meaningful + * only in GTK, so everyone else can ignore it if they + * want). 1 means the list box expects to have individual + * items selected, whereas 2 means it expects the user to + * want to select a large contiguous range at a time. + */ + int multisel; + /* + * Percentage of the dialog-box width used by the list box. + * If this is set to 100, the label is on its own line; + * otherwise the label is on the same line as the box + * itself. Setting this to anything other than 100 is not + * guaranteed to work on a _non_-drop-down list, so don't + * try it! + */ + int percentwidth; + /* + * Some list boxes contain strings that contain tab + * characters. If `ncols' is greater than 0, then + * `percentages' is expected to be non-zero and to contain + * the respective widths of `ncols' columns, which together + * will exactly fit the width of the list box. Otherwise + * `percentages' must be NULL. + * + * There should never be more than one column in a + * drop-down list (one with height==0), because front ends + * may have to implement it as a special case of an + * editable combo box. + */ + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Flag which can be set to FALSE to suppress the horizontal + * scroll bar if a list box entry goes off the right-hand + * side. + */ + int hscroll; + } listbox; + struct { + STANDARD_PREFIX; + char shortcut; + /* + * `filter' dictates what type of files will be selected by + * default; for example, when selecting private key files + * the file selector would do well to only show .PPK files + * (on those systems where this is the chosen extension). + * + * The precise contents of `filter' are platform-defined, + * unfortunately. The special value NULL means `all files' + * and is always a valid fallback. + * + * Unlike almost all strings in this structure, this value + * is NOT expected to require freeing (although of course + * you can always use ctrl_alloc if you do need to create + * one on the fly). This is because the likely mode of use + * is to define string constants in a platform-specific + * header file, and directly reference those. Or worse, a + * particular platform might choose to cast integers into + * this pointer type... + */ + char const *filter; + /* + * Some systems like to know whether a file selector is + * choosing a file to read or one to write (and possibly + * create). + */ + int for_writing; + /* + * On at least some platforms, the file selector is a + * separate dialog box, and contains a user-settable title. + * + * This value _is_ expected to require freeing. + */ + char *title; + } fileselect; + struct { + /* In this variant, `label' MUST be NULL. */ + STANDARD_PREFIX; + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Every time this control type appears, exactly one of + * `ncols' and the previous number of columns MUST be one. + * Attempting to allow a seamless transition from a four- + * to a five-column layout, for example, would be way more + * trouble than it was worth. If you must lay things out + * like that, define eight unevenly sized columns and use + * column-spanning a lot. But better still, just don't. + * + * `percentages' may be NULL if ncols==1, to save space. + */ + } columns; + struct { + STANDARD_PREFIX; + char shortcut; + } fontselect; +}; + +#undef STANDARD_PREFIX + +/* + * `controlset' is a container holding an array of `union control' + * structures, together with a panel name and a title for the whole + * set. In Windows and any similar-looking GUI, each `controlset' + * in the config will be a container box within a panel. + * + * Special case: if `boxname' is NULL, the control set gives an + * overall title for an entire panel of controls. + */ +struct controlset { + char *pathname; /* panel path, e.g. "SSH/Tunnels" */ + char *boxname; /* internal short name of controlset */ + char *boxtitle; /* title of container box */ + int ncolumns; /* current no. of columns at bottom */ + int ncontrols; /* number of `union control' in array */ + int ctrlsize; /* allocated size of array */ + union control **ctrls; /* actual array */ +}; + +typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */ + +/* + * This is the container structure which holds a complete set of + * controls. + */ +struct controlbox { + int nctrlsets; /* number of ctrlsets */ + int ctrlsetsize; /* ctrlset size */ + struct controlset **ctrlsets; /* actual array of ctrlsets */ + int nfrees; + int freesize; + void **frees; /* array of aux data areas to free */ + ctrl_freefn_t *freefuncs; /* parallel array of free functions */ +}; + +struct controlbox *ctrl_new_box(void); +void ctrl_free_box(struct controlbox *); + +/* + * Standard functions used for populating a controlbox structure. + */ + +/* Set up a panel title. */ +struct controlset *ctrl_settitle(struct controlbox *, + char *path, char *title); +/* Retrieve a pointer to a controlset, creating it if absent. */ +struct controlset *ctrl_getset(struct controlbox *, + char *path, char *name, char *boxtitle); +void ctrl_free_set(struct controlset *); + +void ctrl_free(union control *); + +/* + * This function works like `malloc', but the memory it returns + * will be automatically freed when the controlbox is freed. Note + * that a controlbox is a dialog-box _template_, not an instance, + * and so data allocated through this function is better not used + * to hold modifiable per-instance things. It's mostly here for + * allocating structures to be passed as control handler params. + * + * ctrl_alloc_with_free also allows you to provide a function to free + * the structure, in case there are other dynamically allocated bits + * and pieces dangling off it. + */ +void *ctrl_alloc(struct controlbox *b, size_t size); +void *ctrl_alloc_with_free(struct controlbox *b, size_t size, + ctrl_freefn_t freefunc); + +/* + * Individual routines to create `union control' structures in a controlset. + * + * Most of these routines allow the most common fields to be set + * directly, and put default values in the rest. Each one returns a + * pointer to the `union control' it created, so that final tweaks + * can be made. + */ + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *, int ncolumns, ...); +union control *ctrl_editbox(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +union control *ctrl_combobox(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *, char *label, + char shortcut, int ncolumns, + intorptr helpctx, + handler_fn handler, intorptr context, ...); +union control *ctrl_pushbutton(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_listbox(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_droplist(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_draglist(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_filesel(struct controlset *,char *label,char shortcut, + char const *filter, int write, char *title, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_fontsel(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_text(struct controlset *, char *text, intorptr helpctx); +union control *ctrl_checkbox(struct controlset *, char *label, char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_tabdelay(struct controlset *, union control *); + +/* + * Routines the platform-independent dialog code can call to read + * and write the values of controls. + */ +void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton); +int dlg_radiobutton_get(union control *ctrl, void *dlg); +void dlg_checkbox_set(union control *ctrl, void *dlg, int checked); +int dlg_checkbox_get(union control *ctrl, void *dlg); +void dlg_editbox_set(union control *ctrl, void *dlg, char const *text); +char *dlg_editbox_get(union control *ctrl, void *dlg); /* result must be freed by caller */ +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, void *dlg); +void dlg_listbox_del(union control *ctrl, void *dlg, int index); +void dlg_listbox_add(union control *ctrl, void *dlg, char const *text); +/* + * Each listbox entry may have a numeric id associated with it. + * Note that some front ends only permit a string to be stored at + * each position, which means that _if_ you put two identical + * strings in any listbox then you MUST not assign them different + * IDs and expect to get meaningful results back. + */ +void dlg_listbox_addwithid(union control *ctrl, void *dlg, + char const *text, int id); +int dlg_listbox_getid(union control *ctrl, void *dlg, int index); +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, void *dlg); +int dlg_listbox_issel(union control *ctrl, void *dlg, int index); +void dlg_listbox_select(union control *ctrl, void *dlg, int index); +void dlg_text_set(union control *ctrl, void *dlg, char const *text); +void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn); +Filename *dlg_filesel_get(union control *ctrl, void *dlg); +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn); +FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg); +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, void *dlg); +void dlg_update_done(union control *ctrl, void *dlg); +/* + * Set input focus into a particular control. + */ +void dlg_set_focus(union control *ctrl, void *dlg); +/* + * Change the label text on a control. + */ +void dlg_label_change(union control *ctrl, void *dlg, char const *text); +/* + * Return the `ctrl' structure for the most recent control that had + * the input focus apart from the one mentioned. This is NOT + * GUARANTEED to work on all platforms, so don't base any critical + * functionality on it! + */ +union control *dlg_last_focused(union control *ctrl, void *dlg); +/* + * During event processing, you might well want to give an error + * indication to the user. dlg_beep() is a quick and easy generic + * error; dlg_error() puts up a message-box or equivalent. + */ +void dlg_beep(void *dlg); +void dlg_error_msg(void *dlg, char *msg); +/* + * This function signals to the front end that the dialog's + * processing is completed, and passes an integer value (typically + * a success status). + */ +void dlg_end(void *dlg, int value); + +/* + * Routines to manage a (per-platform) colour selector. + * dlg_coloursel_start() is called in an event handler, and + * schedules the running of a colour selector after the event + * handler returns. The colour selector will send EVENT_CALLBACK to + * the control that spawned it, when it's finished; + * dlg_coloursel_results() fetches the results, as integers from 0 + * to 255; it returns nonzero on success, or zero if the colour + * selector was dismissed by hitting Cancel or similar. + * + * dlg_coloursel_start() accepts an RGB triple which is used to + * initialise the colour selector to its starting value. + */ +void dlg_coloursel_start(union control *ctrl, void *dlg, + int r, int g, int b); +int dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b); + +/* + * This routine is used by the platform-independent code to + * indicate that the value of a particular control is likely to + * have changed. It triggers a call of the handler for that control + * with `event' set to EVENT_REFRESH. + * + * If `ctrl' is NULL, _all_ controls in the dialog get refreshed + * (for loading or saving entire sets of settings). + */ +void dlg_refresh(union control *ctrl, void *dlg); + +/* + * Standard helper functions for reading a controlbox structure. + */ + +/* + * Find the index of next controlset in a controlbox for a given + * path, or -1 if no such controlset exists. If -1 is passed as + * input, finds the first. Intended usage is something like + * + * for (index=-1; (index=ctrl_find_path(ctrlbox, index, path)) >= 0 ;) { + * ... process this controlset ... + * } + */ +int ctrl_find_path(struct controlbox *b, char *path, int index); +int ctrl_path_elements(char *path); +/* Return the number of matching path elements at the starts of p1 and p2, + * or INT_MAX if the paths are identical. */ +int ctrl_path_compare(char *p1, char *p2); diff --git a/netbox/libs/Putty/errsock.c b/netbox/libs/Putty/errsock.c new file mode 100644 index 000000000..854fd8be8 --- /dev/null +++ b/netbox/libs/Putty/errsock.c @@ -0,0 +1,73 @@ +/* + * A dummy Socket implementation which just holds an error message. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct Socket_error_tag *Error_Socket; + +struct Socket_error_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char *error; + Plug plug; +}; + +static Plug sk_error_plug(Socket s, Plug p) +{ + Error_Socket ps = (Error_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_error_close(Socket s) +{ + Error_Socket ps = (Error_Socket) s; + + sfree(ps->error); + sfree(ps); +} + +static const char *sk_error_socket_error(Socket s) +{ + Error_Socket ps = (Error_Socket) s; + return ps->error; +} + +static char *sk_error_peer_info(Socket s) +{ + return NULL; +} + +Socket new_error_socket(const char *errmsg, Plug plug) +{ + static const struct socket_function_table socket_fn_table = { + sk_error_plug, + sk_error_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + NULL /* set_frozen */, + sk_error_socket_error, + sk_error_peer_info, + }; + + Error_Socket ret; + + ret = snew(struct Socket_error_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = dupstr(errmsg); + + return (Socket) ret; +} diff --git a/netbox/libs/Putty/import.c b/netbox/libs/Putty/import.c new file mode 100644 index 000000000..4f3ed1fab --- /dev/null +++ b/netbox/libs/Putty/import.c @@ -0,0 +1,1760 @@ +/* + * Code for PuTTY to import and export private key files in other + * SSH clients' formats. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" + +int openssh_encrypted(const Filename *filename); +struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase, + const char **errmsg_p); +int openssh_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); + +int sshcom_encrypted(const Filename *filename, char **comment); +struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, + const char **errmsg_p); +int sshcom_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); + +/* + * Given a key type, determine whether we know how to import it. + */ +int import_possible(int type) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return 1; + if (type == SSH_KEYTYPE_SSHCOM) + return 1; + return 0; +} + +/* + * Given a key type, determine what native key type + * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once + * we've imported it. + */ +int import_target_type(int type) +{ + /* + * There are no known foreign SSH-1 key formats. + */ + return SSH_KEYTYPE_SSH2; +} + +/* + * Determine whether a foreign key is encrypted. + */ +int import_encrypted(const Filename *filename, int type, char **comment) +{ + if (type == SSH_KEYTYPE_OPENSSH) { + /* OpenSSH doesn't do key comments */ + *comment = dupstr(filename_to_str(filename)); + return openssh_encrypted(filename); + } + if (type == SSH_KEYTYPE_SSHCOM) { + return sshcom_encrypted(filename, comment); + } + return 0; +} + +/* + * Import an SSH-1 key. + */ +int import_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase, const char **errmsg_p) +{ + return 0; +} + +/* + * Import an SSH-2 key. + */ +struct ssh2_userkey *import_ssh2(const Filename *filename, int type, + char *passphrase, const char **errmsg_p) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return openssh_read(filename, passphrase, errmsg_p); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_read(filename, passphrase, errmsg_p); + return NULL; +} + +/* + * Export an SSH-1 key. + */ +int export_ssh1(const Filename *filename, int type, struct RSAKey *key, + char *passphrase) +{ + return 0; +} + +/* + * Export an SSH-2 key. + */ +int export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return openssh_write(filename, key, passphrase); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_write(filename, key, passphrase); + return 0; +} + +/* + * Strip trailing CRs and LFs at the end of a line of text. + */ +void strip_crlf(char *str) +{ + char *p = str + strlen(str); + + while (p > str && (p[-1] == '\r' || p[-1] == '\n')) + *--p = '\0'; +} + +/* ---------------------------------------------------------------------- + * Helper routines. (The base64 ones are defined in sshpubk.c.) + */ + +#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '/' || (c) == '=' \ + ) + +/* + * Read an ASN.1/BER identifier and length pair. + * + * Flags are a combination of the #defines listed below. + * + * Returns -1 if unsuccessful; otherwise returns the number of + * bytes used out of the source data. + */ + +/* ASN.1 tag classes. */ +#define ASN1_CLASS_UNIVERSAL (0 << 6) +#define ASN1_CLASS_APPLICATION (1 << 6) +#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) +#define ASN1_CLASS_PRIVATE (3 << 6) +#define ASN1_CLASS_MASK (3 << 6) + +/* Primitive versus constructed bit. */ +#define ASN1_CONSTRUCTED (1 << 5) + +static int ber_read_id_len(void *source, int sourcelen, + int *id, int *length, int *flags) +{ + unsigned char *p = (unsigned char *) source; + + if (sourcelen == 0) + return -1; + + *flags = (*p & 0xE0); + if ((*p & 0x1F) == 0x1F) { + *id = 0; + while (*p & 0x80) { + p++, sourcelen--; + if (sourcelen == 0) + return -1; + *id = (*id << 7) | (*p & 0x7F); + } + p++, sourcelen--; + } else { + *id = *p & 0x1F; + p++, sourcelen--; + } + + if (sourcelen == 0) + return -1; + + if (*p & 0x80) { + unsigned len; + int n = *p & 0x7F; + p++, sourcelen--; + if (sourcelen < n) + return -1; + len = 0; + while (n--) + len = (len << 8) | (*p++); + sourcelen -= n; + *length = toint(len); + } else { + *length = *p; + p++, sourcelen--; + } + + return p - (unsigned char *) source; +} + +/* + * Write an ASN.1/BER identifier and length pair. Returns the + * number of bytes consumed. Assumes dest contains enough space. + * Will avoid writing anything if dest is NULL, but still return + * amount of space required. + */ +static int ber_write_id_len(void *dest, int id, int length, int flags) +{ + unsigned char *d = (unsigned char *)dest; + int len = 0; + + if (id <= 30) { + /* + * Identifier is one byte. + */ + len++; + if (d) *d++ = id | flags; + } else { + int n; + /* + * Identifier is multiple bytes: the first byte is 11111 + * plus the flags, and subsequent bytes encode the value of + * the identifier, 7 bits at a time, with the top bit of + * each byte 1 except the last one which is 0. + */ + len++; + if (d) *d++ = 0x1F | flags; + for (n = 1; (id >> (7*n)) > 0; n++) + continue; /* count the bytes */ + while (n--) { + len++; + if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F); + } + } + + if (length < 128) { + /* + * Length is one byte. + */ + len++; + if (d) *d++ = length; + } else { + int n; + /* + * Length is multiple bytes. The first is 0x80 plus the + * number of subsequent bytes, and the subsequent bytes + * encode the actual length. + */ + for (n = 1; (length >> (8*n)) > 0; n++) + continue; /* count the bytes */ + len++; + if (d) *d++ = 0x80 | n; + while (n--) { + len++; + if (d) *d++ = (length >> (8*n)) & 0xFF; + } + } + + return len; +} + +static int put_string(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + + PUT_32BIT(d, len); + memcpy(d+4, data, len); + return len+4; +} + +static int put_mp(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + unsigned char *i = (unsigned char *)data; + + if (*i & 0x80) { + PUT_32BIT(d, len+1); + d[4] = 0; + memcpy(d+5, data, len); + return len+5; + } else { + PUT_32BIT(d, len); + memcpy(d+4, data, len); + return len+4; + } +} + +/* Simple structure to point to an mp-int within a blob. */ +struct mpint_pos { void *start; int bytes; }; + +static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret) +{ + int bytes; + unsigned char *d = (unsigned char *) data; + + if (len < 4) + goto error; + bytes = toint(GET_32BIT(d)); + if (bytes < 0 || len-4 < bytes) + goto error; + + ret->start = d + 4; + ret->bytes = bytes; + return bytes+4; + + error: + ret->start = NULL; + ret->bytes = -1; + return len; /* ensure further calls fail as well */ +} + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys. + */ + +enum { OSSH_DSA, OSSH_RSA }; +enum { OSSH_ENC_3DES, OSSH_ENC_AES }; +struct openssh_key { + int type; + int encrypted, encryption; + char iv[32]; + unsigned char *keyblob; + int keyblob_len, keyblob_size; +}; + +static struct openssh_key *load_openssh_key(const Filename *filename, + const char **errmsg_p) +{ + struct openssh_key *ret; + FILE *fp = NULL; + char *line = NULL; + char *errmsg, *p; + int headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct openssh_key); + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + ret->encrypted = 0; + memset(ret->iv, 0, sizeof(ret->iv)); + + fp = f_open(filename, "r", FALSE); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (!strstartswith(line, "-----BEGIN ") || + !strendswith(line, "PRIVATE KEY-----")) { + errmsg = "file does not begin with OpenSSH key header"; + goto error; + } + if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) + ret->type = OSSH_RSA; + else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) + ret->type = OSSH_DSA; + else { + errmsg = "unrecognised key type"; + goto error; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + + headers_done = 0; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (strstartswith(line, "-----END ") && + strendswith(line, "PRIVATE KEY-----")) { + sfree(line); + line = NULL; + break; /* done */ + } + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + if (!strcmp(line, "Proc-Type")) { + if (p[0] != '4' || p[1] != ',') { + errmsg = "Proc-Type is not 4 (only 4 is supported)"; + goto error; + } + p += 2; + if (!strcmp(p, "ENCRYPTED")) + ret->encrypted = 1; + } else if (!strcmp(line, "DEK-Info")) { + int i, j, ivlen; + + if (!strncmp(p, "DES-EDE3-CBC,", 13)) { + ret->encryption = OSSH_ENC_3DES; + ivlen = 8; + } else if (!strncmp(p, "AES-128-CBC,", 12)) { + ret->encryption = OSSH_ENC_AES; + ivlen = 16; + } else { + errmsg = "unsupported cipher"; + goto error; + } + p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ + for (i = 0; i < ivlen; i++) { + if (1 != sscanf(p, "%2x", &j)) { + errmsg = "expected more iv data in DEK-Info"; + goto error; + } + ret->iv[i] = j; + p += 2; + } + if (*p) { + errmsg = "more iv data than expected in DEK-Info"; + goto error; + } + } + } else { + headers_done = 1; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + int len; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + if (ret->keyblob_len + len > ret->keyblob_size) { + ret->keyblob_size = ret->keyblob_len + len + 256; + ret->keyblob = sresize(ret->keyblob, ret->keyblob_size, + unsigned char); + } + + memcpy(ret->keyblob + ret->keyblob_len, out, len); + ret->keyblob_len += len; + + smemclr(out, sizeof(out)); + } + + p++; + } + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + + fclose(fp); + fp = NULL; + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "key body not present"; + goto error; + } + + if (ret->encrypted && ret->keyblob_len % 8 != 0) { + errmsg = "encrypted key blob is not a multiple of cipher block size"; + goto error; + } + + smemclr(base64_bit, sizeof(base64_bit)); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (line) { + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + smemclr(base64_bit, sizeof(base64_bit)); + if (ret) { + if (ret->keyblob) { + smemclr(ret->keyblob, ret->keyblob_size); + sfree(ret->keyblob); + } + smemclr(ret, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + if (fp) fclose(fp); + return NULL; +} + +int openssh_encrypted(const Filename *filename) +{ + struct openssh_key *key = load_openssh_key(filename, NULL); + int ret; + + if (!key) + return 0; + ret = key->encrypted; + smemclr(key->keyblob, key->keyblob_size); + sfree(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + return ret; +} + +struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase, + const char **errmsg_p) +{ + struct openssh_key *key = load_openssh_key(filename, errmsg_p); + struct ssh2_userkey *retkey; + unsigned char *p; + int ret, id, len, flags; + int i, num_integers; + struct ssh2_userkey *retval = NULL; + char *errmsg; + unsigned char *blob; + int blobsize = 0, blobptr, privptr; + char *modptr = NULL; + int modlen = 0; + + blob = NULL; + + if (!key) + return NULL; + + if (key->encrypted) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase || iv) + * - let block B equal MD5(A || passphrase || iv) + * - block C would be MD5(B || passphrase || iv) and so on + * - encryption key is the first N bytes of A || B + * + * (Note that only 8 bytes of the iv are used for key + * derivation, even when the key is encrypted with AES and + * hence there are 16 bytes available.) + */ + struct MD5Context md5c; + unsigned char keybuf[32]; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, (unsigned char *)key->iv, 8); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, keybuf, 16); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, (unsigned char *)key->iv, 8); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + if (key->encryption == OSSH_ENC_3DES) + des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv, + key->keyblob, key->keyblob_len); + else { + void *ctx; + assert(key->encryption == OSSH_ENC_AES); + ctx = aes_make_context(); + aes128_key(ctx, keybuf); + aes_iv(ctx, (unsigned char *)key->iv); + aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len); + aes_free_context(ctx); + } + + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * Now we have a decrypted key blob, which contains an ASN.1 + * encoded private key. We must now untangle the ASN.1. + * + * We expect the whole key blob to be formatted as a SEQUENCE + * (0x30 followed by a length code indicating that the rest of + * the blob is part of the sequence). Within that SEQUENCE we + * expect to see a bunch of INTEGERs. What those integers mean + * depends on the key type: + * + * - For RSA, we expect the integers to be 0, n, e, d, p, q, + * dmp1, dmq1, iqmp in that order. (The last three are d mod + * (p-1), d mod (q-1), inverse of q mod p respectively.) + * + * - For DSA, we expect them to be 0, p, q, g, y, x in that + * order. + */ + + p = key->keyblob; + + /* Expect the SEQUENCE header. Take its absence as a failure to + * decrypt, if the key was encrypted. */ + ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); + p += ret; + if (ret < 0 || id != 16 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + /* Expect a load of INTEGERs. */ + if (key->type == OSSH_RSA) + num_integers = 9; + else if (key->type == OSSH_DSA) + num_integers = 6; + else + num_integers = 0; /* placate compiler warnings */ + + /* + * Space to create key blob in. + */ + blobsize = 256+key->keyblob_len; + blob = snewn(blobsize, unsigned char); + PUT_32BIT(blob, 7); + if (key->type == OSSH_DSA) + memcpy(blob+4, "ssh-dss", 7); + else if (key->type == OSSH_RSA) + memcpy(blob+4, "ssh-rsa", 7); + blobptr = 4+7; + privptr = -1; + + for (i = 0; i < num_integers; i++) { + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + if (ret < 0 || id != 2 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + if (i == 0) { + /* + * The first integer should be zero always (I think + * this is some sort of version indication). + */ + if (len != 1 || p[0] != 0) { + errmsg = "version number mismatch"; + goto error; + } + } else if (key->type == OSSH_RSA) { + /* + * Integers 1 and 2 go into the public blob but in the + * opposite order; integers 3, 4, 5 and 8 go into the + * private blob. The other two (6 and 7) are ignored. + */ + if (i == 1) { + /* Save the details for after we deal with number 2. */ + modptr = (char *)p; + modlen = len; + } else if (i != 6 && i != 7) { + PUT_32BIT(blob+blobptr, len); + memcpy(blob+blobptr+4, p, len); + blobptr += 4+len; + if (i == 2) { + PUT_32BIT(blob+blobptr, modlen); + memcpy(blob+blobptr+4, modptr, modlen); + blobptr += 4+modlen; + privptr = blobptr; + } + } + } else if (key->type == OSSH_DSA) { + /* + * Integers 1-4 go into the public blob; integer 5 goes + * into the private blob. + */ + PUT_32BIT(blob+blobptr, len); + memcpy(blob+blobptr+4, p, len); + blobptr += 4+len; + if (i == 4) + privptr = blobptr; + } + + /* Skip past the number. */ + p += len; + } + + /* + * Now put together the actual key. Simplest way to do this is + * to assemble our own key blobs and feed them to the createkey + * functions; this is a bit faffy but it does mean we get all + * the sanity checks for free. + */ + assert(privptr > 0); /* should have bombed by now if not */ + retkey = snew(struct ssh2_userkey); + retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss); + retkey->data = retkey->alg->createkey(blob, privptr, + blob+privptr, blobptr-privptr); + if (!retkey->data) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + + retkey->comment = dupstr("imported-openssh-key"); + errmsg = NULL; /* no error */ + retval = retkey; + + error: + if (blob) { + smemclr(blob, blobsize); + sfree(blob); + } + smemclr(key->keyblob, key->keyblob_size); + sfree(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return retval; +} + +int openssh_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + unsigned char *pubblob, *privblob, *spareblob; + int publen, privlen, sparelen = 0; + unsigned char *outblob; + int outlen; + struct mpint_pos numbers[9]; + int nnumbers, pos, len, seqlen, i; + char *header, *footer; + char zero[1]; + unsigned char iv[8]; + int ret = 0; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = key->alg->public_blob(key->data, &publen); + privblob = key->alg->private_blob(key->data, &privlen); + spareblob = outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (key->alg == &ssh_rsa) { + int pos; + struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1; + Bignum bd, bp, bq, bdmp1, bdmq1; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); + + assert(e.start && iqmp.start); /* can't go wrong */ + + /* We also need d mod (p-1) and d mod (q-1). */ + bd = bignum_from_bytes(d.start, d.bytes); + bp = bignum_from_bytes(p.start, p.bytes); + bq = bignum_from_bytes(q.start, q.bytes); + decbn(bp); + decbn(bq); + bdmp1 = bigmod(bd, bp); + bdmq1 = bigmod(bd, bq); + freebn(bd); + freebn(bp); + freebn(bq); + + dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8; + dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8; + sparelen = dmp1.bytes + dmq1.bytes; + spareblob = snewn(sparelen, unsigned char); + dmp1.start = spareblob; + dmq1.start = spareblob + dmp1.bytes; + for (i = 0; i < dmp1.bytes; i++) + spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i); + for (i = 0; i < dmq1.bytes; i++) + spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i); + freebn(bdmp1); + freebn(bdmq1); + + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + numbers[1] = n; + numbers[2] = e; + numbers[3] = d; + numbers[4] = p; + numbers[5] = q; + numbers[6] = dmp1; + numbers[7] = dmq1; + numbers[8] = iqmp; + + nnumbers = 9; + header = "-----BEGIN RSA PRIVATE KEY-----\n"; + footer = "-----END RSA PRIVATE KEY-----\n"; + } else if (key->alg == &ssh_dss) { + int pos; + struct mpint_pos p, q, g, y, x; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + + assert(y.start && x.start); /* can't go wrong */ + + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + numbers[1] = p; + numbers[2] = q; + numbers[3] = g; + numbers[4] = y; + numbers[5] = x; + + nnumbers = 6; + header = "-----BEGIN DSA PRIVATE KEY-----\n"; + footer = "-----END DSA PRIVATE KEY-----\n"; + } else { + assert(0); /* zoinks! */ + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + /* + * Now count up the total size of the ASN.1 encoded integers, + * so as to determine the length of the containing SEQUENCE. + */ + len = 0; + for (i = 0; i < nnumbers; i++) { + len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0); + len += numbers[i].bytes; + } + seqlen = len; + /* Now add on the SEQUENCE header. */ + len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); + /* Round up to the cipher block size, ensuring we have at least one + * byte of padding (see below). */ + outlen = len; + if (passphrase) + outlen = (outlen+8) &~ 7; + + /* + * Now we know how big outblob needs to be. Allocate it. + */ + outblob = snewn(outlen, unsigned char); + + /* + * And write the data into it. + */ + pos = 0; + pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); + for (i = 0; i < nnumbers; i++) { + pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0); + memcpy(outblob+pos, numbers[i].start, numbers[i].bytes); + pos += numbers[i].bytes; + } + + /* + * Padding on OpenSSH keys is deterministic. The number of + * padding bytes is always more than zero, and always at most + * the cipher block length. The value of each padding byte is + * equal to the number of padding bytes. So a plaintext that's + * an exact multiple of the block size will be padded with 08 + * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a + * plaintext one byte less than a multiple of the block size + * will be padded with just 01. + * + * This enables the OpenSSL key decryption function to strip + * off the padding algorithmically and return the unpadded + * plaintext to the next layer: it looks at the final byte, and + * then expects to find that many bytes at the end of the data + * with the same value. Those are all removed and the rest is + * returned. + */ + assert(pos == len); + while (pos < outlen) { + outblob[pos++] = outlen - len; + } + + /* + * Encrypt the key. + * + * For the moment, we still encrypt our OpenSSH keys using + * old-style 3DES. + */ + if (passphrase) { + /* + * Invent an iv. Then derive encryption key from passphrase + * and iv/salt: + * + * - let block A equal MD5(passphrase || iv) + * - let block B equal MD5(A || passphrase || iv) + * - block C would be MD5(B || passphrase || iv) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32]; + + for (i = 0; i < 8; i++) iv[i] = random_byte(); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, keybuf, 16); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf+16, &md5c); + + /* + * Now encrypt the key blob. + */ + des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen); + + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs(header, fp); + if (passphrase) { + fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,"); + for (i = 0; i < 8; i++) + fprintf(fp, "%02X", iv[i]); + fprintf(fp, "\n\n"); + } + base64_encode(fp, outblob, outlen, 64); + fputs(footer, fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + smemclr(outblob, outlen); + sfree(outblob); + } + if (spareblob) { + smemclr(spareblob, sparelen); + sfree(spareblob); + } + if (privblob) { + smemclr(privblob, privlen); + sfree(privblob); + } + if (pubblob) { + smemclr(pubblob, publen); + sfree(pubblob); + } + return ret; +} + +/* ---------------------------------------------------------------------- + * Code to read ssh.com private keys. + */ + +/* + * The format of the base64 blob is largely SSH-2-packet-formatted, + * except that mpints are a bit different: they're more like the + * old SSH-1 mpint. You have a 32-bit bit count N, followed by + * (N+7)/8 bytes of data. + * + * So. The blob contains: + * + * - uint32 0x3f6ff9eb (magic number) + * - uint32 size (total blob size) + * - string key-type (see below) + * - string cipher-type (tells you if key is encrypted) + * - string encrypted-blob + * + * (The first size field includes the size field itself and the + * magic number before it. All other size fields are ordinary SSH-2 + * strings, so the size field indicates how much data is to + * _follow_.) + * + * The encrypted blob, once decrypted, contains a single string + * which in turn contains the payload. (This allows padding to be + * added after that string while still making it clear where the + * real payload ends. Also it probably makes for a reasonable + * decryption check.) + * + * The payload blob, for an RSA key, contains: + * - mpint e + * - mpint d + * - mpint n (yes, the public and private stuff is intermixed) + * - mpint u (presumably inverse of p mod q) + * - mpint p (p is the smaller prime) + * - mpint q (q is the larger) + * + * For a DSA key, the payload blob contains: + * - uint32 0 + * - mpint p + * - mpint g + * - mpint q + * - mpint y + * - mpint x + * + * Alternatively, if the parameters are `predefined', that + * (0,p,g,q) sequence can be replaced by a uint32 1 and a string + * containing some predefined parameter specification. *shudder*, + * but I doubt we'll encounter this in real life. + * + * The key type strings are ghastly. The RSA key I looked at had a + * type string of + * + * `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}' + * + * and the DSA key wasn't much better: + * + * `dl-modp{sign{dsa-nist-sha1},dh{plain}}' + * + * It isn't clear that these will always be the same. I think it + * might be wise just to look at the `if-modn{sign{rsa' and + * `dl-modp{sign{dsa' prefixes. + * + * Finally, the encryption. The cipher-type string appears to be + * either `none' or `3des-cbc'. Looks as if this is SSH-2-style + * 3des-cbc (i.e. outer cbc rather than inner). The key is created + * from the passphrase by means of yet another hashing faff: + * + * - first 16 bytes are MD5(passphrase) + * - next 16 bytes are MD5(passphrase || first 16 bytes) + * - if there were more, they'd be MD5(passphrase || first 32), + * and so on. + */ + +#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb + +struct sshcom_key { + char comment[256]; /* allowing any length is overkill */ + unsigned char *keyblob; + int keyblob_len, keyblob_size; +}; + +static struct sshcom_key *load_sshcom_key(const Filename *filename, + const char **errmsg_p) +{ + struct sshcom_key *ret; + FILE *fp; + char *line = NULL; + int hdrstart, len; + char *errmsg, *p; + int headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct sshcom_key); + ret->comment[0] = '\0'; + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + + fp = f_open(filename, "r", FALSE); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) { + errmsg = "file does not begin with ssh.com key header"; + goto error; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + + headers_done = 0; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) { + sfree(line); + line = NULL; + break; /* done */ + } + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + hdrstart = p - line; + + /* + * Header lines can end in a trailing backslash for + * continuation. + */ + len = hdrstart + strlen(line+hdrstart); + assert(!line[len]); + while (line[len-1] == '\\') { + char *line2; + int line2len; + + line2 = fgetline(fp); + if (!line2) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line2); + + line2len = strlen(line2); + line = sresize(line, len + line2len + 1, char); + strcpy(line + len - 1, line2); + len += line2len - 1; + assert(!line[len]); + + smemclr(line2, strlen(line2)); + sfree(line2); + line2 = NULL; + } + p = line + hdrstart; + strip_crlf(p); + if (!strcmp(line, "Comment")) { + /* Strip quotes in comment if present. */ + if (p[0] == '"' && p[strlen(p)-1] == '"') { + p++; + p[strlen(p)-1] = '\0'; + } + strncpy(ret->comment, p, sizeof(ret->comment)); + ret->comment[sizeof(ret->comment)-1] = '\0'; + } + } else { + headers_done = 1; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + if (ret->keyblob_len + len > ret->keyblob_size) { + ret->keyblob_size = ret->keyblob_len + len + 256; + ret->keyblob = sresize(ret->keyblob, ret->keyblob_size, + unsigned char); + } + + memcpy(ret->keyblob + ret->keyblob_len, out, len); + ret->keyblob_len += len; + } + + p++; + } + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "key body not present"; + goto error; + } + + fclose(fp); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (fp) + fclose(fp); + + if (line) { + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + if (ret) { + if (ret->keyblob) { + smemclr(ret->keyblob, ret->keyblob_size); + sfree(ret->keyblob); + } + smemclr(ret, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + return NULL; +} + +int sshcom_encrypted(const Filename *filename, char **comment) +{ + struct sshcom_key *key = load_sshcom_key(filename, NULL); + int pos, len, answer; + + answer = 0; + + *comment = NULL; + if (!key) + goto done; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) { + goto done; /* key is invalid */ + } + + /* + * Find the cipher-type string. + */ + pos = 8; + if (key->keyblob_len < pos+4) + goto done; /* key is far too short */ + len = toint(GET_32BIT(key->keyblob + pos)); + if (len < 0 || len > key->keyblob_len - pos - 4) + goto done; /* key is far too short */ + pos += 4 + len; /* skip key type */ + len = toint(GET_32BIT(key->keyblob + pos)); /* find cipher-type length */ + if (len < 0 || len > key->keyblob_len - pos - 4) + goto done; /* cipher type string is incomplete */ + if (len != 4 || 0 != memcmp(key->keyblob + pos + 4, "none", 4)) + answer = 1; + + done: + if (key) { + *comment = dupstr(key->comment); + smemclr(key->keyblob, key->keyblob_size); + sfree(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + } else { + *comment = dupstr(""); + } + return answer; +} + +static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret) +{ + unsigned bits, bytes; + unsigned char *d = (unsigned char *) data; + + if (len < 4) + goto error; + bits = GET_32BIT(d); + + bytes = (bits + 7) / 8; + if (len < 4+bytes) + goto error; + + ret->start = d + 4; + ret->bytes = bytes; + return bytes+4; + + error: + ret->start = NULL; + ret->bytes = -1; + return len; /* ensure further calls fail as well */ +} + +static int sshcom_put_mpint(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + unsigned char *i = (unsigned char *)data; + int bits = len * 8 - 1; + + while (bits > 0) { + if (*i & (1 << (bits & 7))) + break; + if (!(bits-- & 7)) + i++, len--; + } + + PUT_32BIT(d, bits+1); + memcpy(d+4, i, len); + return len+4; +} + +struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, + const char **errmsg_p) +{ + struct sshcom_key *key = load_sshcom_key(filename, errmsg_p); + char *errmsg; + int pos, len; + const char prefix_rsa[] = "if-modn{sign{rsa"; + const char prefix_dsa[] = "dl-modp{sign{dsa"; + enum { RSA, DSA } type; + int encrypted; + char *ciphertext; + int cipherlen; + struct ssh2_userkey *ret = NULL, *retkey; + const struct ssh_signkey *alg; + unsigned char *blob = NULL; + int blobsize = 0, publen, privlen; + + if (!key) + return NULL; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != SSHCOM_MAGIC_NUMBER) { + errmsg = "key does not begin with magic number"; + goto error; + } + + /* + * Determine the key type. + */ + pos = 8; + if (key->keyblob_len < pos+4 || + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain a key type string"; + goto error; + } + if (len > sizeof(prefix_rsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_rsa, sizeof(prefix_rsa) - 1)) { + type = RSA; + } else if (len > sizeof(prefix_dsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_dsa, sizeof(prefix_dsa) - 1)) { + type = DSA; + } else { + errmsg = "key is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Determine the cipher type. + */ + if (key->keyblob_len < pos+4 || + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain a cipher type string"; + goto error; + } + if (len == 4 && !memcmp(key->keyblob+pos+4, "none", 4)) + encrypted = 0; + else if (len == 8 && !memcmp(key->keyblob+pos+4, "3des-cbc", 8)) + encrypted = 1; + else { + errmsg = "key encryption is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Get hold of the encrypted part of the key. + */ + if (key->keyblob_len < pos+4 || + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain actual key data"; + goto error; + } + ciphertext = (char *)key->keyblob + pos + 4; + cipherlen = len; + if (cipherlen == 0) { + errmsg = "length of key data is zero"; + goto error; + } + + /* + * Decrypt it if necessary. + */ + if (encrypted) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32], iv[8]; + + if (cipherlen % 8 != 0) { + errmsg = "encrypted part of key is not a multiple of cipher block" + " size"; + goto error; + } + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_decrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); + + /* + * Hereafter we return WRONG_PASSPHRASE for any parsing + * error. (But only if we've just tried to decrypt it! + * Returning WRONG_PASSPHRASE for an unencrypted key is + * automatic doom.) + */ + if (encrypted) + ret = SSH2_WRONG_PASSPHRASE; + } + + /* + * Strip away the containing string to get to the real meat. + */ + len = toint(GET_32BIT(ciphertext)); + if (len < 0 || len > cipherlen-4) { + errmsg = "containing string was ill-formed"; + goto error; + } + ciphertext += 4; + cipherlen = len; + + /* + * Now we break down into RSA versus DSA. In either case we'll + * construct public and private blobs in our own format, and + * end up feeding them to alg->createkey(). + */ + blobsize = cipherlen + 256; + blob = snewn(blobsize, unsigned char); + privlen = 0; + if (type == RSA) { + struct mpint_pos n, e, d, u, p, q; + int pos = 0; + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &e); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &d); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &n); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &u); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + if (!q.start) { + errmsg = "key data did not contain six integers"; + goto error; + } + + alg = &ssh_rsa; + pos = 0; + pos += put_string(blob+pos, "ssh-rsa", 7); + pos += put_mp(blob+pos, e.start, e.bytes); + pos += put_mp(blob+pos, n.start, n.bytes); + publen = pos; + pos += put_string(blob+pos, d.start, d.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, u.start, u.bytes); + privlen = pos - publen; + } else { + struct mpint_pos p, q, g, x, y; + int pos = 4; + + assert(type == DSA); /* the only other option from the if above */ + + if (GET_32BIT(ciphertext) != 0) { + errmsg = "predefined DSA parameters not supported"; + goto error; + } + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &g); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &y); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &x); + if (!x.start) { + errmsg = "key data did not contain five integers"; + goto error; + } + + alg = &ssh_dss; + pos = 0; + pos += put_string(blob+pos, "ssh-dss", 7); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, g.start, g.bytes); + pos += put_mp(blob+pos, y.start, y.bytes); + publen = pos; + pos += put_mp(blob+pos, x.start, x.bytes); + privlen = pos - publen; + } + + assert(privlen > 0); /* should have bombed by now if not */ + + retkey = snew(struct ssh2_userkey); + retkey->alg = alg; + retkey->data = alg->createkey(blob, publen, blob+publen, privlen); + if (!retkey->data) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + retkey->comment = dupstr(key->comment); + + errmsg = NULL; /* no error */ + ret = retkey; + + error: + if (blob) { + smemclr(blob, blobsize); + sfree(blob); + } + smemclr(key->keyblob, key->keyblob_size); + sfree(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return ret; +} + +int sshcom_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + unsigned char *pubblob, *privblob; + int publen, privlen; + unsigned char *outblob; + int outlen; + struct mpint_pos numbers[6]; + int nnumbers, initial_zero, pos, lenpos, i; + char *type; + char *ciphertext; + int cipherlen; + int ret = 0; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = key->alg->public_blob(key->data, &publen); + privblob = key->alg->private_blob(key->data, &privlen); + outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (key->alg == &ssh_rsa) { + int pos; + struct mpint_pos n, e, d, p, q, iqmp; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); + + assert(e.start && iqmp.start); /* can't go wrong */ + + numbers[0] = e; + numbers[1] = d; + numbers[2] = n; + numbers[3] = iqmp; + numbers[4] = q; + numbers[5] = p; + + nnumbers = 6; + initial_zero = 0; + type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; + } else if (key->alg == &ssh_dss) { + int pos; + struct mpint_pos p, q, g, y, x; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + + assert(y.start && x.start); /* can't go wrong */ + + numbers[0] = p; + numbers[1] = g; + numbers[2] = q; + numbers[3] = y; + numbers[4] = x; + + nnumbers = 5; + initial_zero = 1; + type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; + } else { + assert(0); /* zoinks! */ + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + /* + * Total size of key blob will be somewhere under 512 plus + * combined length of integers. We'll calculate the more + * precise size as we construct the blob. + */ + outlen = 512; + for (i = 0; i < nnumbers; i++) + outlen += 4 + numbers[i].bytes; + outblob = snewn(outlen, unsigned char); + + /* + * Create the unencrypted key blob. + */ + pos = 0; + PUT_32BIT(outblob+pos, SSHCOM_MAGIC_NUMBER); pos += 4; + pos += 4; /* length field, fill in later */ + pos += put_string(outblob+pos, type, strlen(type)); + { + char *ciphertype = passphrase ? "3des-cbc" : "none"; + pos += put_string(outblob+pos, ciphertype, strlen(ciphertype)); + } + lenpos = pos; /* remember this position */ + pos += 4; /* encrypted-blob size */ + pos += 4; /* encrypted-payload size */ + if (initial_zero) { + PUT_32BIT(outblob+pos, 0); + pos += 4; + } + for (i = 0; i < nnumbers; i++) + pos += sshcom_put_mpint(outblob+pos, + numbers[i].start, numbers[i].bytes); + /* Now wrap up the encrypted payload. */ + PUT_32BIT(outblob+lenpos+4, pos - (lenpos+8)); + /* Pad encrypted blob to a multiple of cipher block size. */ + if (passphrase) { + int padding = -(pos - (lenpos+4)) & 7; + while (padding--) + outblob[pos++] = random_byte(); + } + ciphertext = (char *)outblob+lenpos+4; + cipherlen = pos - (lenpos+4); + assert(!passphrase || cipherlen % 8 == 0); + /* Wrap up the encrypted blob string. */ + PUT_32BIT(outblob+lenpos, cipherlen); + /* And finally fill in the total length field. */ + PUT_32BIT(outblob+4, pos); + + assert(pos < outlen); + + /* + * Encrypt the key. + */ + if (passphrase) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32], iv[8]; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_encrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fprintf(fp, "Comment: \""); + /* + * Comment header is broken with backslash-newline if it goes + * over 70 chars. Although it's surrounded by quotes, it + * _doesn't_ escape backslashes or quotes within the string. + * Don't ask me, I didn't design it. + */ + { + int slen = 60; /* starts at 60 due to "Comment: " */ + char *c = key->comment; + while ((int)strlen(c) > slen) { + fprintf(fp, "%.*s\\\n", slen, c); + c += slen; + slen = 70; /* allow 70 chars on subsequent lines */ + } + fprintf(fp, "%s\"\n", c); + } + base64_encode(fp, outblob, pos, 70); + fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + smemclr(outblob, outlen); + sfree(outblob); + } + if (privblob) { + smemclr(privblob, privlen); + sfree(privblob); + } + if (pubblob) { + smemclr(pubblob, publen); + sfree(pubblob); + } + return ret; +} diff --git a/netbox/libs/Putty/int64.c b/netbox/libs/Putty/int64.c new file mode 100644 index 000000000..b1c986ce0 --- /dev/null +++ b/netbox/libs/Putty/int64.c @@ -0,0 +1,175 @@ +/* + * Handling of the int64 and uint64 types. Done in 32-bit integers, + * for (pre-C99) portability. Hopefully once C99 becomes widespread + * we can kiss this lot goodbye... + */ + +#include +#include + +#include "int64.h" + +uint64 uint64_div10(uint64 x, int *remainder) +{ + uint64 y; + unsigned int rem, r2; + y.hi = x.hi / 10; + y.lo = x.lo / 10; + rem = x.lo % 10; + /* + * Now we have to add in the remainder left over from x.hi. + */ + r2 = x.hi % 10; + y.lo += r2 * 429496729; + rem += r2 * 6; + y.lo += rem / 10; + rem %= 10; + + if (remainder) + *remainder = rem; + return y; +} + +void uint64_decimal(uint64 x, char *buffer) +{ + char buf[20]; + int start = 20; + int d; + + do { + x = uint64_div10(x, &d); + assert(start > 0); + buf[--start] = d + '0'; + } while (x.hi || x.lo); + + memcpy(buffer, buf + start, sizeof(buf) - start); + buffer[sizeof(buf) - start] = '\0'; +} + +uint64 uint64_make(unsigned long hi, unsigned long lo) +{ + uint64 y; + y.hi = hi & 0xFFFFFFFFU; + y.lo = lo & 0xFFFFFFFFU; + return y; +} + +uint64 uint64_add(uint64 x, uint64 y) +{ + x.lo = (x.lo + y.lo) & 0xFFFFFFFFU; + x.hi += y.hi + (x.lo < y.lo ? 1 : 0); + return x; +} + +uint64 uint64_add32(uint64 x, unsigned long y) +{ + uint64 yy; + yy.hi = 0; + yy.lo = y; + return uint64_add(x, yy); +} + +int uint64_compare(uint64 x, uint64 y) +{ + if (x.hi != y.hi) + return x.hi < y.hi ? -1 : +1; + if (x.lo != y.lo) + return x.lo < y.lo ? -1 : +1; + return 0; +} + +uint64 uint64_subtract(uint64 x, uint64 y) +{ + x.lo = (x.lo - y.lo) & 0xFFFFFFFFU; + x.hi = (x.hi - y.hi - (x.lo > (y.lo ^ 0xFFFFFFFFU) ? 1 : 0)) & 0xFFFFFFFFU; + return x; +} + +double uint64_to_double(uint64 x) +{ + return (4294967296.0 * x.hi) + (double)x.lo; +} + +uint64 uint64_shift_right(uint64 x, int shift) +{ + if (shift < 32) { + x.lo >>= shift; + x.lo |= (x.hi << (32-shift)) & 0xFFFFFFFFU; + x.hi >>= shift; + } else { + x.lo = x.hi >> (shift-32); + x.hi = 0; + } + return x; +} + +uint64 uint64_shift_left(uint64 x, int shift) +{ + if (shift < 32) { + x.hi = (x.hi << shift) & 0xFFFFFFFFU; + x.hi |= (x.lo >> (32-shift)); + x.lo = (x.lo << shift) & 0xFFFFFFFFU; + } else { + x.hi = (x.lo << (shift-32)) & 0xFFFFFFFFU; + x.lo = 0; + } + return x; +} + +uint64 uint64_from_decimal(char *str) +{ + uint64 ret; + ret.hi = ret.lo = 0; + while (*str >= '0' && *str <= '9') { + ret = uint64_add(uint64_shift_left(ret, 3), + uint64_shift_left(ret, 1)); + ret = uint64_add32(ret, *str - '0'); + str++; + } + return ret; +} + +#ifdef TESTMODE + +#include + +int main(void) +{ + uint64 x, y, z; + char buf[80]; + + x = uint64_make(0x3456789AUL, 0xDEF01234UL); + printf("%08lx.%08lx\n", x.hi, x.lo); + uint64_decimal(x, buf); + printf("%s\n", buf); + + y = uint64_add32(x, 0xFFFFFFFFU); + printf("%08lx.%08lx\n", y.hi, y.lo); + uint64_decimal(y, buf); + printf("%s\n", buf); + + z = uint64_subtract(y, x); + printf("%08lx.%08lx\n", z.hi, z.lo); + uint64_decimal(z, buf); + printf("%s\n", buf); + + z = uint64_subtract(x, y); + printf("%08lx.%08lx\n", z.hi, z.lo); + uint64_decimal(z, buf); + printf("%s\n", buf); + + y = uint64_shift_right(x, 4); + printf("%08lx.%08lx\n", y.hi, y.lo); + + y = uint64_shift_right(x, 36); + printf("%08lx.%08lx\n", y.hi, y.lo); + + y = uint64_shift_left(x, 4); + printf("%08lx.%08lx\n", x.hi, x.lo); + + y = uint64_shift_left(x, 36); + printf("%08lx.%08lx\n", x.hi, x.lo); + + return 0; +} +#endif diff --git a/netbox/libs/Putty/int64.h b/netbox/libs/Putty/int64.h new file mode 100644 index 000000000..f62f8efce --- /dev/null +++ b/netbox/libs/Putty/int64.h @@ -0,0 +1,24 @@ +/* + * Header for int64.c. + */ + +#ifndef PUTTY_INT64_H +#define PUTTY_INT64_H + +typedef struct { + unsigned long hi, lo; +} uint64; + +uint64 uint64_div10(uint64 x, int *remainder); +void uint64_decimal(uint64 x, char *buffer); +uint64 uint64_make(unsigned long hi, unsigned long lo); +uint64 uint64_add(uint64 x, uint64 y); +uint64 uint64_add32(uint64 x, unsigned long y); +int uint64_compare(uint64 x, uint64 y); +uint64 uint64_subtract(uint64 x, uint64 y); +double uint64_to_double(uint64 x); +uint64 uint64_shift_right(uint64 x, int shift); +uint64 uint64_shift_left(uint64 x, int shift); +uint64 uint64_from_decimal(char *str); + +#endif diff --git a/netbox/libs/Putty/ldisc.c b/netbox/libs/Putty/ldisc.c new file mode 100644 index 000000000..311854060 --- /dev/null +++ b/netbox/libs/Putty/ldisc.c @@ -0,0 +1,372 @@ +/* + * ldisc.c: PuTTY line discipline. Sits between the input coming + * from keypresses in the window, and the output channel leading to + * the back end. Implements echo and/or local line editing, + * depending on what's currently configured. + */ + +#include +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "ldisc.h" + +#define ECHOING (ldisc->localecho == FORCE_ON || \ + (ldisc->localecho == AUTO && \ + (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ + term_ldisc(ldisc->term, LD_ECHO)))) +#define EDITING (ldisc->localedit == FORCE_ON || \ + (ldisc->localedit == AUTO && \ + (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ + term_ldisc(ldisc->term, LD_EDIT)))) + +static void c_write(Ldisc ldisc, char *buf, int len) +{ + from_backend(ldisc->frontend, 0, buf, len); +} + +static int plen(Ldisc ldisc, unsigned char c) +{ + if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) + return 1; + else if (c < 128) + return 2; /* ^x for some x */ + else if (in_utf(ldisc->term) && c >= 0xC0) + return 1; /* UTF-8 introducer character + * (FIXME: combining / wide chars) */ + else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0) + return 0; /* UTF-8 followup character */ + else + return 4; /* hex representation */ +} + +static void pwrite(Ldisc ldisc, unsigned char c) +{ + if ((c >= 32 && c <= 126) || + (!in_utf(ldisc->term) && c >= 0xA0) || + (in_utf(ldisc->term) && c >= 0x80)) { + c_write(ldisc, (char *)&c, 1); + } else if (c < 128) { + char cc[2]; + cc[1] = (c == 127 ? '?' : c + 0x40); + cc[0] = '^'; + c_write(ldisc, cc, 2); + } else { + char cc[5]; + sprintf(cc, "<%02X>", c); + c_write(ldisc, cc, 4); + } +} + +static int char_start(Ldisc ldisc, unsigned char c) +{ + if (in_utf(ldisc->term)) + return (c < 0x80 || c >= 0xC0); + else + return 1; +} + +static void bsb(Ldisc ldisc, int n) +{ + while (n--) + c_write(ldisc, "\010 \010", 3); +} + +#define CTRL(x) (x^'@') +#define KCTRL(x) ((x^'@') | 0x100) + +void *ldisc_create(Conf *conf, Terminal *term, + Backend *back, void *backhandle, + void *frontend) +{ + Ldisc ldisc = snew(struct ldisc_tag); + + ldisc->buf = NULL; + ldisc->buflen = 0; + ldisc->bufsiz = 0; + ldisc->quotenext = 0; + + ldisc->back = back; + ldisc->backhandle = backhandle; + ldisc->term = term; + ldisc->frontend = frontend; + + ldisc_configure(ldisc, conf); + + /* Link ourselves into the backend and the terminal */ + if (term) + term->ldisc = ldisc; + if (back) + back->provide_ldisc(backhandle, ldisc); + + return ldisc; +} + +void ldisc_configure(void *handle, Conf *conf) +{ + Ldisc ldisc = (Ldisc) handle; + + ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard); + ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline); + ldisc->protocol = conf_get_int(conf, CONF_protocol); + ldisc->localecho = conf_get_int(conf, CONF_localecho); + ldisc->localedit = conf_get_int(conf, CONF_localedit); +} + +void ldisc_free(void *handle) +{ + Ldisc ldisc = (Ldisc) handle; + + if (ldisc->term) + ldisc->term->ldisc = NULL; + if (ldisc->back) + ldisc->back->provide_ldisc(ldisc->backhandle, NULL); + if (ldisc->buf) + sfree(ldisc->buf); + sfree(ldisc); +} + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc) handle; + int keyflag = 0; + /* + * Called with len=0 when the options change. We must inform + * the front end in case it needs to know. + */ + if (len == 0) { + ldisc_update(ldisc->frontend, ECHOING, EDITING); + return; + } + + /* + * If that wasn't true, then we expect ldisc->term to be non-NULL + * hereafter. (The only front ends which have an ldisc but no term + * are those which do networking but no terminal emulation, in + * which case they need the above if statement to handle + * ldisc_updates passed from the back ends, but should never send + * any actual input through this function.) + */ + assert(ldisc->term); + + /* + * Notify the front end that something was pressed, in case + * it's depending on finding out (e.g. keypress termination for + * Close On Exit). + */ + frontend_keypress(ldisc->frontend); + + if (interactive) { + /* + * Interrupt a paste from the clipboard, if one was in + * progress when the user pressed a key. This is easier than + * buffering the current piece of data and saving it until the + * terminal has finished pasting, and has the potential side + * benefit of permitting a user to cancel an accidental huge + * paste. + */ + term_nopaste(ldisc->term); + } + + /* + * Less than zero means null terminated special string. + */ + if (len < 0) { + len = strlen(buf); + keyflag = KCTRL('@'); + } + /* + * Either perform local editing, or just send characters. + */ + if (EDITING) { + while (len--) { + int c; + c = (unsigned char)(*buf++) + keyflag; + if (!interactive && c == '\r') + c += KCTRL('@'); + switch (ldisc->quotenext ? ' ' : c) { + /* + * ^h/^?: delete, and output BSBs, to return to + * last character boundary (in UTF-8 mode this may + * be more than one byte) + * ^w: delete, and output BSBs, to return to last + * space/nonspace boundary + * ^u: delete, and output BSBs, to return to BOL + * ^c: Do a ^u then send a telnet IP + * ^z: Do a ^u then send a telnet SUSP + * ^\: Do a ^u then send a telnet ABORT + * ^r: echo "^R\n" and redraw line + * ^v: quote next char + * ^d: if at BOL, end of file and close connection, + * else send line and reset to BOL + * ^m: send line-plus-\r\n and reset to BOL + */ + case KCTRL('H'): + case KCTRL('?'): /* backspace/delete */ + if (ldisc->buflen > 0) { + do { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } while (!char_start(ldisc, ldisc->buf[ldisc->buflen])); + } + break; + case CTRL('W'): /* delete word */ + while (ldisc->buflen > 0) { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + if (ldisc->buflen > 0 && + isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && + !isspace((unsigned char)ldisc->buf[ldisc->buflen])) + break; + } + break; + case CTRL('U'): /* delete line */ + case CTRL('C'): /* Send IP */ + case CTRL('\\'): /* Quit */ + case CTRL('Z'): /* Suspend */ + while (ldisc->buflen > 0) { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } + ldisc->back->special(ldisc->backhandle, TS_EL); + /* + * We don't send IP, SUSP or ABORT if the user has + * configured telnet specials off! This breaks + * talkers otherwise. + */ + if (!ldisc->telnet_keyboard) + goto default_case; + if (c == CTRL('C')) + ldisc->back->special(ldisc->backhandle, TS_IP); + if (c == CTRL('Z')) + ldisc->back->special(ldisc->backhandle, TS_SUSP); + if (c == CTRL('\\')) + ldisc->back->special(ldisc->backhandle, TS_ABORT); + break; + case CTRL('R'): /* redraw line */ + if (ECHOING) { + int i; + c_write(ldisc, "^R\r\n", 4); + for (i = 0; i < ldisc->buflen; i++) + pwrite(ldisc, ldisc->buf[i]); + } + break; + case CTRL('V'): /* quote next char */ + ldisc->quotenext = TRUE; + break; + case CTRL('D'): /* logout or send */ + if (ldisc->buflen == 0) { + ldisc->back->special(ldisc->backhandle, TS_EOF); + } else { + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + ldisc->buflen = 0; + } + break; + /* + * This particularly hideous bit of code from RDB + * allows ordinary ^M^J to do the same thing as + * magic-^M when in Raw protocol. The line `case + * KCTRL('M'):' is _inside_ the if block. Thus: + * + * - receiving regular ^M goes straight to the + * default clause and inserts as a literal ^M. + * - receiving regular ^J _not_ directly after a + * literal ^M (or not in Raw protocol) fails the + * if condition, leaps to the bottom of the if, + * and falls through into the default clause + * again. + * - receiving regular ^J just after a literal ^M + * in Raw protocol passes the if condition, + * deletes the literal ^M, and falls through + * into the magic-^M code + * - receiving a magic-^M empties the line buffer, + * signals end-of-line in one of the various + * entertaining ways, and _doesn't_ fall out of + * the bottom of the if and through to the + * default clause because of the break. + */ + case CTRL('J'): + if (ldisc->protocol == PROT_RAW && + ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + /* FALLTHROUGH */ + case KCTRL('M'): /* send with newline */ + if (ldisc->buflen > 0) + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + if (ldisc->protocol == PROT_RAW) + ldisc->back->send(ldisc->backhandle, "\r\n", 2); + else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) + ldisc->back->special(ldisc->backhandle, TS_EOL); + else + ldisc->back->send(ldisc->backhandle, "\r", 1); + if (ECHOING) + c_write(ldisc, "\r\n", 2); + ldisc->buflen = 0; + break; + } + /* FALLTHROUGH */ + default: /* get to this label from ^V handler */ + default_case: + if (ldisc->buflen >= ldisc->bufsiz) { + ldisc->bufsiz = ldisc->buflen + 256; + ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char); + } + ldisc->buf[ldisc->buflen++] = c; + if (ECHOING) + pwrite(ldisc, (unsigned char) c); + ldisc->quotenext = FALSE; + break; + } + } + } else { + if (ldisc->buflen != 0) { + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + while (ldisc->buflen > 0) { + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } + } + if (len > 0) { + if (ECHOING) + c_write(ldisc, buf, len); + if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) { + switch (buf[0]) { + case CTRL('M'): + if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) + ldisc->back->special(ldisc->backhandle, TS_EOL); + else + ldisc->back->send(ldisc->backhandle, "\r", 1); + break; + case CTRL('?'): + case CTRL('H'): + if (ldisc->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_EC); + break; + } + case CTRL('C'): + if (ldisc->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_IP); + break; + } + case CTRL('Z'): + if (ldisc->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_SUSP); + break; + } + + default: + ldisc->back->send(ldisc->backhandle, buf, len); + break; + } + } else + ldisc->back->send(ldisc->backhandle, buf, len); + } + } +} diff --git a/netbox/libs/Putty/ldisc.h b/netbox/libs/Putty/ldisc.h new file mode 100644 index 000000000..5dbe2a76b --- /dev/null +++ b/netbox/libs/Putty/ldisc.h @@ -0,0 +1,26 @@ +/* + * ldisc.h: defines the Ldisc data structure used by ldisc.c and + * ldiscucs.c. (Unfortunately it was necessary to split the ldisc + * module in two, to avoid unnecessarily linking in the Unicode + * stuff in tools that don't require it.) + */ + +#ifndef PUTTY_LDISC_H +#define PUTTY_LDISC_H + +typedef struct ldisc_tag { + Terminal *term; + Backend *back; + void *backhandle; + void *frontend; + + /* + * Values cached out of conf. + */ + int telnet_keyboard, telnet_newline, protocol, localecho, localedit; + + char *buf; + int buflen, bufsiz, quotenext; +} *Ldisc; + +#endif /* PUTTY_LDISC_H */ diff --git a/netbox/libs/Putty/ldiscucs.c b/netbox/libs/Putty/ldiscucs.c new file mode 100644 index 000000000..fe0da8a6a --- /dev/null +++ b/netbox/libs/Putty/ldiscucs.c @@ -0,0 +1,99 @@ +/* + * ldisc.c: PuTTY line discipline. Sits between the input coming + * from keypresses in the window, and the output channel leading to + * the back end. Implements echo and/or local line editing, + * depending on what's currently configured. + */ + +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "ldisc.h" + +void lpage_send(void *handle, + int codepage, char *buf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc)handle; + wchar_t *widebuffer = 0; + int widesize = 0; + int wclen; + + if (codepage < 0) { + ldisc_send(ldisc, buf, len, interactive); + return; + } + + widesize = len * 2; + widebuffer = snewn(widesize, wchar_t); + + wclen = mb_to_wc(codepage, 0, buf, len, widebuffer, widesize); + luni_send(ldisc, widebuffer, wclen, interactive); + + sfree(widebuffer); +} + +void luni_send(void *handle, wchar_t * widebuf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc)handle; + int ratio = (in_utf(ldisc->term))?3:1; + char *linebuffer; + int linesize; + int i; + char *p; + + linesize = len * ratio * 2; + linebuffer = snewn(linesize, char); + + if (in_utf(ldisc->term)) { + /* UTF is a simple algorithm */ + for (p = linebuffer, i = 0; i < len; i++) { + unsigned long ch = widebuf[i]; + + if (IS_SURROGATE(ch)) { +#ifdef PLATFORM_IS_UTF16 + if (i+1 < len) { + unsigned long ch2 = widebuf[i+1]; + if (IS_SURROGATE_PAIR(ch, ch2)) { + ch = FROM_SURROGATES(ch, ch2); + i++; + } + } else +#endif + { + /* Unrecognised UTF-16 sequence */ + ch = '.'; + } + } + + if (ch < 0x80) { + *p++ = (char) (ch); + } else if (ch < 0x800) { + *p++ = (char) (0xC0 | (ch >> 6)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } else if (ch < 0x10000) { + *p++ = (char) (0xE0 | (ch >> 12)); + *p++ = (char) (0x80 | ((ch >> 6) & 0x3F)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } else { + *p++ = (char) (0xF0 | (ch >> 18)); + *p++ = (char) (0x80 | ((ch >> 12) & 0x3F)); + *p++ = (char) (0x80 | ((ch >> 6) & 0x3F)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } + } + } else { + int rv; + rv = wc_to_mb(ldisc->term->ucsdata->line_codepage, 0, widebuf, len, + linebuffer, linesize, NULL, NULL, ldisc->term->ucsdata); + if (rv >= 0) + p = linebuffer + rv; + else + p = linebuffer; + } + if (p > linebuffer) + ldisc_send(ldisc, linebuffer, p - linebuffer, interactive); + + sfree(linebuffer); +} diff --git a/netbox/libs/Putty/licence.h b/netbox/libs/Putty/licence.h new file mode 100644 index 000000000..3b6e70888 --- /dev/null +++ b/netbox/libs/Putty/licence.h @@ -0,0 +1,19 @@ +/* + * licence.h - macro definitions for the PuTTY licence. + * + * Generated by licence.pl from LICENCE. + * You should edit those files rather than editing this one. + */ + +#define LICENCE_TEXT(parsep) \ + "PuTTY is copyright 1997-2016 Simon Tatham." \ + parsep \ + "Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, and CORE SDI S.A." \ + parsep \ + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:" \ + parsep \ + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software." \ + parsep \ + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + +#define SHORT_COPYRIGHT_DETAILS "1997-2016 Simon Tatham" diff --git a/netbox/libs/Putty/logging.c b/netbox/libs/Putty/logging.c new file mode 100644 index 000000000..a40d32a6e --- /dev/null +++ b/netbox/libs/Putty/logging.c @@ -0,0 +1,509 @@ +/* + * Session logging. + */ + +#include +#include +#include + +#include +#include + +#include "putty.h" + +/* log session to file stuff ... */ +struct LogContext { + FILE *lgfp; + enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state; + bufchain queue; + Filename *currlogfilename; + void *frontend; + Conf *conf; + int logtype; /* cached out of conf */ +}; + +static Filename *xlatlognam(Filename *s, char *hostname, int port, + struct tm *tm); + +/* + * Internal wrapper function which must be called for _all_ output + * to the log file. It takes care of opening the log file if it + * isn't open, buffering data if it's in the process of being + * opened asynchronously, etc. + */ +static void logwrite(struct LogContext *ctx, void *data, int len) +{ + /* + * In state L_CLOSED, we call logfopen, which will set the state + * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of + * those three _after_ processing L_CLOSED. + */ + if (ctx->state == L_CLOSED) + logfopen(ctx); + + if (ctx->state == L_OPENING) { + bufchain_add(&ctx->queue, data, len); + } else if (ctx->state == L_OPEN) { + assert(ctx->lgfp); + if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) { + logfclose(ctx); + ctx->state = L_ERROR; + /* Log state is L_ERROR so this won't cause a loop */ + logevent(ctx->frontend, + "Disabled writing session log due to error while writing"); + } + } /* else L_ERROR, so ignore the write */ +} + +/* + * Convenience wrapper on logwrite() which printf-formats the + * string. + */ +static void logprintf(struct LogContext *ctx, const char *fmt, ...) +{ + va_list ap; + char *data; + + va_start(ap, fmt); + data = dupvprintf(fmt, ap); + va_end(ap); + + logwrite(ctx, data, strlen(data)); + sfree(data); +} + +/* + * Flush any open log file. + */ +void logflush(void *handle) { + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->logtype > 0) + if (ctx->state == L_OPEN) + fflush(ctx->lgfp); +} + +static void logfopen_callback(void *handle, int mode) +{ + struct LogContext *ctx = (struct LogContext *)handle; + char buf[256], *event; + struct tm tm; + const char *fmode; + int shout = FALSE; + + if (mode == 0) { + ctx->state = L_ERROR; /* disable logging */ + } else { + fmode = (mode == 1 ? "ab" : "wb"); + ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE); + if (ctx->lgfp) { + ctx->state = L_OPEN; + } else { + ctx->state = L_ERROR; + shout = TRUE; + } + } + + if (ctx->state == L_OPEN) { + /* Write header line into log file. */ + tm = ltime(); + strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); + logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s" + " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf); + } + + event = dupprintf("%s session log (%s mode) to file: %s", + ctx->state == L_ERROR ? + (mode == 0 ? "Disabled writing" : "Error writing") : + (mode == 1 ? "Appending" : "Writing new"), + (ctx->logtype == LGTYP_ASCII ? "ASCII" : + ctx->logtype == LGTYP_DEBUG ? "raw" : + ctx->logtype == LGTYP_PACKETS ? "SSH packets" : + ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" : + "unknown"), + filename_to_str(ctx->currlogfilename)); + logevent(ctx->frontend, event); + if (shout) { + /* + * If we failed to open the log file due to filesystem error + * (as opposed to user action such as clicking Cancel in the + * askappend box), we should log it more prominently. We do + * this by sending it to the same place that stderr output + * from the main session goes (so, either a console tool's + * actual stderr, or a terminal window). + * + * Of course this is one case in which that policy won't cause + * it to turn up embarrassingly in a log file of real server + * output, because the whole point is that we haven't managed + * to open any such log file :-) + */ + from_backend(ctx->frontend, 1, event, strlen(event)); + from_backend(ctx->frontend, 1, "\r\n", 2); + } + sfree(event); + + /* + * Having either succeeded or failed in opening the log file, + * we should write any queued data out. + */ + assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */ + while (bufchain_size(&ctx->queue)) { + void *data; + int len; + bufchain_prefix(&ctx->queue, &data, &len); + logwrite(ctx, data, len); + bufchain_consume(&ctx->queue, len); + } +} + +/* + * Open the log file. Takes care of detecting an already-existing + * file and asking the user whether they want to append, overwrite + * or cancel logging. + */ +void logfopen(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + struct tm tm; + FILE *fp; + int mode; + + /* Prevent repeat calls */ + if (ctx->state != L_CLOSED) + return; + + if (!ctx->logtype) + return; + + tm = ltime(); + + /* substitute special codes in file name */ + if (ctx->currlogfilename) + filename_free(ctx->currlogfilename); + ctx->currlogfilename = + xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename), + conf_get_str(ctx->conf, CONF_host), + conf_get_int(ctx->conf, CONF_port), &tm); + + fp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ + if (fp) { + int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr); + fclose(fp); + if (logxfovr != LGXF_ASK) { + mode = ((logxfovr == LGXF_OVR) ? 2 : 1); + } else + mode = askappend(ctx->frontend, ctx->currlogfilename, + logfopen_callback, ctx); + } else + mode = 2; /* create == overwrite */ + + if (mode < 0) + ctx->state = L_OPENING; + else + logfopen_callback(ctx, mode); /* open the file */ +} + +void logfclose(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->lgfp) { + fclose(ctx->lgfp); + ctx->lgfp = NULL; + } + ctx->state = L_CLOSED; +} + +/* + * Log session traffic. + */ +void logtraffic(void *handle, unsigned char c, int logmode) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->logtype > 0) { + if (ctx->logtype == logmode) + logwrite(ctx, &c, 1); + } +} + +/* + * Log an Event Log entry. Used in SSH packet logging mode; this is + * also as convenient a place as any to put the output of Event Log + * entries to stderr when a command-line tool is in verbose mode. + * (In particular, this is a better place to put it than in the + * front ends, because it only has to be done once for all + * platforms. Platforms which don't have a meaningful stderr can + * just avoid defining FLAG_STDERR. + */ +void log_eventlog(void *handle, const char *event) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) { + fprintf(stderr, "%s\n", event); + fflush(stderr); + } + /* If we don't have a context yet (eg winnet.c init) then skip entirely */ + if (!ctx) + return; + if (ctx->logtype != LGTYP_PACKETS && + ctx->logtype != LGTYP_SSHRAW) + return; + logprintf(ctx, "Event Log: %s\r\n", event); + logflush(ctx); +} + +/* + * Log an SSH packet. + * If n_blanks != 0, blank or omit some parts. + * Set of blanking areas must be in increasing order. + */ +void log_packet(void *handle, int direction, int type, + char *texttype, const void *data, int len, + int n_blanks, const struct logblank_t *blanks, + const unsigned long *seq, + unsigned downstream_id, const char *additional_log_text) +{ + struct LogContext *ctx = (struct LogContext *)handle; + char dumpdata[80], smalldata[5]; + int p = 0, b = 0, omitted = 0; + int output_pos = 0; /* NZ if pending output in dumpdata */ + + if (!(ctx->logtype == LGTYP_SSHRAW || + (ctx->logtype == LGTYP_PACKETS && texttype))) + return; + + /* Packet header. */ + if (texttype) { + logprintf(ctx, "%s packet ", + direction == PKT_INCOMING ? "Incoming" : "Outgoing"); + + if (seq) + logprintf(ctx, "#0x%lx, ", *seq); + + logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype); + + if (downstream_id) { + logprintf(ctx, " on behalf of downstream #%u", downstream_id); + if (additional_log_text) + logprintf(ctx, " (%s)", additional_log_text); + } + + logprintf(ctx, "\r\n"); + } else { + /* + * Raw data is logged with a timestamp, so that it's possible + * to determine whether a mysterious delay occurred at the + * client or server end. (Timestamping the raw data avoids + * cluttering the normal case of only logging decrypted SSH + * messages, and also adds conceptual rigour in the case where + * an SSH message arrives in several pieces.) + */ + char buf[256]; + struct tm tm; + tm = ltime(); + strftime(buf, 24, "%Y-%m-%d %H:%M:%S", &tm); + logprintf(ctx, "%s raw data at %s\r\n", + direction == PKT_INCOMING ? "Incoming" : "Outgoing", + buf); + } + + /* + * Output a hex/ASCII dump of the packet body, blanking/omitting + * parts as specified. + */ + while (p < len) { + int blktype; + + /* Move to a current entry in the blanking array. */ + while ((b < n_blanks) && + (p >= blanks[b].offset + blanks[b].len)) + b++; + /* Work out what type of blanking to apply to + * this byte. */ + blktype = PKTLOG_EMIT; /* default */ + if ((b < n_blanks) && + (p >= blanks[b].offset) && + (p < blanks[b].offset + blanks[b].len)) + blktype = blanks[b].type; + + /* If we're about to stop omitting, it's time to say how + * much we omitted. */ + if ((blktype != PKTLOG_OMIT) && omitted) { + logprintf(ctx, " (%d byte%s omitted)\r\n", + omitted, (omitted==1?"":"s")); + omitted = 0; + } + + /* (Re-)initialise dumpdata as necessary + * (start of row, or if we've just stopped omitting) */ + if (!output_pos && !omitted) + sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, ""); + + /* Deal with the current byte. */ + if (blktype == PKTLOG_OMIT) { + omitted++; + } else { + int c; + if (blktype == PKTLOG_BLANK) { + c = 'X'; + sprintf(smalldata, "XX"); + } else { /* PKTLOG_EMIT */ + c = ((unsigned char *)data)[p]; + sprintf(smalldata, "%02x", c); + } + dumpdata[10+2+3*(p%16)] = smalldata[0]; + dumpdata[10+2+3*(p%16)+1] = smalldata[1]; + dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.'); + output_pos = (p%16) + 1; + } + + p++; + + /* Flush row if necessary */ + if (((p % 16) == 0) || (p == len) || omitted) { + if (output_pos) { + strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n"); + logwrite(ctx, dumpdata, strlen(dumpdata)); + output_pos = 0; + } + } + + } + + /* Tidy up */ + if (omitted) + logprintf(ctx, " (%d byte%s omitted)\r\n", + omitted, (omitted==1?"":"s")); + logflush(ctx); +} + +void *log_init(void *frontend, Conf *conf) +{ + struct LogContext *ctx = snew(struct LogContext); + ctx->lgfp = NULL; + ctx->state = L_CLOSED; + ctx->frontend = frontend; + ctx->conf = conf_copy(conf); + ctx->logtype = conf_get_int(ctx->conf, CONF_logtype); + ctx->currlogfilename = NULL; + bufchain_init(&ctx->queue); + return ctx; +} + +void log_free(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + + logfclose(ctx); + bufchain_clear(&ctx->queue); + if (ctx->currlogfilename) + filename_free(ctx->currlogfilename); + conf_free(ctx->conf); + sfree(ctx); +} + +void log_reconfig(void *handle, Conf *conf) +{ + struct LogContext *ctx = (struct LogContext *)handle; + int reset_logging; + + if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename), + conf_get_filename(conf, CONF_logfilename)) || + conf_get_int(ctx->conf, CONF_logtype) != + conf_get_int(conf, CONF_logtype)) + reset_logging = TRUE; + else + reset_logging = FALSE; + + if (reset_logging) + logfclose(ctx); + + conf_free(ctx->conf); + ctx->conf = conf_copy(conf); + + ctx->logtype = conf_get_int(ctx->conf, CONF_logtype); + + if (reset_logging) + logfopen(ctx); +} + +/* + * translate format codes into time/date strings + * and insert them into log file name + * + * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h": "&&":& + */ +static Filename *xlatlognam(Filename *src, char *hostname, int port, + struct tm *tm) +{ + char buf[32], *bufp; + int size; + char *buffer; + int buflen, bufsize; + const char *s; + Filename *ret; + + bufsize = FILENAME_MAX; + buffer = snewn(bufsize, char); + buflen = 0; + s = filename_to_str(src); + + while (*s) { + int sanitise = FALSE; + /* Let (bufp, len) be the string to append. */ + bufp = buf; /* don't usually override this */ + if (*s == '&') { + char c; + s++; + size = 0; + if (*s) switch (c = *s++, tolower((unsigned char)c)) { + case 'y': + size = strftime(buf, sizeof(buf), "%Y", tm); + break; + case 'm': + size = strftime(buf, sizeof(buf), "%m", tm); + break; + case 'd': + size = strftime(buf, sizeof(buf), "%d", tm); + break; + case 't': + size = strftime(buf, sizeof(buf), "%H%M%S", tm); + break; + case 'h': + bufp = hostname; + size = strlen(bufp); + break; + case 'p': + size = sprintf(buf, "%d", port); + break; + default: + buf[0] = '&'; + size = 1; + if (c != '&') + buf[size++] = c; + } + /* Never allow path separators - or any other illegal + * filename character - to come out of any of these + * auto-format directives. E.g. 'hostname' can contain + * colons, if it's an IPv6 address, and colons aren't + * legal in filenames on Windows. */ + sanitise = TRUE; + } else { + buf[0] = *s++; + size = 1; + } + if (bufsize <= buflen + size) { + bufsize = (buflen + size) * 5 / 4 + 512; + buffer = sresize(buffer, bufsize, char); + } + while (size-- > 0) { + char c = *bufp++; + if (sanitise) + c = filename_char_sanitise(c); + buffer[buflen++] = c; + } + } + buffer[buflen] = '\0'; + + ret = filename_from_str(buffer); + sfree(buffer); + return ret; +} diff --git a/netbox/libs/Putty/minibidi.c b/netbox/libs/Putty/minibidi.c new file mode 100644 index 000000000..6c0621162 --- /dev/null +++ b/netbox/libs/Putty/minibidi.c @@ -0,0 +1,2023 @@ +/************************************************************************ + * + * ------------ + * Description: + * ------------ + * This is an implemention of Unicode's Bidirectional Algorithm + * (known as UAX #9). + * + * http://www.unicode.org/reports/tr9/ + * + * Author: Ahmad Khalifa + * + * (www.arabeyes.org - under MIT license) + * + ************************************************************************/ + +/* + * TODO: + * ===== + * - Explicit marks need to be handled (they are not 100% now) + * - Ligatures + */ + +#include /* definition of wchar_t*/ + +#include "misc.h" + +#define LMASK 0x3F /* Embedding Level mask */ +#define OMASK 0xC0 /* Override mask */ +#define OISL 0x80 /* Override is L */ +#define OISR 0x40 /* Override is R */ + +/* For standalone compilation in a testing mode. + * Still depends on the PuTTY headers for snewn and sfree, but can avoid + * _linking_ with any other PuTTY code. */ +#ifdef TEST_GETTYPE +#define safemalloc malloc +#define safefree free +#endif + +/* Shaping Helpers */ +#define STYPE(xh) ((((xh) >= SHAPE_FIRST) && ((xh) <= SHAPE_LAST)) ? \ +shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/ +#define SISOLATED(xh) (shapetypes[(xh)-SHAPE_FIRST].form_b) +#define SFINAL(xh) ((xh)+1) +#define SINITIAL(xh) ((xh)+2) +#define SMEDIAL(ch) ((ch)+3) + +#define leastGreaterOdd(x) ( ((x)+1) | 1 ) +#define leastGreaterEven(x) ( ((x)+2) &~ 1 ) + +typedef struct bidi_char { + unsigned int origwc, wc; + unsigned short index; +} bidi_char; + +/* function declarations */ +void flipThisRun(bidi_char *from, unsigned char* level, int max, int count); +int findIndexOfRun(unsigned char* level , int start, int count, int tlevel); +unsigned char getType(int ch); +unsigned char setOverrideBits(unsigned char level, unsigned char override); +int getPreviousLevel(unsigned char* level, int from); +int do_shape(bidi_char *line, bidi_char *to, int count); +int do_bidi(bidi_char *line, int count); +void doMirror(unsigned int *ch); + +/* character types */ +enum { + L, + LRE, + LRO, + R, + AL, + RLE, + RLO, + PDF, + EN, + ES, + ET, + AN, + CS, + NSM, + BN, + B, + S, + WS, + ON +}; + +/* Shaping Types */ +enum { + SL, /* Left-Joining, doesnt exist in U+0600 - U+06FF */ + SR, /* Right-Joining, ie has Isolated, Final */ + SD, /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ + SU, /* Non-Joining */ + SC /* Join-Causing, like U+0640 (TATWEEL) */ +}; + +typedef struct { + char type; + wchar_t form_b; +} shape_node; + +/* Kept near the actual table, for verification. */ +#define SHAPE_FIRST 0x621 +#define SHAPE_LAST (SHAPE_FIRST + lenof(shapetypes) - 1) + +const shape_node shapetypes[] = { + /* index, Typ, Iso, Ligature Index*/ + /* 621 */ {SU, 0xFE80}, + /* 622 */ {SR, 0xFE81}, + /* 623 */ {SR, 0xFE83}, + /* 624 */ {SR, 0xFE85}, + /* 625 */ {SR, 0xFE87}, + /* 626 */ {SD, 0xFE89}, + /* 627 */ {SR, 0xFE8D}, + /* 628 */ {SD, 0xFE8F}, + /* 629 */ {SR, 0xFE93}, + /* 62A */ {SD, 0xFE95}, + /* 62B */ {SD, 0xFE99}, + /* 62C */ {SD, 0xFE9D}, + /* 62D */ {SD, 0xFEA1}, + /* 62E */ {SD, 0xFEA5}, + /* 62F */ {SR, 0xFEA9}, + /* 630 */ {SR, 0xFEAB}, + /* 631 */ {SR, 0xFEAD}, + /* 632 */ {SR, 0xFEAF}, + /* 633 */ {SD, 0xFEB1}, + /* 634 */ {SD, 0xFEB5}, + /* 635 */ {SD, 0xFEB9}, + /* 636 */ {SD, 0xFEBD}, + /* 637 */ {SD, 0xFEC1}, + /* 638 */ {SD, 0xFEC5}, + /* 639 */ {SD, 0xFEC9}, + /* 63A */ {SD, 0xFECD}, + /* 63B */ {SU, 0x0}, + /* 63C */ {SU, 0x0}, + /* 63D */ {SU, 0x0}, + /* 63E */ {SU, 0x0}, + /* 63F */ {SU, 0x0}, + /* 640 */ {SC, 0x0}, + /* 641 */ {SD, 0xFED1}, + /* 642 */ {SD, 0xFED5}, + /* 643 */ {SD, 0xFED9}, + /* 644 */ {SD, 0xFEDD}, + /* 645 */ {SD, 0xFEE1}, + /* 646 */ {SD, 0xFEE5}, + /* 647 */ {SD, 0xFEE9}, + /* 648 */ {SR, 0xFEED}, + /* 649 */ {SR, 0xFEEF}, /* SD */ + /* 64A */ {SD, 0xFEF1}, + /* 64B */ {SU, 0x0}, + /* 64C */ {SU, 0x0}, + /* 64D */ {SU, 0x0}, + /* 64E */ {SU, 0x0}, + /* 64F */ {SU, 0x0}, + /* 650 */ {SU, 0x0}, + /* 651 */ {SU, 0x0}, + /* 652 */ {SU, 0x0}, + /* 653 */ {SU, 0x0}, + /* 654 */ {SU, 0x0}, + /* 655 */ {SU, 0x0}, + /* 656 */ {SU, 0x0}, + /* 657 */ {SU, 0x0}, + /* 658 */ {SU, 0x0}, + /* 659 */ {SU, 0x0}, + /* 65A */ {SU, 0x0}, + /* 65B */ {SU, 0x0}, + /* 65C */ {SU, 0x0}, + /* 65D */ {SU, 0x0}, + /* 65E */ {SU, 0x0}, + /* 65F */ {SU, 0x0}, + /* 660 */ {SU, 0x0}, + /* 661 */ {SU, 0x0}, + /* 662 */ {SU, 0x0}, + /* 663 */ {SU, 0x0}, + /* 664 */ {SU, 0x0}, + /* 665 */ {SU, 0x0}, + /* 666 */ {SU, 0x0}, + /* 667 */ {SU, 0x0}, + /* 668 */ {SU, 0x0}, + /* 669 */ {SU, 0x0}, + /* 66A */ {SU, 0x0}, + /* 66B */ {SU, 0x0}, + /* 66C */ {SU, 0x0}, + /* 66D */ {SU, 0x0}, + /* 66E */ {SU, 0x0}, + /* 66F */ {SU, 0x0}, + /* 670 */ {SU, 0x0}, + /* 671 */ {SR, 0xFB50}, + /* 672 */ {SU, 0x0}, + /* 673 */ {SU, 0x0}, + /* 674 */ {SU, 0x0}, + /* 675 */ {SU, 0x0}, + /* 676 */ {SU, 0x0}, + /* 677 */ {SU, 0x0}, + /* 678 */ {SU, 0x0}, + /* 679 */ {SD, 0xFB66}, + /* 67A */ {SD, 0xFB5E}, + /* 67B */ {SD, 0xFB52}, + /* 67C */ {SU, 0x0}, + /* 67D */ {SU, 0x0}, + /* 67E */ {SD, 0xFB56}, + /* 67F */ {SD, 0xFB62}, + /* 680 */ {SD, 0xFB5A}, + /* 681 */ {SU, 0x0}, + /* 682 */ {SU, 0x0}, + /* 683 */ {SD, 0xFB76}, + /* 684 */ {SD, 0xFB72}, + /* 685 */ {SU, 0x0}, + /* 686 */ {SD, 0xFB7A}, + /* 687 */ {SD, 0xFB7E}, + /* 688 */ {SR, 0xFB88}, + /* 689 */ {SU, 0x0}, + /* 68A */ {SU, 0x0}, + /* 68B */ {SU, 0x0}, + /* 68C */ {SR, 0xFB84}, + /* 68D */ {SR, 0xFB82}, + /* 68E */ {SR, 0xFB86}, + /* 68F */ {SU, 0x0}, + /* 690 */ {SU, 0x0}, + /* 691 */ {SR, 0xFB8C}, + /* 692 */ {SU, 0x0}, + /* 693 */ {SU, 0x0}, + /* 694 */ {SU, 0x0}, + /* 695 */ {SU, 0x0}, + /* 696 */ {SU, 0x0}, + /* 697 */ {SU, 0x0}, + /* 698 */ {SR, 0xFB8A}, + /* 699 */ {SU, 0x0}, + /* 69A */ {SU, 0x0}, + /* 69B */ {SU, 0x0}, + /* 69C */ {SU, 0x0}, + /* 69D */ {SU, 0x0}, + /* 69E */ {SU, 0x0}, + /* 69F */ {SU, 0x0}, + /* 6A0 */ {SU, 0x0}, + /* 6A1 */ {SU, 0x0}, + /* 6A2 */ {SU, 0x0}, + /* 6A3 */ {SU, 0x0}, + /* 6A4 */ {SD, 0xFB6A}, + /* 6A5 */ {SU, 0x0}, + /* 6A6 */ {SD, 0xFB6E}, + /* 6A7 */ {SU, 0x0}, + /* 6A8 */ {SU, 0x0}, + /* 6A9 */ {SD, 0xFB8E}, + /* 6AA */ {SU, 0x0}, + /* 6AB */ {SU, 0x0}, + /* 6AC */ {SU, 0x0}, + /* 6AD */ {SD, 0xFBD3}, + /* 6AE */ {SU, 0x0}, + /* 6AF */ {SD, 0xFB92}, + /* 6B0 */ {SU, 0x0}, + /* 6B1 */ {SD, 0xFB9A}, + /* 6B2 */ {SU, 0x0}, + /* 6B3 */ {SD, 0xFB96}, + /* 6B4 */ {SU, 0x0}, + /* 6B5 */ {SU, 0x0}, + /* 6B6 */ {SU, 0x0}, + /* 6B7 */ {SU, 0x0}, + /* 6B8 */ {SU, 0x0}, + /* 6B9 */ {SU, 0x0}, + /* 6BA */ {SR, 0xFB9E}, + /* 6BB */ {SD, 0xFBA0}, + /* 6BC */ {SU, 0x0}, + /* 6BD */ {SU, 0x0}, + /* 6BE */ {SD, 0xFBAA}, + /* 6BF */ {SU, 0x0}, + /* 6C0 */ {SR, 0xFBA4}, + /* 6C1 */ {SD, 0xFBA6}, + /* 6C2 */ {SU, 0x0}, + /* 6C3 */ {SU, 0x0}, + /* 6C4 */ {SU, 0x0}, + /* 6C5 */ {SR, 0xFBE0}, + /* 6C6 */ {SR, 0xFBD9}, + /* 6C7 */ {SR, 0xFBD7}, + /* 6C8 */ {SR, 0xFBDB}, + /* 6C9 */ {SR, 0xFBE2}, + /* 6CA */ {SU, 0x0}, + /* 6CB */ {SR, 0xFBDE}, + /* 6CC */ {SD, 0xFBFC}, + /* 6CD */ {SU, 0x0}, + /* 6CE */ {SU, 0x0}, + /* 6CF */ {SU, 0x0}, + /* 6D0 */ {SU, 0x0}, + /* 6D1 */ {SU, 0x0}, + /* 6D2 */ {SR, 0xFBAE}, +}; + +/* + * Flips the text buffer, according to max level, and + * all higher levels + * + * Input: + * from: text buffer, on which to apply flipping + * level: resolved levels buffer + * max: the maximum level found in this line (should be unsigned char) + * count: line size in bidi_char + */ +void flipThisRun(bidi_char *from, unsigned char *level, int max, int count) +{ + int i, j, k, tlevel; + bidi_char temp; + + j = i = 0; + while (i j; k--, j++) { + temp = from[k]; + from[k] = from[j]; + from[j] = temp; + } + } +} + +/* + * Finds the index of a run with level equals tlevel + */ +int findIndexOfRun(unsigned char* level , int start, int count, int tlevel) +{ + int i; + for (i=start; i 1) { + k = (i + j) / 2; + if (ch < lookup[k].first) + j = k; + else if (ch > lookup[k].last) + i = k; + else + return lookup[k].type; + } + + /* + * If we reach here, the character was not in any of the + * intervals listed in the lookup table. This means we return + * ON (`Other Neutrals'). This is the appropriate code for any + * character genuinely not listed in the Unicode table, and + * also the table above has deliberately left out any + * characters _explicitly_ listed as ON (to save space!). + */ + return ON; +} + +/* + * Function exported to front ends to allow them to identify + * bidi-active characters (in case, for example, the platform's + * text display function can't conveniently be prevented from doing + * its own bidi and so special treatment is required for characters + * that would cause the bidi algorithm to activate). + * + * This function is passed a single Unicode code point, and returns + * nonzero if the presence of this code point can possibly cause + * the bidi algorithm to do any reordering. Thus, any string + * composed entirely of characters for which is_rtl() returns zero + * should be safe to pass to a bidi-active platform display + * function without fear. + * + * (is_rtl() must therefore also return true for any character + * which would be affected by Arabic shaping, but this isn't + * important because all such characters are right-to-left so it + * would have flagged them anyway.) + */ +int is_rtl(int c) +{ + /* + * After careful reading of the Unicode bidi algorithm (URL as + * given at the top of this file) I believe that the only + * character classes which can possibly cause trouble are R, + * AL, RLE and RLO. I think that any string containing no + * character in any of those classes will be displayed + * uniformly left-to-right by the Unicode bidi algorithm. + */ + const int mask = (1< 0) { + unsigned char current = level[--from]; + + while (from >= 0 && level[from] == current) + from--; + + if (from >= 0) + return level[from]; + + return -1; + } else + return -1; +} + +/* The Main shaping function, and the only one to be used + * by the outside world. + * + * line: buffer to apply shaping to. this must be passed by doBidi() first + * to: output buffer for the shaped data + * count: number of characters in line + */ +int do_shape(bidi_char *line, bidi_char *to, int count) +{ + int i, tempShape, ligFlag; + + for (ligFlag=i=0; i 0) switch (line[i-1].wc) { + case 0x622: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF6; + else + to[i].wc = 0xFEF5; + break; + case 0x623: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF8; + else + to[i].wc = 0xFEF7; + break; + case 0x625: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEFA; + else + to[i].wc = 0xFEF9; + break; + case 0x627: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEFC; + else + to[i].wc = 0xFEFB; + break; + } + if (ligFlag) { + to[i-1].wc = 0x20; + ligFlag = 0; + break; + } + } + + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) { + tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU); + if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = SMEDIAL((SISOLATED(line[i].wc))); + else + to[i].wc = SFINAL((SISOLATED(line[i].wc))); + break; + } + + tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU); + if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = SINITIAL((SISOLATED(line[i].wc))); + else + to[i].wc = SISOLATED(line[i].wc); + break; + + + } + } + return 1; +} + +/* + * The Main Bidi Function, and the only function that should + * be used by the outside world. + * + * line: a buffer of size count containing text to apply + * the Bidirectional algorithm to. + */ + +int do_bidi(bidi_char *line, int count) +{ + unsigned char* types; + unsigned char* levels; + unsigned char paragraphLevel; + unsigned char currentEmbedding; + unsigned char currentOverride; + unsigned char tempType; + int i, j, yes, bover; + + /* Check the presence of R or AL types as optimization */ + yes = 0; + for (i=0; i= 0) { + if (types[j] == AL) { + types[i] = AN; + break; + } else if (types[j] == R || types[j] == L) { + break; + } + j--; + } + } + } + + /* Rule (W3) + * W3. Change all ALs to R. + * + * Optimization: on Rule Xn, we might set a flag on AL type + * to prevent this loop in L R lines only... + */ + for (i=0; i 0 && types[i-1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == ET) { + j=i; + while (j = 0) { + if (types[j] == L) { + types[i] = L; + break; + } else if (types[j] == R || types[j] == AL) { + break; + } + j--; + } + } + } + + /* Rule (N1) + * N1. A sequence of neutrals takes the direction of the surrounding + * strong text if the text on both sides has the same direction. European + * and Arabic numbers are treated as though they were R. + */ + if (count >= 2 && types[0] == ON) { + if ((types[1] == R) || (types[1] == EN) || (types[1] == AN)) + types[0] = R; + else if (types[1] == L) + types[0] = L; + } + for (i=1; i<(count-1); i++) { + if (types[i] == ON) { + if (types[i-1] == L) { + j=i; + while (j<(count-1) && types[j] == ON) { + j++; + } + if (types[j] == L) { + while (i= 2 && types[count-1] == ON) { + if (types[count-2] == R || types[count-2] == EN || types[count-2] == AN) + types[count-1] = R; + else if (types[count-2] == L) + types[count-1] = L; + } + + /* Rule (N2) + * N2. Any remaining neutrals take the embedding direction. + */ + for (i=0; i0 && (getType(line[j].wc) == WS)) { + j--; + } + if (j < (count-1)) { + for (j++; j=i ; j--) { + levels[j] = paragraphLevel; + } + } + } else if (tempType == B || tempType == S) { + levels[i] = paragraphLevel; + } + } + + /* Rule (L4) NOT IMPLEMENTED + * L4. A character that possesses the mirrored property as specified by + * Section 4.7, Mirrored, must be depicted by a mirrored glyph if the + * resolved directionality of that character is R. + */ + /* Note: this is implemented before L2 for efficiency */ + for (i=0; i tempType) + tempType = levels[i]; + i++; + } + /* maximum level in tempType. */ + while (tempType > 0) { /* loop from highest level to the least odd, */ + /* which i assume is 1 */ + flipThisRun(line, levels, tempType, count); + tempType--; + } + + /* Rule (L3) NOT IMPLEMENTED + * L3. Combining marks applied to a right-to-left base character will at + * this point precede their base character. If the rendering engine + * expects them to follow the base characters in the final display + * process, then the ordering of the marks and the base character must + * be reversed. + */ + sfree(types); + sfree(levels); + return R; +} + + +/* + * Bad, Horrible function + * takes a pointer to a character that is checked for + * having a mirror glyph. + */ +void doMirror(unsigned int *ch) +{ + if ((*ch & 0xFF00) == 0) { + switch (*ch) { + case 0x0028: *ch = 0x0029; break; + case 0x0029: *ch = 0x0028; break; + case 0x003C: *ch = 0x003E; break; + case 0x003E: *ch = 0x003C; break; + case 0x005B: *ch = 0x005D; break; + case 0x005D: *ch = 0x005B; break; + case 0x007B: *ch = 0x007D; break; + case 0x007D: *ch = 0x007B; break; + case 0x00AB: *ch = 0x00BB; break; + case 0x00BB: *ch = 0x00AB; break; + } + } else if ((*ch & 0xFF00) == 0x2000) { + switch (*ch) { + case 0x2039: *ch = 0x203A; break; + case 0x203A: *ch = 0x2039; break; + case 0x2045: *ch = 0x2046; break; + case 0x2046: *ch = 0x2045; break; + case 0x207D: *ch = 0x207E; break; + case 0x207E: *ch = 0x207D; break; + case 0x208D: *ch = 0x208E; break; + case 0x208E: *ch = 0x208D; break; + } + } else if ((*ch & 0xFF00) == 0x2200) { + switch (*ch) { + case 0x2208: *ch = 0x220B; break; + case 0x2209: *ch = 0x220C; break; + case 0x220A: *ch = 0x220D; break; + case 0x220B: *ch = 0x2208; break; + case 0x220C: *ch = 0x2209; break; + case 0x220D: *ch = 0x220A; break; + case 0x2215: *ch = 0x29F5; break; + case 0x223C: *ch = 0x223D; break; + case 0x223D: *ch = 0x223C; break; + case 0x2243: *ch = 0x22CD; break; + case 0x2252: *ch = 0x2253; break; + case 0x2253: *ch = 0x2252; break; + case 0x2254: *ch = 0x2255; break; + case 0x2255: *ch = 0x2254; break; + case 0x2264: *ch = 0x2265; break; + case 0x2265: *ch = 0x2264; break; + case 0x2266: *ch = 0x2267; break; + case 0x2267: *ch = 0x2266; break; + case 0x2268: *ch = 0x2269; break; + case 0x2269: *ch = 0x2268; break; + case 0x226A: *ch = 0x226B; break; + case 0x226B: *ch = 0x226A; break; + case 0x226E: *ch = 0x226F; break; + case 0x226F: *ch = 0x226E; break; + case 0x2270: *ch = 0x2271; break; + case 0x2271: *ch = 0x2270; break; + case 0x2272: *ch = 0x2273; break; + case 0x2273: *ch = 0x2272; break; + case 0x2274: *ch = 0x2275; break; + case 0x2275: *ch = 0x2274; break; + case 0x2276: *ch = 0x2277; break; + case 0x2277: *ch = 0x2276; break; + case 0x2278: *ch = 0x2279; break; + case 0x2279: *ch = 0x2278; break; + case 0x227A: *ch = 0x227B; break; + case 0x227B: *ch = 0x227A; break; + case 0x227C: *ch = 0x227D; break; + case 0x227D: *ch = 0x227C; break; + case 0x227E: *ch = 0x227F; break; + case 0x227F: *ch = 0x227E; break; + case 0x2280: *ch = 0x2281; break; + case 0x2281: *ch = 0x2280; break; + case 0x2282: *ch = 0x2283; break; + case 0x2283: *ch = 0x2282; break; + case 0x2284: *ch = 0x2285; break; + case 0x2285: *ch = 0x2284; break; + case 0x2286: *ch = 0x2287; break; + case 0x2287: *ch = 0x2286; break; + case 0x2288: *ch = 0x2289; break; + case 0x2289: *ch = 0x2288; break; + case 0x228A: *ch = 0x228B; break; + case 0x228B: *ch = 0x228A; break; + case 0x228F: *ch = 0x2290; break; + case 0x2290: *ch = 0x228F; break; + case 0x2291: *ch = 0x2292; break; + case 0x2292: *ch = 0x2291; break; + case 0x2298: *ch = 0x29B8; break; + case 0x22A2: *ch = 0x22A3; break; + case 0x22A3: *ch = 0x22A2; break; + case 0x22A6: *ch = 0x2ADE; break; + case 0x22A8: *ch = 0x2AE4; break; + case 0x22A9: *ch = 0x2AE3; break; + case 0x22AB: *ch = 0x2AE5; break; + case 0x22B0: *ch = 0x22B1; break; + case 0x22B1: *ch = 0x22B0; break; + case 0x22B2: *ch = 0x22B3; break; + case 0x22B3: *ch = 0x22B2; break; + case 0x22B4: *ch = 0x22B5; break; + case 0x22B5: *ch = 0x22B4; break; + case 0x22B6: *ch = 0x22B7; break; + case 0x22B7: *ch = 0x22B6; break; + case 0x22C9: *ch = 0x22CA; break; + case 0x22CA: *ch = 0x22C9; break; + case 0x22CB: *ch = 0x22CC; break; + case 0x22CC: *ch = 0x22CB; break; + case 0x22CD: *ch = 0x2243; break; + case 0x22D0: *ch = 0x22D1; break; + case 0x22D1: *ch = 0x22D0; break; + case 0x22D6: *ch = 0x22D7; break; + case 0x22D7: *ch = 0x22D6; break; + case 0x22D8: *ch = 0x22D9; break; + case 0x22D9: *ch = 0x22D8; break; + case 0x22DA: *ch = 0x22DB; break; + case 0x22DB: *ch = 0x22DA; break; + case 0x22DC: *ch = 0x22DD; break; + case 0x22DD: *ch = 0x22DC; break; + case 0x22DE: *ch = 0x22DF; break; + case 0x22DF: *ch = 0x22DE; break; + case 0x22E0: *ch = 0x22E1; break; + case 0x22E1: *ch = 0x22E0; break; + case 0x22E2: *ch = 0x22E3; break; + case 0x22E3: *ch = 0x22E2; break; + case 0x22E4: *ch = 0x22E5; break; + case 0x22E5: *ch = 0x22E4; break; + case 0x22E6: *ch = 0x22E7; break; + case 0x22E7: *ch = 0x22E6; break; + case 0x22E8: *ch = 0x22E9; break; + case 0x22E9: *ch = 0x22E8; break; + case 0x22EA: *ch = 0x22EB; break; + case 0x22EB: *ch = 0x22EA; break; + case 0x22EC: *ch = 0x22ED; break; + case 0x22ED: *ch = 0x22EC; break; + case 0x22F0: *ch = 0x22F1; break; + case 0x22F1: *ch = 0x22F0; break; + case 0x22F2: *ch = 0x22FA; break; + case 0x22F3: *ch = 0x22FB; break; + case 0x22F4: *ch = 0x22FC; break; + case 0x22F6: *ch = 0x22FD; break; + case 0x22F7: *ch = 0x22FE; break; + case 0x22FA: *ch = 0x22F2; break; + case 0x22FB: *ch = 0x22F3; break; + case 0x22FC: *ch = 0x22F4; break; + case 0x22FD: *ch = 0x22F6; break; + case 0x22FE: *ch = 0x22F7; break; + } + } else if ((*ch & 0xFF00) == 0x2300) { + switch (*ch) { + case 0x2308: *ch = 0x2309; break; + case 0x2309: *ch = 0x2308; break; + case 0x230A: *ch = 0x230B; break; + case 0x230B: *ch = 0x230A; break; + case 0x2329: *ch = 0x232A; break; + case 0x232A: *ch = 0x2329; break; + } + } else if ((*ch & 0xFF00) == 0x2700) { + switch (*ch) { + case 0x2768: *ch = 0x2769; break; + case 0x2769: *ch = 0x2768; break; + case 0x276A: *ch = 0x276B; break; + case 0x276B: *ch = 0x276A; break; + case 0x276C: *ch = 0x276D; break; + case 0x276D: *ch = 0x276C; break; + case 0x276E: *ch = 0x276F; break; + case 0x276F: *ch = 0x276E; break; + case 0x2770: *ch = 0x2771; break; + case 0x2771: *ch = 0x2770; break; + case 0x2772: *ch = 0x2773; break; + case 0x2773: *ch = 0x2772; break; + case 0x2774: *ch = 0x2775; break; + case 0x2775: *ch = 0x2774; break; + case 0x27D5: *ch = 0x27D6; break; + case 0x27D6: *ch = 0x27D5; break; + case 0x27DD: *ch = 0x27DE; break; + case 0x27DE: *ch = 0x27DD; break; + case 0x27E2: *ch = 0x27E3; break; + case 0x27E3: *ch = 0x27E2; break; + case 0x27E4: *ch = 0x27E5; break; + case 0x27E5: *ch = 0x27E4; break; + case 0x27E6: *ch = 0x27E7; break; + case 0x27E7: *ch = 0x27E6; break; + case 0x27E8: *ch = 0x27E9; break; + case 0x27E9: *ch = 0x27E8; break; + case 0x27EA: *ch = 0x27EB; break; + case 0x27EB: *ch = 0x27EA; break; + } + } else if ((*ch & 0xFF00) == 0x2900) { + switch (*ch) { + case 0x2983: *ch = 0x2984; break; + case 0x2984: *ch = 0x2983; break; + case 0x2985: *ch = 0x2986; break; + case 0x2986: *ch = 0x2985; break; + case 0x2987: *ch = 0x2988; break; + case 0x2988: *ch = 0x2987; break; + case 0x2989: *ch = 0x298A; break; + case 0x298A: *ch = 0x2989; break; + case 0x298B: *ch = 0x298C; break; + case 0x298C: *ch = 0x298B; break; + case 0x298D: *ch = 0x2990; break; + case 0x298E: *ch = 0x298F; break; + case 0x298F: *ch = 0x298E; break; + case 0x2990: *ch = 0x298D; break; + case 0x2991: *ch = 0x2992; break; + case 0x2992: *ch = 0x2991; break; + case 0x2993: *ch = 0x2994; break; + case 0x2994: *ch = 0x2993; break; + case 0x2995: *ch = 0x2996; break; + case 0x2996: *ch = 0x2995; break; + case 0x2997: *ch = 0x2998; break; + case 0x2998: *ch = 0x2997; break; + case 0x29B8: *ch = 0x2298; break; + case 0x29C0: *ch = 0x29C1; break; + case 0x29C1: *ch = 0x29C0; break; + case 0x29C4: *ch = 0x29C5; break; + case 0x29C5: *ch = 0x29C4; break; + case 0x29CF: *ch = 0x29D0; break; + case 0x29D0: *ch = 0x29CF; break; + case 0x29D1: *ch = 0x29D2; break; + case 0x29D2: *ch = 0x29D1; break; + case 0x29D4: *ch = 0x29D5; break; + case 0x29D5: *ch = 0x29D4; break; + case 0x29D8: *ch = 0x29D9; break; + case 0x29D9: *ch = 0x29D8; break; + case 0x29DA: *ch = 0x29DB; break; + case 0x29DB: *ch = 0x29DA; break; + case 0x29F5: *ch = 0x2215; break; + case 0x29F8: *ch = 0x29F9; break; + case 0x29F9: *ch = 0x29F8; break; + case 0x29FC: *ch = 0x29FD; break; + case 0x29FD: *ch = 0x29FC; break; + } + } else if ((*ch & 0xFF00) == 0x2A00) { + switch (*ch) { + case 0x2A2B: *ch = 0x2A2C; break; + case 0x2A2C: *ch = 0x2A2B; break; + case 0x2A2D: *ch = 0x2A2C; break; + case 0x2A2E: *ch = 0x2A2D; break; + case 0x2A34: *ch = 0x2A35; break; + case 0x2A35: *ch = 0x2A34; break; + case 0x2A3C: *ch = 0x2A3D; break; + case 0x2A3D: *ch = 0x2A3C; break; + case 0x2A64: *ch = 0x2A65; break; + case 0x2A65: *ch = 0x2A64; break; + case 0x2A79: *ch = 0x2A7A; break; + case 0x2A7A: *ch = 0x2A79; break; + case 0x2A7D: *ch = 0x2A7E; break; + case 0x2A7E: *ch = 0x2A7D; break; + case 0x2A7F: *ch = 0x2A80; break; + case 0x2A80: *ch = 0x2A7F; break; + case 0x2A81: *ch = 0x2A82; break; + case 0x2A82: *ch = 0x2A81; break; + case 0x2A83: *ch = 0x2A84; break; + case 0x2A84: *ch = 0x2A83; break; + case 0x2A8B: *ch = 0x2A8C; break; + case 0x2A8C: *ch = 0x2A8B; break; + case 0x2A91: *ch = 0x2A92; break; + case 0x2A92: *ch = 0x2A91; break; + case 0x2A93: *ch = 0x2A94; break; + case 0x2A94: *ch = 0x2A93; break; + case 0x2A95: *ch = 0x2A96; break; + case 0x2A96: *ch = 0x2A95; break; + case 0x2A97: *ch = 0x2A98; break; + case 0x2A98: *ch = 0x2A97; break; + case 0x2A99: *ch = 0x2A9A; break; + case 0x2A9A: *ch = 0x2A99; break; + case 0x2A9B: *ch = 0x2A9C; break; + case 0x2A9C: *ch = 0x2A9B; break; + case 0x2AA1: *ch = 0x2AA2; break; + case 0x2AA2: *ch = 0x2AA1; break; + case 0x2AA6: *ch = 0x2AA7; break; + case 0x2AA7: *ch = 0x2AA6; break; + case 0x2AA8: *ch = 0x2AA9; break; + case 0x2AA9: *ch = 0x2AA8; break; + case 0x2AAA: *ch = 0x2AAB; break; + case 0x2AAB: *ch = 0x2AAA; break; + case 0x2AAC: *ch = 0x2AAD; break; + case 0x2AAD: *ch = 0x2AAC; break; + case 0x2AAF: *ch = 0x2AB0; break; + case 0x2AB0: *ch = 0x2AAF; break; + case 0x2AB3: *ch = 0x2AB4; break; + case 0x2AB4: *ch = 0x2AB3; break; + case 0x2ABB: *ch = 0x2ABC; break; + case 0x2ABC: *ch = 0x2ABB; break; + case 0x2ABD: *ch = 0x2ABE; break; + case 0x2ABE: *ch = 0x2ABD; break; + case 0x2ABF: *ch = 0x2AC0; break; + case 0x2AC0: *ch = 0x2ABF; break; + case 0x2AC1: *ch = 0x2AC2; break; + case 0x2AC2: *ch = 0x2AC1; break; + case 0x2AC3: *ch = 0x2AC4; break; + case 0x2AC4: *ch = 0x2AC3; break; + case 0x2AC5: *ch = 0x2AC6; break; + case 0x2AC6: *ch = 0x2AC5; break; + case 0x2ACD: *ch = 0x2ACE; break; + case 0x2ACE: *ch = 0x2ACD; break; + case 0x2ACF: *ch = 0x2AD0; break; + case 0x2AD0: *ch = 0x2ACF; break; + case 0x2AD1: *ch = 0x2AD2; break; + case 0x2AD2: *ch = 0x2AD1; break; + case 0x2AD3: *ch = 0x2AD4; break; + case 0x2AD4: *ch = 0x2AD3; break; + case 0x2AD5: *ch = 0x2AD6; break; + case 0x2AD6: *ch = 0x2AD5; break; + case 0x2ADE: *ch = 0x22A6; break; + case 0x2AE3: *ch = 0x22A9; break; + case 0x2AE4: *ch = 0x22A8; break; + case 0x2AE5: *ch = 0x22AB; break; + case 0x2AEC: *ch = 0x2AED; break; + case 0x2AED: *ch = 0x2AEC; break; + case 0x2AF7: *ch = 0x2AF8; break; + case 0x2AF8: *ch = 0x2AF7; break; + case 0x2AF9: *ch = 0x2AFA; break; + case 0x2AFA: *ch = 0x2AF9; break; + } + } else if ((*ch & 0xFF00) == 0x3000) { + switch (*ch) { + case 0x3008: *ch = 0x3009; break; + case 0x3009: *ch = 0x3008; break; + case 0x300A: *ch = 0x300B; break; + case 0x300B: *ch = 0x300A; break; + case 0x300C: *ch = 0x300D; break; + case 0x300D: *ch = 0x300C; break; + case 0x300E: *ch = 0x300F; break; + case 0x300F: *ch = 0x300E; break; + case 0x3010: *ch = 0x3011; break; + case 0x3011: *ch = 0x3010; break; + case 0x3014: *ch = 0x3015; break; + case 0x3015: *ch = 0x3014; break; + case 0x3016: *ch = 0x3017; break; + case 0x3017: *ch = 0x3016; break; + case 0x3018: *ch = 0x3019; break; + case 0x3019: *ch = 0x3018; break; + case 0x301A: *ch = 0x301B; break; + case 0x301B: *ch = 0x301A; break; + } + } else if ((*ch & 0xFF00) == 0xFF00) { + switch (*ch) { + case 0xFF08: *ch = 0xFF09; break; + case 0xFF09: *ch = 0xFF08; break; + case 0xFF1C: *ch = 0xFF1E; break; + case 0xFF1E: *ch = 0xFF1C; break; + case 0xFF3B: *ch = 0xFF3D; break; + case 0xFF3D: *ch = 0xFF3B; break; + case 0xFF5B: *ch = 0xFF5D; break; + case 0xFF5D: *ch = 0xFF5B; break; + case 0xFF5F: *ch = 0xFF60; break; + case 0xFF60: *ch = 0xFF5F; break; + case 0xFF62: *ch = 0xFF63; break; + case 0xFF63: *ch = 0xFF62; break; + } + } +} + +#ifdef TEST_GETTYPE + +#include +#include + +int main(int argc, char **argv) +{ + static const struct { int type; char *name; } typetoname[] = { +#define TYPETONAME(X) { X , #X } + TYPETONAME(L), + TYPETONAME(LRE), + TYPETONAME(LRO), + TYPETONAME(R), + TYPETONAME(AL), + TYPETONAME(RLE), + TYPETONAME(RLO), + TYPETONAME(PDF), + TYPETONAME(EN), + TYPETONAME(ES), + TYPETONAME(ET), + TYPETONAME(AN), + TYPETONAME(CS), + TYPETONAME(NSM), + TYPETONAME(BN), + TYPETONAME(B), + TYPETONAME(S), + TYPETONAME(WS), + TYPETONAME(ON), +#undef TYPETONAME + }; + int i; + + for (i = 1; i < argc; i++) { + unsigned long chr = strtoul(argv[i], NULL, 0); + int type = getType(chr); + assert(typetoname[type].type == type); + printf("U+%04x: %s\n", chr, typetoname[type].name); + } + + return 0; +} + +#endif diff --git a/netbox/libs/Putty/misc.c b/netbox/libs/Putty/misc.c new file mode 100644 index 000000000..a8fb24fab --- /dev/null +++ b/netbox/libs/Putty/misc.c @@ -0,0 +1,1091 @@ +/* + * Platform-independent routines shared between all PuTTY programs. + */ + +#include +#include +#include +#include +#include +#include +#include "putty.h" + +/* + * Parse a string block size specification. This is approximately a + * subset of the block size specs supported by GNU fileutils: + * "nk" = n kilobytes + * "nM" = n megabytes + * "nG" = n gigabytes + * All numbers are decimal, and suffixes refer to powers of two. + * Case-insensitive. + */ +uint64_t parse_blocksize(const char *bs) +{ + char *suf; + uint64_t r = strtoul(bs, &suf, 10); + if (*suf != '\0') { + while (*suf && isspace((unsigned char)*suf)) suf++; + switch (*suf) { + case 'k': case 'K': + r *= 1024ul; + break; + case 'm': case 'M': + r *= 1024ul * 1024ul; + break; + case 'g': case 'G': + r *= 1024ul * 1024ul * 1024ul; + break; + case '\0': + default: + break; + } + } + return r; +} + +/* + * Parse a ^C style character specification. + * Returns NULL in `next' if we didn't recognise it as a control character, + * in which case `c' should be ignored. + * The precise current parsing is an oddity inherited from the terminal + * answerback-string parsing code. All sequences start with ^; all except + * ^<123> are two characters. The ones that are worth keeping are probably: + * ^? 127 + * ^@A-Z[\]^_ 0-31 + * a-z 1-26 + * specified by number (decimal, 0octal, 0xHEX) + * ~ ^ escape + */ +char ctrlparse(char *s, char **next) +{ + char c = 0; + if (*s != '^') { + *next = NULL; + } else { + s++; + if (*s == '\0') { + *next = NULL; + } else if (*s == '<') { + s++; + c = (char)strtol(s, next, 0); + if ((*next == s) || (**next != '>')) { + c = 0; + *next = NULL; + } else + (*next)++; + } else if (*s >= 'a' && *s <= 'z') { + c = (*s - ('a' - 1)); + *next = s+1; + } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) { + c = ('@' ^ *s); + *next = s+1; + } else if (*s == '~') { + c = '^'; + *next = s+1; + } + } + return c; +} + +/* + * Find a character in a string, unless it's a colon contained within + * square brackets. Used for untangling strings of the form + * 'host:port', where host can be an IPv6 literal. + * + * We provide several variants of this function, with semantics like + * various standard string.h functions. + */ +static const char *host_strchr_internal(const char *s, const char *set, + int first) +{ + int brackets = 0; + const char *ret = NULL; + + while (1) { + if (!*s) + return ret; + + if (*s == '[') + brackets++; + else if (*s == ']' && brackets > 0) + brackets--; + else if (brackets && *s == ':') + /* never match */ ; + else if (strchr(set, *s)) { + ret = s; + if (first) + return ret; + } + + s++; + } +} +size_t host_strcspn(const char *s, const char *set) +{ + const char *answer = host_strchr_internal(s, set, TRUE); + if (answer) + return answer - s; + else + return strlen(s); +} +char *host_strchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, TRUE); +} +char *host_strrchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, FALSE); +} + +#ifdef TEST_HOST_STRFOO +int main(void) +{ + int passes = 0, fails = 0; + +#define TEST1(func, string, arg2, suffix, result) do \ + { \ + const char *str = string; \ + unsigned ret = func(string, arg2) suffix; \ + if (ret == result) { \ + passes++; \ + } else { \ + printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ + #func, #string, #arg2, #suffix, ret, result); \ + fails++; \ + } \ +} while (0) + + TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7); + TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9); + TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7); + TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strcspn, "[1:2:3]", "/:",, 7); + TEST1(host_strcspn, "[1:2/3]", "/:",, 4); + TEST1(host_strcspn, "[1:2:3]/", "/:",, 7); + + printf("passed %d failed %d total %d\n", passes, fails, passes+fails); + return fails != 0 ? 1 : 0; +} +/* Stubs to stop the rest of this module causing compile failures. */ +void modalfatalbox(char *fmt, ...) {} +int conf_get_int(Conf *conf, int primary) { return 0; } +char *conf_get_str(Conf *conf, int primary) { return NULL; } +#endif /* TEST_HOST_STRFOO */ + +/* + * Trim square brackets off the outside of an IPv6 address literal. + * Leave all other strings unchanged. Returns a fresh dynamically + * allocated string. + */ +char *host_strduptrim(const char *s) +{ + if (s[0] == '[') { + const char *p = s+1; + int colons = 0; + while (*p && *p != ']') { + if (isxdigit((unsigned char)*p)) + /* OK */; + else if (*p == ':') + colons++; + else + break; + p++; + } + if (*p == ']' && !p[1] && colons > 1) { + /* + * This looks like an IPv6 address literal (hex digits and + * at least two colons, contained in square brackets). + * Trim off the brackets. + */ + return dupprintf("%.*s", (int)(p - (s+1)), s+1); + } + } + + /* + * Any other shape of string is simply duplicated. + */ + return dupstr(s); +} + +prompts_t *new_prompts(void *frontend) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = 0; + p->frontend = frontend; + p->data = NULL; + p->to_server = TRUE; /* to be on the safe side */ + p->name = p->instruction = NULL; + p->name_reqd = p->instr_reqd = FALSE; + return p; +} +void add_prompt(prompts_t *p, char *promptstr, int echo) +{ + prompt_t *pr = snew(prompt_t); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = NULL; + pr->resultsize = 0; + p->n_prompts++; + p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); + p->prompts[p->n_prompts-1] = pr; +} +void prompt_ensure_result_size(prompt_t *pr, int newlen) +{ + if ((int)pr->resultsize < newlen) { + char *newbuf; + newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */ + + /* + * We don't use sresize / realloc here, because we will be + * storing sensitive stuff like passwords in here, and we want + * to make sure that the data doesn't get copied around in + * memory without the old copy being destroyed. + */ + newbuf = snewn(newlen, char); + memcpy(newbuf, pr->result, pr->resultsize); + smemclr(pr->result, pr->resultsize); + sfree(pr->result); + pr->result = newbuf; + pr->resultsize = newlen; + } +} +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + prompt_ensure_result_size(pr, strlen(newstr) + 1); + strcpy(pr->result, newstr); +} +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + smemclr(pr->result, pr->resultsize); /* burn the evidence */ + sfree(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} + +/* ---------------------------------------------------------------------- + * String handling routines. + */ + +char *dupstr(const char *s) +{ + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } + return p; +} + +/* Allocate the concatenation of N strings. Terminate arg list with NULL. */ +char *dupcat(const char *s1, ...) +{ + int len; + char *p, *q, *sn; + va_list ap; + + len = strlen(s1); + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + len += strlen(sn); + } + va_end(ap); + + p = snewn(len + 1, char); + strcpy(p, s1); + q = p + strlen(p); + + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + strcpy(q, sn); + q += strlen(q); + } + va_end(ap); + + return p; +} + +void burnstr(char *string) /* sfree(str), only clear it first */ +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} + +int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value'! + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} + +/* + * Do an sprintf(), but into a custom-allocated buffer. + * + * Currently I'm doing this via vsnprintf. This has worked so far, + * but it's not good, because vsnprintf is not available on all + * platforms. There's an ifdef to use `_vsnprintf', which seems + * to be the local name for it on Windows. Other platforms may + * lack it completely, in which case it'll be time to rewrite + * this function in a totally different way. + * + * The only `properly' portable solution I can think of is to + * implement my own format string scanner, which figures out an + * upper bound for the length of each formatting directive, + * allocates the buffer as it goes along, and calls sprintf() to + * actually process each directive. If I ever need to actually do + * this, some caveats: + * + * - It's very hard to find a reliable upper bound for + * floating-point values. %f, in particular, when supplied with + * a number near to the upper or lower limit of representable + * numbers, could easily take several hundred characters. It's + * probably feasible to predict this statically using the + * constants in , or even to predict it dynamically by + * looking at the exponent of the specific float provided, but + * it won't be fun. + * + * - Don't forget to _check_, after calling sprintf, that it's + * used at most the amount of space we had available. + * + * - Fault any formatting directive we don't fully understand. The + * aim here is to _guarantee_ that we never overflow the buffer, + * because this is a security-critical function. If we see a + * directive we don't know about, we should panic and die rather + * than run any risk. + */ +char *dupprintf(const char *fmt, ...) +{ + char *ret; + va_list ap; + va_start(ap, fmt); + ret = dupvprintf(fmt, ap); + va_end(ap); + return ret; +} +char *dupvprintf(const char *fmt, va_list ap) +{ + char *buf; + int len, size; + + buf = snewn(512, char); + size = 512; + + while (1) { +#ifdef _WINDOWS +#define vsnprintf _vsnprintf +#endif +#ifdef va_copy + /* Use the `va_copy' macro mandated by C99, if present. + * XXX some environments may have this as __va_copy() */ + va_list aq; + va_copy(aq, ap); + len = vsnprintf(buf, size, fmt, aq); + va_end(aq); +#else + /* Ugh. No va_copy macro, so do something nasty. + * Technically, you can't reuse a va_list like this: it is left + * unspecified whether advancing a va_list pointer modifies its + * value or something it points to, so on some platforms calling + * vsnprintf twice on the same va_list might fail hideously + * (indeed, it has been observed to). + * XXX the autoconf manual suggests that using memcpy() will give + * "maximum portability". */ + len = vsnprintf(buf, size, fmt, ap); +#endif + if (len >= 0 && len < size) { + /* This is the C99-specified criterion for snprintf to have + * been completely successful. */ + return buf; + } else if (len > 0) { + /* This is the C99 error condition: the returned length is + * the required buffer size not counting the NUL. */ + size = len + 1; + } else { + /* This is the pre-C99 glibc error condition: <0 means the + * buffer wasn't big enough, so we enlarge it a bit and hope. */ + size += 512; + } + buf = sresize(buf, size, char); + } +} + +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + int size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (len > 0 && ret[len-1] == '\n') + break; /* got a newline, we're done */ + size = len + 512; + ret = sresize(ret, size, char); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} + +/* ---------------------------------------------------------------------- + * Core base64 encoding and decoding routines. + */ + +void base64_encode_atom(unsigned char *data, int n, char *out) +{ + static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + unsigned word; + + word = data[0] << 16; + if (n > 1) + word |= data[1] << 8; + if (n > 2) + word |= data[2]; + out[0] = base64_chars[(word >> 18) & 0x3F]; + out[1] = base64_chars[(word >> 12) & 0x3F]; + if (n > 1) + out[2] = base64_chars[(word >> 6) & 0x3F]; + else + out[2] = '='; + if (n > 2) + out[3] = base64_chars[word & 0x3F]; + else + out[3] = '='; +} + +int base64_decode_atom(char *atom, unsigned char *out) +{ + int vals[4]; + int i, v, len; + unsigned word; + char c; + + for (i = 0; i < 4; i++) { + c = atom[i]; + if (c >= 'A' && c <= 'Z') + v = c - 'A'; + else if (c >= 'a' && c <= 'z') + v = c - 'a' + 26; + else if (c >= '0' && c <= '9') + v = c - '0' + 52; + else if (c == '+') + v = 62; + else if (c == '/') + v = 63; + else if (c == '=') + v = -1; + else + return 0; /* invalid atom */ + vals[i] = v; + } + + if (vals[0] == -1 || vals[1] == -1) + return 0; + if (vals[2] == -1 && vals[3] != -1) + return 0; + + if (vals[3] != -1) + len = 3; + else if (vals[2] != -1) + len = 2; + else + len = 1; + + word = ((vals[0] << 18) | + (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F)); + out[0] = (word >> 16) & 0xFF; + if (len > 1) + out[1] = (word >> 8) & 0xFF; + if (len > 2) + out[2] = word & 0xFF; + return len; +} + +/* ---------------------------------------------------------------------- + * Generic routines to deal with send buffers: a linked list of + * smallish blocks, with the operations + * + * - add an arbitrary amount of data to the end of the list + * - remove the first N bytes from the list + * - return a (pointer,length) pair giving some initial data in + * the list, suitable for passing to a send or write system + * call + * - retrieve a larger amount of initial data from the list + * - return the current size of the buffer chain in bytes + */ + +#define BUFFER_MIN_GRANULE 512 + +struct bufchain_granule { + struct bufchain_granule *next; + char *bufpos, *bufend, *bufmax; +}; + +void bufchain_init(bufchain *ch) +{ + ch->head = ch->tail = NULL; + ch->buffersize = 0; +} + +void bufchain_clear(bufchain *ch) +{ + struct bufchain_granule *b; + while (ch->head) { + b = ch->head; + ch->head = ch->head->next; + sfree(b); + } + ch->tail = NULL; + ch->buffersize = 0; +} + +int bufchain_size(bufchain *ch) +{ + return ch->buffersize; +} + +void bufchain_add(bufchain *ch, const void *data, int len) +{ + const char *buf = (const char *)data; + + if (len == 0) return; + + ch->buffersize += len; + + while (len > 0) { + if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { + int copylen = min(len, ch->tail->bufmax - ch->tail->bufend); + memcpy(ch->tail->bufend, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->bufend += copylen; + } + if (len > 0) { + int grainlen = + max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); + struct bufchain_granule *newbuf; + newbuf = smalloc(grainlen); + newbuf->bufpos = newbuf->bufend = + (char *)newbuf + sizeof(struct bufchain_granule); + newbuf->bufmax = (char *)newbuf + grainlen; + newbuf->next = NULL; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = newbuf; + ch->tail = newbuf; + } + } +} + +void bufchain_consume(bufchain *ch, int len) +{ + struct bufchain_granule *tmp; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + assert(ch->head != NULL); + if (remlen >= ch->head->bufend - ch->head->bufpos) { + remlen = ch->head->bufend - ch->head->bufpos; + tmp = ch->head; + ch->head = tmp->next; + if (!ch->head) + ch->tail = NULL; + sfree(tmp); + } else + ch->head->bufpos += remlen; + ch->buffersize -= remlen; + len -= remlen; + } +} + +void bufchain_prefix(bufchain *ch, void **data, int *len) +{ + *len = ch->head->bufend - ch->head->bufpos; + *data = ch->head->bufpos; +} + +void bufchain_fetch(bufchain *ch, void *data, int len) +{ + struct bufchain_granule *tmp; + char *data_c = (char *)data; + + tmp = ch->head; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + + assert(tmp != NULL); + if (remlen >= tmp->bufend - tmp->bufpos) + remlen = tmp->bufend - tmp->bufpos; + memcpy(data_c, tmp->bufpos, remlen); + + tmp = tmp->next; + len -= remlen; + data_c += remlen; + } +} + +/* ---------------------------------------------------------------------- + * My own versions of malloc, realloc and free. Because I want + * malloc and realloc to bomb out and exit the program if they run + * out of memory, realloc to reliably call malloc if passed a NULL + * pointer, and free to reliably do nothing if passed a NULL + * pointer. We can also put trace printouts in, if we need to; and + * we can also replace the allocator with an ElectricFence-like + * one. + */ + +#ifdef MINEFIELD +void *minefield_c_malloc(size_t size); +void minefield_c_free(void *p); +void *minefield_c_realloc(void *p, size_t size); +#endif + +#ifdef MALLOC_LOG +static FILE *fp = NULL; + +static char *mlog_file = NULL; +static int mlog_line = 0; + +void mlog(char *file, int line) +{ + mlog_file = file; + mlog_line = line; + if (!fp) { + fp = fopen("putty_mem.log", "w"); + setvbuf(fp, NULL, _IONBF, BUFSIZ); + } + if (fp) + fprintf(fp, "%s:%d: ", file, line); +} +#endif + +void *safemalloc(size_t n, size_t size) +{ + void *p; + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (size == 0) size = 1; +#ifdef MINEFIELD + p = minefield_c_malloc(size); +#else + p = malloc(size); +#endif + } + + if (!p) { + char str[200]; +#ifdef MALLOC_LOG + sprintf(str, "Out of memory! (%s:%d, size=%d)", + mlog_file, mlog_line, size); + fprintf(fp, "*** %s\n", str); + fclose(fp); +#else + strcpy(str, "Out of memory!"); +#endif + modalfatalbox("%s", str); + } +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "malloc(%d) returns %p\n", size, p); +#endif + return p; +} + +void *saferealloc(void *ptr, size_t n, size_t size) +{ + void *p; + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (!ptr) { +#ifdef MINEFIELD + p = minefield_c_malloc(size); +#else + p = malloc(size); +#endif + } else { +#ifdef MINEFIELD + p = minefield_c_realloc(ptr, size); +#else + p = realloc(ptr, size); +#endif + } + } + + if (!p) { + char str[200]; +#ifdef MALLOC_LOG + sprintf(str, "Out of memory! (%s:%d, size=%d)", + mlog_file, mlog_line, size); + fprintf(fp, "*** %s\n", str); + fclose(fp); +#else + strcpy(str, "Out of memory!"); +#endif + modalfatalbox("%s", str); + } +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p); +#endif + return p; +} + +void safefree(void *ptr) +{ + if (ptr) { +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "free(%p)\n", ptr); +#endif +#ifdef MINEFIELD + minefield_c_free(ptr); +#else + free(ptr); +#endif + } +#ifdef MALLOC_LOG + else if (fp) + fprintf(fp, "freeing null pointer - no action taken\n"); +#endif +} + +/* ---------------------------------------------------------------------- + * Debugging routines. + */ + +#ifdef DEBUG +extern void dputs(char *); /* defined in per-platform *misc.c */ + +void debug_printf(char *fmt, ...) +{ + char *buf; + va_list ap; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + dputs(buf); + sfree(buf); + va_end(ap); +} + + +void debug_memdump(void *buf, int len, int L) +{ + int i; + unsigned char *p = buf; + char foo[17]; + if (L) { + int delta; + debug_printf("\t%d (0x%x) bytes:\n", len, len); + delta = 15 & (unsigned long int) p; + p -= delta; + len += delta; + } + for (; 0 < len; p += 16, len -= 16) { + dputs(" "); + if (L) + debug_printf("%p: ", p); + strcpy(foo, "................"); /* sixteen dots */ + for (i = 0; i < 16 && i < len; ++i) { + if (&p[i] < (unsigned char *) buf) { + dputs(" "); /* 3 spaces */ + foo[i] = ' '; + } else { + debug_printf("%c%02.2x", + &p[i] != (unsigned char *) buf + && i % 4 ? '.' : ' ', p[i] + ); + if (p[i] >= ' ' && p[i] <= '~') + foo[i] = (char) p[i]; + } + } + foo[i] = '\0'; + debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); + } +} + +#endif /* def DEBUG */ + +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ +int conf_launchable(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline)[0] != 0; + else + return conf_get_str(conf, CONF_host)[0] != 0; +} + +char const *conf_dest(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline); + else + return conf_get_str(conf, CONF_host); +} + +#ifndef PLATFORM_HAS_SMEMCLR +/* + * Securely wipe memory. + * + * The actual wiping is no different from what memset would do: the + * point of 'securely' is to try to be sure over-clever compilers + * won't optimise away memsets on variables that are about to be freed + * or go out of scope. See + * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html + * + * Some platforms (e.g. Windows) may provide their own version of this + * function. + */ +void smemclr(void *b, size_t n) { + volatile char *vp; + + if (b && n > 0) { + /* + * Zero out the memory. + */ + memset(b, 0, n); + + /* + * Perform a volatile access to the object, forcing the + * compiler to admit that the previous memset was important. + * + * This while loop should in practice run for zero iterations + * (since we know we just zeroed the object out), but in + * theory (as far as the compiler knows) it might range over + * the whole object. (If we had just written, say, '*vp = + * *vp;', a compiler could in principle have 'helpfully' + * optimised the memset into only zeroing out the first byte. + * This should be robust.) + */ + vp = b; + while (*vp) vp++; + } +} +#endif + +/* + * Validate a manual host key specification (either entered in the + * GUI, or via -hostkey). If valid, we return TRUE, and update 'key' + * to contain a canonicalised version of the key string in 'key' + * (which is guaranteed to take up at most as much space as the + * original version), suitable for putting into the Conf. If not + * valid, we return FALSE. + */ +int validate_manual_hostkey(char *key) +{ + char *p, *q, *r, *s; + + /* + * Step through the string word by word, looking for a word that's + * in one of the formats we like. + */ + p = key; + while ((p += strspn(p, " \t"))[0]) { + q = p; + p += strcspn(p, " \t"); + if (*p) *p++ = '\0'; + + /* + * Now q is our word. + */ + + if (strlen(q) == 16*3 - 1 && + q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { + /* + * Might be a key fingerprint. Check the colons are in the + * right places, and if so, return the same fingerprint + * canonicalised into lowercase. + */ + int i; + for (i = 0; i < 16; i++) + if (q[3*i] == ':' || q[3*i+1] == ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 15; i++) + if (q[3*i+2] != ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 16*3 - 1; i++) + key[i] = tolower(q[i]); + key[16*3 - 1] = '\0'; + return TRUE; + } + not_fingerprint:; + + /* + * Before we check for a public-key blob, trim newlines out of + * the middle of the word, in case someone's managed to paste + * in a public-key blob _with_ them. + */ + for (r = s = q; *r; r++) + if (*r != '\n' && *r != '\r') + *s++ = *r; + *s = '\0'; + + if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && + q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz+/=")] == 0) { + /* + * Might be a base64-encoded SSH-2 public key blob. Check + * that it starts with a sensible algorithm string. No + * canonicalisation is necessary for this string type. + * + * The algorithm string must be at most 64 characters long + * (RFC 4251 section 6). + */ + unsigned char decoded[6]; + unsigned alglen; + int minlen; + int len = 0; + + len += base64_decode_atom(q, decoded+len); + if (len < 3) + goto not_ssh2_blob; /* sorry */ + len += base64_decode_atom(q+4, decoded+len); + if (len < 4) + goto not_ssh2_blob; /* sorry */ + + alglen = GET_32BIT_MSB_FIRST(decoded); + if (alglen > 64) + goto not_ssh2_blob; /* sorry */ + + minlen = ((alglen + 4) + 2) / 3; + if (strlen(q) < minlen) + goto not_ssh2_blob; /* sorry */ + + strcpy(key, q); + return TRUE; + } + not_ssh2_blob:; + } + + return FALSE; +} + +int smemeq(const void *av, const void *bv, size_t len) +{ + const unsigned char *a = (const unsigned char *)av; + const unsigned char *b = (const unsigned char *)bv; + unsigned val = 0; + + while (len-- > 0) { + val |= *a++ ^ *b++; + } + /* Now val is 0 iff we want to return 1, and in the range + * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 + * will clear bit 8 iff we want to return 0, and leave it set iff + * we want to return 1, so then we can just shift down. */ + return (0x100 - val) >> 8; +} + +int strstartswith(const char *s, const char *t) +{ + return !memcmp(s, t, strlen(t)); +} + +int strendswith(const char *s, const char *t) +{ + size_t slen = strlen(s), tlen = strlen(t); + return slen >= tlen && !strcmp(s + (slen - tlen), t); +} + +#ifdef MPEXT + +#include "version.h" + +int match_ssh_id(int stringlen, const void *string, const char *id) +{ + int idlen = strlen(id); + return (idlen == stringlen && !memcmp(string, id, idlen)); +} + +int term_ldisc(Terminal *term, int mode) +{ + return FALSE; +} + +void ldisc_update(void *frontend, int echo, int edit) +{ +} + +char *platform_get_x_display(void) +{ + return dupstr(getenv("DISPLAY")); +} + +char *x_get_default(const char *key) +{ + return NULL; +} + +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +void frontend_keypress(void *handle) +{ +} + +const char * get_putty_version() +{ + return TEXTVER; +} + +#endif diff --git a/netbox/libs/Putty/misc.h b/netbox/libs/Putty/misc.h new file mode 100644 index 000000000..75833ea3c --- /dev/null +++ b/netbox/libs/Putty/misc.h @@ -0,0 +1,178 @@ +/* + * Header for misc.c. + */ + +#ifndef PUTTY_MISC_H +#define PUTTY_MISC_H + +#include "puttymem.h" + +#include /* for FILE * */ +#include /* for va_list */ +#include /* for struct tm */ +#include + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +typedef struct Filename Filename; +typedef struct FontSpec FontSpec; + +uint64_t parse_blocksize(const char *bs); +char ctrlparse(char *s, char **next); + +size_t host_strcspn(const char *s, const char *set); +char *host_strchr(const char *s, int c); +char *host_strrchr(const char *s, int c); +char *host_strduptrim(const char *s); + +char *dupstr(const char *s); +char *dupcat(const char *s1, ...); +char *dupprintf(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif + ; +char *dupvprintf(const char *fmt, va_list ap); +void burnstr(char *string); + +/* String-to-Unicode converters that auto-allocate the destination and + * work around the rather deficient interface of mb_to_wc. + * + * These actually live in miscucs.c, not misc.c (the distinction being + * that the former is only linked into tools that also have the main + * Unicode support). */ +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); + +int toint(unsigned); + +char *fgetline(FILE *fp); +int strstartswith(const char *s, const char *t); +int strendswith(const char *s, const char *t); + +void base64_encode_atom(unsigned char *data, int n, char *out); +int base64_decode_atom(char *atom, unsigned char *out); + +struct bufchain_granule; +typedef struct bufchain_tag { + struct bufchain_granule *head, *tail; + int buffersize; /* current amount of buffered data */ +} bufchain; + +void bufchain_init(bufchain *ch); +void bufchain_clear(bufchain *ch); +int bufchain_size(bufchain *ch); +void bufchain_add(bufchain *ch, const void *data, int len); +void bufchain_prefix(bufchain *ch, void **data, int *len); +void bufchain_consume(bufchain *ch, int len); +void bufchain_fetch(bufchain *ch, void *data, int len); + +int validate_manual_hostkey(char *key); + +struct tm ltime(void); + +/* Wipe sensitive data out of memory that's about to be freed. Simpler + * than memset because we don't need the fill char parameter; also + * attempts (by fiddly use of volatile) to inhibit the compiler from + * over-cleverly trying to optimise the memset away because it knows + * the variable is going out of scope. */ +void smemclr(void *b, size_t len); + +/* Compare two fixed-length chunks of memory for equality, without + * data-dependent control flow (so an attacker with a very accurate + * stopwatch can't try to guess where the first mismatching byte was). + * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at + * by the 'eq' in the name. */ +int smemeq(const void *av, const void *bv, size_t len); + +/* + * Debugging functions. + * + * Output goes to debug.log + * + * debug(()) (note the double brackets) is like printf(). + * + * dmemdump() and dmemdumpl() both do memory dumps. The difference + * is that dmemdumpl() is more suited for when the memory address is + * important (say because you'll be recording pointer values later + * on). dmemdump() is more concise. + */ + +#ifdef DEBUG +void debug_printf(char *fmt, ...); +void debug_memdump(void *buf, int len, int L); +#define debug(x) (debug_printf x) +#define dmemdump(buf,len) debug_memdump (buf, len, 0); +#define dmemdumpl(buf,len) debug_memdump (buf, len, 1); +#else +#define debug(x) +#define dmemdump(buf,len) +#define dmemdumpl(buf,len) +#endif + +#ifndef lenof +#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) +#endif + +#ifndef min +#define min(x,y) ( (x) < (y) ? (x) : (y) ) +#endif +#ifndef max +#define max(x,y) ( (x) > (y) ? (x) : (y) ) +#endif + +#define GET_32BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8) | \ + ((unsigned long)(unsigned char)(cp)[2] << 16) | \ + ((unsigned long)(unsigned char)(cp)[3] << 24)) + +#define PUT_32BIT_LSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)(value), \ + (cp)[1] = (unsigned char)((value) >> 8), \ + (cp)[2] = (unsigned char)((value) >> 16), \ + (cp)[3] = (unsigned char)((value) >> 24) ) + +#define GET_16BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8)) + +#define PUT_16BIT_LSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)(value), \ + (cp)[1] = (unsigned char)((value) >> 8) ) + +#define GET_32BIT_MSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp) + +#define PUT_32BIT_MSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)((value) >> 24), \ + (cp)[1] = (unsigned char)((value) >> 16), \ + (cp)[2] = (unsigned char)((value) >> 8), \ + (cp)[3] = (unsigned char)(value) ) + +#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value) + +#define GET_16BIT_MSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 8) | \ + ((unsigned long)(unsigned char)(cp)[1])) + +#define PUT_16BIT_MSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)((value) >> 8), \ + (cp)[1] = (unsigned char)(value) ) + +/* Replace NULL with the empty string, permitting an idiom in which we + * get a string (pointer,length) pair that might be NULL,0 and can + * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ +#define NULLTOEMPTY(s) ((s)?(s):"") + +#endif diff --git a/netbox/libs/Putty/miscucs.c b/netbox/libs/Putty/miscucs.c new file mode 100644 index 000000000..7785f9b66 --- /dev/null +++ b/netbox/libs/Putty/miscucs.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} diff --git a/netbox/libs/Putty/network.h b/netbox/libs/Putty/network.h new file mode 100644 index 000000000..3d1259eb0 --- /dev/null +++ b/netbox/libs/Putty/network.h @@ -0,0 +1,249 @@ +/* + * Networking abstraction in PuTTY. + * + * The way this works is: a back end can choose to open any number + * of sockets - including zero, which might be necessary in some. + * It can register a bunch of callbacks (most notably for when + * data is received) for each socket, and it can call the networking + * abstraction to send data without having to worry about blocking. + * The stuff behind the abstraction takes care of selects and + * nonblocking writes and all that sort of painful gubbins. + */ + +#ifndef PUTTY_NETWORK_H +#define PUTTY_NETWORK_H + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct conf_tag Conf; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +typedef struct SockAddr_tag *SockAddr; +/* pay attention to levels of indirection */ +typedef struct socket_function_table **Socket; +typedef struct plug_function_table **Plug; + +struct socket_function_table { + Plug(*plug) (Socket s, Plug p); + /* use a different plug (return the old one) */ + /* if p is NULL, it doesn't change the plug */ + /* but it does return the one it's using */ + void (*close) (Socket s); + int (*write) (Socket s, const char *data, int len); + int (*write_oob) (Socket s, const char *data, int len); + void (*write_eof) (Socket s); + void (*flush) (Socket s); + void (*set_frozen) (Socket s, int is_frozen); + /* ignored by tcp, but vital for ssl */ + const char *(*socket_error) (Socket s); + char *(*peer_info) (Socket s); +}; + +typedef union { void *p; int i; } accept_ctx_t; +typedef Socket (*accept_fn_t)(accept_ctx_t ctx, Plug plug); + +struct plug_function_table { + void (*log)(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code); + /* + * Passes the client progress reports on the process of setting + * up the connection. + * + * - type==0 means we are about to try to connect to address + * `addr' (error_msg and error_code are ignored) + * - type==1 means we have failed to connect to address `addr' + * (error_msg and error_code are supplied). This is not a + * fatal error - we may well have other candidate addresses + * to fall back to. When it _is_ fatal, the closing() + * function will be called. + */ + int (*closing) + (Plug p, const char *error_msg, int error_code, int calling_back); + /* error_msg is NULL iff it is not an error (ie it closed normally) */ + /* calling_back != 0 iff there is a Plug function */ + /* currently running (would cure the fixme in try_send()) */ + int (*receive) (Plug p, int urgent, char *data, int len); + /* + * - urgent==0. `data' points to `len' bytes of perfectly + * ordinary data. + * + * - urgent==1. `data' points to `len' bytes of data, + * which were read from before an Urgent pointer. + * + * - urgent==2. `data' points to `len' bytes of data, + * the first of which was the one at the Urgent mark. + */ + void (*sent) (Plug p, int bufsize); + /* + * The `sent' function is called when the pending send backlog + * on a socket is cleared or partially cleared. The new backlog + * size is passed in the `bufsize' parameter. + */ + int (*accepting)(Plug p, accept_fn_t constructor, accept_ctx_t ctx); + /* + * `accepting' is called only on listener-type sockets, and is + * passed a constructor function+context that will create a fresh + * Socket describing the connection. It returns nonzero if it + * doesn't want the connection for some reason, or 0 on success. + */ +}; + +/* proxy indirection layer */ +/* NB, control of 'addr' is passed via new_connection, which takes + * responsibility for freeing it */ +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf); +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, + Conf *conf, int addressfamily); +SockAddr name_lookup(char *host, int port, char **canonicalname, + Conf *conf, int addressfamily); +int proxy_for_destination (SockAddr addr, const char *hostname, int port, + Conf *conf); + +/* platform-dependent callback from new_connection() */ +/* (same caveat about addr as new_connection()) */ +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf); + +/* socket functions */ + +void sk_init(void); /* called once at program startup */ +void sk_cleanup(void); /* called just before program exit */ + +SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family); +SockAddr sk_nonamelookup(const char *host); +void sk_getaddr(SockAddr addr, char *buf, int buflen); +int sk_addr_needs_port(SockAddr addr); +int sk_hostname_is_local(const char *name); +int sk_address_is_local(SockAddr addr); +int sk_address_is_special_local(SockAddr addr); +int sk_addrtype(SockAddr addr); +void sk_addrcopy(SockAddr addr, char *buf); +void sk_addr_free(SockAddr addr); +/* sk_addr_dup generates another SockAddr which contains the same data + * as the original one and can be freed independently. May not actually + * physically _duplicate_ it: incrementing a reference count so that + * one more free is required before it disappears is an acceptable + * implementation. */ +SockAddr sk_addr_dup(SockAddr addr); + +/* NB, control of 'addr' is passed via sk_new, which takes responsibility + * for freeing it, as for new_connection() */ +Socket putty_sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug p); + +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family); + +#define sk_plug(s,p) (((*s)->plug) (s, p)) +#define sk_close(s) (((*s)->close) (s)) +#define sk_write(s,buf,len) (((*s)->write) (s, buf, len)) +#define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len)) +#define sk_write_eof(s) (((*s)->write_eof) (s)) +#define sk_flush(s) (((*s)->flush) (s)) + +#ifdef DEFINE_PLUG_METHOD_MACROS +#define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code)) +#define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback)) +#define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len)) +#define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize)) +#define plug_accepting(p, constructor, ctx) (((*p)->accepting)(p, constructor, ctx)) +#endif + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr); +#define sk_socket_error(s) (((*s)->socket_error) (s)) + +/* + * Set the `frozen' flag on a socket. A frozen socket is one in + * which all READABLE notifications are ignored, so that data is + * not accepted from the peer until the socket is unfrozen. This + * exists for two purposes: + * + * - Port forwarding: when a local listening port receives a + * connection, we do not want to receive data from the new + * socket until we have somewhere to send it. Hence, we freeze + * the socket until its associated SSH channel is ready; then we + * unfreeze it and pending data is delivered. + * + * - Socket buffering: if an SSH channel (or the whole connection) + * backs up or presents a zero window, we must freeze the + * associated local socket in order to avoid unbounded buffer + * growth. + */ +#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen)) + +/* + * Return a (dynamically allocated) string giving some information + * about the other end of the socket, suitable for putting in log + * files. May be NULL if nothing is available at all. + */ +#define sk_peer_info(s) (((*s)->peer_info) (s)) + +/* + * Simple wrapper on getservbyname(), needed by ssh.c. Returns the + * port number, in host byte order (suitable for printf and so on). + * Returns 0 on failure. Any platform not supporting getservbyname + * can just return 0 - this function is not required to handle + * numeric port specifications. + */ +int net_service_lookup(char *service); + +/* + * Look up the local hostname; return value needs freeing. + * May return NULL. + */ +char *get_hostname(void); + +/* + * Trivial socket implementation which just stores an error. Found in + * errsock.c. + */ +Socket new_error_socket(const char *errmsg, Plug plug); + +/********** SSL stuff **********/ + +/* + * This section is subject to change, but you get the general idea + * of what it will eventually look like. + */ + +typedef struct certificate *Certificate; +typedef struct our_certificate *Our_Certificate; + /* to be defined somewhere else, somehow */ + +typedef struct ssl_client_socket_function_table **SSL_Client_Socket; +typedef struct ssl_client_plug_function_table **SSL_Client_Plug; + +struct ssl_client_socket_function_table { + struct socket_function_table base; + void (*renegotiate) (SSL_Client_Socket s); + /* renegotiate the cipher spec */ +}; + +struct ssl_client_plug_function_table { + struct plug_function_table base; + int (*refuse_cert) (SSL_Client_Plug p, Certificate cert[]); + /* do we accept this certificate chain? If not, why not? */ + /* cert[0] is the server's certificate, cert[] is NULL-terminated */ + /* the last certificate may or may not be the root certificate */ + Our_Certificate(*client_cert) (SSL_Client_Plug p); + /* the server wants us to identify ourselves */ + /* may return NULL if we want anonymity */ +}; + +SSL_Client_Socket sk_ssl_client_over(Socket s, /* pre-existing (tcp) connection */ + SSL_Client_Plug p); + +#define sk_renegotiate(s) (((*s)->renegotiate) (s)) + +#endif diff --git a/netbox/libs/Putty/nocproxy.c b/netbox/libs/Putty/nocproxy.c new file mode 100644 index 000000000..d2aeb9785 --- /dev/null +++ b/netbox/libs/Putty/nocproxy.c @@ -0,0 +1,36 @@ +/* + * Routines to refuse to do cryptographic interaction with proxies + * in PuTTY. This is a stub implementation of the same interfaces + * provided by cproxy.c, for use in PuTTYtel. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "proxy.h" + +void proxy_socks5_offerencryptedauth(char * command, int * len) +{ + /* For telnet, don't add any new encrypted authentication routines */ +} + +int proxy_socks5_handlechap (Proxy_Socket p) +{ + + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} + +int proxy_socks5_selectchap(Proxy_Socket p) +{ + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} diff --git a/netbox/libs/Putty/nogss.c b/netbox/libs/Putty/nogss.c new file mode 100644 index 000000000..844a1323f --- /dev/null +++ b/netbox/libs/Putty/nogss.c @@ -0,0 +1,11 @@ +/* + * Stub definitions of the GSSAPI library list, for Unix pterm and + * any other application that needs the symbols defined but has no + * use for them. + */ + +#include "putty.h" + +const int ngsslibs = 0; +const char *const gsslibnames[1] = { "dummy" }; +const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } }; diff --git a/netbox/libs/Putty/noprint.c b/netbox/libs/Putty/noprint.c new file mode 100644 index 000000000..b3e9da5d0 --- /dev/null +++ b/netbox/libs/Putty/noprint.c @@ -0,0 +1,38 @@ +/* + * Stub implementation of the printing interface for PuTTY, for the + * benefit of non-printing terminal applications. + */ + +#include +#include +#include "putty.h" + +struct printer_job_tag { + int dummy; +}; + +printer_job *printer_start_job(char *printer) +{ + return NULL; +} + +void printer_job_data(printer_job *pj, void *data, int len) +{ +} + +void printer_finish_job(printer_job *pj) +{ +} + +printer_enum *printer_start_enum(int *nprinters_ptr) +{ + *nprinters_ptr = 0; + return NULL; +} +char *printer_get_name(printer_enum *pe, int i) +{ + return NULL; +} +void printer_finish_enum(printer_enum *pe) +{ +} diff --git a/netbox/libs/Putty/noshare.c b/netbox/libs/Putty/noshare.c new file mode 100644 index 000000000..9a8884541 --- /dev/null +++ b/netbox/libs/Putty/noshare.c @@ -0,0 +1,25 @@ +/* + * Stub implementation of SSH connection-sharing IPC, for any + * platform which can't support it at all. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "ssh.h" +#include "network.h" + +int platform_ssh_share(const char *name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream) +{ + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} diff --git a/netbox/libs/Putty/noterm.c b/netbox/libs/Putty/noterm.c new file mode 100644 index 000000000..4ca99fa20 --- /dev/null +++ b/netbox/libs/Putty/noterm.c @@ -0,0 +1,11 @@ +/* + * Stubs of functions in terminal.c, for use in programs that don't + * have a terminal. + */ + +#include "putty.h" +#include "terminal.h" + +void term_nopaste(Terminal *term) +{ +} diff --git a/netbox/libs/Putty/notiming.c b/netbox/libs/Putty/notiming.c new file mode 100644 index 000000000..5fe440389 --- /dev/null +++ b/netbox/libs/Putty/notiming.c @@ -0,0 +1,21 @@ +/* + * notiming.c: stub version of timing API. + * + * Used in any tool which needs a subsystem linked against the + * timing API but doesn't want to actually provide timing. For + * example, key generation tools need the random number generator, + * but they don't want the hassle of calling noise_regular() at + * regular intervals - and they don't _need_ it either, since they + * have their own rigorous and different means of noise collection. + */ + +#include "putty.h" + +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + return 0; +} + +void expire_timer_context(void *ctx) +{ +} diff --git a/netbox/libs/Putty/pgssapi.c b/netbox/libs/Putty/pgssapi.c new file mode 100644 index 000000000..5d9ffeb41 --- /dev/null +++ b/netbox/libs/Putty/pgssapi.c @@ -0,0 +1,105 @@ +/* This file actually defines the GSSAPI function pointers for + * functions we plan to import from a GSSAPI library. + */ +#include "putty.h" + +#ifndef NO_GSSAPI + +#include "pgssapi.h" + +#ifndef NO_LIBDL + +/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */ +static const gss_OID_desc oids[] = { + /* The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant + * GSS_C_NT_USER_NAME should be initialized to point + * to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. + * The constant GSS_C_NT_MACHINE_UID_NAME should be + * initialized to point to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. + * The constant GSS_C_NT_STRING_UID_NAME should be + * initialized to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) org(3) dod(6) internet(1) security(5) + * nametypes(6) gss-host-based-services(2)). The constant + * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point + * to that gss_OID_desc. This is a deprecated OID value, and + * implementations wishing to support hostbased-service names + * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID, + * defined below, to identify such names; + * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym + * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input + * parameter, but should not be emitted by GSS-API + * implementations + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}, + /* corresponding to an object-identifier value of {iso(1) + * member-body(2) Unites States(840) mit(113554) infosys(1) + * gssapi(2) generic(1) service_name(4)}. The constant + * GSS_C_NT_HOSTBASED_SERVICE should be initialized + * to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + /* corresponding to an object identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 3(gss-anonymous-name)}. The constant + * and GSS_C_NT_ANONYMOUS should be initialized to point + * to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + /* corresponding to an object-identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 4(gss-api-exported-name)}. The constant + * GSS_C_NT_EXPORT_NAME should be initialized to point + * to that gss_OID_desc. + */ +}; + +/* Here are the constants which point to the static structure above. + * + * Constants of the form GSS_C_NT_* are specified by rfc 2744. + */ +const_gss_OID GSS_C_NT_USER_NAME = oids+0; +const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1; +const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4; +const_gss_OID GSS_C_NT_ANONYMOUS = oids+5; +const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6; + +#endif /* NO_LIBDL */ + +static gss_OID_desc gss_mech_krb5_desc = +{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/ +const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc; + +#endif /* NO_GSSAPI */ diff --git a/netbox/libs/Putty/pgssapi.h b/netbox/libs/Putty/pgssapi.h new file mode 100644 index 000000000..f6370c2d7 --- /dev/null +++ b/netbox/libs/Putty/pgssapi.h @@ -0,0 +1,296 @@ +#ifndef PUTTY_PGSSAPI_H +#define PUTTY_PGSSAPI_H + +#include "putty.h" + +#ifndef NO_GSSAPI + +/* + * On Unix, if we're statically linking against GSSAPI, we leave the + * declaration of all this lot to the official header. If we're + * dynamically linking, we declare it ourselves, because that avoids + * us needing the official header at compile time. + * + * However, we still need the function pointer types, because even + * with statically linked GSSAPI we use the ssh_gss_library wrapper. + */ +#ifdef STATIC_GSSAPI +#include +typedef gss_OID const_gss_OID; /* for our prototypes below */ +#else /* STATIC_GSSAPI */ + +/******************************************************************************* + * GSSAPI Definitions, taken from RFC 2744 + ******************************************************************************/ + +/* GSSAPI Type Definitions */ +typedef uint32 OM_uint32; + +typedef struct gss_OID_desc_struct { + OM_uint32 length; + void *elements; +} gss_OID_desc; +typedef const gss_OID_desc *const_gss_OID; +typedef gss_OID_desc *gss_OID; + +typedef struct gss_OID_set_desc_struct { + size_t count; + gss_OID elements; +} gss_OID_set_desc; +typedef const gss_OID_set_desc *const_gss_OID_set; +typedef gss_OID_set_desc *gss_OID_set; + +typedef struct gss_buffer_desc_struct { + size_t length; + void *value; +} gss_buffer_desc, *gss_buffer_t; + +typedef struct gss_channel_bindings_struct { + OM_uint32 initiator_addrtype; + gss_buffer_desc initiator_address; + OM_uint32 acceptor_addrtype; + gss_buffer_desc acceptor_address; + gss_buffer_desc application_data; +} *gss_channel_bindings_t; + +typedef void * gss_ctx_id_t; +typedef void * gss_name_t; +typedef void * gss_cred_id_t; + +typedef OM_uint32 gss_qop_t; + +/* Flag bits for context-level services. */ + +#define GSS_C_DELEG_FLAG 1 +#define GSS_C_MUTUAL_FLAG 2 +#define GSS_C_REPLAY_FLAG 4 +#define GSS_C_SEQUENCE_FLAG 8 +#define GSS_C_CONF_FLAG 16 +#define GSS_C_INTEG_FLAG 32 +#define GSS_C_ANON_FLAG 64 +#define GSS_C_PROT_READY_FLAG 128 +#define GSS_C_TRANS_FLAG 256 + +/* Credential usage options */ +#define GSS_C_BOTH 0 +#define GSS_C_INITIATE 1 +#define GSS_C_ACCEPT 2 + +/* Status code types for gss_display_status */ +#define GSS_C_GSS_CODE 1 +#define GSS_C_MECH_CODE 2 + +/* The constant definitions for channel-bindings address families */ +#define GSS_C_AF_UNSPEC 0 +#define GSS_C_AF_LOCAL 1 +#define GSS_C_AF_INET 2 +#define GSS_C_AF_IMPLINK 3 +#define GSS_C_AF_PUP 4 +#define GSS_C_AF_CHAOS 5 +#define GSS_C_AF_NS 6 +#define GSS_C_AF_NBS 7 +#define GSS_C_AF_ECMA 8 +#define GSS_C_AF_DATAKIT 9 +#define GSS_C_AF_CCITT 10 +#define GSS_C_AF_SNA 11 +#define GSS_C_AF_DECnet 12 +#define GSS_C_AF_DLI 13 +#define GSS_C_AF_LAT 14 +#define GSS_C_AF_HYLINK 15 +#define GSS_C_AF_APPLETALK 16 +#define GSS_C_AF_BSC 17 +#define GSS_C_AF_DSS 18 +#define GSS_C_AF_OSI 19 +#define GSS_C_AF_X25 21 + +#define GSS_C_AF_NULLADDR 255 + +/* Various Null values */ +#define GSS_C_NO_NAME ((gss_name_t) 0) +#define GSS_C_NO_BUFFER ((gss_buffer_t) 0) +#define GSS_C_NO_OID ((gss_OID) 0) +#define GSS_C_NO_OID_SET ((gss_OID_set) 0) +#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0) +#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0) +#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0) +#define GSS_C_EMPTY_BUFFER {0, NULL} + +/* Major status codes */ +#define GSS_S_COMPLETE 0 + +/* Some "helper" definitions to make the status code macros obvious. */ +#define GSS_C_CALLING_ERROR_OFFSET 24 +#define GSS_C_ROUTINE_ERROR_OFFSET 16 + +#define GSS_C_SUPPLEMENTARY_OFFSET 0 +#define GSS_C_CALLING_ERROR_MASK 0377ul +#define GSS_C_ROUTINE_ERROR_MASK 0377ul +#define GSS_C_SUPPLEMENTARY_MASK 0177777ul + +/* + * The macros that test status codes for error conditions. + * Note that the GSS_ERROR() macro has changed slightly from + * the V1 GSS-API so that it now evaluates its argument + * only once. + */ +#define GSS_CALLING_ERROR(x) \ + (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET)) +#define GSS_ROUTINE_ERROR(x) \ + (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)) +#define GSS_SUPPLEMENTARY_INFO(x) \ + (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET)) +#define GSS_ERROR(x) \ + (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \ + (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))) + +/* Now the actual status code definitions */ + +/* Calling errors: */ +#define GSS_S_CALL_INACCESSIBLE_READ \ + (1ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_INACCESSIBLE_WRITE \ + (2ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_BAD_STRUCTURE \ + (3ul << GSS_C_CALLING_ERROR_OFFSET) + +/* Routine errors: */ +#define GSS_S_BAD_MECH (1ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAME (2ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAMETYPE (3ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_BINDINGS (4ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_STATUS (5ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_SIG (6ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_MIC GSS_S_BAD_SIG +#define GSS_S_NO_CRED (7ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NO_CONTEXT (8ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_TOKEN (9ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CREDENTIALS_EXPIRED (11ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CONTEXT_EXPIRED (12ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_FAILURE (13ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_QOP (14ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAUTHORIZED (15ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAVAILABLE (16ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DUPLICATE_ELEMENT (17ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NAME_NOT_MN (18ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) + +/* Supplementary info bits: */ +#define GSS_S_CONTINUE_NEEDED \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0)) +#define GSS_S_DUPLICATE_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1)) +#define GSS_S_OLD_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2)) +#define GSS_S_UNSEQ_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3)) +#define GSS_S_GAP_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4)) + +extern const_gss_OID GSS_C_NT_USER_NAME; +extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME; +extern const_gss_OID GSS_C_NT_STRING_UID_NAME; +extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X; +extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE; +extern const_gss_OID GSS_C_NT_ANONYMOUS; +extern const_gss_OID GSS_C_NT_EXPORT_NAME; + +#endif /* STATIC_GSSAPI */ + +extern const gss_OID GSS_MECH_KRB5; + +/* GSSAPI functions we use. + * TODO: Replace with all GSSAPI functions from RFC? + */ + +/* Calling convention, just in case we need one. */ +#ifndef GSS_CC +#define GSS_CC +#endif /*GSS_CC*/ + +typedef OM_uint32 (GSS_CC *t_gss_release_cred) + (OM_uint32 * /*minor_status*/, + gss_cred_id_t * /*cred_handle*/); + +typedef OM_uint32 (GSS_CC *t_gss_init_sec_context) + (OM_uint32 * /*minor_status*/, + const gss_cred_id_t /*initiator_cred_handle*/, + gss_ctx_id_t * /*context_handle*/, + const gss_name_t /*target_name*/, + const gss_OID /*mech_type*/, + OM_uint32 /*req_flags*/, + OM_uint32 /*time_req*/, + const gss_channel_bindings_t /*input_chan_bindings*/, + const gss_buffer_t /*input_token*/, + gss_OID * /*actual_mech_type*/, + gss_buffer_t /*output_token*/, + OM_uint32 * /*ret_flags*/, + OM_uint32 * /*time_rec*/); + +typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context) + (OM_uint32 * /*minor_status*/, + gss_ctx_id_t * /*context_handle*/, + gss_buffer_t /*output_token*/); + +typedef OM_uint32 (GSS_CC *t_gss_get_mic) + (OM_uint32 * /*minor_status*/, + const gss_ctx_id_t /*context_handle*/, + gss_qop_t /*qop_req*/, + const gss_buffer_t /*message_buffer*/, + gss_buffer_t /*msg_token*/); + +typedef OM_uint32 (GSS_CC *t_gss_display_status) + (OM_uint32 * /*minor_status*/, + OM_uint32 /*status_value*/, + int /*status_type*/, + const gss_OID /*mech_type*/, + OM_uint32 * /*message_context*/, + gss_buffer_t /*status_string*/); + + +typedef OM_uint32 (GSS_CC *t_gss_import_name) + (OM_uint32 * /*minor_status*/, + const gss_buffer_t /*input_name_buffer*/, + const_gss_OID /*input_name_type*/, + gss_name_t * /*output_name*/); + + +typedef OM_uint32 (GSS_CC *t_gss_release_name) + (OM_uint32 * /*minor_status*/, + gss_name_t * /*name*/); + +typedef OM_uint32 (GSS_CC *t_gss_release_buffer) + (OM_uint32 * /*minor_status*/, + gss_buffer_t /*buffer*/); + +struct gssapi_functions { + t_gss_delete_sec_context delete_sec_context; + t_gss_display_status display_status; + t_gss_get_mic get_mic; + t_gss_import_name import_name; + t_gss_init_sec_context init_sec_context; + t_gss_release_buffer release_buffer; + t_gss_release_cred release_cred; + t_gss_release_name release_name; +}; + +#endif /* NO_GSSAPI */ + +#endif /* PUTTY_PGSSAPI_H */ diff --git a/netbox/libs/Putty/pinger.c b/netbox/libs/Putty/pinger.c new file mode 100644 index 000000000..3f533ae6e --- /dev/null +++ b/netbox/libs/Putty/pinger.c @@ -0,0 +1,72 @@ +/* + * pinger.c: centralised module that deals with sending TS_PING + * keepalives, to avoid replicating this code in multiple backends. + */ + +#include "putty.h" + +struct pinger_tag { + int interval; + int pending; + unsigned long next; + Backend *back; + void *backhandle; +}; + +static void pinger_schedule(Pinger pinger); + +static void pinger_timer(void *ctx, unsigned long now) +{ + Pinger pinger = (Pinger)ctx; + + if (pinger->pending && now == pinger->next) { + pinger->back->special(pinger->backhandle, TS_PING); + pinger->pending = FALSE; + pinger_schedule(pinger); + } +} + +static void pinger_schedule(Pinger pinger) +{ + int next; + + if (!pinger->interval) { + pinger->pending = FALSE; /* cancel any pending ping */ + return; + } + + next = schedule_timer(pinger->interval * TICKSPERSEC, + pinger_timer, pinger); + if (!pinger->pending || next < pinger->next) { + pinger->next = next; + pinger->pending = TRUE; + } +} + +Pinger pinger_new(Conf *conf, Backend *back, void *backhandle) +{ + Pinger pinger = snew(struct pinger_tag); + + pinger->interval = conf_get_int(conf, CONF_ping_interval); + pinger->pending = FALSE; + pinger->back = back; + pinger->backhandle = backhandle; + pinger_schedule(pinger); + + return pinger; +} + +void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf) +{ + int newinterval = conf_get_int(newconf, CONF_ping_interval); + if (conf_get_int(oldconf, CONF_ping_interval) != newinterval) { + pinger->interval = newinterval; + pinger_schedule(pinger); + } +} + +void pinger_free(Pinger pinger) +{ + expire_timer_context(pinger); + sfree(pinger); +} diff --git a/netbox/libs/Putty/portfwd.c b/netbox/libs/Putty/portfwd.c new file mode 100644 index 000000000..41c13e69b --- /dev/null +++ b/netbox/libs/Putty/portfwd.c @@ -0,0 +1,680 @@ +/* + * SSH port forwarding. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +struct PortForwarding { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + struct ssh_channel *c; /* channel structure held by ssh.c */ + void *backhandle; /* instance of SSH backend itself */ + /* Note that backhandle need not be filled in if c is non-NULL */ + Socket s; + int throttled, throttle_override; + int ready; + /* + * `dynamic' does double duty. It's set to 0 for an ordinary + * forwarded port, and nonzero for SOCKS-style dynamic port + * forwarding; but the nonzero values are also a state machine + * tracking where the SOCKS exchange has got to. + */ + int dynamic; + /* + * `hostname' and `port' are the real hostname and port, once + * we know what we're connecting to. + */ + char *hostname; + int port; + /* + * `socksbuf' is the buffer we use to accumulate a SOCKS request. + */ + char *socksbuf; + int sockslen, sockssize; + /* + * When doing dynamic port forwarding, we can receive + * connection data before we are actually able to send it; so + * we may have to temporarily hold some in a dynamically + * allocated buffer here. + */ + void *buffer; + int buflen; +}; + +struct PortListener { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + void *backhandle; /* instance of SSH backend itself */ + Socket s; + /* + * `dynamic' is set to 0 for an ordinary forwarded port, and + * nonzero for SOCKS-style dynamic port forwarding. + */ + int dynamic; + /* + * `hostname' and `port' are the real hostname and port, for + * ordinary forwardings. + */ + char *hostname; + int port; +}; + +static struct PortForwarding *new_portfwd_state(void) +{ + struct PortForwarding *pf = snew(struct PortForwarding); + pf->hostname = NULL; + pf->socksbuf = NULL; + pf->sockslen = pf->sockssize = 0; + pf->buffer = NULL; + return pf; +} + +static void free_portfwd_state(struct PortForwarding *pf) +{ + if (!pf) + return; + sfree(pf->hostname); + sfree(pf->socksbuf); + sfree(pf->buffer); + sfree(pf); +} + +static struct PortListener *new_portlistener_state(void) +{ + struct PortListener *pl = snew(struct PortListener); + pl->hostname = NULL; + return pl; +} + +static void free_portlistener_state(struct PortListener *pl) +{ + if (!pl) + return; + sfree(pl->hostname); + sfree(pl); +} + +static void pfd_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static void pfl_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static int pfd_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct PortForwarding *pf = (struct PortForwarding *) plug; + + if (error_msg) { + /* + * Socket error. Slam the connection instantly shut. + */ + if (pf->c) { + sshfwd_unclean_close(pf->c, error_msg); + } else { + /* + * We might not have an SSH channel, if a socket error + * occurred during SOCKS negotiation. If not, we must + * clean ourself up without sshfwd_unclean_close's call + * back to pfd_close. + */ + pfd_close(pf); + } + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (pf->c) + sshfwd_write_eof(pf->c); + } + + return 1; +} + +static int pfl_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct PortListener *pl = (struct PortListener *) plug; + pfl_terminate(pl); + return 1; +} + +static void wrap_send_port_open(void *channel, char *hostname, int port, + Socket s) +{ + char *peerinfo, *description; + peerinfo = sk_peer_info(s); + if (peerinfo) { + description = dupprintf("forwarding from %s", peerinfo); + sfree(peerinfo); + } else { + description = dupstr("forwarding"); + } + ssh_send_port_open(channel, hostname, port, description); + sfree(description); +} + +static int pfd_receive(Plug plug, int urgent, char *data, int len) +{ + struct PortForwarding *pf = (struct PortForwarding *) plug; + if (pf->dynamic) { + while (len--) { + if (pf->sockslen >= pf->sockssize) { + pf->sockssize = pf->sockslen * 5 / 4 + 256; + pf->socksbuf = sresize(pf->socksbuf, pf->sockssize, char); + } + pf->socksbuf[pf->sockslen++] = *data++; + + /* + * Now check what's in the buffer to see if it's a + * valid and complete message in the SOCKS exchange. + */ + if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 4) && + pf->socksbuf[0] == 4) { + /* + * SOCKS 4. + */ + if (pf->dynamic == 1) + pf->dynamic = 0x4000; + if (pf->sockslen < 2) + continue; /* don't have command code yet */ + if (pf->socksbuf[1] != 1) { + /* Not CONNECT. */ + /* Send back a SOCKS 4 error before closing. */ + char data[8]; + memset(data, 0, sizeof(data)); + data[1] = 91; /* generic `request rejected' */ + sk_write(pf->s, data, 8); + pfd_close(pf); + return 1; + } + if (pf->sockslen <= 8) + continue; /* haven't started user/hostname */ + if (pf->socksbuf[pf->sockslen-1] != 0) + continue; /* haven't _finished_ user/hostname */ + /* + * Now we have a full SOCKS 4 request. Check it to + * see if it's a SOCKS 4A request. + */ + if (pf->socksbuf[4] == 0 && pf->socksbuf[5] == 0 && + pf->socksbuf[6] == 0 && pf->socksbuf[7] != 0) { + /* + * It's SOCKS 4A. So if we haven't yet + * collected the host name, we should continue + * waiting for data in order to do so; if we + * have, we can go ahead. + */ + int len; + if (pf->dynamic == 0x4000) { + pf->dynamic = 0x4001; + pf->sockslen = 8; /* reset buffer to overwrite name */ + continue; + } + pf->socksbuf[0] = 0; /* reply version code */ + pf->socksbuf[1] = 90; /* request granted */ + sk_write(pf->s, pf->socksbuf, 8); + len = pf->sockslen - 8; + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); + pf->hostname = snewn(len+1, char); + pf->hostname[len] = '\0'; + memcpy(pf->hostname, pf->socksbuf + 8, len); + goto connect; + } else { + /* + * It's SOCKS 4, which means we should format + * the IP address into the hostname string and + * then just go. + */ + pf->socksbuf[0] = 0; /* reply version code */ + pf->socksbuf[1] = 90; /* request granted */ + sk_write(pf->s, pf->socksbuf, 8); + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); + pf->hostname = dupprintf("%d.%d.%d.%d", + (unsigned char)pf->socksbuf[4], + (unsigned char)pf->socksbuf[5], + (unsigned char)pf->socksbuf[6], + (unsigned char)pf->socksbuf[7]); + goto connect; + } + } + + if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 5) && + pf->socksbuf[0] == 5) { + /* + * SOCKS 5. + */ + if (pf->dynamic == 1) + pf->dynamic = 0x5000; + + if (pf->dynamic == 0x5000) { + int i, method; + char data[2]; + /* + * We're receiving a set of method identifiers. + */ + if (pf->sockslen < 2) + continue; /* no method count yet */ + if (pf->sockslen < 2 + (unsigned char)pf->socksbuf[1]) + continue; /* no methods yet */ + method = 0xFF; /* invalid */ + for (i = 0; i < (unsigned char)pf->socksbuf[1]; i++) + if (pf->socksbuf[2+i] == 0) { + method = 0;/* no auth */ + break; + } + data[0] = 5; + data[1] = method; + sk_write(pf->s, data, 2); + pf->dynamic = 0x5001; + pf->sockslen = 0; /* re-empty the buffer */ + continue; + } + + if (pf->dynamic == 0x5001) { + /* + * We're receiving a SOCKS request. + */ + unsigned char reply[10]; /* SOCKS5 atyp=1 reply */ + int atype, alen = 0; + + /* + * Pre-fill reply packet. + * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0 + * (atyp=1) in the reply; if we succeed, we don't know + * the right answers, and if we fail, they should be + * ignored. + */ + memset(reply, 0, lenof(reply)); + reply[0] = 5; /* VER */ + reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */ + + if (pf->sockslen < 6) continue; + atype = (unsigned char)pf->socksbuf[3]; + if (atype == 1) /* IPv4 address */ + alen = 4; + if (atype == 4) /* IPv6 address */ + alen = 16; + if (atype == 3) /* domain name has leading length */ + alen = 1 + (unsigned char)pf->socksbuf[4]; + if (pf->sockslen < 6 + alen) continue; + if (pf->socksbuf[1] != 1 || pf->socksbuf[2] != 0) { + /* Not CONNECT or reserved field nonzero - error */ + reply[1] = 1; /* generic failure */ + sk_write(pf->s, (char *) reply, lenof(reply)); + pfd_close(pf); + return 1; + } + /* + * Now we have a viable connect request. Switch + * on atype. + */ + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+4+alen); + if (atype == 1) { + /* REP=0 (success) already */ + sk_write(pf->s, (char *) reply, lenof(reply)); + pf->hostname = dupprintf("%d.%d.%d.%d", + (unsigned char)pf->socksbuf[4], + (unsigned char)pf->socksbuf[5], + (unsigned char)pf->socksbuf[6], + (unsigned char)pf->socksbuf[7]); + goto connect; + } else if (atype == 3) { + /* REP=0 (success) already */ + sk_write(pf->s, (char *) reply, lenof(reply)); + pf->hostname = snewn(alen, char); + pf->hostname[alen-1] = '\0'; + memcpy(pf->hostname, pf->socksbuf + 5, alen-1); + goto connect; + } else { + /* + * Unknown address type. (FIXME: support IPv6!) + */ + reply[1] = 8; /* atype not supported */ + sk_write(pf->s, (char *) reply, lenof(reply)); + pfd_close(pf); + return 1; + } + } + } + + /* + * If we get here without either having done `continue' + * or `goto connect', it must be because there is no + * sensible interpretation of what's in our buffer. So + * close the connection rudely. + */ + pfd_close(pf); + return 1; + } + return 1; + + /* + * We come here when we're ready to make an actual + * connection. + */ + connect: + sfree(pf->socksbuf); + pf->socksbuf = NULL; + + /* + * Freeze the socket until the SSH server confirms the + * connection. + */ + sk_set_frozen(pf->s, 1); + + pf->c = new_sock_channel(pf->backhandle, pf); + if (pf->c == NULL) { + pfd_close(pf); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + wrap_send_port_open(pf->c, pf->hostname, pf->port, pf->s); + } + pf->dynamic = 0; + + /* + * If there's any data remaining in our current buffer, + * save it to be sent on pfd_confirm(). + */ + if (len > 0) { + pf->buffer = snewn(len, char); + memcpy(pf->buffer, data, len); + pf->buflen = len; + } + } + if (pf->ready) { + if (sshfwd_write(pf->c, data, len) > 0) { + pf->throttled = 1; + sk_set_frozen(pf->s, 1); + } + } + return 1; +} + +static void pfd_sent(Plug plug, int bufsize) +{ + struct PortForwarding *pf = (struct PortForwarding *) plug; + + if (pf->c) + sshfwd_unthrottle(pf->c, bufsize); +} + +/* + * Called when receiving a PORT OPEN from the server to make a + * connection to a destination host. + * + * On success, returns NULL and fills in *pf_ret. On error, returns a + * dynamically allocated error message string. + */ +char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, + void *c, Conf *conf, int addressfamily) +{ + static const struct plug_function_table fn_table = { + pfd_log, + pfd_closing, + pfd_receive, + pfd_sent, + NULL + }; + + SockAddr addr; + const char *err; + char *dummy_realhost; + struct PortForwarding *pf; + + /* + * Try to find host. + */ + addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + char *err_ret = dupstr(err); + sk_addr_free(addr); + sfree(dummy_realhost); + return err_ret; + } + + /* + * Open socket. + */ + pf = *pf_ret = new_portfwd_state(); + pf->fn = &fn_table; + pf->throttled = pf->throttle_override = 0; + pf->ready = 1; + pf->c = c; + pf->backhandle = NULL; /* we shouldn't need this */ + pf->dynamic = 0; + + pf->s = new_connection(addr, dummy_realhost, port, + 0, 1, 0, 0, (Plug) pf, conf); + sfree(dummy_realhost); + if ((err = sk_socket_error(pf->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pf->s); + free_portfwd_state(pf); + *pf_ret = NULL; + return err_ret; + } + + return NULL; +} + +/* + called when someone connects to the local port + */ + +static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) +{ + static const struct plug_function_table fn_table = { + pfd_log, + pfd_closing, + pfd_receive, + pfd_sent, + NULL + }; + struct PortForwarding *pf; + struct PortListener *pl; + Socket s; + const char *err; + + pl = (struct PortListener *)p; + pf = new_portfwd_state(); + pf->fn = &fn_table; + + pf->c = NULL; + pf->backhandle = pl->backhandle; + + pf->s = s = constructor(ctx, (Plug) pf); + if ((err = sk_socket_error(s)) != NULL) { + free_portfwd_state(pf); + return err != NULL; + } + + pf->throttled = pf->throttle_override = 0; + pf->ready = 0; + + if (pl->dynamic) { + pf->dynamic = 1; + pf->port = 0; /* "hostname" buffer is so far empty */ + sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */ + } else { + pf->dynamic = 0; + pf->hostname = dupstr(pl->hostname); + pf->port = pl->port; + pf->c = new_sock_channel(pl->backhandle, pf); + + if (pf->c == NULL) { + free_portfwd_state(pf); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + wrap_send_port_open(pf->c, pf->hostname, pf->port, s); + } + } + + return 0; +} + + +/* + * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. + * + * On success, returns NULL and fills in *pl_ret. On error, returns a + * dynamically allocated error message string. + */ +char *pfl_listen(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, Conf *conf, + struct PortListener **pl_ret, int address_family) +{ + static const struct plug_function_table fn_table = { + pfl_log, + pfl_closing, + NULL, /* recv */ + NULL, /* send */ + pfl_accepting + }; + + const char *err; + struct PortListener *pl; + + /* + * Open socket. + */ + pl = *pl_ret = new_portlistener_state(); + pl->fn = &fn_table; + if (desthost) { + pl->hostname = dupstr(desthost); + pl->port = destport; + pl->dynamic = 0; + } else + pl->dynamic = 1; + pl->backhandle = backhandle; + + pl->s = new_listener(srcaddr, port, (Plug) pl, + !conf_get_int(conf, CONF_lport_acceptall), + conf, address_family); + if ((err = sk_socket_error(pl->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pl->s); + free_portlistener_state(pl); + *pl_ret = NULL; + return err_ret; + } + + return NULL; +} + +void pfd_close(struct PortForwarding *pf) +{ + if (!pf) + return; + + sk_close(pf->s); + free_portfwd_state(pf); +} + +/* + * Terminate a listener. + */ +void pfl_terminate(struct PortListener *pl) +{ + if (!pl) + return; + + sk_close(pl->s); + free_portlistener_state(pl); +} + +void pfd_unthrottle(struct PortForwarding *pf) +{ + if (!pf) + return; + + pf->throttled = 0; + sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); +} + +void pfd_override_throttle(struct PortForwarding *pf, int enable) +{ + if (!pf) + return; + + pf->throttle_override = enable; + sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); +} + +/* + * Called to send data down the raw connection. + */ +int pfd_send(struct PortForwarding *pf, char *data, int len) +{ + if (pf == NULL) + return 0; + return sk_write(pf->s, data, len); +} + +void pfd_send_eof(struct PortForwarding *pf) +{ + sk_write_eof(pf->s); +} + +void pfd_confirm(struct PortForwarding *pf) +{ + if (pf == NULL) + return; + + pf->ready = 1; + sk_set_frozen(pf->s, 0); + sk_write(pf->s, NULL, 0); + if (pf->buffer) { + sshfwd_write(pf->c, pf->buffer, pf->buflen); + sfree(pf->buffer); + pf->buffer = NULL; + } +} + +#ifdef MPEXT + +#include "puttyexp.h" + +int is_pfwd(void * handle) +{ + Plug fn = (Plug)handle; + return + ((*fn)->closing == pfd_closing) || + ((*fn)->closing == pfl_closing); +} + +void * get_pfwd_backend(void * handle) +{ + void * backend = NULL; + Plug fn = (Plug)handle; + if ((*fn)->closing == pfl_closing) + { + backend = ((struct PortListener *)handle)->backhandle; + } + else if ((*fn)->closing == pfd_closing) + { + backend = ((struct PortForwarding *)handle)->backhandle; + } + return backend; +} + +#endif diff --git a/netbox/libs/Putty/pproxy.c b/netbox/libs/Putty/pproxy.c new file mode 100644 index 000000000..1bea6fb84 --- /dev/null +++ b/netbox/libs/Putty/pproxy.c @@ -0,0 +1,17 @@ +/* + * pproxy.c: dummy implementation of platform_new_connection(), to + * be supplanted on any platform which has its own local proxy + * method. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf) +{ + return NULL; +} diff --git a/netbox/libs/Putty/proxy.c b/netbox/libs/Putty/proxy.c new file mode 100644 index 000000000..36928e65d --- /dev/null +++ b/netbox/libs/Putty/proxy.c @@ -0,0 +1,1512 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the network + * code and the higher level backend. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "proxy.h" + +#define do_proxy_dns(conf) \ + (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ + (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ + conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (Proxy_Socket p) +{ + void *data; + int len; + long output_before, output_after; + + p->state = PROXY_STATE_ACTIVE; + + /* we want to ignore new receive events until we have sent + * all of our buffered receive data. + */ + sk_set_frozen(p->sub_socket, 1); + + /* how many bytes of output have we buffered? */ + output_before = bufchain_size(&p->pending_oob_output_data) + + bufchain_size(&p->pending_output_data); + /* and keep track of how many bytes do not get sent. */ + output_after = 0; + + /* send buffered OOB writes */ + while (bufchain_size(&p->pending_oob_output_data) > 0) { + bufchain_prefix(&p->pending_oob_output_data, &data, &len); + output_after += sk_write_oob(p->sub_socket, data, len); + bufchain_consume(&p->pending_oob_output_data, len); + } + + /* send buffered normal writes */ + while (bufchain_size(&p->pending_output_data) > 0) { + bufchain_prefix(&p->pending_output_data, &data, &len); + output_after += sk_write(p->sub_socket, data, len); + bufchain_consume(&p->pending_output_data, len); + } + + /* if we managed to send any data, let the higher levels know. */ + if (output_after < output_before) + plug_sent(p->plug, output_after); + + /* if we were asked to flush the output during + * the proxy negotiation process, do so now. + */ + if (p->pending_flush) sk_flush(p->sub_socket); + + /* if we have a pending EOF to send, send it */ + if (p->pending_eof) sk_write_eof(p->sub_socket); + + /* if the backend wanted the socket unfrozen, try to unfreeze. + * our set_frozen handler will flush buffered receive data before + * unfreezing the actual underlying socket. + */ + if (!p->freeze) + sk_set_frozen((Socket)p, 0); +} + +/* basic proxy socket functions */ + +static Plug sk_proxy_plug (Socket s, Plug p) +{ + Proxy_Socket ps = (Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_proxy_close (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + sk_close(ps->sub_socket); + sk_addr_free(ps->remote_addr); + sfree(ps); +} + +static int sk_proxy_write (Socket s, const char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_add(&ps->pending_output_data, data, len); + return bufchain_size(&ps->pending_output_data); + } + return sk_write(ps->sub_socket, data, len); +} + +static int sk_proxy_write_oob (Socket s, const char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_clear(&ps->pending_output_data); + bufchain_clear(&ps->pending_oob_output_data); + bufchain_add(&ps->pending_oob_output_data, data, len); + return len; + } + return sk_write_oob(ps->sub_socket, data, len); +} + +static void sk_proxy_write_eof (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_eof = 1; + return; + } + sk_write_eof(ps->sub_socket); +} + +static void sk_proxy_flush (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_flush = 1; + return; + } + sk_flush(ps->sub_socket); +} + +static void sk_proxy_set_frozen (Socket s, int is_frozen) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->freeze = is_frozen; + return; + } + + /* handle any remaining buffered recv data first */ + if (bufchain_size(&ps->pending_input_data) > 0) { + ps->freeze = is_frozen; + + /* loop while we still have buffered data, and while we are + * unfrozen. the plug_receive call in the loop could result + * in a call back into this function refreezing the socket, + * so we have to check each time. + */ + while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { + void *data; + char databuf[512]; + int len; + bufchain_prefix(&ps->pending_input_data, &data, &len); + if (len > lenof(databuf)) + len = lenof(databuf); + memcpy(databuf, data, len); + bufchain_consume(&ps->pending_input_data, len); + plug_receive(ps->plug, 0, databuf, len); + } + + /* if we're still frozen, we'll have to wait for another + * call from the backend to finish unbuffering the data. + */ + if (ps->freeze) return; + } + + sk_set_frozen(ps->sub_socket, is_frozen); +} + +static const char * sk_proxy_socket_error (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + if (ps->error != NULL || ps->sub_socket == NULL) { + return ps->error; + } + return sk_socket_error(ps->sub_socket); +} + +/* basic proxy plug functions */ + +static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Proxy_Plug pp = (Proxy_Plug) plug; + Proxy_Socket ps = pp->proxy_socket; + + plug_log(ps->plug, type, addr, port, error_msg, error_code); +} + +static int plug_proxy_closing (Plug p, const char *error_msg, + int error_code, int calling_back) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->closing_calling_back = calling_back; + return ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } + return plug_closing(ps->plug, error_msg, + error_code, calling_back); +} + +static int plug_proxy_receive (Plug p, int urgent, char *data, int len) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + /* we will lose the urgentness of this data, but since most, + * if not all, of this data will be consumed by the negotiation + * process, hopefully it won't affect the protocol above us + */ + bufchain_add(&ps->pending_input_data, data, len); + ps->receive_urgent = urgent; + ps->receive_data = data; + ps->receive_len = len; + return ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } + return plug_receive(ps->plug, urgent, data, len); +} + +static void plug_proxy_sent (Plug p, int bufsize) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->sent_bufsize = bufsize; + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting(Plug p, + accept_fn_t constructor, accept_ctx_t ctx) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_constructor = constructor; + ps->accepting_ctx = ctx; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, constructor, ctx); +} + +/* + * This function can accept a NULL pointer as `addr', in which case + * it will only check the host name. + */ +int proxy_for_destination (SockAddr addr, const char *hostname, + int port, Conf *conf) +{ + int s = 0, e = 0; + char hostip[64]; + int hostip_len, hostname_len; + const char *exclude_list; + + /* + * Special local connections such as Unix-domain sockets + * unconditionally cannot be proxied, even in proxy-localhost + * mode. There just isn't any way to ask any known proxy type for + * them. + */ + if (addr && sk_address_is_special_local(addr)) + return 0; /* do not proxy */ + + /* + * Check the host name and IP against the hard-coded + * representations of `localhost'. + */ + if (!conf_get_int(conf, CONF_even_proxy_localhost) && + (sk_hostname_is_local(hostname) || + (addr && sk_address_is_local(addr)))) + return 0; /* do not proxy */ + + /* we want a string representation of the IP address for comparisons */ + if (addr) { + sk_getaddr(addr, hostip, 64); + hostip_len = strlen(hostip); + } else + hostip_len = 0; /* placate gcc; shouldn't be required */ + + hostname_len = strlen(hostname); + + exclude_list = conf_get_str(conf, CONF_proxy_exclude_list); + + /* now parse the exclude list, and see if either our IP + * or hostname matches anything in it. + */ + + while (exclude_list[s]) { + while (exclude_list[s] && + (isspace((unsigned char)exclude_list[s]) || + exclude_list[s] == ',')) s++; + + if (!exclude_list[s]) break; + + e = s; + + while (exclude_list[e] && + (isalnum((unsigned char)exclude_list[e]) || + exclude_list[e] == '-' || + exclude_list[e] == '.' || + exclude_list[e] == '*')) e++; + + if (exclude_list[s] == '*') { + /* wildcard at beginning of entry */ + + if ((addr && strnicmp(hostip + hostip_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) || + strnicmp(hostname + hostname_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else if (exclude_list[e-1] == '*') { + /* wildcard at end of entry */ + + if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) || + strnicmp(hostname, exclude_list + s, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else { + /* no wildcard at either end, so let's try an absolute + * match (ie. a specific IP) + */ + + if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + if (strnicmp(hostname, exclude_list + s, e - s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + } + + s = e; + + /* Make sure we really have reached the next comma or end-of-string */ + while (exclude_list[s] && + !isspace((unsigned char)exclude_list[s]) && + exclude_list[s] != ',') s++; + } + + /* no matches in the exclude list, so use the proxy */ + return 1; +} + +SockAddr name_lookup(char *host, int port, char **canonicalname, + Conf *conf, int addressfamily) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + do_proxy_dns(conf) && + proxy_for_destination(NULL, host, port, conf)) { + *canonicalname = dupstr(host); + return sk_nonamelookup(host); + } + + return sk_namelookup(host, canonicalname, addressfamily); +} + +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf) +{ + static const struct socket_function_table socket_fn_table = { + sk_proxy_plug, + sk_proxy_close, + sk_proxy_write, + sk_proxy_write_oob, + sk_proxy_write_eof, + sk_proxy_flush, + sk_proxy_set_frozen, + sk_proxy_socket_error, + NULL, /* peer_info */ + }; + + static const struct plug_function_table plug_fn_table = { + plug_proxy_log, + plug_proxy_closing, + plug_proxy_receive, + plug_proxy_sent, + plug_proxy_accepting + }; + + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + proxy_for_destination(addr, hostname, port, conf)) + { + Proxy_Socket ret; + Proxy_Plug pplug; + SockAddr proxy_addr; + char *proxy_canonical_name; + Socket sret; + int type; + + if ((sret = platform_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf)) != + NULL) + return sret; + + ret = snew(struct Socket_proxy_tag); + ret->fn = &socket_fn_table; + ret->conf = conf_copy(conf); + ret->plug = plug; + ret->remote_addr = addr; /* will need to be freed on close */ + ret->remote_port = port; + + ret->error = NULL; + ret->pending_flush = 0; + ret->pending_eof = 0; + ret->freeze = 0; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + bufchain_init(&ret->pending_oob_output_data); + + ret->sub_socket = NULL; + ret->state = PROXY_STATE_NEW; + ret->negotiate = NULL; + + type = conf_get_int(conf, CONF_proxy_type); + if (type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + } else if (type == PROXY_SOCKS4) { + ret->negotiate = proxy_socks4_negotiate; + } else if (type == PROXY_SOCKS5) { + ret->negotiate = proxy_socks5_negotiate; + } else if (type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + } else { + ret->error = "Proxy error: Unknown proxy method"; + return (Socket) ret; + } + + /* create the proxy plug to map calls from the actual + * socket into our proxy socket layer */ + pplug = snew(struct Plug_proxy_tag); + pplug->fn = &plug_fn_table; + pplug->proxy_socket = ret; + + /* look-up proxy */ + proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), + &proxy_canonical_name, + conf_get_int(conf, CONF_addressfamily)); + if (sk_addr_error(proxy_addr) != NULL) { + ret->error = "Proxy error: Unable to resolve proxy host name"; + sfree(pplug); + sk_addr_free(proxy_addr); + return (Socket)ret; + } + sfree(proxy_canonical_name); + + /* create the actual socket we will be using, + * connected to our proxy server and port. + */ + ret->sub_socket = putty_sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), + privport, oobinline, + nodelay, keepalive, (Plug) pplug); + if (sk_socket_error(ret->sub_socket) != NULL) + return (Socket) ret; + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, 0); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return (Socket) ret; + } + + /* no proxy, so just return the direct socket */ + return putty_sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); +} + +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, + Conf *conf, int addressfamily) +{ + /* TODO: SOCKS (and potentially others) support inbound + * TODO: connections via the proxy. support them. + */ + + return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); +} + +/* ---------------------------------------------------------------------- + * HTTP CONNECT proxy type. + */ + +static int get_line_end (char * data, int len) +{ + int off = 0; + + while (off < len) + { + if (data[off] == '\n') { + /* we have a newline */ + off++; + + /* is that the only thing on this line? */ + if (off <= 2) return off; + + /* if not, then there is the possibility that this header + * continues onto the next line, if it starts with a space + * or a tab. + */ + + if (off + 1 < len && + data[off+1] != ' ' && + data[off+1] != '\t') return off; + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return -1; +} + +int proxy_http_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_STATE_NEW) { + /* we are just beginning the proxy negotiate process, + * so we'll send off the initial bits of the request. + * for this proxy method, it's just a simple HTTP + * request + */ + char *buf, dest[512]; + char *username, *password; + + sk_getaddr(p->remote_addr, dest, lenof(dest)); + + buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", + dest, p->remote_port, dest, p->remote_port); + sk_write(p->sub_socket, buf, strlen(buf)); + sfree(buf); + + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char *buf, *buf2; + int i, j, len; + buf = dupprintf("%s:%s", username, password); + len = strlen(buf); + buf2 = snewn(len * 4 / 3 + 100, char); + sprintf(buf2, "Proxy-Authorization: Basic "); + for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) + base64_encode_atom((unsigned char *)(buf+i), + (len-i > 3 ? 3 : len-i), buf2+j); + strcpy(buf2+j, "\r\n"); + sk_write(p->sub_socket, buf2, strlen(buf2)); + sfree(buf); + sfree(buf2); + } + + sk_write(p->sub_socket, "\r\n", 2); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + char *data, *datap; + int len; + int eol; + + if (p->state == 1) { + + int min_ver, maj_ver, status; + + /* get the status line */ + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len+1, char); + bufchain_fetch(&p->pending_input_data, data, len); + /* + * We must NUL-terminate this data, because Windows + * sscanf appears to require a NUL at the end of the + * string because it strlens it _first_. Sigh. + */ + data[len] = '\0'; + + eol = get_line_end(data, len); + if (eol < 0) { + sfree(data); + return 1; + } + + status = -1; + /* We can't rely on whether the %n incremented the sscanf return */ + if (sscanf((char *)data, "HTTP/%i.%i %n", + &maj_ver, &min_ver, &status) < 2 || status == -1) { + plug_closing(p->plug, "Proxy error: HTTP response was absent", + PROXY_ERROR_GENERAL, 0); + sfree(data); + return 1; + } + + /* remove the status line from the input buffer. */ + bufchain_consume(&p->pending_input_data, eol); + if (data[status] != '2') { + /* error */ + char *buf; + data[eol] = '\0'; + while (eol > status && + (data[eol-1] == '\r' || data[eol-1] == '\n')) + data[--eol] = '\0'; + buf = dupprintf("Proxy error: %s", data+status); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + sfree(buf); + sfree(data); + return 1; + } + + sfree(data); + + p->state = 2; + } + + if (p->state == 2) { + + /* get headers. we're done when we get a + * header of length 2, (ie. just "\r\n") + */ + + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len, char); + datap = data; + bufchain_fetch(&p->pending_input_data, data, len); + + eol = get_line_end(datap, len); + if (eol < 0) { + sfree(data); + return 1; + } + while (eol > 2) + { + bufchain_consume(&p->pending_input_data, eol); + datap += eol; + len -= eol; + eol = get_line_end(datap, len); + } + + if (eol == 2) { + /* we're done */ + bufchain_consume(&p->pending_input_data, 2); + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + sfree(data); + return 1; + } + + sfree(data); + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type. + */ + +/* SOCKS version 4 */ +int proxy_socks4_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* request format: + * version number (1 byte) = 4 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * dest. port (2 bytes) [network order] + * dest. address (4 bytes) + * user ID (variable length, null terminated string) + */ + + int length, type, namelen; + char *command, addr[4], hostname[512]; + char *username; + + type = sk_addrtype(p->remote_addr); + if (type == ADDRTYPE_IPV6) { + p->error = "Proxy error: SOCKS version 4 does not support IPv6"; + return 1; + } else if (type == ADDRTYPE_IPV4) { + namelen = 0; + sk_addrcopy(p->remote_addr, addr); + } else { /* type == ADDRTYPE_NAME */ + assert(type == ADDRTYPE_NAME); + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + namelen = strlen(hostname) + 1; /* include the NUL */ + addr[0] = addr[1] = addr[2] = 0; + addr[3] = 1; + } + + username = conf_get_str(p->conf, CONF_proxy_username); + length = strlen(username) + namelen + 9; + command = snewn(length, char); + strcpy(command + 8, username); + + command[0] = 4; /* version 4 */ + command[1] = 1; /* CONNECT command */ + + /* port */ + command[2] = (char) (p->remote_port >> 8) & 0xff; + command[3] = (char) p->remote_port & 0xff; + + /* address */ + memcpy(command + 4, addr, 4); + + /* hostname */ + memcpy(command + 8 + strlen(username) + 1, + hostname, namelen); + + sk_write(p->sub_socket, command, length); + sfree(username); + sfree(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + /* response format: + * version number (1 byte) = 4 + * reply code (1 byte) + * 90 = request granted + * 91 = request rejected or failed + * 92 = request rejected due to lack of IDENTD on client + * 93 = request rejected due to difference in user ID + * (what we sent vs. what IDENTD said) + * dest. port (2 bytes) + * dest. address (4 bytes) + */ + + char data[8]; + + if (bufchain_size(&p->pending_input_data) < 8) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 8); + + if (data[0] != 0) { + plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " + "unexpected reply code version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 90) { + + switch (data[1]) { + case 92: + plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", + PROXY_ERROR_GENERAL, 0); + break; + case 93: + plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", + PROXY_ERROR_GENERAL, 0); + break; + case 91: + default: + plug_closing(p->plug, "Proxy error: Error while communicating with proxy", + PROXY_ERROR_GENERAL, 0); + break; + } + + return 1; + } + bufchain_consume(&p->pending_input_data, 8); + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* SOCKS version 5 */ +int proxy_socks5_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* initial command: + * version number (1 byte) = 5 + * number of available authentication methods (1 byte) + * available authentication methods (1 byte * previous value) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + */ + + char command[5]; + char *username, *password; + int len; + + command[0] = 5; /* version 5 */ + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + command[2] = 0x00; /* no authentication */ + len = 3; + proxy_socks5_offerencryptedauth (command, &len); + command[len++] = 0x02; /* username/password */ + command[1] = len - 2; /* Number of methods supported */ + } else { + command[1] = 1; /* one methods supported: */ + command[2] = 0x00; /* no authentication */ + len = 3; + } + + sk_write(p->sub_socket, command, len); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + + /* initial response: + * version number (1 byte) = 5 + * authentication method (1 byte) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + * 0xff = no acceptable methods + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] == 0x00) p->state = 2; /* no authentication needed */ + else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */ + else if (data[1] == 0x02) p->state = 5; /* username/password authentication */ + else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */ + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->state == 7) { + + /* password authentication reply format: + * version number (1 bytes) = 1 + * reply code (1 byte) + * 0 = succeeded + * >0 = failed + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 1) { + plug_closing(p->plug, "Proxy error: SOCKS password " + "subnegotiation contained wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + + plug_closing(p->plug, "Proxy error: SOCKS proxy refused" + " password authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + bufchain_consume(&p->pending_input_data, 2); + p->state = 2; /* now proceed as authenticated */ + } + + if (p->state == 8) { + int ret; + ret = proxy_socks5_handlechap(p); + if (ret) return ret; + } + + if (p->state == 2) { + + /* request format: + * version number (1 byte) = 5 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * 3 = UDP ASSOCIATE + * reserved (1 byte) = 0x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * dest. address (variable) + * dest. port (2 bytes) [network order] + */ + + char command[512]; + int len; + int type; + + type = sk_addrtype(p->remote_addr); + if (type == ADDRTYPE_IPV4) { + len = 10; /* 4 hdr + 4 addr + 2 trailer */ + command[3] = 1; /* IPv4 */ + sk_addrcopy(p->remote_addr, command+4); + } else if (type == ADDRTYPE_IPV6) { + len = 22; /* 4 hdr + 16 addr + 2 trailer */ + command[3] = 4; /* IPv6 */ + sk_addrcopy(p->remote_addr, command+4); + } else { + assert(type == ADDRTYPE_NAME); + command[3] = 3; + sk_getaddr(p->remote_addr, command+5, 256); + command[4] = strlen(command+5); + len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */ + } + + command[0] = 5; /* version 5 */ + command[1] = 1; /* CONNECT command */ + command[2] = 0x00; + + /* port */ + command[len-2] = (char) (p->remote_port >> 8) & 0xff; + command[len-1] = (char) p->remote_port & 0xff; + + sk_write(p->sub_socket, command, len); + + p->state = 3; + return 1; + } + + if (p->state == 3) { + + /* reply format: + * version number (1 bytes) = 5 + * reply code (1 byte) + * 0 = succeeded + * 1 = general SOCKS server failure + * 2 = connection not allowed by ruleset + * 3 = network unreachable + * 4 = host unreachable + * 5 = connection refused + * 6 = TTL expired + * 7 = command not supported + * 8 = address type not supported + * reserved (1 byte) = x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * server bound address (variable) + * server bound port (2 bytes) [network order] + */ + char data[5]; + int len; + + /* First 5 bytes of packet are enough to tell its length. */ + if (bufchain_size(&p->pending_input_data) < 5) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 5); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + char buf[256]; + + strcpy(buf, "Proxy error: "); + + switch (data[1]) { + case 1: strcat(buf, "General SOCKS server failure"); break; + case 2: strcat(buf, "Connection not allowed by ruleset"); break; + case 3: strcat(buf, "Network unreachable"); break; + case 4: strcat(buf, "Host unreachable"); break; + case 5: strcat(buf, "Connection refused"); break; + case 6: strcat(buf, "TTL expired"); break; + case 7: strcat(buf, "Command not supported"); break; + case 8: strcat(buf, "Address type not supported"); break; + default: sprintf(buf+strlen(buf), + "Unrecognised SOCKS error code %d", + data[1]); + break; + } + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + + return 1; + } + + /* + * Eat the rest of the reply packet. + */ + len = 6; /* first 4 bytes, last 2 */ + switch (data[3]) { + case 1: len += 4; break; /* IPv4 address */ + case 4: len += 16; break;/* IPv6 address */ + case 3: len += (unsigned char)data[4]; break; /* domain name */ + default: + plug_closing(p->plug, "Proxy error: SOCKS proxy returned " + "unrecognised address format", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (bufchain_size(&p->pending_input_data) < len) + return 1; /* not got whole reply yet */ + bufchain_consume(&p->pending_input_data, len); + + /* we're done */ + proxy_activate(p); + return 1; + } + + if (p->state == 4) { + /* TODO: Handle GSSAPI authentication */ + plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 5) { + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char userpwbuf[255 + 255 + 3]; + int ulen, plen; + ulen = strlen(username); + if (ulen > 255) ulen = 255; + if (ulen < 1) ulen = 1; + plen = strlen(password); + if (plen > 255) plen = 255; + if (plen < 1) plen = 1; + userpwbuf[0] = 1; /* version number of subnegotiation */ + userpwbuf[1] = ulen; + memcpy(userpwbuf+2, username, ulen); + userpwbuf[ulen+2] = plen; + memcpy(userpwbuf+ulen+3, password, plen); + sk_write(p->sub_socket, userpwbuf, ulen + plen + 3); + p->state = 7; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "username/password authentication but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 6) { + int ret; + ret = proxy_socks5_selectchap(p); + if (ret) return ret; + } + + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * `Telnet' proxy type. + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +char *format_telnet_command(SockAddr addr, int port, Conf *conf) +{ + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); + char *ret = NULL; + int retlen = 0, retsize = 0; + int so = 0, eo = 0; +#define ENSURE(n) do { \ + if (retsize < retlen + n) { \ + retsize = retlen + n + 512; \ + ret = sresize(ret, retsize, char); \ + } \ +} while (0) + + /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, + * %%, %host, %port, %user, and %pass + */ + + while (fmt[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; + + /* if we hit eol, break out of our escaping loop */ + if (fmt[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) { + ENSURE(eo - so); + memcpy(ret + retlen, fmt + so, eo - so); + retlen += eo - so; + } + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (fmt[eo] == 0) break; + + if (fmt[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (fmt[eo]) { + + case '\\': + ENSURE(1); + ret[retlen++] = '\\'; + eo++; + break; + + case '%': + ENSURE(1); + ret[retlen++] = '%'; + eo++; + break; + + case 'r': + ENSURE(1); + ret[retlen++] = '\r'; + eo++; + break; + + case 'n': + ENSURE(1); + ret[retlen++] = '\n'; + eo++; + break; + + case 't': + ENSURE(1); + ret[retlen++] = '\t'; + eo++; + break; + + case 'x': + case 'X': + { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + ENSURE(1); + ret[retlen++] = '\\'; + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + ENSURE(1); + ret[retlen++] = v; + eo++; + break; + } + + i++; + v <<= 4; + } + } + break; + + default: + ENSURE(2); + memcpy(ret+retlen, fmt + so, 2); + retlen += 2; + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (fmt[eo] == '%') { + ENSURE(1); + ret[retlen++] = '%'; + eo++; + } + else if (strnicmp(fmt + eo, "host", 4) == 0) { + char dest[512]; + int destlen; + sk_getaddr(addr, dest, lenof(dest)); + destlen = strlen(dest); + ENSURE(destlen); + memcpy(ret+retlen, dest, destlen); + retlen += destlen; + eo += 4; + } + else if (strnicmp(fmt + eo, "port", 4) == 0) { + char portstr[8], portlen; + portlen = sprintf(portstr, "%i", port); + ENSURE(portlen); + memcpy(ret + retlen, portstr, portlen); + retlen += portlen; + eo += 4; + } + else if (strnicmp(fmt + eo, "user", 4) == 0) { + char *username = conf_get_str(conf, CONF_proxy_username); + int userlen = strlen(username); + ENSURE(userlen); + memcpy(ret+retlen, username, userlen); + retlen += userlen; + eo += 4; + } + else if (strnicmp(fmt + eo, "pass", 4) == 0) { + char *password = conf_get_str(conf, CONF_proxy_password); + int passlen = strlen(password); + ENSURE(passlen); + memcpy(ret+retlen, password, passlen); + retlen += passlen; + eo += 4; + } + else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { + char *host = conf_get_str(conf, CONF_proxy_host); + int phlen = strlen(host); + ENSURE(phlen); + memcpy(ret+retlen, host, phlen); + retlen += phlen; + eo += 9; + } + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); + char pport[50]; + int pplen; + sprintf(pport, "%d", port); + pplen = strlen(pport); + ENSURE(pplen); + memcpy(ret+retlen, pport, pplen); + retlen += pplen; + eo += 9; + } + else { + /* we don't escape this, so send the % now, and + * don't advance eo, so that we'll consider the + * text immediately following the % as unescaped. + */ + ENSURE(1); + ret[retlen++] = '%'; + } + } + + /* resume scanning for additional escapes after this one. */ + so = eo; + } + + /* if there is any unescaped text at the end of the line, send it */ + if (eo != so) { + ENSURE(eo - so); + memcpy(ret + retlen, fmt + so, eo - so); + retlen += eo - so; + } + + ENSURE(1); + ret[retlen] = '\0'; + return ret; + +#undef ENSURE +} + +int proxy_telnet_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + char *formatted_cmd; + + formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, + p->conf); + + sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); + sfree(formatted_cmd); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} diff --git a/netbox/libs/Putty/proxy.h b/netbox/libs/Putty/proxy.h new file mode 100644 index 000000000..12b47e16d --- /dev/null +++ b/netbox/libs/Putty/proxy.h @@ -0,0 +1,125 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the + * network code and the higher level backend. + * + * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 + */ + +#ifndef PUTTY_PROXY_H +#define PUTTY_PROXY_H + +#define PROXY_ERROR_GENERAL 8000 +#define PROXY_ERROR_UNEXPECTED 8001 + +typedef struct Socket_proxy_tag * Proxy_Socket; + +struct Socket_proxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char * error; + + Socket sub_socket; + Plug plug; + SockAddr remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + int pending_flush; + bufchain pending_input_data; + int pending_eof; + +#define PROXY_STATE_NEW -1 +#define PROXY_STATE_ACTIVE 0 + + int state; /* proxy states greater than 0 are implementation + * dependent, but represent various stages/states + * of the initialization/setup/negotiation with the + * proxy server. + */ + int freeze; /* should we freeze the underlying socket when + * we are done with the proxy negotiation? this + * simply caches the value of sk_set_frozen calls. + */ + +#define PROXY_CHANGE_NEW -1 +#define PROXY_CHANGE_CLOSING 0 +#define PROXY_CHANGE_SENT 1 +#define PROXY_CHANGE_RECEIVE 2 +#define PROXY_CHANGE_ACCEPTING 3 + + /* something has changed (a call from the sub socket + * layer into our Proxy Plug layer, or we were just + * created, etc), so the proxy layer needs to handle + * this change (the type of which is the second argument) + * and further the proxy negotiation process. + */ + + int (*negotiate) (Proxy_Socket /* this */, int /* change type */); + + /* current arguments of plug handlers + * (for use by proxy's negotiate function) + */ + + /* closing */ + const char *closing_error_msg; + int closing_error_code; + int closing_calling_back; + + /* receive */ + int receive_urgent; + char *receive_data; + int receive_len; + + /* sent */ + int sent_bufsize; + + /* accepting */ + accept_fn_t accepting_constructor; + accept_ctx_t accepting_ctx; + + /* configuration, used to look up proxy settings */ + Conf *conf; + + /* CHAP transient data */ + int chap_num_attributes; + int chap_num_attributes_processed; + int chap_current_attribute; + int chap_current_datalen; +}; + +typedef struct Plug_proxy_tag * Proxy_Plug; + +struct Plug_proxy_tag { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + Proxy_Socket proxy_socket; + +}; + +extern void proxy_activate (Proxy_Socket); + +extern int proxy_http_negotiate (Proxy_Socket, int); +extern int proxy_telnet_negotiate (Proxy_Socket, int); +extern int proxy_socks4_negotiate (Proxy_Socket, int); +extern int proxy_socks5_negotiate (Proxy_Socket, int); + +/* + * This may be reused by local-command proxies on individual + * platforms. + */ +char *format_telnet_command(SockAddr addr, int port, Conf *conf); + +/* + * These are implemented in cproxy.c or nocproxy.c, depending on + * whether encrypted proxy authentication is available. + */ +extern void proxy_socks5_offerencryptedauth(char *command, int *len); +extern int proxy_socks5_handlechap (Proxy_Socket p); +extern int proxy_socks5_selectchap(Proxy_Socket p); + +#endif diff --git a/netbox/libs/Putty/pscp.c b/netbox/libs/Putty/pscp.c new file mode 100644 index 000000000..dc9e1f501 --- /dev/null +++ b/netbox/libs/Putty/pscp.c @@ -0,0 +1,2410 @@ +/* + * scp.c - Scp (Secure Copy) client for PuTTY. + * Joris van Rantwijk, Simon Tatham + * + * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen. + * They, in turn, used stuff from BSD rcp. + * + * (SGT, 2001-09-10: Joris van Rantwijk assures me that although + * this file as originally submitted was inspired by, and + * _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any + * actual code duplicated, so the above comment shouldn't give rise + * to licensing issues.) + */ + +#include +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "psftp.h" +#include "ssh.h" +#include "sftp.h" +#include "storage.h" +#include "int64.h" + +static int list = 0; +static int verbose = 0; +static int recursive = 0; +static int preserve = 0; +static int targetshouldbedirectory = 0; +static int statistics = 1; +static int prev_stats_len = 0; +static int scp_unsafe_mode = 0; +static int errs = 0; +static int try_scp = 1; +static int try_sftp = 1; +static int main_cmd_is_sftp = 0; +static int fallback_cmd_is_sftp = 0; +static int using_sftp = 0; +static int uploading = 0; + +static Backend *back; +static void *backhandle; +static Conf *conf; +int sent_eof = FALSE; + +static void source(char *src); +static void rsource(char *src); +static void sink(char *targ, char *src); + +const char *const appname = "PSCP"; + +/* + * The maximum amount of queued data we accept before we stop and + * wait for the server to process some. + */ +#define MAX_SCP_BUFSIZE 16384 + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + /* + * This is only here because of the calls to ldisc_send(NULL, + * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc + * as an ldisc. So if we get called with any real data, I want + * to know about it. + */ + assert(len == 0); +} + +static void tell_char(FILE * stream, char c) +{ + fputc(c, stream); +} + +static void tell_str(FILE * stream, char *str) +{ + unsigned int i; + + for (i = 0; i < strlen(str); ++i) + tell_char(stream, str[i]); +} + +static void tell_user(FILE * stream, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + va_end(ap); + str2 = dupcat(str, "\n", NULL); + sfree(str); + tell_str(stream, str2); + sfree(str2); +} + +/* + * Print an error message and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} +void modalfatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} +void nonfatal(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Error: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; +} +void connection_fatal(void *frontend, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} + +/* + * In pscp, all agent requests should be synchronous, so this is a + * never-called stub. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + assert(!"We shouldn't be here"); +} + +/* + * Receive a block of data from the SSH link. Block until all data + * is available. + * + * To do this, we repeatedly call the SSH protocol module, with our + * own trap in from_backend() to catch the data that comes back. We + * do this until we have enough data. + */ + +static unsigned char *outptr; /* where to put the data */ +static unsigned outlen; /* how much data required */ +static unsigned char *pending = NULL; /* any spare data */ +static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ +int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +{ + unsigned char *p = (unsigned char *) data; + unsigned len = (unsigned) datalen; + + /* + * stderr data is just spouted to local stderr and otherwise + * ignored. + */ + if (is_stderr) { + if (len > 0) + if (fwrite(data, 1, len, stderr) < len) + /* oh well */; + return 0; + } + + if ((outlen > 0) && (len > 0)) { + unsigned used = outlen; + if (used > len) + used = len; + memcpy(outptr, p, used); + outptr += used; + outlen -= used; + p += used; + len -= used; + } + + if (len > 0) { + if (pendsize < pendlen + len) { + pendsize = pendlen + len + 4096; + pending = sresize(pending, pendsize, unsigned char); + } + memcpy(pending + pendlen, p, len); + pendlen += len; + } + + return 0; +} +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} +int from_backend_eof(void *frontend) +{ + /* + * We usually expect to be the party deciding when to close the + * connection, so if we see EOF before we sent it ourselves, we + * should panic. The exception is if we're using old-style scp and + * downloading rather than uploading. + */ + if ((using_sftp || uploading) && !sent_eof) { + connection_fatal(frontend, + "Received unexpected end-of-file from server"); + } + return FALSE; +} +static int ssh_scp_recv(unsigned char *buf, int len) +{ + outptr = buf; + outlen = len; + + /* + * See if the pending-input block contains some of what we + * need. + */ + if (pendlen > 0) { + unsigned pendused = pendlen; + if (pendused > outlen) + pendused = outlen; + memcpy(outptr, pending, pendused); + memmove(pending, pending + pendused, pendlen - pendused); + outptr += pendused; + outlen -= pendused; + pendlen -= pendused; + if (pendlen == 0) { + pendsize = 0; + sfree(pending); + pending = NULL; + } + if (outlen == 0) + return len; + } + + while (outlen > 0) { + if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) + return 0; /* doom */ + } + + return len; +} + +/* + * Loop through the ssh connection and authentication process. + */ +static void ssh_scp_init(void) +{ + while (!back->sendok(backhandle)) { + if (back->exitcode(backhandle) >= 0) { + errs++; + return; + } + if (ssh_sftp_loop_iteration() < 0) { + errs++; + return; /* doom */ + } + } + + /* Work out which backend we ended up using. */ + if (!ssh_fallback_cmd(backhandle)) + using_sftp = main_cmd_is_sftp; + else + using_sftp = fallback_cmd_is_sftp; + + if (verbose) { + if (using_sftp) + tell_user(stderr, "Using SFTP"); + else + tell_user(stderr, "Using SCP1"); + } +} + +/* + * Print an error message and exit after closing the SSH link. + */ +static void bump(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + va_end(ap); + str2 = dupcat(str, "\n", NULL); + sfree(str); + tell_str(stderr, str2); + sfree(str2); + errs++; + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + ssh_scp_recv((unsigned char *) &ch, 1); + } + + cleanup_exit(1); +} + +/* + * Wait for the reply to a single SFTP request. Parallels the same + * function in psftp.c (but isn't centralised into sftp.c because the + * latter module handles SFTP only and shouldn't assume that SFTP is + * the only thing going on by calling connection_fatal). + */ +struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) +{ + struct sftp_packet *pktin; + struct sftp_request *rreq; + + sftp_register(req); + pktin = sftp_recv(); + if (pktin == NULL) + connection_fatal(NULL, "did not receive SFTP response packet " + "from server"); + rreq = sftp_find_request(pktin); + if (rreq != req) + connection_fatal(NULL, "unable to understand SFTP response packet " + "from server: %s", fxp_error()); + return pktin; +} + +/* + * Open an SSH connection to user@host and execute cmd. + */ +static void do_cmd(char *host, char *user, char *cmd) +{ + const char *err; + char *realhost; + void *logctx; + + if (host == NULL || host[0] == '\0') + bump("Empty host name"); + + /* + * Remove a colon suffix. + */ + host[host_strcspn(host, ":")] = '\0'; + + /* + * If we haven't loaded session details already (e.g., from -load), + * try looking for a session called "host". + */ + if (!loaded_session) { + /* Try to load settings for `host' into a temporary config */ + Conf *conf2 = conf_new(); + conf_set_str(conf2, CONF_host, ""); + do_defaults(host, conf2); + if (conf_get_str(conf2, CONF_host)[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, conf); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + conf_set_str(conf, CONF_host, host); + } + } else { + /* Patch in hostname `host' to session details. */ + conf_set_str(conf, CONF_host, host); + } + + /* + * Force use of SSH. (If they got the protocol wrong we assume the + * port is useless too.) + */ + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { + conf_set_int(conf, CONF_protocol, PROT_SSH); + conf_set_int(conf, CONF_port, 22); + } + + /* + * Enact command-line overrides. + */ + cmdline_run_saved(conf); + + /* + * Muck about with the hostname in various ways. + */ + { + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); + } + + /* Set username */ + if (user != NULL && user[0] != '\0') { + conf_set_str(conf, CONF_username, user); + } else if (conf_get_str(conf, CONF_username)[0] == '\0') { + user = get_username(); + if (!user) + bump("Empty user name"); + else { + if (verbose) + tell_user(stderr, "Guessing user name: %s", user); + conf_set_str(conf, CONF_username, user); + sfree(user); + } + } + + /* + * Disable scary things which shouldn't be enabled for simple + * things like SCP and SFTP: agent forwarding, port forwarding, + * X forwarding. + */ + conf_set_int(conf, CONF_x11_forward, 0); + conf_set_int(conf, CONF_agentfwd, 0); + conf_set_int(conf, CONF_ssh_simple, TRUE); + { + char *key; + while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) + conf_del_str_str(conf, CONF_portfwd, key); + } + + /* + * Set up main and possibly fallback command depending on + * options specified by user. + * Attempt to start the SFTP subsystem as a first choice, + * falling back to the provided scp command if that fails. + */ + conf_set_str(conf, CONF_remote_cmd2, ""); + if (try_sftp) { + /* First choice is SFTP subsystem. */ + main_cmd_is_sftp = 1; + conf_set_str(conf, CONF_remote_cmd, "sftp"); + conf_set_int(conf, CONF_ssh_subsys, TRUE); + if (try_scp) { + /* Fallback is to use the provided scp command. */ + fallback_cmd_is_sftp = 0; + conf_set_str(conf, CONF_remote_cmd2, cmd); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); + } else { + /* Since we're not going to try SCP, we may as well try + * harder to find an SFTP server, since in the current + * implementation we have a spare slot. */ + fallback_cmd_is_sftp = 1; + /* see psftp.c for full explanation of this kludge */ + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server &&" + " exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server &&" + " exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); + } + } else { + /* Don't try SFTP at all; just try the scp command. */ + main_cmd_is_sftp = 0; + conf_set_str(conf, CONF_remote_cmd, cmd); + conf_set_int(conf, CONF_ssh_subsys, FALSE); + } + conf_set_int(conf, CONF_nopty, TRUE); + + back = &ssh_backend; + + err = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_int(conf, CONF_tcp_keepalives)); + if (err != NULL) + bump("ssh_init: %s", err); + logctx = log_init(NULL, conf); + back->provide_logctx(backhandle, logctx); + console_provide_logctx(logctx); + ssh_scp_init(); + if (verbose && realhost != NULL && errs == 0) + tell_user(stderr, "Connected to %s", realhost); + sfree(realhost); +} + +/* + * Update statistic information about current file. + */ +static void print_stats(char *name, uint64 size, uint64 done, + time_t start, time_t now) +{ + float ratebs; + unsigned long eta; + char *etastr; + int pct; + int len; + int elap; + double donedbl; + double sizedbl; + + elap = (unsigned long) difftime(now, start); + + if (now > start) + ratebs = (float) (uint64_to_double(done) / elap); + else + ratebs = (float) uint64_to_double(done); + + if (ratebs < 1.0) + eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done))); + else { + eta = (unsigned long) + ((uint64_to_double(uint64_subtract(size, done)) / ratebs)); + } + + etastr = dupprintf("%02ld:%02ld:%02ld", + eta / 3600, (eta % 3600) / 60, eta % 60); + + donedbl = uint64_to_double(done); + sizedbl = uint64_to_double(size); + pct = (int) (100 * (donedbl * 1.0 / sizedbl)); + + { + char donekb[40]; + /* divide by 1024 to provide kB */ + uint64_decimal(uint64_shift_right(done, 10), donekb); + len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%", + name, + donekb, ratebs / 1024.0, etastr, pct); + if (len < prev_stats_len) + printf("%*s", prev_stats_len - len, ""); + prev_stats_len = len; + + if (uint64_compare(done, size) == 0) + printf("\n"); + + fflush(stdout); + } + + free(etastr); +} + +/* + * Find a colon in str and return a pointer to the colon. + * This is used to separate hostname from filename. + */ +static char *colon(char *str) +{ + /* We ignore a leading colon, since the hostname cannot be + empty. We also ignore a colon as second character because + of filenames like f:myfile.txt. */ + if (str[0] == '\0' || str[0] == ':' || + (str[0] != '[' && str[1] == ':')) + return (NULL); + str += host_strcspn(str, ":/\\"); + if (*str == ':') + return (str); + else + return (NULL); +} + +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + +/* + * Determine whether a string is entirely composed of dots. + */ +static int is_dots(char *str) +{ + return str[strspn(str, ".")] == '\0'; +} + +/* + * Wait for a response from the other side. + * Return 0 if ok, -1 if error. + */ +static int response(void) +{ + char ch, resp, rbuf[2048]; + int p; + + if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0) + bump("Lost connection"); + + p = 0; + switch (resp) { + case 0: /* ok */ + return (0); + default: + rbuf[p++] = resp; + /* fallthrough */ + case 1: /* error */ + case 2: /* fatal error */ + do { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + bump("Protocol error: Lost connection"); + rbuf[p++] = ch; + } while (p < sizeof(rbuf) && ch != '\n'); + rbuf[p - 1] = '\0'; + if (resp == 1) + tell_user(stderr, "%s", rbuf); + else + bump("%s", rbuf); + errs++; + return (-1); + } +} + +int sftp_recvdata(char *buf, int len) +{ + return ssh_scp_recv((unsigned char *) buf, len); +} +int sftp_senddata(char *buf, int len) +{ + back->send(backhandle, buf, len); + return 1; +} + +/* ---------------------------------------------------------------------- + * sftp-based replacement for the hacky `pscp -ls'. + */ +static int sftp_ls_compare(const void *av, const void *bv) +{ + const struct fxp_name *a = (const struct fxp_name *) av; + const struct fxp_name *b = (const struct fxp_name *) bv; + return strcmp(a->filename, b->filename); +} +void scp_sftp_listdir(char *dirname) +{ + struct fxp_handle *dirh; + struct fxp_names *names; + struct fxp_name *ournames; + struct sftp_packet *pktin; + struct sftp_request *req; + int nnames, namesize; + int i; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return; + } + + printf("Listing directory %s\n", dirname); + + req = fxp_opendir_send(dirname); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + + if (dirh == NULL) { + printf("Unable to open %s: %s\n", dirname, fxp_error()); + } else { + nnames = namesize = 0; + ournames = NULL; + + while (1) { + + req = fxp_readdir_send(dirh); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("Reading directory %s: %s\n", dirname, fxp_error()); + break; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name); + } + + for (i = 0; i < names->nnames; i++) + ournames[nnames++] = names->names[i]; + names->nnames = 0; /* prevent free_names */ + fxp_free_names(names); + } + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + /* + * Now we have our filenames. Sort them by actual file + * name, and then output the longname parts. + */ + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); + + /* + * And print them. + */ + for (i = 0; i < nnames; i++) + printf("%s\n", ournames[i].longname); + + sfree(ournames); + } +} + +/* ---------------------------------------------------------------------- + * Helper routines that contain the actual SCP protocol elements, + * implemented both as SCP1 and SFTP. + */ + +static struct scp_sftp_dirstack { + struct scp_sftp_dirstack *next; + struct fxp_name *names; + int namepos, namelen; + char *dirpath; + char *wildcard; + int matched_something; /* wildcard match set was non-empty */ +} *scp_sftp_dirstack_head; +static char *scp_sftp_remotepath, *scp_sftp_currentname; +static char *scp_sftp_wildcard; +static int scp_sftp_targetisdir, scp_sftp_donethistarget; +static int scp_sftp_preserve, scp_sftp_recursive; +static unsigned long scp_sftp_mtime, scp_sftp_atime; +static int scp_has_times; +static struct fxp_handle *scp_sftp_filehandle; +static struct fxp_xfer *scp_sftp_xfer; +static uint64 scp_sftp_fileoffset; + +int scp_source_setup(char *target, int shouldbedir) +{ + if (using_sftp) { + /* + * Find out whether the target filespec is in fact a + * directory. + */ + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_attrs attrs; + int ret; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return 1; + } + + req = fxp_stat_send(target); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) + scp_sftp_targetisdir = 0; + else + scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0; + + if (shouldbedir && !scp_sftp_targetisdir) { + bump("pscp: remote filespec %s: not a directory\n", target); + } + + scp_sftp_remotepath = dupstr(target); + + scp_has_times = 0; + } else { + (void) response(); + } + return 0; +} + +int scp_send_errmsg(char *str) +{ + if (using_sftp) { + /* do nothing; we never need to send our errors to the server */ + } else { + back->send(backhandle, "\001", 1);/* scp protocol error prefix */ + back->send(backhandle, str, strlen(str)); + } + return 0; /* can't fail */ +} + +int scp_send_filetimes(unsigned long mtime, unsigned long atime) +{ + if (using_sftp) { + scp_sftp_mtime = mtime; + scp_sftp_atime = atime; + scp_has_times = 1; + return 0; + } else { + char buf[80]; + sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime); + back->send(backhandle, buf, strlen(buf)); + return response(); + } +} + +int scp_send_filename(char *name, uint64 size, int permissions) +{ + if (using_sftp) { + char *fullname; + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_attrs attrs; + + if (scp_sftp_targetisdir) { + fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + } else { + fullname = dupstr(scp_sftp_remotepath); + } + + attrs.flags = 0; + PUT_PERMISSIONS(attrs, permissions); + + req = fxp_open_send(fullname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + &attrs); + pktin = sftp_wait_for_reply(req); + scp_sftp_filehandle = fxp_open_recv(pktin, req); + + if (!scp_sftp_filehandle) { + tell_user(stderr, "pscp: unable to open %s: %s", + fullname, fxp_error()); + sfree(fullname); + errs++; + return 1; + } + scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle, + scp_sftp_fileoffset); + sfree(fullname); + return 0; + } else { + char buf[40]; + char sizestr[40]; + uint64_decimal(size, sizestr); + if (permissions < 0) + permissions = 0644; + sprintf(buf, "C%04o %s ", (int)(permissions & 07777), sizestr); + back->send(backhandle, buf, strlen(buf)); + back->send(backhandle, name, strlen(name)); + back->send(backhandle, "\n", 1); + return response(); + } +} + +int scp_send_filedata(char *data, int len) +{ + if (using_sftp) { + int ret; + struct sftp_packet *pktin; + + if (!scp_sftp_filehandle) { + return 1; + } + + while (!xfer_upload_ready(scp_sftp_xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "error while writing: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return 1; + } + } + + xfer_upload_data(scp_sftp_xfer, data, len); + + scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len); + return 0; + } else { + int bufsize = back->send(backhandle, data, len); + + /* + * If the network transfer is backing up - that is, the + * remote site is not accepting data as fast as we can + * produce it - then we must loop on network events until + * we have space in the buffer again. + */ + while (bufsize > MAX_SCP_BUFSIZE) { + if (ssh_sftp_loop_iteration() < 0) + return 1; + bufsize = back->sendbuffer(backhandle); + } + + return 0; + } +} + +int scp_send_finish(void) +{ + if (using_sftp) { + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req; + int ret; + + while (!xfer_done(scp_sftp_xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "error while writing: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return 1; + } + } + xfer_cleanup(scp_sftp_xfer); + + if (!scp_sftp_filehandle) { + return 1; + } + if (scp_has_times) { + attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attrs.atime = scp_sftp_atime; + attrs.mtime = scp_sftp_mtime; + req = fxp_fsetstat_send(scp_sftp_filehandle, attrs); + pktin = sftp_wait_for_reply(req); + ret = fxp_fsetstat_recv(pktin, req); + if (!ret) { + tell_user(stderr, "unable to set file times: %s", fxp_error()); + errs++; + } + } + req = fxp_close_send(scp_sftp_filehandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + scp_has_times = 0; + return 0; + } else { + back->send(backhandle, "", 1); + return response(); + } +} + +char *scp_save_remotepath(void) +{ + if (using_sftp) + return scp_sftp_remotepath; + else + return NULL; +} + +void scp_restore_remotepath(char *data) +{ + if (using_sftp) + scp_sftp_remotepath = data; +} + +int scp_send_dirname(char *name, int modes) +{ + if (using_sftp) { + char *fullname; + char const *err; + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req; + int ret; + + if (scp_sftp_targetisdir) { + fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + } else { + fullname = dupstr(scp_sftp_remotepath); + } + + /* + * We don't worry about whether we managed to create the + * directory, because if it exists already it's OK just to + * use it. Instead, we will stat it afterwards, and if it + * exists and is a directory we will assume we were either + * successful or it didn't matter. + */ + req = fxp_mkdir_send(fullname); + pktin = sftp_wait_for_reply(req); + ret = fxp_mkdir_recv(pktin, req); + + if (!ret) + err = fxp_error(); + else + err = "server reported no error"; + + req = fxp_stat_send(fullname); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + tell_user(stderr, "unable to create directory %s: %s", + fullname, err); + sfree(fullname); + errs++; + return 1; + } + + scp_sftp_remotepath = fullname; + + return 0; + } else { + char buf[40]; + sprintf(buf, "D%04o 0 ", modes); + back->send(backhandle, buf, strlen(buf)); + back->send(backhandle, name, strlen(name)); + back->send(backhandle, "\n", 1); + return response(); + } +} + +int scp_send_enddir(void) +{ + if (using_sftp) { + sfree(scp_sftp_remotepath); + return 0; + } else { + back->send(backhandle, "E\n", 2); + return response(); + } +} + +/* + * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init. + * That's bad. The difference is that scp_sink_setup is called once + * right at the start, whereas scp_sink_init is called to + * initialise every level of recursion in the protocol. + */ +int scp_sink_setup(char *source, int preserve, int recursive) +{ + if (using_sftp) { + char *newsource; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return 1; + } + /* + * It's possible that the source string we've been given + * contains a wildcard. If so, we must split the directory + * away from the wildcard itself (throwing an error if any + * wildcardness comes before the final slash) and arrange + * things so that a dirstack entry will be set up. + */ + newsource = snewn(1+strlen(source), char); + if (!wc_unescape(newsource, source)) { + /* Yes, here we go; it's a wildcard. Bah. */ + char *dupsource, *lastpart, *dirpart, *wildcard; + + sfree(newsource); + + dupsource = dupstr(source); + lastpart = stripslashes(dupsource, 0); + wildcard = dupstr(lastpart); + *lastpart = '\0'; + if (*dupsource && dupsource[1]) { + /* + * The remains of dupsource are at least two + * characters long, meaning the pathname wasn't + * empty or just `/'. Hence, we remove the trailing + * slash. + */ + lastpart[-1] = '\0'; + } else if (!*dupsource) { + /* + * The remains of dupsource are _empty_ - the whole + * pathname was a wildcard. Hence we need to + * replace it with ".". + */ + sfree(dupsource); + dupsource = dupstr("."); + } + + /* + * Now we have separated our string into dupsource (the + * directory part) and wildcard. Both of these will + * need freeing at some point. Next step is to remove + * wildcard escapes from the directory part, throwing + * an error if it contains a real wildcard. + */ + dirpart = snewn(1+strlen(dupsource), char); + if (!wc_unescape(dirpart, dupsource)) { + tell_user(stderr, "%s: multiple-level wildcards unsupported", + source); + errs++; + sfree(dirpart); + sfree(wildcard); + sfree(dupsource); + return 1; + } + + /* + * Now we have dirpart (unescaped, ie a valid remote + * path), and wildcard (a wildcard). This will be + * sufficient to arrange a dirstack entry. + */ + scp_sftp_remotepath = dirpart; + scp_sftp_wildcard = wildcard; + sfree(dupsource); + } else { + scp_sftp_remotepath = newsource; + scp_sftp_wildcard = NULL; + } + scp_sftp_preserve = preserve; + scp_sftp_recursive = recursive; + scp_sftp_donethistarget = 0; + scp_sftp_dirstack_head = NULL; + } + return 0; +} + +int scp_sink_init(void) +{ + if (!using_sftp) { + back->send(backhandle, "", 1); + } + return 0; +} + +#define SCP_SINK_FILE 1 +#define SCP_SINK_DIR 2 +#define SCP_SINK_ENDDIR 3 +#define SCP_SINK_RETRY 4 /* not an action; just try again */ +struct scp_sink_action { + int action; /* FILE, DIR, ENDDIR */ + char *buf; /* will need freeing after use */ + char *name; /* filename or dirname (not ENDDIR) */ + long permissions; /* access permissions (not ENDDIR) */ + uint64 size; /* file size (not ENDDIR) */ + int settime; /* 1 if atime and mtime are filled */ + unsigned long atime, mtime; /* access times for the file */ +}; + +int scp_get_sink_action(struct scp_sink_action *act) +{ + if (using_sftp) { + char *fname; + int must_free_fname; + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req; + int ret; + + if (!scp_sftp_dirstack_head) { + if (!scp_sftp_donethistarget) { + /* + * Simple case: we are only dealing with one file. + */ + fname = scp_sftp_remotepath; + must_free_fname = 0; + scp_sftp_donethistarget = 1; + } else { + /* + * Even simpler case: one file _which we've done_. + * Return 1 (finished). + */ + return 1; + } + } else { + /* + * We're now in the middle of stepping through a list + * of names returned from fxp_readdir(); so let's carry + * on. + */ + struct scp_sftp_dirstack *head = scp_sftp_dirstack_head; + while (head->namepos < head->namelen && + (is_dots(head->names[head->namepos].filename) || + (head->wildcard && + !wc_match(head->wildcard, + head->names[head->namepos].filename)))) + head->namepos++; /* skip . and .. */ + if (head->namepos < head->namelen) { + head->matched_something = 1; + fname = dupcat(head->dirpath, "/", + head->names[head->namepos++].filename, + NULL); + must_free_fname = 1; + } else { + /* + * We've come to the end of the list; pop it off + * the stack and return an ENDDIR action (or RETRY + * if this was a wildcard match). + */ + if (head->wildcard) { + act->action = SCP_SINK_RETRY; + if (!head->matched_something) { + tell_user(stderr, "pscp: wildcard '%s' matched " + "no files", head->wildcard); + errs++; + } + sfree(head->wildcard); + + } else { + act->action = SCP_SINK_ENDDIR; + } + + sfree(head->dirpath); + sfree(head->names); + scp_sftp_dirstack_head = head->next; + sfree(head); + + return 0; + } + } + + /* + * Now we have a filename. Stat it, and see if it's a file + * or a directory. + */ + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + tell_user(stderr, "unable to identify %s: %s", fname, + ret ? "file type not supplied" : fxp_error()); + if (must_free_fname) sfree(fname); + errs++; + return 1; + } + + if (attrs.permissions & 0040000) { + struct scp_sftp_dirstack *newitem; + struct fxp_handle *dirhandle; + int nnames, namesize; + struct fxp_name *ournames; + struct fxp_names *names; + + /* + * It's a directory. If we're not in recursive mode, + * this merits a complaint (which is fatal if the name + * was specified directly, but not if it was matched by + * a wildcard). + * + * We skip this complaint completely if + * scp_sftp_wildcard is set, because that's an + * indication that we're not actually supposed to + * _recursively_ transfer the dir, just scan it for + * things matching the wildcard. + */ + if (!scp_sftp_recursive && !scp_sftp_wildcard) { + tell_user(stderr, "pscp: %s: is a directory", fname); + errs++; + if (must_free_fname) sfree(fname); + if (scp_sftp_dirstack_head) { + act->action = SCP_SINK_RETRY; + return 0; + } else { + return 1; + } + } + + /* + * Otherwise, the fun begins. We must fxp_opendir() the + * directory, slurp the filenames into memory, return + * SCP_SINK_DIR (unless this is a wildcard match), and + * set targetisdir. The next time we're called, we will + * run through the list of filenames one by one, + * matching them against a wildcard if present. + * + * If targetisdir is _already_ set (meaning we're + * already in the middle of going through another such + * list), we must push the other (target,namelist) pair + * on a stack. + */ + req = fxp_opendir_send(fname); + pktin = sftp_wait_for_reply(req); + dirhandle = fxp_opendir_recv(pktin, req); + + if (!dirhandle) { + tell_user(stderr, "pscp: unable to open directory %s: %s", + fname, fxp_error()); + if (must_free_fname) sfree(fname); + errs++; + return 1; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + req = fxp_readdir_send(dirhandle); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + tell_user(stderr, "pscp: reading directory %s: %s", + fname, fxp_error()); + + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + if (must_free_fname) sfree(fname); + sfree(ournames); + errs++; + return 1; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name); + } + for (i = 0; i < names->nnames; i++) { + if (!strcmp(names->names[i].filename, ".") || + !strcmp(names->names[i].filename, "..")) { + /* + * . and .. are normal consequences of + * reading a directory, and aren't worth + * complaining about. + */ + } else if (!vet_filename(names->names[i].filename)) { + tell_user(stderr, "ignoring potentially dangerous server-" + "supplied filename '%s'", + names->names[i].filename); + } else + ournames[nnames++] = names->names[i]; + } + names->nnames = 0; /* prevent free_names */ + fxp_free_names(names); + } + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + newitem = snew(struct scp_sftp_dirstack); + newitem->next = scp_sftp_dirstack_head; + newitem->names = ournames; + newitem->namepos = 0; + newitem->namelen = nnames; + if (must_free_fname) + newitem->dirpath = fname; + else + newitem->dirpath = dupstr(fname); + if (scp_sftp_wildcard) { + newitem->wildcard = scp_sftp_wildcard; + newitem->matched_something = 0; + scp_sftp_wildcard = NULL; + } else { + newitem->wildcard = NULL; + } + scp_sftp_dirstack_head = newitem; + + if (newitem->wildcard) { + act->action = SCP_SINK_RETRY; + } else { + act->action = SCP_SINK_DIR; + act->buf = dupstr(stripslashes(fname, 0)); + act->name = act->buf; + act->size = uint64_make(0,0); /* duhh, it's a directory */ + act->permissions = 07777 & attrs.permissions; + if (scp_sftp_preserve && + (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + act->atime = attrs.atime; + act->mtime = attrs.mtime; + act->settime = 1; + } else + act->settime = 0; + } + return 0; + + } else { + /* + * It's a file. Return SCP_SINK_FILE. + */ + act->action = SCP_SINK_FILE; + act->buf = dupstr(stripslashes(fname, 0)); + act->name = act->buf; + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { + act->size = attrs.size; + } else + act->size = uint64_make(ULONG_MAX,ULONG_MAX); /* no idea */ + act->permissions = 07777 & attrs.permissions; + if (scp_sftp_preserve && + (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + act->atime = attrs.atime; + act->mtime = attrs.mtime; + act->settime = 1; + } else + act->settime = 0; + if (must_free_fname) + scp_sftp_currentname = fname; + else + scp_sftp_currentname = dupstr(fname); + return 0; + } + + } else { + int done = 0; + int i, bufsize; + int action; + char ch; + + act->settime = 0; + act->buf = NULL; + bufsize = 0; + + while (!done) { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + return 1; + if (ch == '\n') + bump("Protocol error: Unexpected newline"); + i = 0; + action = ch; + do { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + bump("Lost connection"); + if (i >= bufsize) { + bufsize = i + 128; + act->buf = sresize(act->buf, bufsize, char); + } + act->buf[i++] = ch; + } while (ch != '\n'); + act->buf[i - 1] = '\0'; + switch (action) { + case '\01': /* error */ + tell_user(stderr, "%s", act->buf); + errs++; + continue; /* go round again */ + case '\02': /* fatal error */ + bump("%s", act->buf); + case 'E': + back->send(backhandle, "", 1); + act->action = SCP_SINK_ENDDIR; + return 0; + case 'T': + if (sscanf(act->buf, "%ld %*d %ld %*d", + &act->mtime, &act->atime) == 2) { + act->settime = 1; + back->send(backhandle, "", 1); + continue; /* go round again */ + } + bump("Protocol error: Illegal time format"); + case 'C': + case 'D': + act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR); + break; + default: + bump("Protocol error: Expected control record"); + } + /* + * We will go round this loop only once, unless we hit + * `continue' above. + */ + done = 1; + } + + /* + * If we get here, we must have seen SCP_SINK_FILE or + * SCP_SINK_DIR. + */ + { + char sizestr[40]; + + if (sscanf(act->buf, "%lo %39s %n", &act->permissions, + sizestr, &i) != 2) + bump("Protocol error: Illegal file descriptor format"); + act->size = uint64_from_decimal(sizestr); + act->name = act->buf + i; + return 0; + } + } +} + +int scp_accept_filexfer(void) +{ + if (using_sftp) { + struct sftp_packet *pktin; + struct sftp_request *req; + + req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ, NULL); + pktin = sftp_wait_for_reply(req); + scp_sftp_filehandle = fxp_open_recv(pktin, req); + + if (!scp_sftp_filehandle) { + tell_user(stderr, "pscp: unable to open %s: %s", + scp_sftp_currentname, fxp_error()); + errs++; + return 1; + } + scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_xfer = xfer_download_init(scp_sftp_filehandle, + scp_sftp_fileoffset); + sfree(scp_sftp_currentname); + return 0; + } else { + back->send(backhandle, "", 1); + return 0; /* can't fail */ + } +} + +int scp_recv_filedata(char *data, int len) +{ + if (using_sftp) { + struct sftp_packet *pktin; + int ret, actuallen; + void *vbuf; + + xfer_download_queue(scp_sftp_xfer); + pktin = sftp_recv(); + ret = xfer_download_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "pscp: error while reading: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return -1; + } + + if (xfer_download_data(scp_sftp_xfer, &vbuf, &actuallen)) { + /* + * This assertion relies on the fact that the natural + * block size used in the xfer manager is at most that + * used in this module. I don't like crossing layers in + * this way, but it'll do for now. + */ + assert(actuallen <= len); + memcpy(data, vbuf, actuallen); + sfree(vbuf); + } else + actuallen = 0; + + scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen); + + return actuallen; + } else { + return ssh_scp_recv((unsigned char *) data, len); + } +} + +int scp_finish_filerecv(void) +{ + if (using_sftp) { + struct sftp_packet *pktin; + struct sftp_request *req; + + /* + * Ensure that xfer_done() will work correctly, so we can + * clean up any outstanding requests from the file + * transfer. + */ + xfer_set_error(scp_sftp_xfer); + while (!xfer_done(scp_sftp_xfer)) { + void *vbuf; + int ret, len; + + pktin = sftp_recv(); + ret = xfer_download_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "pscp: error while reading: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return -1; + } + if (xfer_download_data(scp_sftp_xfer, &vbuf, &len)) + sfree(vbuf); + } + xfer_cleanup(scp_sftp_xfer); + + req = fxp_close_send(scp_sftp_filehandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + return 0; + } else { + back->send(backhandle, "", 1); + return response(); + } +} + +/* ---------------------------------------------------------------------- + * Send an error message to the other side and to the screen. + * Increment error counter. + */ +static void run_err(const char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + errs++; + str = dupvprintf(fmt, ap); + str2 = dupcat("pscp: ", str, "\n", NULL); + sfree(str); + scp_send_errmsg(str2); + tell_user(stderr, "%s", str2); + va_end(ap); + sfree(str2); +} + +/* + * Execute the source part of the SCP protocol. + */ +static void source(char *src) +{ + uint64 size; + unsigned long mtime, atime; + long permissions; + char *last; + RFile *f; + int attr; + uint64 i; + uint64 stat_bytes; + time_t stat_starttime, stat_lasttime; + + attr = file_type(src); + if (attr == FILE_TYPE_NONEXISTENT || + attr == FILE_TYPE_WEIRD) { + run_err("%s: %s file or directory", src, + (attr == FILE_TYPE_WEIRD ? "Not a" : "No such")); + return; + } + + if (attr == FILE_TYPE_DIRECTORY) { + if (recursive) { + /* + * Avoid . and .. directories. + */ + char *p; + p = strrchr(src, '/'); + if (!p) + p = strrchr(src, '\\'); + if (!p) + p = src; + else + p++; + if (!strcmp(p, ".") || !strcmp(p, "..")) + /* skip . and .. */ ; + else + rsource(src); + } else { + run_err("%s: not a regular file", src); + } + return; + } + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + f = open_existing_file(src, &size, &mtime, &atime, &permissions); + if (f == NULL) { + run_err("%s: Cannot open file", src); + return; + } + if (preserve) { + if (scp_send_filetimes(mtime, atime)) { + close_rfile(f); + return; + } + } + + if (verbose) { + char sizestr[40]; + uint64_decimal(size, sizestr); + tell_user(stderr, "Sending file %s, size=%s", last, sizestr); + } + if (scp_send_filename(last, size, permissions)) { + close_rfile(f); + return; + } + + stat_bytes = uint64_make(0,0); + stat_starttime = time(NULL); + stat_lasttime = 0; + + for (i = uint64_make(0,0); + uint64_compare(i,size) < 0; + i = uint64_add32(i,4096)) { + char transbuf[4096]; + int j, k = 4096; + + if (uint64_compare(uint64_add32(i, k),size) > 0) /* i + k > size */ + k = (uint64_subtract(size, i)).lo; /* k = size - i; */ + if ((j = read_from_file(f, transbuf, k)) != k) { + if (statistics) + printf("\n"); + bump("%s: Read error", src); + } + if (scp_send_filedata(transbuf, k)) + bump("%s: Network error occurred", src); + + if (statistics) { + stat_bytes = uint64_add32(stat_bytes, k); + if (time(NULL) != stat_lasttime || + (uint64_compare(uint64_add32(i, k), size) == 0)) { + stat_lasttime = time(NULL); + print_stats(last, size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + + } + close_rfile(f); + + (void) scp_send_finish(); +} + +/* + * Recursively send the contents of a directory. + */ +static void rsource(char *src) +{ + char *last; + char *save_target; + DirHandle *dir; + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + /* maybe send filetime */ + + save_target = scp_save_remotepath(); + + if (verbose) + tell_user(stderr, "Entering directory: %s", last); + if (scp_send_dirname(last, 0755)) + return; + + dir = open_directory(src); + if (dir != NULL) { + char *filename; + while ((filename = read_filename(dir)) != NULL) { + char *foundfile = dupcat(src, "/", filename, NULL); + source(foundfile); + sfree(foundfile); + sfree(filename); + } + } + close_directory(dir); + + (void) scp_send_enddir(); + + scp_restore_remotepath(save_target); +} + +/* + * Execute the sink part of the SCP protocol. + */ +static void sink(char *targ, char *src) +{ + char *destfname; + int targisdir = 0; + int exists; + int attr; + WFile *f; + uint64 received; + int wrerror = 0; + uint64 stat_bytes; + time_t stat_starttime, stat_lasttime; + char *stat_name; + + attr = file_type(targ); + if (attr == FILE_TYPE_DIRECTORY) + targisdir = 1; + + if (targetshouldbedirectory && !targisdir) + bump("%s: Not a directory", targ); + + scp_sink_init(); + while (1) { + struct scp_sink_action act; + if (scp_get_sink_action(&act)) + return; + + if (act.action == SCP_SINK_ENDDIR) + return; + + if (act.action == SCP_SINK_RETRY) + continue; + + if (targisdir) { + /* + * Prevent the remote side from maliciously writing to + * files outside the target area by sending a filename + * containing `../'. In fact, it shouldn't be sending + * filenames with any slashes or colons in at all; so + * we'll find the last slash, backslash or colon in the + * filename and use only the part after that. (And + * warn!) + * + * In addition, we also ensure here that if we're + * copying a single file and the target is a directory + * (common usage: `pscp host:filename .') the remote + * can't send us a _different_ file name. We can + * distinguish this case because `src' will be non-NULL + * and the last component of that will fail to match + * (the last component of) the name sent. + * + * Well, not always; if `src' is a wildcard, we do + * expect to get back filenames that don't correspond + * exactly to it. Ideally in this case, we would like + * to ensure that the returned filename actually + * matches the wildcard pattern - but one of SCP's + * protocol infelicities is that wildcard matching is + * done at the server end _by the server's rules_ and + * so in general this is infeasible. Hence, we only + * accept filenames that don't correspond to `src' if + * unsafe mode is enabled or we are using SFTP (which + * resolves remote wildcards on the client side and can + * be trusted). + */ + char *striptarget, *stripsrc; + + striptarget = stripslashes(act.name, 1); + if (striptarget != act.name) { + tell_user(stderr, "warning: remote host sent a compound" + " pathname '%s'", act.name); + tell_user(stderr, " renaming local file to '%s'", + striptarget); + } + + /* + * Also check to see if the target filename is '.' or + * '..', or indeed '...' and so on because Windows + * appears to interpret those like '..'. + */ + if (is_dots(striptarget)) { + bump("security violation: remote host attempted to write to" + " a '.' or '..' path!"); + } + + if (src) { + stripsrc = stripslashes(src, 1); + if (strcmp(striptarget, stripsrc) && + !using_sftp && !scp_unsafe_mode) { + tell_user(stderr, "warning: remote host tried to write " + "to a file called '%s'", striptarget); + tell_user(stderr, " when we requested a file " + "called '%s'.", stripsrc); + tell_user(stderr, " If this is a wildcard, " + "consider upgrading to SSH-2 or using"); + tell_user(stderr, " the '-unsafe' option. Renaming" + " of this file has been disallowed."); + /* Override the name the server provided with our own. */ + striptarget = stripsrc; + } + } + + if (targ[0] != '\0') + destfname = dir_file_cat(targ, striptarget); + else + destfname = dupstr(striptarget); + } else { + /* + * In this branch of the if, the target area is a + * single file with an explicitly specified name in any + * case, so there's no danger. + */ + destfname = dupstr(targ); + } + attr = file_type(destfname); + exists = (attr != FILE_TYPE_NONEXISTENT); + + if (act.action == SCP_SINK_DIR) { + if (exists && attr != FILE_TYPE_DIRECTORY) { + run_err("%s: Not a directory", destfname); + sfree(destfname); + continue; + } + if (!exists) { + if (!create_directory(destfname)) { + run_err("%s: Cannot create directory", destfname); + sfree(destfname); + continue; + } + } + sink(destfname, NULL); + /* can we set the timestamp for directories ? */ + sfree(destfname); + continue; + } + + f = open_new_file(destfname, act.permissions); + if (f == NULL) { + run_err("%s: Cannot create file", destfname); + sfree(destfname); + continue; + } + + if (scp_accept_filexfer()) { + sfree(destfname); + close_wfile(f); + return; + } + + stat_bytes = uint64_make(0, 0); + stat_starttime = time(NULL); + stat_lasttime = 0; + stat_name = stripslashes(destfname, 1); + + received = uint64_make(0, 0); + while (uint64_compare(received,act.size) < 0) { + char transbuf[32768]; + uint64 blksize; + int read; + blksize = uint64_make(0, 32768); + if (uint64_compare(blksize,uint64_subtract(act.size,received)) > 0) + blksize = uint64_subtract(act.size,received); + read = scp_recv_filedata(transbuf, (int)blksize.lo); + if (read <= 0) + bump("Lost connection"); + if (wrerror) + continue; + if (write_to_file(f, transbuf, read) != (int)read) { + wrerror = 1; + /* FIXME: in sftp we can actually abort the transfer */ + if (statistics) + printf("\r%-25.25s | %50s\n", + stat_name, + "Write error.. waiting for end of file"); + continue; + } + if (statistics) { + stat_bytes = uint64_add32(stat_bytes,read); + if (time(NULL) > stat_lasttime || + uint64_compare(uint64_add32(received, read), act.size) == 0) { + stat_lasttime = time(NULL); + print_stats(stat_name, act.size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + received = uint64_add32(received, read); + } + if (act.settime) { + set_file_times(f, act.mtime, act.atime); + } + + close_wfile(f); + if (wrerror) { + run_err("%s: Write error", destfname); + sfree(destfname); + continue; + } + (void) scp_finish_filerecv(); + sfree(destfname); + sfree(act.buf); + } +} + +/* + * We will copy local files to a remote server. + */ +static void toremote(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + int i, wc_type; + + uploading = 1; + + targ = argv[argc - 1]; + + /* Separate host from filename */ + host = targ; + targ = colon(targ); + if (targ == NULL) + bump("targ == NULL in toremote()"); + *targ++ = '\0'; + if (*targ == '\0') + targ = "."; + /* Substitute "." for empty target */ + + /* Separate host and username */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + if (argc == 2) { + if (colon(argv[0]) != NULL) + bump("%s: Remote to remote not supported", argv[0]); + + wc_type = test_wildcard(argv[0], 1); + if (wc_type == WCTYPE_NONEXISTENT) + bump("%s: No such file or directory\n", argv[0]); + else if (wc_type == WCTYPE_WILDCARD) + targetshouldbedirectory = 1; + } + + cmd = dupprintf("scp%s%s%s%s -t %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", targ); + do_cmd(host, user, cmd); + sfree(cmd); + + if (scp_source_setup(targ, targetshouldbedirectory)) + return; + + for (i = 0; i < argc - 1; i++) { + src = argv[i]; + if (colon(src) != NULL) { + tell_user(stderr, "%s: Remote to remote not supported\n", src); + errs++; + continue; + } + + wc_type = test_wildcard(src, 1); + if (wc_type == WCTYPE_NONEXISTENT) { + run_err("%s: No such file or directory", src); + continue; + } else if (wc_type == WCTYPE_FILENAME) { + source(src); + continue; + } else { + WildcardMatcher *wc; + char *filename; + + wc = begin_wildcard_matching(src); + if (wc == NULL) { + run_err("%s: No such file or directory", src); + continue; + } + + while ((filename = wildcard_get_filename(wc)) != NULL) { + source(filename); + sfree(filename); + } + + finish_wildcard_matching(wc); + } + } +} + +/* + * We will copy files from a remote server to the local machine. + */ +static void tolocal(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + + uploading = 0; + + if (argc != 2) + bump("More than one remote source not supported"); + + src = argv[0]; + targ = argv[1]; + + /* Separate host from filename */ + host = src; + src = colon(src); + if (src == NULL) + bump("Local to local copy not supported"); + *src++ = '\0'; + if (*src == '\0') + src = "."; + /* Substitute "." for empty filename */ + + /* Separate username and hostname */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + cmd = dupprintf("scp%s%s%s%s -f %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", src); + do_cmd(host, user, cmd); + sfree(cmd); + + if (scp_sink_setup(src, preserve, recursive)) + return; + + sink(targ, src); +} + +/* + * We will issue a list command to get a remote directory. + */ +static void get_dir_list(int argc, char *argv[]) +{ + char *src, *host, *user; + char *cmd, *p, *q; + char c; + + src = argv[0]; + + /* Separate host from filename */ + host = src; + src = colon(src); + if (src == NULL) + bump("Local file listing not supported"); + *src++ = '\0'; + if (*src == '\0') + src = "."; + /* Substitute "." for empty filename */ + + /* Separate username and hostname */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + cmd = snewn(4 * strlen(src) + 100, char); + strcpy(cmd, "ls -la '"); + p = cmd + strlen(cmd); + for (q = src; *q; q++) { + if (*q == '\'') { + *p++ = '\''; + *p++ = '\\'; + *p++ = '\''; + *p++ = '\''; + } else { + *p++ = *q; + } + } + *p++ = '\''; + *p = '\0'; + + do_cmd(host, user, cmd); + sfree(cmd); + + if (using_sftp) { + scp_sftp_listdir(src); + } else { + while (ssh_scp_recv((unsigned char *) &c, 1) > 0) + tell_char(stdout, c); + } +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Secure Copy client\n"); + printf("%s\n", ver); + printf("Usage: pscp [options] [user@]host:source target\n"); + printf + (" pscp [options] source [source...] [user@]host:target\n"); + printf(" pscp [options] -ls [user@]host:filespec\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -p preserve file attributes\n"); + printf(" -q quiet, don't show statistics\n"); + printf(" -r copy directories recursively\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -P port connect to specified port\n"); + printf(" -l user connect with specified username\n"); + printf(" -pw passw login with specified password\n"); + printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -hostkey aa:bb:cc:...\n"); + printf(" manually specify a host key (may be repeated)\n"); + printf(" -batch disable all interactive prompts\n"); + printf(" -unsafe allow server-side wildcards (DANGEROUS)\n"); + printf(" -sftp force use of SFTP protocol\n"); + printf(" -scp force use of SCP protocol\n"); + printf(" -sshlog file\n"); + printf(" -sshrawlog file\n"); + printf(" log protocol details to a file\n"); +#if 0 + /* + * -gui is an internal option, used by GUI front ends to get + * pscp to pass progress reports back to them. It's not an + * ordinary user-accessible option, so it shouldn't be part of + * the command-line help. The only people who need to know + * about it are programmers, and they can read the source. + */ + printf + (" -gui hWnd GUI mode with the windows handle for receiving messages\n"); +#endif + cleanup_exit(1); +} + +void version(void) +{ + printf("pscp: %s\n", ver); + cleanup_exit(1); +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "pscp: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fprintf(stderr, "\n try typing just \"pscp\" for help\n"); + exit(1); +} + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = FALSE; + +/* + * Main program. (Called `psftp_main' because it gets called from + * *sftp.c; bit silly, I know, but it had to be called _something_.) + */ +int psftp_main(int argc, char *argv[]) +{ + int i; + + default_protocol = PROT_TELNET; + + flags = FLAG_STDERR +#ifdef FLAG_SYNCAGENT + | FLAG_SYNCAGENT +#endif + ; + cmdline_tooltype = TOOLTYPE_FILETRANSFER; + sk_init(); + + /* Load Default Settings before doing anything else. */ + conf = conf_new(); + do_defaults(NULL, conf); + loaded_session = FALSE; + + for (i = 1; i < argc; i++) { + int ret; + if (argv[i][0] != '-') + break; + ret = cmdline_process_param(argv[i], i+1 2) + targetshouldbedirectory = 1; + + if (colon(argv[argc - 1]) != NULL) + toremote(argc, argv); + else + tolocal(argc, argv); + } + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + ssh_scp_recv((unsigned char *) &ch, 1); + } + random_save_seed(); + + cmdline_cleanup(); + console_provide_logctx(NULL); + back->free(backhandle); + backhandle = NULL; + back = NULL; + sk_cleanup(); + return (errs == 0 ? 0 : 1); +} + +/* end */ diff --git a/netbox/libs/Putty/psftp.c b/netbox/libs/Putty/psftp.c new file mode 100644 index 000000000..7f081ab56 --- /dev/null +++ b/netbox/libs/Putty/psftp.c @@ -0,0 +1,3017 @@ +/* + * psftp.c: (platform-independent) front end for PSFTP. + */ + +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "psftp.h" +#include "storage.h" +#include "ssh.h" +#include "sftp.h" +#include "int64.h" + +const char *const appname = "PSFTP"; + +/* + * Since SFTP is a request-response oriented protocol, it requires + * no buffer management: when we send data, we stop and wait for an + * acknowledgement _anyway_, and so we can't possibly overfill our + * send buffer. + */ + +static int psftp_connect(char *userhost, char *user, int portnumber); +static int do_sftp_init(void); +void do_sftp_cleanup(); + +/* ---------------------------------------------------------------------- + * sftp client state. + */ + +char *pwd, *homedir; +static Backend *back; +static void *backhandle; +static Conf *conf; +int sent_eof = FALSE; + +/* ---------------------------------------------------------------------- + * Manage sending requests and waiting for replies. + */ +struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) +{ + struct sftp_packet *pktin; + struct sftp_request *rreq; + + sftp_register(req); + pktin = sftp_recv(); + if (pktin == NULL) + connection_fatal(NULL, "did not receive SFTP response packet " + "from server"); + rreq = sftp_find_request(pktin); + if (rreq != req) + connection_fatal(NULL, "unable to understand SFTP response packet " + "from server: %s", fxp_error()); + return pktin; +} + +/* ---------------------------------------------------------------------- + * Higher-level helper functions used in commands. + */ + +/* + * Attempt to canonify a pathname starting from the pwd. If + * canonification fails, at least fall back to returning a _valid_ + * pathname (though it may be ugly, eg /home/simon/../foobar). + */ +char *canonify(char *name) +{ + char *fullname, *canonname; + struct sftp_packet *pktin; + struct sftp_request *req; + + if (name[0] == '/') { + fullname = dupstr(name); + } else { + char *slash; + if (pwd[strlen(pwd) - 1] == '/') + slash = ""; + else + slash = "/"; + fullname = dupcat(pwd, slash, name, NULL); + } + + req = fxp_realpath_send(fullname); + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); + + if (canonname) { + sfree(fullname); + return canonname; + } else { + /* + * Attempt number 2. Some FXP_REALPATH implementations + * (glibc-based ones, in particular) require the _whole_ + * path to point to something that exists, whereas others + * (BSD-based) only require all but the last component to + * exist. So if the first call failed, we should strip off + * everything from the last slash onwards and try again, + * then put the final component back on. + * + * Special cases: + * + * - if the last component is "/." or "/..", then we don't + * bother trying this because there's no way it can work. + * + * - if the thing actually ends with a "/", we remove it + * before we start. Except if the string is "/" itself + * (although I can't see why we'd have got here if so, + * because surely "/" would have worked the first + * time?), in which case we don't bother. + * + * - if there's no slash in the string at all, give up in + * confusion (we expect at least one because of the way + * we constructed the string). + */ + + int i; + char *returnname; + + i = strlen(fullname); + if (i > 2 && fullname[i - 1] == '/') + fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */ + while (i > 0 && fullname[--i] != '/'); + + /* + * Give up on special cases. + */ + if (fullname[i] != '/' || /* no slash at all */ + !strcmp(fullname + i, "/.") || /* ends in /. */ + !strcmp(fullname + i, "/..") || /* ends in /.. */ + !strcmp(fullname, "/")) { + return fullname; + } + + /* + * Now i points at the slash. Deal with the final special + * case i==0 (ie the whole path was "/nonexistentfile"). + */ + fullname[i] = '\0'; /* separate the string */ + if (i == 0) { + req = fxp_realpath_send("/"); + } else { + req = fxp_realpath_send(fullname); + } + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); + + if (!canonname) { + /* Even that failed. Restore our best guess at the + * constructed filename and give up */ + fullname[i] = '/'; /* restore slash and last component */ + return fullname; + } + + /* + * We have a canonical name for all but the last path + * component. Concatenate the last component and return. + */ + returnname = dupcat(canonname, + canonname[strlen(canonname) - 1] == + '/' ? "" : "/", fullname + i + 1, NULL); + sfree(fullname); + sfree(canonname); + return returnname; + } +} + +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + +/* + * qsort comparison routine for fxp_name structures. Sorts by real + * file name. + */ +static int sftp_name_compare(const void *av, const void *bv) +{ + const struct fxp_name *const *a = (const struct fxp_name *const *) av; + const struct fxp_name *const *b = (const struct fxp_name *const *) bv; + return strcmp((*a)->filename, (*b)->filename); +} + +/* + * Likewise, but for a bare char *. + */ +static int bare_name_compare(const void *av, const void *bv) +{ + const char **a = (const char **) av; + const char **b = (const char **) bv; + return strcmp(*a, *b); +} + +static void not_connected(void) +{ + printf("psftp: not connected to a host; use \"open host.name\"\n"); +} + +/* ---------------------------------------------------------------------- + * The meat of the `get' and `put' commands. + */ +int sftp_get_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_xfer *xfer; + uint64 offset; + WFile *file; + int ret, shown_err = FALSE; + struct fxp_attrs attrs; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent FXP_OPEN will return a usable error message.) + */ + if (recurse) { + int result; + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) { + + struct fxp_handle *dirhandle; + int nnames, namesize; + struct fxp_name **ournames; + struct fxp_names *names; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + if (file_type(outfname) != FILE_TYPE_DIRECTORY && + !create_directory(outfname)) { + printf("%s: Cannot create directory\n", outfname); + return 0; + } + + /* + * Now get the list of filenames in the remote + * directory. + */ + req = fxp_opendir_send(fname); + pktin = sftp_wait_for_reply(req); + dirhandle = fxp_opendir_recv(pktin, req); + + if (!dirhandle) { + printf("%s: unable to open directory: %s\n", + fname, fxp_error()); + return 0; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + req = fxp_readdir_send(dirhandle); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("%s: reading directory: %s\n", fname, fxp_error()); + + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + sfree(ournames); + return 0; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name *); + } + for (i = 0; i < names->nnames; i++) + if (strcmp(names->names[i].filename, ".") && + strcmp(names->names[i].filename, "..")) { + if (!vet_filename(names->names[i].filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", + names->names[i].filename); + } else { + ournames[nnames++] = + fxp_dup_name(&names->names[i]); + } + } + fxp_free_names(names); + } + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + /* + * Sort the names into a clear order. This ought to + * make things more predictable when we're doing a + * reget of the same directory, just in case two + * readdirs on the same remote directory return a + * different order. + */ + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + + /* + * If we're in restart mode, find the last filename on + * this list that already exists. We may have to do a + * reget on _that_ file, but shouldn't have to do + * anything on the previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + if (restart) { + while (i < nnames) { + char *nextoutfname; + int ret; + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); + ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT); + sfree(nextoutfname); + if (ret) + break; + i++; + } + if (i > 0) + i--; + } + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_get_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); + nextoutfname = dir_file_cat(outfname, ournames[i]->filename); + ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + + return 1; + } + } + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + if (!fxp_stat_recv(pktin, req, &attrs)) + attrs.flags = 0; + + req = fxp_open_send(fname, SSH_FXF_READ, NULL); + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); + + if (!fh) { + printf("%s: open for read: %s\n", fname, fxp_error()); + return 0; + } + + if (restart) { + file = open_existing_wfile(outfname, NULL); + } else { + file = open_new_file(outfname, GET_PERMISSIONS(attrs)); + } + + if (!file) { + printf("local: unable to open %s\n", outfname); + + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return 0; + } + + if (restart) { + char decbuf[30]; + if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) { + close_wfile(file); + printf("reget: cannot restart %s - file too large\n", + outfname); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return 0; + } + + offset = get_file_posn(file); + uint64_decimal(offset, decbuf); + printf("reget: restarting at file position %s\n", decbuf); + } else { + offset = uint64_make(0, 0); + } + + printf("remote:%s => local:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_download_init(fh, offset); + while (!xfer_done(xfer)) { + void *vbuf; + int ret, len; + int wpos, wlen; + + xfer_download_queue(xfer); + pktin = sftp_recv(); + ret = xfer_download_gotpkt(xfer, pktin); + if (ret <= 0) { + if (!shown_err) { + printf("error while reading: %s\n", fxp_error()); + shown_err = TRUE; + } + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + ret = 0; + } + + while (xfer_download_data(xfer, &vbuf, &len)) { + unsigned char *buf = (unsigned char *)vbuf; + + wpos = 0; + while (wpos < len) { + wlen = write_to_file(file, buf + wpos, len - wpos); + if (wlen <= 0) { + printf("error while writing local file\n"); + ret = 0; + xfer_set_error(xfer); + break; + } + wpos += wlen; + } + if (wpos < len) { /* we had an error */ + ret = 0; + xfer_set_error(xfer); + } + + sfree(vbuf); + } + } + + xfer_cleanup(xfer); + + close_wfile(file); + + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return ret; +} + +int sftp_put_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct fxp_xfer *xfer; + struct sftp_packet *pktin; + struct sftp_request *req; + uint64 offset; + RFile *file; + int ret, err, eof; + struct fxp_attrs attrs; + long permissions; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent fopen will return an error message.) + */ + if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { + int result; + int nnames, namesize; + char *name, **ournames; + DirHandle *dh; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + req = fxp_stat_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + if (!result || + !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + req = fxp_mkdir_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); + + if (!result) { + printf("%s: create directory: %s\n", + outfname, fxp_error()); + return 0; + } + } + + /* + * Now get the list of filenames in the local directory. + */ + nnames = namesize = 0; + ournames = NULL; + + dh = open_directory(fname); + if (!dh) { + printf("%s: unable to open directory\n", fname); + return 0; + } + while ((name = read_filename(dh)) != NULL) { + if (nnames >= namesize) { + namesize += 128; + ournames = sresize(ournames, namesize, char *); + } + ournames[nnames++] = name; + } + close_directory(dh); + + /* + * Sort the names into a clear order. This ought to make + * things more predictable when we're doing a reput of the + * same directory, just in case two readdirs on the same + * local directory return a different order. + */ + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), bare_name_compare); + + /* + * If we're in restart mode, find the last filename on this + * list that already exists. We may have to do a reput on + * _that_ file, but shouldn't have to do anything on the + * previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + if (restart) { + while (i < nnames) { + char *nextoutfname; + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + req = fxp_stat_send(nextoutfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + sfree(nextoutfname); + if (!result) + break; + i++; + } + if (i > 0) + i--; + } + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_put_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + nextfname = dir_file_cat(fname, ournames[i]); + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + + return 1; + } + + file = open_existing_file(fname, NULL, NULL, NULL, &permissions); + if (!file) { + printf("local: unable to open %s\n", fname); + return 0; + } + attrs.flags = 0; + PUT_PERMISSIONS(attrs, permissions); + if (restart) { + req = fxp_open_send(outfname, SSH_FXF_WRITE, &attrs); + } else { + req = fxp_open_send(outfname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + &attrs); + } + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); + + if (!fh) { + close_rfile(file); + printf("%s: open for write: %s\n", outfname, fxp_error()); + return 0; + } + + if (restart) { + char decbuf[30]; + struct fxp_attrs attrs; + + req = fxp_fstat_send(fh); + pktin = sftp_wait_for_reply(req); + ret = fxp_fstat_recv(pktin, req, &attrs); + + if (!ret) { + printf("read size of %s: %s\n", outfname, fxp_error()); + goto cleanup; + } + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + printf("read size of %s: size was not given\n", outfname); + ret = 0; + goto cleanup; + } + offset = attrs.size; + uint64_decimal(offset, decbuf); + printf("reput: restarting at file position %s\n", decbuf); + + if (seek_file((WFile *)file, offset, FROM_START) != 0) + seek_file((WFile *)file, uint64_make(0,0), FROM_END); /* *shrug* */ + } else { + offset = uint64_make(0, 0); + } + + printf("local:%s => remote:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_upload_init(fh, offset); + err = eof = 0; + while ((!err && !eof) || !xfer_done(xfer)) { + char buffer[4096]; + int len, ret; + + while (xfer_upload_ready(xfer) && !err && !eof) { + len = read_from_file(file, buffer, sizeof(buffer)); + if (len == -1) { + printf("error while reading local file\n"); + err = 1; + } else if (len == 0) { + eof = 1; + } else { + xfer_upload_data(xfer, buffer, len); + } + } + + if (!xfer_done(xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(xfer, pktin); + if (ret <= 0) { + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + if (!err) { + printf("error while writing: %s\n", fxp_error()); + err = 1; + } + } + } + } + + xfer_cleanup(xfer); + + cleanup: + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + close_rfile(file); + + return ret; +} + +/* ---------------------------------------------------------------------- + * A remote wildcard matcher, providing a similar interface to the + * local one in psftp.h. + */ + +typedef struct SftpWildcardMatcher { + struct fxp_handle *dirh; + struct fxp_names *names; + int namepos; + char *wildcard, *prefix; +} SftpWildcardMatcher; + +SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + char *wildcard; + char *unwcdir, *tmpdir, *cdir; + int len, check; + SftpWildcardMatcher *swcm; + struct fxp_handle *dirh; + + /* + * We don't handle multi-level wildcards; so we expect to find + * a fully specified directory part, followed by a wildcard + * after that. + */ + wildcard = stripslashes(name, 0); + + unwcdir = dupstr(name); + len = wildcard - name; + unwcdir[len] = '\0'; + if (len > 0 && unwcdir[len-1] == '/') + unwcdir[len-1] = '\0'; + tmpdir = snewn(1 + len, char); + check = wc_unescape(tmpdir, unwcdir); + sfree(tmpdir); + + if (!check) { + printf("Multiple-level wildcards are not supported\n"); + sfree(unwcdir); + return NULL; + } + + cdir = canonify(unwcdir); + + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + + if (dirh) { + swcm = snew(SftpWildcardMatcher); + swcm->dirh = dirh; + swcm->names = NULL; + swcm->wildcard = dupstr(wildcard); + swcm->prefix = unwcdir; + } else { + printf("Unable to open %s: %s\n", cdir, fxp_error()); + swcm = NULL; + sfree(unwcdir); + } + + sfree(cdir); + + return swcm; +} + +char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm) +{ + struct fxp_name *name; + struct sftp_packet *pktin; + struct sftp_request *req; + + while (1) { + if (swcm->names && swcm->namepos >= swcm->names->nnames) { + fxp_free_names(swcm->names); + swcm->names = NULL; + } + + if (!swcm->names) { + req = fxp_readdir_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + swcm->names = fxp_readdir_recv(pktin, req); + + if (!swcm->names) { + if (fxp_error_type() != SSH_FX_EOF) + printf("%s: reading directory: %s\n", swcm->prefix, + fxp_error()); + return NULL; + } else if (swcm->names->nnames == 0) { + /* + * Another failure mode which we treat as EOF is if + * the server reports success from FXP_READDIR but + * returns no actual names. This is unusual, since + * from most servers you'd expect at least "." and + * "..", but there's nothing forbidding a server from + * omitting those if it wants to. + */ + return NULL; + } + + swcm->namepos = 0; + } + + assert(swcm->names && swcm->namepos < swcm->names->nnames); + + name = &swcm->names->names[swcm->namepos++]; + + if (!strcmp(name->filename, ".") || !strcmp(name->filename, "..")) + continue; /* expected bad filenames */ + + if (!vet_filename(name->filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", name->filename); + continue; /* unexpected bad filename */ + } + + if (!wc_match(swcm->wildcard, name->filename)) + continue; /* doesn't match the wildcard */ + + /* + * We have a working filename. Return it. + */ + return dupprintf("%s%s%s", swcm->prefix, + (!swcm->prefix[0] || + swcm->prefix[strlen(swcm->prefix)-1]=='/' ? + "" : "/"), + name->filename); + } +} + +void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + + req = fxp_close_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + if (swcm->names) + fxp_free_names(swcm->names); + + sfree(swcm->prefix); + sfree(swcm->wildcard); + + sfree(swcm); +} + +/* + * General function to match a potential wildcard in a filename + * argument and iterate over every matching file. Used in several + * PSFTP commands (rmdir, rm, chmod, mv). + */ +int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx) +{ + char *unwcfname, *newname, *cname; + int is_wc, ret; + + unwcfname = snewn(strlen(filename)+1, char); + is_wc = !wc_unescape(unwcfname, filename); + + if (is_wc) { + SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename); + int matched = FALSE; + sfree(unwcfname); + + if (!swcm) + return 0; + + ret = 1; + + while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) { + cname = canonify(newname); + if (!cname) { + printf("%s: canonify: %s\n", newname, fxp_error()); + ret = 0; + } + sfree(newname); + matched = TRUE; + ret &= func(ctx, cname); + sfree(cname); + } + + if (!matched) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", filename); + } + + sftp_finish_wildcard_matching(swcm); + } else { + cname = canonify(unwcfname); + if (!cname) { + printf("%s: canonify: %s\n", filename, fxp_error()); + ret = 0; + } + ret = func(ctx, cname); + sfree(cname); + sfree(unwcfname); + } + + return ret; +} + +/* + * Handy helper function. + */ +int is_wildcard(char *name) +{ + char *unwcfname = snewn(strlen(name)+1, char); + int is_wc = !wc_unescape(unwcfname, name); + sfree(unwcfname); + return is_wc; +} + +/* ---------------------------------------------------------------------- + * Actual sftp commands. + */ +struct sftp_command { + char **words; + int nwords, wordssize; + int (*obey) (struct sftp_command *); /* returns <0 to quit */ +}; + +int sftp_cmd_null(struct sftp_command *cmd) +{ + return 1; /* success */ +} + +int sftp_cmd_unknown(struct sftp_command *cmd) +{ + printf("psftp: unknown command \"%s\"\n", cmd->words[0]); + return 0; /* failure */ +} + +int sftp_cmd_quit(struct sftp_command *cmd) +{ + return -1; +} + +int sftp_cmd_close(struct sftp_command *cmd) +{ + if (back == NULL) { + not_connected(); + return 0; + } + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + sftp_recvdata(&ch, 1); + } + do_sftp_cleanup(); + + return 0; +} + +/* + * List a directory. If no arguments are given, list pwd; otherwise + * list the directory given in words[1]. + */ +int sftp_cmd_ls(struct sftp_command *cmd) +{ + struct fxp_handle *dirh; + struct fxp_names *names; + struct fxp_name **ournames; + int nnames, namesize; + char *dir, *cdir, *unwcdir, *wildcard; + struct sftp_packet *pktin; + struct sftp_request *req; + int i; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) + dir = "."; + else + dir = cmd->words[1]; + + unwcdir = snewn(1 + strlen(dir), char); + if (wc_unescape(unwcdir, dir)) { + dir = unwcdir; + wildcard = NULL; + } else { + char *tmpdir; + int len, check; + + sfree(unwcdir); + wildcard = stripslashes(dir, 0); + unwcdir = dupstr(dir); + len = wildcard - dir; + unwcdir[len] = '\0'; + if (len > 0 && unwcdir[len-1] == '/') + unwcdir[len-1] = '\0'; + tmpdir = snewn(1 + len, char); + check = wc_unescape(tmpdir, unwcdir); + sfree(tmpdir); + if (!check) { + printf("Multiple-level wildcards are not supported\n"); + sfree(unwcdir); + return 0; + } + dir = unwcdir; + } + + cdir = canonify(dir); + if (!cdir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + sfree(unwcdir); + return 0; + } + + printf("Listing directory %s\n", cdir); + + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + + if (dirh == NULL) { + printf("Unable to open %s: %s\n", dir, fxp_error()); + } else { + nnames = namesize = 0; + ournames = NULL; + + while (1) { + + req = fxp_readdir_send(dirh); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("Reading directory %s: %s\n", dir, fxp_error()); + break; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name *); + } + + for (i = 0; i < names->nnames; i++) + if (!wildcard || wc_match(wildcard, names->names[i].filename)) + ournames[nnames++] = fxp_dup_name(&names->names[i]); + + fxp_free_names(names); + } + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + /* + * Now we have our filenames. Sort them by actual file + * name, and then output the longname parts. + */ + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + + /* + * And print them. + */ + for (i = 0; i < nnames; i++) { + printf("%s\n", ournames[i]->longname); + fxp_free_name(ournames[i]); + } + sfree(ournames); + } + + sfree(cdir); + sfree(unwcdir); + + return 1; +} + +/* + * Change directories. We do this by canonifying the new name, then + * trying to OPENDIR it. Only if that succeeds do we set the new pwd. + */ +int sftp_cmd_cd(struct sftp_command *cmd) +{ + struct fxp_handle *dirh; + struct sftp_packet *pktin; + struct sftp_request *req; + char *dir; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) + dir = dupstr(homedir); + else + dir = canonify(cmd->words[1]); + + if (!dir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + return 0; + } + + req = fxp_opendir_send(dir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + + if (!dirh) { + printf("Directory %s: %s\n", dir, fxp_error()); + sfree(dir); + return 0; + } + + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + sfree(pwd); + pwd = dir; + printf("Remote directory is now %s\n", pwd); + + return 1; +} + +/* + * Print current directory. Easy as pie. + */ +int sftp_cmd_pwd(struct sftp_command *cmd) +{ + if (back == NULL) { + not_connected(); + return 0; + } + + printf("Remote directory is %s\n", pwd); + return 1; +} + +/* + * Get a file and save it at the local end. We have three very + * similar commands here. The basic one is `get'; `reget' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mget' + * differs in that it interprets all its arguments as files to + * transfer (never as a different local name for a remote file) and + * can handle wildcards. + */ +int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) +{ + char *fname, *unwcfname, *origfname, *origwfname, *outfname; + int i, ret; + int recurse = FALSE; + + if (back == NULL) { + not_connected(); + return 0; + } + + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); + return 0; + } + i++; + } + + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); + return 0; + } + + ret = 1; + do { + SftpWildcardMatcher *swcm; + + origfname = cmd->words[i++]; + unwcfname = snewn(strlen(origfname)+1, char); + + if (multiple && !wc_unescape(unwcfname, origfname)) { + swcm = sftp_begin_wildcard_matching(origfname); + if (!swcm) { + sfree(unwcfname); + continue; + } + origwfname = sftp_wildcard_get_filename(swcm); + if (!origwfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", origfname); + sftp_finish_wildcard_matching(swcm); + sfree(unwcfname); + continue; + } + } else { + origwfname = origfname; + swcm = NULL; + } + + while (origwfname) { + fname = canonify(origwfname); + + if (!fname) { + sftp_finish_wildcard_matching(swcm); + printf("%s: canonify: %s\n", origwfname, fxp_error()); + sfree(origwfname); + sfree(unwcfname); + return 0; + } + + if (!multiple && i < cmd->nwords) + outfname = cmd->words[i++]; + else + outfname = stripslashes(origwfname, 0); + + ret = sftp_get_file(fname, outfname, recurse, restart); + + sfree(fname); + + if (swcm) { + sfree(origwfname); + origwfname = sftp_wildcard_get_filename(swcm); + } else { + origwfname = NULL; + } + } + sfree(unwcfname); + if (swcm) + sftp_finish_wildcard_matching(swcm); + if (!ret) + return ret; + + } while (multiple && i < cmd->nwords); + + return ret; +} +int sftp_cmd_get(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0, 0); +} +int sftp_cmd_mget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0, 1); +} +int sftp_cmd_reget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 1, 0); +} + +/* + * Send a file and store it at the remote end. We have three very + * similar commands here. The basic one is `put'; `reput' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mput' + * differs in that it interprets all its arguments as files to + * transfer (never as a different remote name for a local file) and + * can handle wildcards. + */ +int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) +{ + char *fname, *wfname, *origoutfname, *outfname; + int i, ret; + int recurse = FALSE; + + if (back == NULL) { + not_connected(); + return 0; + } + + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); + return 0; + } + i++; + } + + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); + return 0; + } + + ret = 1; + do { + WildcardMatcher *wcm; + fname = cmd->words[i++]; + + if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { + wcm = begin_wildcard_matching(fname); + wfname = wildcard_get_filename(wcm); + if (!wfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", fname); + finish_wildcard_matching(wcm); + continue; + } + } else { + wfname = fname; + wcm = NULL; + } + + while (wfname) { + if (!multiple && i < cmd->nwords) + origoutfname = cmd->words[i++]; + else + origoutfname = stripslashes(wfname, 1); + + outfname = canonify(origoutfname); + if (!outfname) { + printf("%s: canonify: %s\n", origoutfname, fxp_error()); + if (wcm) { + sfree(wfname); + finish_wildcard_matching(wcm); + } + return 0; + } + ret = sftp_put_file(wfname, outfname, recurse, restart); + sfree(outfname); + + if (wcm) { + sfree(wfname); + wfname = wildcard_get_filename(wcm); + } else { + wfname = NULL; + } + } + + if (wcm) + finish_wildcard_matching(wcm); + + if (!ret) + return ret; + + } while (multiple && i < cmd->nwords); + + return ret; +} +int sftp_cmd_put(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0, 0); +} +int sftp_cmd_mput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0, 1); +} +int sftp_cmd_reput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 1, 0); +} + +int sftp_cmd_mkdir(struct sftp_command *cmd) +{ + char *dir; + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("mkdir: expects a directory\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) { + dir = canonify(cmd->words[i]); + if (!dir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + return 0; + } + + req = fxp_mkdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); + + if (!result) { + printf("mkdir %s: %s\n", dir, fxp_error()); + ret = 0; + } else + printf("mkdir %s: OK\n", dir); + + sfree(dir); + } + + return ret; +} + +static int sftp_action_rmdir(void *vctx, char *dir) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + + req = fxp_rmdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_rmdir_recv(pktin, req); + + if (!result) { + printf("rmdir %s: %s\n", dir, fxp_error()); + return 0; + } + + printf("rmdir %s: OK\n", dir); + + return 1; +} + +int sftp_cmd_rmdir(struct sftp_command *cmd) +{ + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("rmdir: expects a directory\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL); + + return ret; +} + +static int sftp_action_rm(void *vctx, char *fname) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + + req = fxp_remove_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_remove_recv(pktin, req); + + if (!result) { + printf("rm %s: %s\n", fname, fxp_error()); + return 0; + } + + printf("rm %s: OK\n", fname); + + return 1; +} + +int sftp_cmd_rm(struct sftp_command *cmd) +{ + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("rm: expects a filename\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL); + + return ret; +} + +static int check_is_dir(char *dstfname) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_attrs attrs; + int result; + + req = fxp_stat_send(dstfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) + return TRUE; + else + return FALSE; +} + +struct sftp_context_mv { + char *dstfname; + int dest_is_dir; +}; + +static int sftp_action_mv(void *vctx, char *srcfname) +{ + struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx; + struct sftp_packet *pktin; + struct sftp_request *req; + const char *error; + char *finalfname, *newcanon = NULL; + int ret, result; + + if (ctx->dest_is_dir) { + char *p; + char *newname; + + p = srcfname + strlen(srcfname); + while (p > srcfname && p[-1] != '/') p--; + newname = dupcat(ctx->dstfname, "/", p, NULL); + newcanon = canonify(newname); + if (!newcanon) { + printf("%s: canonify: %s\n", newname, fxp_error()); + sfree(newname); + return 0; + } + sfree(newname); + + finalfname = newcanon; + } else { + finalfname = ctx->dstfname; + } + + req = fxp_rename_send(srcfname, finalfname); + pktin = sftp_wait_for_reply(req); + result = fxp_rename_recv(pktin, req); + + error = result ? NULL : fxp_error(); + + if (error) { + printf("mv %s %s: %s\n", srcfname, finalfname, error); + ret = 0; + } else { + printf("%s -> %s\n", srcfname, finalfname); + ret = 1; + } + + sfree(newcanon); + return ret; +} + +int sftp_cmd_mv(struct sftp_command *cmd) +{ + struct sftp_context_mv actx, *ctx = &actx; + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 3) { + printf("mv: expects two filenames\n"); + return 0; + } + + ctx->dstfname = canonify(cmd->words[cmd->nwords-1]); + if (!ctx->dstfname) { + printf("%s: canonify: %s\n", ctx->dstfname, fxp_error()); + return 0; + } + + /* + * If there's more than one source argument, or one source + * argument which is a wildcard, we _require_ that the + * destination is a directory. + */ + ctx->dest_is_dir = check_is_dir(ctx->dstfname); + if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) { + printf("mv: multiple or wildcard arguments require the destination" + " to be a directory\n"); + sfree(ctx->dstfname); + return 0; + } + + /* + * Now iterate over the source arguments. + */ + ret = 1; + for (i = 1; i < cmd->nwords-1; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx); + + sfree(ctx->dstfname); + return ret; +} + +struct sftp_context_chmod { + unsigned attrs_clr, attrs_xor; +}; + +static int sftp_action_chmod(void *vctx, char *fname) +{ + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + unsigned oldperms, newperms; + struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx; + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + printf("get attrs for %s: %s\n", fname, + result ? "file permissions not provided" : fxp_error()); + return 0; + } + + attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ + oldperms = attrs.permissions & 07777; + attrs.permissions &= ~ctx->attrs_clr; + attrs.permissions ^= ctx->attrs_xor; + newperms = attrs.permissions & 07777; + + if (oldperms == newperms) + return 1; /* no need to do anything! */ + + req = fxp_setstat_send(fname, attrs); + pktin = sftp_wait_for_reply(req); + result = fxp_setstat_recv(pktin, req); + + if (!result) { + printf("set attrs for %s: %s\n", fname, fxp_error()); + return 0; + } + + printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + + return 1; +} + +int sftp_cmd_chmod(struct sftp_command *cmd) +{ + char *mode; + int i, ret; + struct sftp_context_chmod actx, *ctx = &actx; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 3) { + printf("chmod: expects a mode specifier and a filename\n"); + return 0; + } + + /* + * Attempt to parse the mode specifier in cmd->words[1]. We + * don't support the full horror of Unix chmod; instead we + * support a much simpler syntax in which the user can either + * specify an octal number, or a comma-separated sequence of + * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may + * _only_ be omitted if the only attribute mentioned is t, + * since all others require a user/group/other specification. + * Additionally, the s attribute may not be specified for any + * [ugoa] specifications other than exactly u or exactly g. + */ + ctx->attrs_clr = ctx->attrs_xor = 0; + mode = cmd->words[1]; + if (mode[0] >= '0' && mode[0] <= '9') { + if (mode[strspn(mode, "01234567")]) { + printf("chmod: numeric file modes should" + " contain digits 0-7 only\n"); + return 0; + } + ctx->attrs_clr = 07777; + sscanf(mode, "%o", &ctx->attrs_xor); + ctx->attrs_xor &= ctx->attrs_clr; + } else { + while (*mode) { + char *modebegin = mode; + unsigned subset, perms; + int action; + + subset = 0; + while (*mode && *mode != ',' && + *mode != '+' && *mode != '-' && *mode != '=') { + switch (*mode) { + case 'u': subset |= 04700; break; /* setuid, user perms */ + case 'g': subset |= 02070; break; /* setgid, group perms */ + case 'o': subset |= 00007; break; /* just other perms */ + case 'a': subset |= 06777; break; /* all of the above */ + default: + printf("chmod: file mode '%.*s' contains unrecognised" + " user/group/other specifier '%c'\n", + (int)strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + mode++; + } + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + action = *mode++; + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms = 0; + while (*mode && *mode != ',') { + switch (*mode) { + case 'r': perms |= 00444; break; + case 'w': perms |= 00222; break; + case 'x': perms |= 00111; break; + case 't': perms |= 01000; subset |= 01000; break; + case 's': + if ((subset & 06777) != 04700 && + (subset & 06777) != 02070) { + printf("chmod: file mode '%.*s': set[ug]id bit should" + " be used with exactly one of u or g only\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms |= 06000; + break; + default: + printf("chmod: file mode '%.*s' contains unrecognised" + " permission specifier '%c'\n", + (int)strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + mode++; + } + if (!(subset & 06777) && (perms &~ subset)) { + printf("chmod: file mode '%.*s' contains no user/group/other" + " specifier and permissions other than 't' \n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms &= subset; + switch (action) { + case '+': + ctx->attrs_clr |= perms; + ctx->attrs_xor |= perms; + break; + case '-': + ctx->attrs_clr |= perms; + ctx->attrs_xor &= ~perms; + break; + case '=': + ctx->attrs_clr |= subset; + ctx->attrs_xor |= perms; + break; + } + if (*mode) mode++; /* eat comma */ + } + } + + ret = 1; + for (i = 2; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx); + + return ret; +} + +static int sftp_cmd_open(struct sftp_command *cmd) +{ + int portnumber; + + if (back != NULL) { + printf("psftp: already connected\n"); + return 0; + } + + if (cmd->nwords < 2) { + printf("open: expects a host name\n"); + return 0; + } + + if (cmd->nwords > 2) { + portnumber = atoi(cmd->words[2]); + if (portnumber == 0) { + printf("open: invalid port number\n"); + return 0; + } + } else + portnumber = 0; + + if (psftp_connect(cmd->words[1], NULL, portnumber)) { + back = NULL; /* connection is already closed */ + return -1; /* this is fatal */ + } + do_sftp_init(); + return 1; +} + +static int sftp_cmd_lcd(struct sftp_command *cmd) +{ + char *currdir, *errmsg; + + if (cmd->nwords < 2) { + printf("lcd: expects a local directory name\n"); + return 0; + } + + errmsg = psftp_lcd(cmd->words[1]); + if (errmsg) { + printf("lcd: unable to change directory: %s\n", errmsg); + sfree(errmsg); + return 0; + } + + currdir = psftp_getcwd(); + printf("New local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_lpwd(struct sftp_command *cmd) +{ + char *currdir; + + currdir = psftp_getcwd(); + printf("Current local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_pling(struct sftp_command *cmd) +{ + int exitcode; + + exitcode = system(cmd->words[1]); + return (exitcode == 0); +} + +static int sftp_cmd_help(struct sftp_command *cmd); + +static struct sftp_cmd_lookup { + char *name; + /* + * For help purposes, there are two kinds of command: + * + * - primary commands, in which `longhelp' is non-NULL. In + * this case `shorthelp' is descriptive text, and `longhelp' + * is longer descriptive text intended to be printed after + * the command name. + * + * - alias commands, in which `longhelp' is NULL. In this case + * `shorthelp' is the name of a primary command, which + * contains the help that should double up for this command. + */ + int listed; /* do we list this in primary help? */ + char *shorthelp; + char *longhelp; + int (*obey) (struct sftp_command *); +} sftp_lookup[] = { + /* + * List of sftp commands. This is binary-searched so it MUST be + * in ASCII order. + */ + { + "!", TRUE, "run a local command", + "\n" + /* FIXME: this example is crap for non-Windows. */ + " Runs a local command. For example, \"!del myfile\".\n", + sftp_cmd_pling + }, + { + "bye", TRUE, "finish your SFTP session", + "\n" + " Terminates your SFTP session and quits the PSFTP program.\n", + sftp_cmd_quit + }, + { + "cd", TRUE, "change your remote working directory", + " [ ]\n" + " Change the remote working directory for your SFTP session.\n" + " If a new working directory is not supplied, you will be\n" + " returned to your home directory.\n", + sftp_cmd_cd + }, + { + "chmod", TRUE, "change file permissions and modes", + " [ ... ]\n" + " Change the file permissions on one or more remote files or\n" + " directories.\n" + " can be any octal Unix permission specifier.\n" + " Alternatively, can include the following modifiers:\n" + " u+r make file readable by owning user\n" + " u+w make file writable by owning user\n" + " u+x make file executable by owning user\n" + " u-r make file not readable by owning user\n" + " [also u-w, u-x]\n" + " g+r make file readable by members of owning group\n" + " [also g+w, g+x, g-r, g-w, g-x]\n" + " o+r make file readable by all other users\n" + " [also o+w, o+x, o-r, o-w, o-x]\n" + " a+r make file readable by absolutely everybody\n" + " [also a+w, a+x, a-r, a-w, a-x]\n" + " u+s enable the Unix set-user-ID bit\n" + " u-s disable the Unix set-user-ID bit\n" + " g+s enable the Unix set-group-ID bit\n" + " g-s disable the Unix set-group-ID bit\n" + " +t enable the Unix \"sticky bit\"\n" + " You can give more than one modifier for the same user (\"g-rwx\"), and\n" + " more than one user for the same modifier (\"ug+w\"). You can\n" + " use commas to separate different modifiers (\"u+rwx,g+s\").\n", + sftp_cmd_chmod + }, + { + "close", TRUE, "finish your SFTP session but do not quit PSFTP", + "\n" + " Terminates your SFTP session, but does not quit the PSFTP\n" + " program. You can then use \"open\" to start another SFTP\n" + " session, to the same server or to a different one.\n", + sftp_cmd_close + }, + { + "del", TRUE, "delete files on the remote server", + " [ ... ]\n" + " Delete a file or files from the server.\n", + sftp_cmd_rm + }, + { + "delete", FALSE, "del", NULL, sftp_cmd_rm + }, + { + "dir", TRUE, "list remote files", + " [ ]/[ ]\n" + " List the contents of a specified directory on the server.\n" + " If is not given, the current working directory\n" + " is assumed.\n" + " If is given, it is treated as a set of files to\n" + " list; otherwise, all files are listed.\n", + sftp_cmd_ls + }, + { + "exit", TRUE, "bye", NULL, sftp_cmd_quit + }, + { + "get", TRUE, "download a file from the server to your local machine", + " [ -r ] [ -- ] [ ]\n" + " Downloads a file on the server and stores it locally under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n" + " If -r specified, recursively fetch a directory.\n", + sftp_cmd_get + }, + { + "help", TRUE, "give help", + " [ [ ... ] ]\n" + " Give general help if no commands are specified.\n" + " If one or more commands are specified, give specific help on\n" + " those particular commands.\n", + sftp_cmd_help + }, + { + "lcd", TRUE, "change local working directory", + " \n" + " Change the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lcd + }, + { + "lpwd", TRUE, "print local working directory", + "\n" + " Print the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lpwd + }, + { + "ls", TRUE, "dir", NULL, + sftp_cmd_ls + }, + { + "mget", TRUE, "download multiple files at once", + " [ -r ] [ -- ] [ ... ]\n" + " Downloads many files from the server, storing each one under\n" + " the same name it has on the server side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively fetch files and directories.\n", + sftp_cmd_mget + }, + { + "mkdir", TRUE, "create directories on the remote server", + " [ ... ]\n" + " Creates directories with the given names on the server.\n", + sftp_cmd_mkdir + }, + { + "mput", TRUE, "upload multiple files at once", + " [ -r ] [ -- ] [ ... ]\n" + " Uploads many files to the server, storing each one under the\n" + " same name it has on the client side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively store files and directories.\n", + sftp_cmd_mput + }, + { + "mv", TRUE, "move or rename file(s) on the remote server", + " [ ... ] \n" + " Moves or renames (s) on the server to ,\n" + " also on the server.\n" + " If specifies an existing directory, then \n" + " may be a wildcard, and multiple s may be given; all\n" + " source files are moved into .\n" + " Otherwise, must specify a single file, which is moved\n" + " or renamed so that it is accessible under the name .\n", + sftp_cmd_mv + }, + { + "open", TRUE, "connect to a host", + " [@] []\n" + " Establishes an SFTP connection to a given host. Only usable\n" + " when you are not already connected to a server.\n", + sftp_cmd_open + }, + { + "put", TRUE, "upload a file from your local machine to the server", + " [ -r ] [ -- ] [ ]\n" + " Uploads a file to the server and stores it there under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n" + " If -r specified, recursively store a directory.\n", + sftp_cmd_put + }, + { + "pwd", TRUE, "print your remote working directory", + "\n" + " Print the current remote working directory for your SFTP session.\n", + sftp_cmd_pwd + }, + { + "quit", TRUE, "bye", NULL, + sftp_cmd_quit + }, + { + "reget", TRUE, "continue downloading files", + " [ -r ] [ -- ] [ ]\n" + " Works exactly like the \"get\" command, but the local file\n" + " must already exist. The download will begin at the end of the\n" + " file. This is for resuming a download that was interrupted.\n" + " If -r specified, resume interrupted \"get -r\".\n", + sftp_cmd_reget + }, + { + "ren", TRUE, "mv", NULL, + sftp_cmd_mv + }, + { + "rename", FALSE, "mv", NULL, + sftp_cmd_mv + }, + { + "reput", TRUE, "continue uploading files", + " [ -r ] [ -- ] [ ]\n" + " Works exactly like the \"put\" command, but the remote file\n" + " must already exist. The upload will begin at the end of the\n" + " file. This is for resuming an upload that was interrupted.\n" + " If -r specified, resume interrupted \"put -r\".\n", + sftp_cmd_reput + }, + { + "rm", TRUE, "del", NULL, + sftp_cmd_rm + }, + { + "rmdir", TRUE, "remove directories on the remote server", + " [ ... ]\n" + " Removes the directory with the given name on the server.\n" + " The directory will not be removed unless it is empty.\n" + " Wildcards may be used to specify multiple directories.\n", + sftp_cmd_rmdir + } +}; + +const struct sftp_cmd_lookup *lookup_command(char *name) +{ + int i, j, k, cmp; + + i = -1; + j = sizeof(sftp_lookup) / sizeof(*sftp_lookup); + while (j - i > 1) { + k = (j + i) / 2; + cmp = strcmp(name, sftp_lookup[k].name); + if (cmp < 0) + j = k; + else if (cmp > 0) + i = k; + else { + return &sftp_lookup[k]; + } + } + return NULL; +} + +static int sftp_cmd_help(struct sftp_command *cmd) +{ + int i; + if (cmd->nwords == 1) { + /* + * Give short help on each command. + */ + int maxlen; + maxlen = 0; + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + int len; + if (!sftp_lookup[i].listed) + continue; + len = strlen(sftp_lookup[i].name); + if (maxlen < len) + maxlen = len; + } + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + const struct sftp_cmd_lookup *lookup; + if (!sftp_lookup[i].listed) + continue; + lookup = &sftp_lookup[i]; + printf("%-*s", maxlen+2, lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s\n", lookup->shorthelp); + } + } else { + /* + * Give long help on specific commands. + */ + for (i = 1; i < cmd->nwords; i++) { + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[i]); + if (!lookup) { + printf("help: %s: command not found\n", cmd->words[i]); + } else { + printf("%s", lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s", lookup->longhelp); + } + } + } + return 1; +} + +/* ---------------------------------------------------------------------- + * Command line reading and parsing. + */ +struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) +{ + char *line; + struct sftp_command *cmd; + char *p, *q, *r; + int quoting; + + cmd = snew(struct sftp_command); + cmd->words = NULL; + cmd->nwords = 0; + cmd->wordssize = 0; + + line = NULL; + + if (fp) { + if (modeflags & 1) + printf("psftp> "); + line = fgetline(fp); + } else { + line = ssh_sftp_get_cmdline("psftp> ", back == NULL); + } + + if (!line || !*line) { + cmd->obey = sftp_cmd_quit; + if ((mode == 0) || (modeflags & 1)) + printf("quit\n"); + sfree(line); + return cmd; /* eof */ + } + + line[strcspn(line, "\r\n")] = '\0'; + + if (modeflags & 1) { + printf("%s\n", line); + } + + p = line; + while (*p && (*p == ' ' || *p == '\t')) + p++; + + if (*p == '!') { + /* + * Special case: the ! command. This is always parsed as + * exactly two words: one containing the !, and the second + * containing everything else on the line. + */ + cmd->nwords = cmd->wordssize = 2; + cmd->words = sresize(cmd->words, cmd->wordssize, char *); + cmd->words[0] = dupstr("!"); + cmd->words[1] = dupstr(p+1); + } else if (*p == '#') { + /* + * Special case: comment. Entire line is ignored. + */ + cmd->nwords = cmd->wordssize = 0; + } else { + + /* + * Parse the command line into words. The syntax is: + * - double quotes are removed, but cause spaces within to be + * treated as non-separating. + * - a double-doublequote pair is a literal double quote, inside + * _or_ outside quotes. Like this: + * + * firstword "second word" "this has ""quotes"" in" and""this"" + * + * becomes + * + * >firstword< + * >second word< + * >this has "quotes" in< + * >and"this"< + */ + while (1) { + /* skip whitespace */ + while (*p && (*p == ' ' || *p == '\t')) + p++; + /* terminate loop */ + if (!*p) + break; + /* mark start of word */ + q = r = p; /* q sits at start, r writes word */ + quoting = 0; + while (*p) { + if (!quoting && (*p == ' ' || *p == '\t')) + break; /* reached end of word */ + else if (*p == '"' && p[1] == '"') + p += 2, *r++ = '"'; /* a literal quote */ + else if (*p == '"') + p++, quoting = !quoting; + else + *r++ = *p++; + } + if (*p) + p++; /* skip over the whitespace */ + *r = '\0'; + if (cmd->nwords >= cmd->wordssize) { + cmd->wordssize = cmd->nwords + 16; + cmd->words = sresize(cmd->words, cmd->wordssize, char *); + } + cmd->words[cmd->nwords++] = dupstr(q); + } + } + + sfree(line); + + /* + * Now parse the first word and assign a function. + */ + + if (cmd->nwords == 0) + cmd->obey = sftp_cmd_null; + else { + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[0]); + if (!lookup) + cmd->obey = sftp_cmd_unknown; + else + cmd->obey = lookup->obey; + } + + return cmd; +} + +static int do_sftp_init(void) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + + /* + * Do protocol initialisation. + */ + if (!fxp_init()) { + fprintf(stderr, + "Fatal: unable to initialise SFTP: %s\n", fxp_error()); + return 1; /* failure */ + } + + /* + * Find out where our home directory is. + */ + req = fxp_realpath_send("."); + pktin = sftp_wait_for_reply(req); + homedir = fxp_realpath_recv(pktin, req); + + if (!homedir) { + fprintf(stderr, + "Warning: failed to resolve home directory: %s\n", + fxp_error()); + homedir = dupstr("."); + } else { + printf("Remote working directory is %s\n", homedir); + } + pwd = dupstr(homedir); + return 0; +} + +void do_sftp_cleanup() +{ + char ch; + if (back) { + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + sftp_recvdata(&ch, 1); + back->free(backhandle); + sftp_cleanup_request(); + back = NULL; + backhandle = NULL; + } + if (pwd) { + sfree(pwd); + pwd = NULL; + } + if (homedir) { + sfree(homedir); + homedir = NULL; + } +} + +int do_sftp(int mode, int modeflags, char *batchfile) +{ + FILE *fp; + int ret; + + /* + * Batch mode? + */ + if (mode == 0) { + + /* ------------------------------------------------------------------ + * Now we're ready to do Real Stuff. + */ + while (1) { + struct sftp_command *cmd; + cmd = sftp_getcmd(NULL, 0, 0); + if (!cmd) + break; + ret = cmd->obey(cmd); + if (cmd->words) { + int i; + for(i = 0; i < cmd->nwords; i++) + sfree(cmd->words[i]); + sfree(cmd->words); + } + sfree(cmd); + if (ret < 0) + break; + } + } else { + fp = fopen(batchfile, "r"); + if (!fp) { + printf("Fatal: unable to open %s\n", batchfile); + return 1; + } + ret = 0; + while (1) { + struct sftp_command *cmd; + cmd = sftp_getcmd(fp, mode, modeflags); + if (!cmd) + break; + ret = cmd->obey(cmd); + if (ret < 0) + break; + if (ret == 0) { + if (!(modeflags & 2)) + break; + } + } + fclose(fp); + /* + * In batch mode, and if exit on command failure is enabled, + * any command failure causes the whole of PSFTP to fail. + */ + if (ret == 0 && !(modeflags & 2)) return 2; + } + return 0; +} + +/* ---------------------------------------------------------------------- + * Dirty bits: integration with PuTTY. + */ + +static int verbose = 0; + +/* + * Print an error message and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} +void modalfatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} +void nonfatal(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Error: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); +} +void connection_fatal(void *frontend, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + /* + * This is only here because of the calls to ldisc_send(NULL, + * 0) in ssh.c. Nothing in PSFTP actually needs to use the + * ldisc as an ldisc. So if we get called with any real data, I + * want to know about it. + */ + assert(len == 0); +} + +/* + * In psftp, all agent requests should be synchronous, so this is a + * never-called stub. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + assert(!"We shouldn't be here"); +} + +/* + * Receive a block of data from the SSH link. Block until all data + * is available. + * + * To do this, we repeatedly call the SSH protocol module, with our + * own trap in from_backend() to catch the data that comes back. We + * do this until we have enough data. + */ + +static unsigned char *outptr; /* where to put the data */ +static unsigned outlen; /* how much data required */ +static unsigned char *pending = NULL; /* any spare data */ +static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ +int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +{ + unsigned char *p = (unsigned char *) data; + unsigned len = (unsigned) datalen; + + /* + * stderr data is just spouted to local stderr and otherwise + * ignored. + */ + if (is_stderr) { + if (len > 0) + if (fwrite(data, 1, len, stderr) < len) + /* oh well */; + return 0; + } + + /* + * If this is before the real session begins, just return. + */ + if (!outptr) + return 0; + + if ((outlen > 0) && (len > 0)) { + unsigned used = outlen; + if (used > len) + used = len; + memcpy(outptr, p, used); + outptr += used; + outlen -= used; + p += used; + len -= used; + } + + if (len > 0) { + if (pendsize < pendlen + len) { + pendsize = pendlen + len + 4096; + pending = sresize(pending, pendsize, unsigned char); + } + memcpy(pending + pendlen, p, len); + pendlen += len; + } + + return 0; +} +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} +int from_backend_eof(void *frontend) +{ + /* + * We expect to be the party deciding when to close the + * connection, so if we see EOF before we sent it ourselves, we + * should panic. + */ + if (!sent_eof) { + connection_fatal(frontend, + "Received unexpected end-of-file from SFTP server"); + } + return FALSE; +} +int sftp_recvdata(char *buf, int len) +{ + outptr = (unsigned char *) buf; + outlen = len; + + /* + * See if the pending-input block contains some of what we + * need. + */ + if (pendlen > 0) { + unsigned pendused = pendlen; + if (pendused > outlen) + pendused = outlen; + memcpy(outptr, pending, pendused); + memmove(pending, pending + pendused, pendlen - pendused); + outptr += pendused; + outlen -= pendused; + pendlen -= pendused; + if (pendlen == 0) { + pendsize = 0; + sfree(pending); + pending = NULL; + } + if (outlen == 0) + return 1; + } + + while (outlen > 0) { + if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) + return 0; /* doom */ + } + + return 1; +} +int sftp_senddata(char *buf, int len) +{ + back->send(backhandle, buf, len); + return 1; +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Secure File Transfer (SFTP) client\n"); + printf("%s\n", ver); + printf("Usage: psftp [options] [user@]host\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -b file use specified batchfile\n"); + printf(" -bc output batchfile commands\n"); + printf(" -be don't stop batchfile processing if errors\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -l user connect with specified username\n"); + printf(" -P port connect to specified port\n"); + printf(" -pw passw login with specified password\n"); + printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -hostkey aa:bb:cc:...\n"); + printf(" manually specify a host key (may be repeated)\n"); + printf(" -batch disable all interactive prompts\n"); + printf(" -sshlog file\n"); + printf(" -sshrawlog file\n"); + printf(" log protocol details to a file\n"); + cleanup_exit(1); +} + +static void version(void) +{ + printf("psftp: %s\n", ver); + cleanup_exit(1); +} + +/* + * Connect to a host. + */ +static int psftp_connect(char *userhost, char *user, int portnumber) +{ + char *host, *realhost; + const char *err; + void *logctx; + + /* Separate host and username */ + host = userhost; + host = strrchr(host, '@'); + if (host == NULL) { + host = userhost; + } else { + *host++ = '\0'; + if (user) { + printf("psftp: multiple usernames specified; using \"%s\"\n", + user); + } else + user = userhost; + } + + /* + * If we haven't loaded session details already (e.g., from -load), + * try looking for a session called "host". + */ + if (!loaded_session) { + /* Try to load settings for `host' into a temporary config */ + Conf *conf2 = conf_new(); + conf_set_str(conf2, CONF_host, ""); + do_defaults(host, conf2); + if (conf_get_str(conf2, CONF_host)[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, conf); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + conf_set_str(conf, CONF_host, host); + } + conf_free(conf2); + } else { + /* Patch in hostname `host' to session details. */ + conf_set_str(conf, CONF_host, host); + } + + /* + * Force use of SSH. (If they got the protocol wrong we assume the + * port is useless too.) + */ + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { + conf_set_int(conf, CONF_protocol, PROT_SSH); + conf_set_int(conf, CONF_port, 22); + } + + /* + * If saved session / Default Settings says SSH-1 (`1 only' or `1'), + * then change it to SSH-2, on the grounds that that's more likely to + * work for SFTP. (Can be overridden with `-1' option.) + * But if it says `2 only' or `2', respect which. + */ + if ((conf_get_int(conf, CONF_sshprot) & ~1) != 2) /* is it 2 or 3? */ + conf_set_int(conf, CONF_sshprot, 2); + + /* + * Enact command-line overrides. + */ + cmdline_run_saved(conf); + + /* + * Muck about with the hostname in various ways. + */ + { + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); + } + + /* Set username */ + if (user != NULL && user[0] != '\0') { + conf_set_str(conf, CONF_username, user); + } + + if (portnumber) + conf_set_int(conf, CONF_port, portnumber); + + /* + * Disable scary things which shouldn't be enabled for simple + * things like SCP and SFTP: agent forwarding, port forwarding, + * X forwarding. + */ + conf_set_int(conf, CONF_x11_forward, 0); + conf_set_int(conf, CONF_agentfwd, 0); + conf_set_int(conf, CONF_ssh_simple, TRUE); + { + char *key; + while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) + conf_del_str_str(conf, CONF_portfwd, key); + } + + /* Set up subsystem name. */ + conf_set_str(conf, CONF_remote_cmd, "sftp"); + conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_int(conf, CONF_nopty, TRUE); + + /* + * Set up fallback option, for SSH-1 servers or servers with the + * sftp subsystem not enabled but the server binary installed + * in the usual place. We only support fallback on Unix + * systems, and we use a kludgy piece of shellery which should + * try to find sftp-server in various places (the obvious + * systemwide spots /usr/lib and /usr/local/lib, and then the + * user's PATH) and finally give up. + * + * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server + * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server + * exec sftp-server + * + * the idea being that this will attempt to use either of the + * obvious pathnames and then give up, and when it does give up + * it will print the preferred pathname in the error messages. + */ + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server &&" + " exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server &&" + " exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); + + back = &ssh_backend; + + err = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_int(conf, CONF_tcp_keepalives)); + if (err != NULL) { + fprintf(stderr, "ssh_init: %s\n", err); + return 1; + } + logctx = log_init(NULL, conf); + back->provide_logctx(backhandle, logctx); + console_provide_logctx(logctx); + while (!back->sendok(backhandle)) { + if (back->exitcode(backhandle) >= 0) + return 1; + if (ssh_sftp_loop_iteration() < 0) { + fprintf(stderr, "ssh_init: error during SSH connection setup\n"); + return 1; + } + } + if (verbose && realhost != NULL) + printf("Connected to %s\n", realhost); + if (realhost != NULL) + sfree(realhost); + return 0; +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "psftp: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fprintf(stderr, "\n try typing \"psftp -h\" for help\n"); + exit(1); +} + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = FALSE; + +/* + * Main program. Parse arguments etc. + */ +int psftp_main(int argc, char *argv[]) +{ + int i, ret; + int portnumber = 0; + char *userhost, *user; + int mode = 0; + int modeflags = 0; + char *batchfile = NULL; + + flags = FLAG_STDERR | FLAG_INTERACTIVE +#ifdef FLAG_SYNCAGENT + | FLAG_SYNCAGENT +#endif + ; + cmdline_tooltype = TOOLTYPE_FILETRANSFER; + sk_init(); + + userhost = user = NULL; + + /* Load Default Settings before doing anything else. */ + conf = conf_new(); + do_defaults(NULL, conf); + loaded_session = FALSE; + + for (i = 1; i < argc; i++) { + int ret; + if (argv[i][0] != '-') { + if (userhost) + usage(); + else + userhost = dupstr(argv[i]); + continue; + } + ret = cmdline_process_param(argv[i], i+1connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + sftp_recvdata(&ch, 1); + } + do_sftp_cleanup(); + random_save_seed(); + cmdline_cleanup(); + console_provide_logctx(NULL); + sk_cleanup(); + + return ret; +} diff --git a/netbox/libs/Putty/psftp.h b/netbox/libs/Putty/psftp.h new file mode 100644 index 000000000..e6ad00f69 --- /dev/null +++ b/netbox/libs/Putty/psftp.h @@ -0,0 +1,180 @@ +/* + * psftp.h: interface between psftp.c / scp.c and each + * platform-specific SFTP module. + */ + +#include "int64.h" + +#ifndef PUTTY_PSFTP_H +#define PUTTY_PSFTP_H + +/* + * psftp_getcwd returns the local current directory. The returned + * string must be freed by the caller. + */ +char *psftp_getcwd(void); + +/* + * psftp_lcd changes the local current directory. The return value + * is NULL on success, or else an error message which must be freed + * by the caller. + */ +char *psftp_lcd(char *newdir); + +/* + * Retrieve file times on a local file. Must return two unsigned + * longs in POSIX time_t format. + */ +void get_file_times(char *filename, unsigned long *mtime, + unsigned long *atime); + +/* + * One iteration of the PSFTP event loop: wait for network data and + * process it, once. + */ +int ssh_sftp_loop_iteration(void); + +/* + * Read a command line for PSFTP from standard input. Caller must + * free. + * + * If `backend_required' is TRUE, should also listen for activity + * at the backend (rekeys, clientalives, unexpected closures etc) + * and respond as necessary, and if the backend closes it should + * treat this as a failure condition. If `backend_required' is + * FALSE, a back end is not (intentionally) active at all (e.g. + * psftp before an `open' command). + */ +char *ssh_sftp_get_cmdline(char *prompt, int backend_required); + +/* + * The main program in psftp.c. Called from main() in the platform- + * specific code, after doing any platform-specific initialisation. + */ +int psftp_main(int argc, char *argv[]); + +/* + * These functions are used by PSCP to transmit progress updates + * and error information to a GUI window managing it. This will + * probably only ever be supported on Windows, so these functions + * can safely be stubs on all other platforms. + */ +void gui_update_stats(char *name, unsigned long size, + int percentage, unsigned long elapsed, + unsigned long done, unsigned long eta, + unsigned long ratebs); +void gui_send_errcount(int list, int errs); +void gui_send_char(int is_stderr, int c); +void gui_enable(char *arg); + +/* + * It's likely that a given platform's implementation of file + * transfer utilities is going to want to do things with them that + * aren't present in stdio. Hence we supply an alternative + * abstraction for file access functions. + * + * This abstraction tells you the size and access times when you + * open an existing file (platforms may choose the meaning of the + * file times if it's not clear; whatever they choose will be what + * PSCP sends to the server as mtime and atime), and lets you set + * the times when saving a new file. + * + * On the other hand, the abstraction is pretty simple: it supports + * only opening a file and reading it, or creating a file and writing + * it. None of this read-and-write, seeking-back-and-forth stuff. + */ +typedef struct RFile RFile; +typedef struct WFile WFile; +/* Output params size, perms, mtime and atime can all be NULL if + * desired. perms will be -1 if the OS does not support POSIX permissions. */ +RFile *open_existing_file(char *name, uint64 *size, + unsigned long *mtime, unsigned long *atime, + long *perms); +WFile *open_existing_wfile(char *name, uint64 *size); +/* Returns <0 on error, 0 on eof, or number of bytes read, as usual */ +int read_from_file(RFile *f, void *buffer, int length); +/* Closes and frees the RFile */ +void close_rfile(RFile *f); +WFile *open_new_file(char *name, long perms); +/* Returns <0 on error, 0 on eof, or number of bytes written, as usual */ +int write_to_file(WFile *f, void *buffer, int length); +void set_file_times(WFile *f, unsigned long mtime, unsigned long atime); +/* Closes and frees the WFile */ +void close_wfile(WFile *f); +/* Seek offset bytes through file */ +enum { FROM_START, FROM_CURRENT, FROM_END }; +int seek_file(WFile *f, uint64 offset, int whence); +/* Get file position */ +uint64 get_file_posn(WFile *f); +/* + * Determine the type of a file: nonexistent, file, directory or + * weird. `weird' covers anything else - named pipes, Unix sockets, + * device files, fish, badgers, you name it. Things marked `weird' + * will be skipped over in recursive file transfers, so the only + * real reason for not lumping them in with `nonexistent' is that + * it allows a slightly more sane error message. + */ +enum { + FILE_TYPE_NONEXISTENT, FILE_TYPE_FILE, FILE_TYPE_DIRECTORY, FILE_TYPE_WEIRD +}; +int file_type(char *name); + +/* + * Read all the file names out of a directory. + */ +typedef struct DirHandle DirHandle; +DirHandle *open_directory(char *name); +/* The string returned from this will need freeing if not NULL */ +char *read_filename(DirHandle *dir); +void close_directory(DirHandle *dir); + +/* + * Test a filespec to see whether it's a local wildcard or not. + * Return values: + * + * - WCTYPE_WILDCARD (this is a wildcard). + * - WCTYPE_FILENAME (this is a single file name). + * - WCTYPE_NONEXISTENT (whichever it was, nothing of that name exists). + * + * Some platforms may choose not to support local wildcards when + * they come from the command line; in this case they simply never + * return WCTYPE_WILDCARD, but still test the file's existence. + * (However, all platforms will probably want to support wildcards + * inside the PSFTP CLI.) + */ +enum { + WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD +}; +int test_wildcard(char *name, int cmdline); + +/* + * Actually return matching file names for a local wildcard. + */ +typedef struct WildcardMatcher WildcardMatcher; +WildcardMatcher *begin_wildcard_matching(char *name); +/* The string returned from this will need freeing if not NULL */ +char *wildcard_get_filename(WildcardMatcher *dir); +void finish_wildcard_matching(WildcardMatcher *dir); + +/* + * Vet a filename returned from the remote host, to ensure it isn't + * in some way malicious. The idea is that this function is applied + * to filenames returned from FXP_READDIR, which means we can panic + * if we see _anything_ resembling a directory separator. + * + * Returns TRUE if the filename is kosher, FALSE if dangerous. + */ +int vet_filename(char *name); + +/* + * Create a directory. Returns 0 on error, !=0 on success. + */ +int create_directory(char *name); + +/* + * Concatenate a directory name and a file name. The way this is + * done will depend on the OS. + */ +char *dir_file_cat(char *dir, char *file); + +#endif /* PUTTY_PSFTP_H */ diff --git a/netbox/libs/Putty/putty.h b/netbox/libs/Putty/putty.h new file mode 100644 index 000000000..f750909b1 --- /dev/null +++ b/netbox/libs/Putty/putty.h @@ -0,0 +1,1489 @@ +#ifndef PUTTY_PUTTY_H +#define PUTTY_PUTTY_H + +#include /* for wchar_t */ + +/* + * Global variables. Most modules declare these `extern', but + * window.c will do `#define PUTTY_DO_GLOBALS' before including this + * module, and so will get them properly defined. + */ +#ifndef GLOBAL +#ifdef PUTTY_DO_GLOBALS +#define GLOBAL +#else +#define GLOBAL extern +#endif +#endif + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct conf_tag Conf; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +#include "puttyps.h" +#include "network.h" +#include "misc.h" + +/* + * Fingerprints of the PGP master keys that can be used to establish a trust + * path between an executable and other files. + */ +#define PGP_MASTER_KEY_FP \ + "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C" +#define PGP_RSA_MASTER_KEY_FP \ + "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C" +#define PGP_DSA_MASTER_KEY_FP \ + "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E" + +/* Three attribute types: + * The ATTRs (normal attributes) are stored with the characters in + * the main display arrays + * + * The TATTRs (temporary attributes) are generated on the fly, they + * can overlap with characters but not with normal attributes. + * + * The LATTRs (line attributes) are an entirely disjoint space of + * flags. + * + * The DATTRs (display attributes) are internal to terminal.c (but + * defined here because their values have to match the others + * here); they reuse the TATTR_* space but are always masked off + * before sending to the front end. + * + * ATTR_INVALID is an illegal colour combination. + */ + +#define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */ +#define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */ +#define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */ +#define TATTR_COMBINING 0x80000000UL /* combining characters */ + +#define DATTR_STARTRUN 0x80000000UL /* start of redraw run */ + +#define TDATTR_MASK 0xF0000000UL +#define TATTR_MASK (TDATTR_MASK) +#define DATTR_MASK (TDATTR_MASK) + +#define LATTR_NORM 0x00000000UL +#define LATTR_WIDE 0x00000001UL +#define LATTR_TOP 0x00000002UL +#define LATTR_BOT 0x00000003UL +#define LATTR_MODE 0x00000003UL +#define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */ +#define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character + wrapped to next line, so last + single-width cell is empty */ + +#define ATTR_INVALID 0x03FFFFU + +/* Like Linux use the F000 page for direct to font. */ +#define CSET_OEMCP 0x0000F000UL /* OEM Codepage DTF */ +#define CSET_ACP 0x0000F100UL /* Ansi Codepage DTF */ + +/* These are internal use overlapping with the UTF-16 surrogates */ +#define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */ +#define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */ +#define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */ +#define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */ +#define CSET_MASK 0xFFFFFF00UL /* Character set mask */ + +#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800) +#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000) + +#define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */ +/* + * UCSWIDE is a special value used in the terminal data to signify + * the character cell containing the right-hand half of a CJK wide + * character. We use 0xDFFF because it's part of the surrogate + * range and hence won't be used for anything else (it's impossible + * to input it via UTF-8 because our UTF-8 decoder correctly + * rejects surrogates). + */ +#define UCSWIDE 0xDFFF + +#define ATTR_NARROW 0x800000U +#define ATTR_WIDE 0x400000U +#define ATTR_BOLD 0x040000U +#define ATTR_UNDER 0x080000U +#define ATTR_REVERSE 0x100000U +#define ATTR_BLINK 0x200000U +#define ATTR_FGMASK 0x0001FFU +#define ATTR_BGMASK 0x03FE00U +#define ATTR_COLOURS 0x03FFFFU +#define ATTR_FGSHIFT 0 +#define ATTR_BGSHIFT 9 + +/* + * The definitive list of colour numbers stored in terminal + * attribute words is kept here. It is: + * + * - 0-7 are ANSI colours (KRGYBMCW). + * - 8-15 are the bold versions of those colours. + * - 16-255 are the remains of the xterm 256-colour mode (a + * 216-colour cube with R at most significant and B at least, + * followed by a uniform series of grey shades running between + * black and white but not including either on grounds of + * redundancy). + * - 256 is default foreground + * - 257 is default bold foreground + * - 258 is default background + * - 259 is default bold background + * - 260 is cursor foreground + * - 261 is cursor background + */ + +#define ATTR_DEFFG (256 << ATTR_FGSHIFT) +#define ATTR_DEFBG (258 << ATTR_BGSHIFT) +#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG) + +struct sesslist { + int nsessions; + char **sessions; + char *buffer; /* so memory can be freed later */ +}; + +struct unicode_data { + char **uni_tbl; + int dbcs_screenfont; + int font_codepage; + int line_codepage; + wchar_t unitab_scoacs[256]; + wchar_t unitab_line[256]; + wchar_t unitab_font[256]; + wchar_t unitab_xterm[256]; + wchar_t unitab_oemcp[256]; + unsigned char unitab_ctrl[256]; +}; + +#define LGXF_OVR 1 /* existing logfile overwrite */ +#define LGXF_APN 0 /* existing logfile append */ +#define LGXF_ASK -1 /* existing logfile ask */ +#define LGTYP_NONE 0 /* logmode: no logging */ +#define LGTYP_ASCII 1 /* logmode: pure ascii */ +#define LGTYP_DEBUG 2 /* logmode: all chars of traffic */ +#define LGTYP_PACKETS 3 /* logmode: SSH data packets */ +#define LGTYP_SSHRAW 4 /* logmode: SSH raw data */ + +typedef enum { + /* Actual special commands. Originally Telnet, but some codes have + * been re-used for similar specials in other protocols. */ + TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT, + TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING, + TS_EOL, + /* Special command for SSH. */ + TS_REKEY, + /* POSIX-style signals. (not Telnet) */ + TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL, + TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV, + TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2, + /* Pseudo-specials used for constructing the specials menu. */ + TS_SEP, /* Separator */ + TS_SUBMENU, /* Start a new submenu with specified name */ + TS_EXITMENU /* Exit current submenu or end of specials */ +} Telnet_Special; + +struct telnet_special { + const char *name; + int code; +}; + +typedef enum { + MBT_NOTHING, + MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */ + MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */ + MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */ +} Mouse_Button; + +typedef enum { + MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE +} Mouse_Action; + +/* Keyboard modifiers -- keys the user is actually holding down */ + +#define PKM_SHIFT 0x01 +#define PKM_CONTROL 0x02 +#define PKM_META 0x04 +#define PKM_ALT 0x08 + +/* Keyboard flags that aren't really modifiers */ +#define PKF_CAPSLOCK 0x10 +#define PKF_NUMLOCK 0x20 +#define PKF_REPEAT 0x40 + +/* Stand-alone keysyms for function keys */ + +typedef enum { + PK_NULL, /* No symbol for this key */ + /* Main keypad keys */ + PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE, + /* Editing keys */ + PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN, + /* Cursor keys */ + PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST, + /* Numeric keypad */ /* Real one looks like: */ + PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */ + PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */ + PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */ + PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */ + PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */ + /* Top row */ + PK_F1, PK_F2, PK_F3, PK_F4, PK_F5, + PK_F6, PK_F7, PK_F8, PK_F9, PK_F10, + PK_F11, PK_F12, PK_F13, PK_F14, PK_F15, + PK_F16, PK_F17, PK_F18, PK_F19, PK_F20, + PK_PAUSE +} Key_Sym; + +#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN) +#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST) +#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER) +#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20) + +enum { + VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE +}; + +enum { + /* + * SSH-2 key exchange algorithms + */ + KEX_WARN, + KEX_DHGROUP1, + KEX_DHGROUP14, + KEX_DHGEX, + KEX_RSA, + KEX_MAX +}; + +enum { + /* + * SSH ciphers (both SSH-1 and SSH-2) + */ + CIPHER_WARN, /* pseudo 'cipher' */ + CIPHER_3DES, + CIPHER_BLOWFISH, + CIPHER_AES, /* (SSH-2 only) */ + CIPHER_DES, + CIPHER_ARCFOUR, + CIPHER_MAX /* no. ciphers (inc warn) */ +}; + +enum { + /* + * Several different bits of the PuTTY configuration seem to be + * three-way settings whose values are `always yes', `always + * no', and `decide by some more complex automated means'. This + * is true of line discipline options (local echo and line + * editing), proxy DNS, Close On Exit, and SSH server bug + * workarounds. Accordingly I supply a single enum here to deal + * with them all. + */ + FORCE_ON, FORCE_OFF, AUTO +}; + +enum { + /* + * Proxy types. + */ + PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, + PROXY_HTTP, PROXY_TELNET, PROXY_CMD +}; + +enum { + /* + * Line discipline options which the backend might try to control. + */ + LD_EDIT, /* local line editing */ + LD_ECHO /* local echo */ +}; + +enum { + /* Actions on remote window title query */ + TITLE_NONE, TITLE_EMPTY, TITLE_REAL +}; + +enum { + /* Protocol back ends. (CONF_protocol) */ + PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, + /* PROT_SERIAL is supported on a subset of platforms, but it doesn't + * hurt to define it globally. */ + PROT_SERIAL +}; + +enum { + /* Bell settings (CONF_beep) */ + BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER +}; + +enum { + /* Taskbar flashing indication on bell (CONF_beep_ind) */ + B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY +}; + +enum { + /* Resize actions (CONF_resize_action) */ + RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER +}; + +enum { + /* Function key types (CONF_funky_type) */ + FUNKY_TILDE, + FUNKY_LINUX, + FUNKY_XTERM, + FUNKY_VT400, + FUNKY_VT100P, + FUNKY_SCO +}; + +enum { + FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE +}; + +enum { + SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE +}; + +enum { + SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR +}; + +/* + * Tables of string <-> enum value mappings used in settings.c. + * Defined here so that backends can export their GSS library tables + * to the cross-platform settings code. + */ +struct keyvalwhere { + /* + * Two fields which define a string and enum value to be + * equivalent to each other. + */ + char *s; + int v; + + /* + * The next pair of fields are used by gprefs() in settings.c to + * arrange that when it reads a list of strings representing a + * preference list and translates it into the corresponding list + * of integers, strings not appearing in the list are entered in a + * configurable position rather than uniformly at the end. + */ + + /* + * 'vrel' indicates which other value in the list to place this + * element relative to. It should be a value that has occurred in + * a 'v' field of some other element of the array, or -1 to + * indicate that we simply place relative to one or other end of + * the list. + * + * gprefs will try to process the elements in an order which makes + * this field work (i.e. so that the element referenced has been + * added before processing this one). + */ + int vrel; + + /* + * 'where' indicates whether to place the new value before or + * after the one referred to by vrel. -1 means before; +1 means + * after. + * + * When vrel is -1, this also implicitly indicates which end of + * the array to use. So vrel=-1, where=-1 means to place _before_ + * some end of the list (hence, at the last element); vrel=-1, + * where=+1 means to place _after_ an end (hence, at the first). + */ + int where; +}; + +#ifndef NO_GSSAPI +extern const int ngsslibs; +extern const char *const gsslibnames[]; /* for displaying in configuration */ +extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */ +#endif + +extern const char *const ttymodes[]; + +enum { + /* + * Network address types. Used for specifying choice of IPv4/v6 + * in config; also used in proxy.c to indicate whether a given + * host name has already been resolved or will be resolved at + * the proxy end. + */ + ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME +}; + +struct backend_tag { + const char *(*init) (void *frontend_handle, void **backend_handle, + Conf *conf, char *host, int port, char **realhost, + int nodelay, int keepalive); + void (*free) (void *handle); + /* back->reconfig() passes in a replacement configuration. */ + void (*reconfig) (void *handle, Conf *conf); + /* back->send() returns the current amount of buffered data. */ + int (*send) (void *handle, char *buf, int len); + /* back->sendbuffer() does the same thing but without attempting a send */ + int (*sendbuffer) (void *handle); + void (*size) (void *handle, int width, int height); + void (*special) (void *handle, Telnet_Special code); + const struct telnet_special *(*get_specials) (void *handle); + int (*connected) (void *handle); + int (*exitcode) (void *handle); + /* If back->sendok() returns FALSE, data sent to it from the frontend + * may be lost. */ + int (*sendok) (void *handle); + int (*ldisc) (void *handle, int); + void (*provide_ldisc) (void *handle, void *ldisc); + void (*provide_logctx) (void *handle, void *logctx); + /* + * back->unthrottle() tells the back end that the front end + * buffer is clearing. + */ + void (*unthrottle) (void *handle, int); + int (*cfg_info) (void *handle); + char *name; + int protocol; + int default_port; +}; + +extern Backend *backends[]; + +/* + * Suggested default protocol provided by the backend link module. + * The application is free to ignore this. + */ +extern const int be_default_protocol; + +/* + * Name of this particular application, for use in the config box + * and other pieces of text. + */ +extern const char *const appname; + +/* + * Some global flags denoting the type of application. + * + * FLAG_VERBOSE is set when the user requests verbose details. + * + * FLAG_STDERR is set in command-line applications (which have a + * functioning stderr that it makes sense to write to) and not in + * GUI applications (which don't). + * + * FLAG_INTERACTIVE is set when a full interactive shell session is + * being run, _either_ because no remote command has been provided + * _or_ because the application is GUI and can't run non- + * interactively. + * + * These flags describe the type of _application_ - they wouldn't + * vary between individual sessions - and so it's OK to have this + * variable be GLOBAL. + * + * Note that additional flags may be defined in platform-specific + * headers. It's probably best if those ones start from 0x1000, to + * avoid collision. + */ +#define FLAG_VERBOSE 0x0001 +#define FLAG_STDERR 0x0002 +#define FLAG_INTERACTIVE 0x0004 +GLOBAL int flags; + +/* + * Likewise, these two variables are set up when the application + * initialises, and inform all default-settings accesses after + * that. + */ +GLOBAL int default_protocol; +GLOBAL int default_port; + +/* + * This is set TRUE by cmdline.c iff a session is loaded with "-load". + */ +GLOBAL int loaded_session; +/* + * This is set to the name of the loaded session. + */ +GLOBAL char *cmdline_session_name; + +struct RSAKey; /* be a little careful of scope */ + +/* + * Mechanism for getting text strings such as usernames and passwords + * from the front-end. + * The fields are mostly modelled after SSH's keyboard-interactive auth. + * FIXME We should probably mandate a character set/encoding (probably UTF-8). + * + * Since many of the pieces of text involved may be chosen by the server, + * the caller must take care to ensure that the server can't spoof locally- + * generated prompts such as key passphrase prompts. Some ground rules: + * - If the front-end needs to truncate a string, it should lop off the + * end. + * - The front-end should filter out any dangerous characters and + * generally not trust the strings. (But \n is required to behave + * vaguely sensibly, at least in `instruction', and ideally in + * `prompt[]' too.) + */ +typedef struct { + char *prompt; + int echo; + /* + * 'result' must be a dynamically allocated array of exactly + * 'resultsize' chars. The code for actually reading input may + * realloc it bigger (and adjust resultsize accordingly) if it has + * to. The caller should free it again when finished with it. + * + * If resultsize==0, then result may be NULL. When setting up a + * prompt_t, it's therefore easiest to initialise them this way, + * which means all actual allocation is done by the callee. This + * is what add_prompt does. + */ + char *result; + size_t resultsize; +} prompt_t; +typedef struct { + /* + * Indicates whether the information entered is to be used locally + * (for instance a key passphrase prompt), or is destined for the wire. + * This is a hint only; the front-end is at liberty not to use this + * information (so the caller should ensure that the supplied text is + * sufficient). + */ + int to_server; + char *name; /* Short description, perhaps for dialog box title */ + int name_reqd; /* Display of `name' required or optional? */ + char *instruction; /* Long description, maybe with embedded newlines */ + int instr_reqd; /* Display of `instruction' required or optional? */ + size_t n_prompts; /* May be zero (in which case display the foregoing, + * if any, and return success) */ + prompt_t **prompts; + void *frontend; + void *data; /* slot for housekeeping data, managed by + * get_userpass_input(); initially NULL */ +} prompts_t; +prompts_t *new_prompts(void *frontend); +void add_prompt(prompts_t *p, char *promptstr, int echo); +void prompt_set_result(prompt_t *pr, const char *newstr); +void prompt_ensure_result_size(prompt_t *pr, int len); +/* Burn the evidence. (Assumes _all_ strings want free()ing.) */ +void free_prompts(prompts_t *p); + +/* + * Exports from the front end. + */ +void request_resize(void *frontend, int, int); +void do_text(Context, int, int, wchar_t *, int, unsigned long, int); +void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int); +int char_width(Context ctx, int uc); +#ifdef OPTIMISE_SCROLL +void do_scroll(Context, int, int, int); +#endif +void set_title(void *frontend, char *); +void set_icon(void *frontend, char *); +void set_sbar(void *frontend, int, int, int); +Context get_ctx(void *frontend); +void free_ctx(Context); +void palette_set(void *frontend, int, int, int, int); +void palette_reset(void *frontend); +void write_aclip(void *frontend, char *, int, int); +void write_clip(void *frontend, wchar_t *, int *, int, int); +void get_clip(void *frontend, wchar_t **, int *); +void optimised_move(void *frontend, int, int, int); +void set_raw_mouse_mode(void *frontend, int); +void connection_fatal(void *frontend, const char *, ...); +void nonfatal(const char *, ...); +void fatalbox(const char *, ...); +void modalfatalbox(const char *, ...); +#ifdef macintosh +#pragma noreturn(fatalbox) +#pragma noreturn(modalfatalbox) +#endif +void do_beep(void *frontend, int); +void begin_session(void *frontend); +void sys_cursor(void *frontend, int x, int y); +void request_paste(void *frontend); +void frontend_keypress(void *frontend); +void ldisc_update(void *frontend, int echo, int edit); +/* It's the backend's responsibility to invoke this at the start of a + * connection, if necessary; it can also invoke it later if the set of + * special commands changes. It does not need to invoke it at session + * shutdown. */ +void update_specials_menu(void *frontend); +int from_backend(void *frontend, int is_stderr, const char *data, int len); +int from_backend_untrusted(void *frontend, const char *data, int len); +/* Called when the back end wants to indicate that EOF has arrived on + * the server-to-client stream. Returns FALSE to indicate that we + * intend to keep the session open in the other direction, or TRUE to + * indicate that if they're closing so are we. */ +int from_backend_eof(void *frontend); +void notify_remote_exit(void *frontend); +/* Get a sensible value for a tty mode. NULL return = don't set. + * Otherwise, returned value should be freed by caller. */ +char *get_ttymode(void *frontend, const char *mode); +/* + * >0 = `got all results, carry on' + * 0 = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?) + * <0 = `please call back later with more in/inlen' + */ +int get_userpass_input(prompts_t *p, const uint8_t *in, int inlen); +#define OPTIMISE_IS_SCROLL 1 + +void set_iconic(void *frontend, int iconic); +void move_window(void *frontend, int x, int y); +void set_zorder(void *frontend, int top); +void refresh_window(void *frontend); +void set_zoomed(void *frontend, int zoomed); +int is_iconic(void *frontend); +void get_window_pos(void *frontend, int *x, int *y); +void get_window_pixels(void *frontend, int *x, int *y); +char *get_window_title(void *frontend, int icon); +/* Hint from backend to frontend about time-consuming operations. + * Initial state is assumed to be BUSY_NOT. */ +enum { + BUSY_NOT, /* Not busy, all user interaction OK */ + BUSY_WAITING, /* Waiting for something; local event loops still running + so some local interaction (e.g. menus) OK, but network + stuff is suspended */ + BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */ +}; +void set_busy_status(void *frontend, int status); + +void cleanup_exit(int); + +/* + * Exports from conf.c, and a big enum (via parametric macro) of + * configuration option keys. + */ +#define CONFIG_OPTIONS(X) \ + /* X(value-type, subkey-type, keyword) */ \ + X(STR, NONE, host) \ + X(INT, NONE, port) \ + X(INT, NONE, protocol) \ + X(INT, NONE, addressfamily) \ + X(INT, NONE, close_on_exit) \ + X(INT, NONE, warn_on_close) \ + X(INT, NONE, ping_interval) /* in seconds */ \ + X(INT, NONE, tcp_nodelay) \ + X(INT, NONE, tcp_keepalives) \ + X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \ + /* Proxy options */ \ + X(STR, NONE, proxy_exclude_list) \ + X(INT, NONE, proxy_dns) \ + X(INT, NONE, even_proxy_localhost) \ + X(INT, NONE, proxy_type) \ + X(STR, NONE, proxy_host) \ + X(INT, NONE, proxy_port) \ + X(STR, NONE, proxy_username) \ + X(STR, NONE, proxy_password) \ + X(STR, NONE, proxy_telnet_command) \ + /* SSH options */ \ + X(STR, NONE, remote_cmd) \ + X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ + X(INT, NONE, nopty) \ + X(INT, NONE, compression) \ + X(INT, INT, ssh_kexlist) \ + X(INT, NONE, ssh_rekey_time) /* in minutes */ \ + X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ + X(INT, NONE, tryagent) \ + X(INT, NONE, agentfwd) \ + X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \ + X(INT, INT, ssh_cipherlist) \ + X(FILENAME, NONE, keyfile) \ + X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \ + X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ + X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ + X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ + X(INT, NONE, try_tis_auth) \ + X(INT, NONE, try_ki_auth) \ + X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \ + X(INT, NONE, gssapifwd) /* forward tgt via gss */ \ + X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ + X(FILENAME, NONE, ssh_gss_custom) \ + X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ + X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ + X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \ + X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \ + X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \ + /* Telnet options */ \ + X(STR, NONE, termtype) \ + X(STR, NONE, termspeed) \ + X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \ + X(STR, STR, environmt) \ + X(STR, NONE, username) \ + X(INT, NONE, username_from_env) \ + X(STR, NONE, localusername) \ + X(INT, NONE, rfc_environ) \ + X(INT, NONE, passive_telnet) \ + /* Serial port options */ \ + X(STR, NONE, serline) \ + X(INT, NONE, serspeed) \ + X(INT, NONE, serdatabits) \ + X(INT, NONE, serstopbits) \ + X(INT, NONE, serparity) \ + X(INT, NONE, serflow) \ + /* Keyboard options */ \ + X(INT, NONE, bksp_is_delete) \ + X(INT, NONE, rxvt_homeend) \ + X(INT, NONE, funky_type) \ + X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \ + X(INT, NONE, no_applic_k) /* totally disable app keypad */ \ + X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ + X(INT, NONE, no_remote_resize) /* disable remote resizing */ \ + X(INT, NONE, no_alt_screen) /* disable alternate screen */ \ + X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \ + X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \ + X(INT, NONE, no_remote_charset) /* disable remote charset config */ \ + X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \ + X(INT, NONE, app_cursor) \ + X(INT, NONE, app_keypad) \ + X(INT, NONE, nethack_keypad) \ + X(INT, NONE, telnet_keyboard) \ + X(INT, NONE, telnet_newline) \ + X(INT, NONE, alt_f4) /* is it special? */ \ + X(INT, NONE, alt_space) /* is it special? */ \ + X(INT, NONE, alt_only) /* is it special? */ \ + X(INT, NONE, localecho) \ + X(INT, NONE, localedit) \ + X(INT, NONE, alwaysontop) \ + X(INT, NONE, fullscreenonaltenter) \ + X(INT, NONE, scroll_on_key) \ + X(INT, NONE, scroll_on_disp) \ + X(INT, NONE, erase_to_scrollback) \ + X(INT, NONE, compose_key) \ + X(INT, NONE, ctrlaltkeys) \ + X(STR, NONE, wintitle) /* initial window title */ \ + /* Terminal options */ \ + X(INT, NONE, savelines) \ + X(INT, NONE, dec_om) \ + X(INT, NONE, wrap_mode) \ + X(INT, NONE, lfhascr) \ + X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ + X(INT, NONE, blink_cur) \ + X(INT, NONE, beep) \ + X(INT, NONE, beep_ind) \ + X(INT, NONE, bellovl) /* bell overload protection active? */ \ + X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \ + X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \ + X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \ + X(FILENAME, NONE, bell_wavefile) \ + X(INT, NONE, scrollbar) \ + X(INT, NONE, scrollbar_in_fullscreen) \ + X(INT, NONE, resize_action) \ + X(INT, NONE, bce) \ + X(INT, NONE, blinktext) \ + X(INT, NONE, win_name_always) \ + X(INT, NONE, width) \ + X(INT, NONE, height) \ + X(FONT, NONE, font) \ + X(INT, NONE, font_quality) \ + X(FILENAME, NONE, logfilename) \ + X(INT, NONE, logtype) \ + X(INT, NONE, logxfovr) \ + X(INT, NONE, logflush) \ + X(INT, NONE, logomitpass) \ + X(INT, NONE, logomitdata) \ + X(INT, NONE, hide_mouseptr) \ + X(INT, NONE, sunken_edge) \ + X(INT, NONE, window_border) \ + X(STR, NONE, answerback) \ + X(STR, NONE, printer) \ + X(INT, NONE, arabicshaping) \ + X(INT, NONE, bidi) \ + /* Colour options */ \ + X(INT, NONE, ansi_colour) \ + X(INT, NONE, xterm_256_colour) \ + X(INT, NONE, system_colour) \ + X(INT, NONE, try_palette) \ + X(INT, NONE, bold_style) \ + X(INT, INT, colours) \ + /* Selection options */ \ + X(INT, NONE, mouse_is_xterm) \ + X(INT, NONE, rect_select) \ + X(INT, NONE, rawcnp) \ + X(INT, NONE, rtf_paste) \ + X(INT, NONE, mouse_override) \ + X(INT, INT, wordness) \ + /* translations */ \ + X(INT, NONE, vtmode) \ + X(STR, NONE, line_codepage) \ + X(INT, NONE, cjk_ambig_wide) \ + X(INT, NONE, utf8_override) \ + X(INT, NONE, xlat_capslockcyr) \ + /* X11 forwarding */ \ + X(INT, NONE, x11_forward) \ + X(STR, NONE, x11_display) \ + X(INT, NONE, x11_auth) \ + X(FILENAME, NONE, xauthfile) \ + /* port forwarding */ \ + X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ + X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ + /* \ + * Subkeys for 'portfwd' can have the following forms: \ + * \ + * [LR]localport \ + * [LR]localaddr:localport \ + * \ + * Dynamic forwardings are indicated by an 'L' key, and the \ + * special value "D". For all other forwardings, the value \ + * should be of the form 'host:port'. \ + */ \ + X(STR, STR, portfwd) \ + /* SSH bug compatibility modes */ \ + X(INT, NONE, sshbug_ignore1) \ + X(INT, NONE, sshbug_plainpw1) \ + X(INT, NONE, sshbug_rsa1) \ + X(INT, NONE, sshbug_hmac2) \ + X(INT, NONE, sshbug_derivekey2) \ + X(INT, NONE, sshbug_rsapad2) \ + X(INT, NONE, sshbug_pksessid2) \ + X(INT, NONE, sshbug_rekey2) \ + X(INT, NONE, sshbug_maxpkt2) \ + X(INT, NONE, sshbug_ignore2) \ + X(INT, NONE, sshbug_oldgex2) \ + X(INT, NONE, sshbug_winadj) \ + X(INT, NONE, sshbug_chanreq) \ + /* \ + * ssh_simple means that we promise never to open any channel \ + * other than the main one, which means it can safely use a very \ + * large window in SSH-2. \ + */ \ + X(INT, NONE, ssh_simple) \ + X(INT, NONE, ssh_connection_sharing) \ + X(INT, NONE, ssh_connection_sharing_upstream) \ + X(INT, NONE, ssh_connection_sharing_downstream) \ + /* + * ssh_manual_hostkeys is conceptually a set rather than a + * dictionary: the string subkeys are the important thing, and the + * actual values to which those subkeys map are all "". + */ \ + X(STR, STR, ssh_manual_hostkeys) \ + /* Options for pterm. Should split out into platform-dependent part. */ \ + X(INT, NONE, stamp_utmp) \ + X(INT, NONE, login_shell) \ + X(INT, NONE, scrollbar_on_left) \ + X(INT, NONE, shadowbold) \ + X(FONT, NONE, boldfont) \ + X(FONT, NONE, widefont) \ + X(FONT, NONE, wideboldfont) \ + X(INT, NONE, shadowboldoffset) \ + X(INT, NONE, crhaslf) \ + X(STR, NONE, winclass) \ + /* MPEXT BEGIN */ \ + X(INT, NONE, connect_timeout) \ + X(INT, NONE, sndbuf) \ + X(INT, NONE, force_remote_cmd2) \ + /* MPEXT END */ \ + +/* Now define the actual enum of option keywords using that macro. */ +#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword, +enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS }; +#undef CONF_ENUM_DEF + +#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */ + +/* Functions handling configuration structures. */ +Conf *conf_new(void); /* create an empty configuration */ +void conf_free(Conf *conf); +Conf *conf_copy(Conf *oldconf); +void conf_copy_into(Conf *dest, Conf *src); +/* Mandatory accessor functions: enforce by assertion that keys exist. */ +int conf_get_int(Conf *conf, int key); +int conf_get_int_int(Conf *conf, int key, int subkey); +char *conf_get_str(Conf *conf, int key); /* result still owned by conf */ +char *conf_get_str_str(Conf *conf, int key, const char *subkey); +Filename *conf_get_filename(Conf *conf, int key); +FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */ +/* Optional accessor function: return NULL if key does not exist. */ +char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey); +/* Accessor function to step through a string-subkeyed list. + * Returns the next subkey after the provided one, or the first if NULL. + * Returns NULL if there are none left. + * Both the return value and *subkeyout are still owned by conf. */ +char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout); +/* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */ +char *conf_get_str_nthstrkey(Conf *conf, int key, int n); +/* Functions to set entries in configuration. Always copy their inputs. */ +void conf_set_int(Conf *conf, int key, int value); +void conf_set_int_int(Conf *conf, int key, int subkey, int value); +void conf_set_str(Conf *conf, int key, const char *value); +void conf_set_str_str(Conf *conf, int key, + const char *subkey, const char *val); +void conf_del_str_str(Conf *conf, int key, const char *subkey); +void conf_set_filename(Conf *conf, int key, const Filename *val); +void conf_set_fontspec(Conf *conf, int key, const FontSpec *val); +/* Serialisation functions for Duplicate Session */ +int conf_serialised_size(Conf *conf); +void conf_serialise(Conf *conf, void *data); +int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/ + +/* + * Functions to copy, free, serialise and deserialise FontSpecs. + * Provided per-platform, to go with the platform's idea of a + * FontSpec's contents. + * + * fontspec_serialise returns the number of bytes written, and can + * handle data==NULL without crashing. So you can call it once to find + * out a size, then again once you've allocated a buffer. + */ +FontSpec *fontspec_copy(const FontSpec *f); +void fontspec_free(FontSpec *f); +int fontspec_serialise(FontSpec *f, void *data); +FontSpec *fontspec_deserialise(void *data, int maxsize, int *used); + +/* + * Exports from noise.c. + */ +void noise_get_heavy(void (*func) (void *, int)); +void noise_get_light(void (*func) (void *, int)); +void noise_regular(void); +void noise_ultralight(unsigned long data); +void random_save_seed(void); +void random_destroy_seed(void); + +/* + * Exports from settings.c. + */ +Backend *backend_from_name(const char *name); +Backend *backend_from_proto(int proto); +char *get_remote_username(Conf *conf); /* dynamically allocated */ +char *save_settings(char *section, Conf *conf); +void save_open_settings(void *sesskey, Conf *conf); +void load_settings(char *section, Conf *conf); +void load_open_settings(void *sesskey, Conf *conf); +void get_sesslist(struct sesslist *, int allocate); +void do_defaults(char *, Conf *); +void registry_cleanup(void); + +/* + * Functions used by settings.c to provide platform-specific + * default settings. + * + * (The integer one is expected to return `def' if it has no clear + * opinion of its own. This is because there's no integer value + * which I can reliably set aside to indicate `nil'. The string + * function is perfectly all right returning NULL, of course. The + * Filename and FontSpec functions are _not allowed_ to fail to + * return, since these defaults _must_ be per-platform.) + * + * The 'Filename *' returned by platform_default_filename, and the + * 'FontSpec *' returned by platform_default_fontspec, have ownership + * transferred to the caller, and must be freed. + */ +char *platform_default_s(const char *name); +int platform_default_i(const char *name, int def); +Filename *platform_default_filename(const char *name); +FontSpec *platform_default_fontspec(const char *name); + +/* + * Exports from terminal.c. + */ + +Terminal *term_init(Conf *, struct unicode_data *, void *); +void term_free(Terminal *); +void term_size(Terminal *, int, int, int); +void term_paint(Terminal *, Context, int, int, int, int, int); +void term_scroll(Terminal *, int, int); +void term_scroll_to_selection(Terminal *, int); +void term_pwron(Terminal *, int); +void term_clrsb(Terminal *); +void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, + int,int,int,int,int); +void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, + unsigned int); +void term_deselect(Terminal *); +void term_update(Terminal *); +void term_invalidate(Terminal *); +void term_blink(Terminal *, int set_cursor); +void term_do_paste(Terminal *); +void term_nopaste(Terminal *); +int term_ldisc(Terminal *, int option); +void term_copyall(Terminal *); +void term_reconfig(Terminal *, Conf *); +void term_seen_key_event(Terminal *); +int term_data(Terminal *, int is_stderr, const char *data, int len); +int term_data_untrusted(Terminal *, const char *data, int len); +void term_provide_resize_fn(Terminal *term, + void (*resize_fn)(void *, int, int), + void *resize_ctx); +void term_provide_logctx(Terminal *term, void *logctx); +void term_set_focus(Terminal *term, int has_focus); +char *term_get_ttymode(Terminal *term, const char *mode); +int term_get_userpass_input(Terminal *term, prompts_t *p, + unsigned char *in, int inlen); + +int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl); + +/* + * Exports from logging.c. + */ +void *log_init(void *frontend, Conf *conf); +void log_free(void *logctx); +void log_reconfig(void *logctx, Conf *conf); +void logfopen(void *logctx); +void logfclose(void *logctx); +void logtraffic(void *logctx, unsigned char c, int logmode); +void logflush(void *logctx); +void log_eventlog(void *logctx, const char *string); +enum { PKT_INCOMING, PKT_OUTGOING }; +enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT }; +struct logblank_t { + int offset; + int len; + int type; +}; +void log_packet(void *logctx, int direction, int type, + char *texttype, const void *data, int len, + int n_blanks, const struct logblank_t *blanks, + const unsigned long *sequence, + unsigned downstream_id, const char *additional_log_text); + +/* + * Exports from testback.c + */ + +extern Backend null_backend; +extern Backend loop_backend; + +/* + * Exports from raw.c. + */ + +extern Backend raw_backend; + +/* + * Exports from rlogin.c. + */ + +extern Backend rlogin_backend; + +/* + * Exports from telnet.c. + */ + +extern Backend telnet_backend; + +/* + * Exports from ssh.c. + */ +extern Backend ssh_backend; + +/* + * Exports from ldisc.c. + */ +void *ldisc_create(Conf *, Terminal *, Backend *, void *, void *); +void ldisc_configure(void *, Conf *); +void ldisc_free(void *); +void ldisc_send(void *handle, char *buf, int len, int interactive); + +/* + * Exports from ldiscucs.c. + */ +void lpage_send(void *, int codepage, char *buf, int len, int interactive); +void luni_send(void *, wchar_t * widebuf, int len, int interactive); + +/* + * Exports from sshrand.c. + */ + +void random_add_noise(void *noise, int length); +int random_byte(void); +void random_get_savedata(void **data, int *len); +extern int random_active; +/* The random number subsystem is activated if at least one other entity + * within the program expresses an interest in it. So each SSH session + * calls random_ref on startup and random_unref on shutdown. */ +void random_ref(void); +void random_unref(void); + +/* + * Exports from pinger.c. + */ +typedef struct pinger_tag *Pinger; +Pinger pinger_new(Conf *conf, Backend *back, void *backhandle); +void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf); +void pinger_free(Pinger); + +/* + * Exports from misc.c. + */ + +#include "misc.h" +int conf_launchable(Conf *conf); +char const *conf_dest(Conf *conf); + +/* + * Exports from sercfg.c. + */ +void ser_setup_config_box(struct controlbox *b, int midsession, + int parity_mask, int flow_mask); + +/* + * Exports from version.c. + */ +extern char ver[]; + +/* + * Exports from unicode.c. + */ +#ifndef CP_UTF8 +#define CP_UTF8 65001 +#endif +/* void init_ucs(void); -- this is now in platform-specific headers */ +int is_dbcs_leadbyte(int codepage, char byte); +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, + wchar_t *wcstr, int wclen); +int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, + char *mbstr, int mblen, char *defchr, int *defused, + struct unicode_data *ucsdata); +wchar_t xlat_uskbd2cyrllic(int ch); +int check_compose(int first, int second); +int decode_codepage(char *cp_name); +const char *cp_enumerate (int index); +const char *cp_name(int codepage); +void get_unitab(int codepage, wchar_t * unitab, int ftype); + +/* + * Exports from wcwidth.c + */ +int mk_wcwidth(unsigned int ucs); +int mk_wcswidth(const unsigned int *pwcs, size_t n); +int mk_wcwidth_cjk(unsigned int ucs); +int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); + +/* + * Exports from mscrypto.c + */ +#ifdef MSCRYPTOAPI +int crypto_startup(); +void crypto_wrapup(); +#endif + +/* + * Exports from pageantc.c. + * + * agent_query returns 1 for here's-a-response, and 0 for query-in- + * progress. In the latter case there will be a call to `callback' + * at some future point, passing callback_ctx as the first + * parameter and the actual reply data as the second and third. + * + * The response may be a NULL pointer (in either of the synchronous + * or asynchronous cases), which indicates failure to receive a + * response. + */ +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx); +int agent_exists(void); + +/* + * Exports from wildcard.c + */ +const char *wc_error(int value); +int wc_match(const char *wildcard, const char *target); +int wc_unescape(char *output, const char *wildcard); + +/* + * Exports from frontend (windlg.c etc) + */ +void logevent(void *frontend, const char *); +void pgp_fingerprints(void); +/* + * verify_ssh_host_key() can return one of three values: + * + * - +1 means `key was OK' (either already known or the user just + * approved it) `so continue with the connection' + * + * - 0 means `key was not OK, abandon the connection' + * + * - -1 means `I've initiated enquiries, please wait to be called + * back via the provided function with a result that's either 0 + * or +1'. + */ +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx); +/* + * askalg has the same set of return values as verify_ssh_host_key. + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +/* + * askappend can return four values: + * + * - 2 means overwrite the log file + * - 1 means append to the log file + * - 0 means cancel logging for this session + * - -1 means please wait. + */ +int askappend(void *frontend, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx); + +/* + * Exports from console frontends (wincons.c, uxcons.c) + * that aren't equivalents to things in windlg.c et al. + */ +extern int console_batch_mode; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen); +void console_provide_logctx(void *logctx); +int is_interactive(void); + +/* + * Exports from printing.c. + */ +typedef struct printer_enum_tag printer_enum; +typedef struct printer_job_tag printer_job; +printer_enum *printer_start_enum(int *nprinters); +char *printer_get_name(printer_enum *, int); +void printer_finish_enum(printer_enum *); +printer_job *printer_start_job(char *printer); +void printer_job_data(printer_job *, void *, int); +void printer_finish_job(printer_job *); + +/* + * Exports from cmdline.c (and also cmdline_error(), which is + * defined differently in various places and required _by_ + * cmdline.c). + */ +int cmdline_process_param(char *, char *, int, Conf *); +void cmdline_run_saved(Conf *); +void cmdline_cleanup(void); +int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen); +#define TOOLTYPE_FILETRANSFER 1 +#define TOOLTYPE_NONNETWORK 2 +extern int cmdline_tooltype; + +void cmdline_error(char *, ...); + +/* + * Exports from config.c. + */ +struct controlbox; +union control; +void conf_radiobutton_handler(union control *ctrl, void *dlg, + void *data, int event); +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_editbox_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_filesel_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_fontsel_handler(union control *ctrl, void *dlg, + void *data, int event); +void setup_config_box(struct controlbox *b, int midsession, + int protocol, int protcfginfo); + +/* + * Exports from minibidi.c. + */ +typedef struct bidi_char { + unsigned int origwc, wc; + unsigned short index; +} bidi_char; +int do_bidi(bidi_char *line, int count); +int do_shape(bidi_char *line, bidi_char *to, int count); +int is_rtl(int c); + +/* + * X11 auth mechanisms we know about. + */ +enum { + X11_NO_AUTH, + X11_MIT, /* MIT-MAGIC-COOKIE-1 */ + X11_XDM, /* XDM-AUTHORIZATION-1 */ + X11_NAUTHS +}; +extern const char *const x11_authnames[]; /* declared in x11fwd.c */ + +/* + * Miscellaneous exports from the platform-specific code. + * + * filename_serialise and filename_deserialise have the same semantics + * as fontspec_serialise and fontspec_deserialise above. + */ +Filename *filename_from_str(const char *string); +const char *filename_to_str(const Filename *fn); +int filename_equal(const Filename *f1, const Filename *f2); +int filename_is_null(const Filename *fn); +Filename *filename_copy(const Filename *fn); +void filename_free(Filename *fn); +int filename_serialise(const Filename *f, void *data); +Filename *filename_deserialise(void *data, int maxsize, int *used); +char *get_username(void); /* return value needs freeing */ +char *get_random_data(int bytes); /* used in cmdgen.c */ +char filename_char_sanitise(char c); /* rewrite special pathname chars */ + +/* + * Exports and imports from timing.c. + * + * schedule_timer() asks the front end to schedule a callback to a + * timer function in a given number of ticks. The returned value is + * the time (in ticks since an arbitrary offset) at which the + * callback can be expected. This value will also be passed as the + * `now' parameter to the callback function. Hence, you can (for + * example) schedule an event at a particular time by calling + * schedule_timer() and storing the return value in your context + * structure as the time when that event is due. The first time a + * callback function gives you that value or more as `now', you do + * the thing. + * + * expire_timer_context() drops all current timers associated with + * a given value of ctx (for when you're about to free ctx). + * + * run_timers() is called from the front end when it has reason to + * think some timers have reached their moment, or when it simply + * needs to know how long to wait next. We pass it the time we + * think it is. It returns TRUE and places the time when the next + * timer needs to go off in `next', or alternatively it returns + * FALSE if there are no timers at all pending. + * + * timer_change_notify() must be supplied by the front end; it + * notifies the front end that a new timer has been added to the + * list which is sooner than any existing ones. It provides the + * time when that timer needs to go off. + * + * *** FRONT END IMPLEMENTORS NOTE: + * + * There's an important subtlety in the front-end implementation of + * the timer interface. When a front end is given a `next' value, + * either returned from run_timers() or via timer_change_notify(), + * it should ensure that it really passes _that value_ as the `now' + * parameter to its next run_timers call. It should _not_ simply + * call GETTICKCOUNT() to get the `now' parameter when invoking + * run_timers(). + * + * The reason for this is that an OS's system clock might not agree + * exactly with the timing mechanisms it supplies to wait for a + * given interval. I'll illustrate this by the simple example of + * Unix Plink, which uses timeouts to select() in a way which for + * these purposes can simply be considered to be a wait() function. + * Suppose, for the sake of argument, that this wait() function + * tends to return early by 1%. Then a possible sequence of actions + * is: + * + * - run_timers() tells the front end that the next timer firing + * is 10000ms from now. + * - Front end calls wait(10000ms), but according to + * GETTICKCOUNT() it has only waited for 9900ms. + * - Front end calls run_timers() again, passing time T-100ms as + * `now'. + * - run_timers() does nothing, and says the next timer firing is + * still 100ms from now. + * - Front end calls wait(100ms), which only waits for 99ms. + * - Front end calls run_timers() yet again, passing time T-1ms. + * - run_timers() says there's still 1ms to wait. + * - Front end calls wait(1ms). + * + * If you're _lucky_ at this point, wait(1ms) will actually wait + * for 1ms and you'll only have woken the program up three times. + * If you're unlucky, wait(1ms) might do nothing at all due to + * being below some minimum threshold, and you might find your + * program spends the whole of the last millisecond tight-looping + * between wait() and run_timers(). + * + * Instead, what you should do is to _save_ the precise `next' + * value provided by run_timers() or via timer_change_notify(), and + * use that precise value as the input to the next run_timers() + * call. So: + * + * - run_timers() tells the front end that the next timer firing + * is at time T, 10000ms from now. + * - Front end calls wait(10000ms). + * - Front end then immediately calls run_timers() and passes it + * time T, without stopping to check GETTICKCOUNT() at all. + * + * This guarantees that the program wakes up only as many times as + * there are actual timer actions to be taken, and that the timing + * mechanism will never send it into a tight loop. + * + * (It does also mean that the timer action in the above example + * will occur 100ms early, but this is not generally critical. And + * the hypothetical 1% error in wait() will be partially corrected + * for anyway when, _after_ run_timers() returns, you call + * GETTICKCOUNT() and compare the result with the returned `next' + * value to find out how long you have to make your next wait().) + */ +typedef void (*timer_fn_t)(void *ctx, unsigned long now); +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx); +void expire_timer_context(void *ctx); +int run_timers(unsigned long now, unsigned long *next); +void timer_change_notify(unsigned long next); + +/* + * Exports from callback.c. + * + * This provides a method of queuing function calls to be run at the + * earliest convenience from the top-level event loop. Use it if + * you're deep in a nested chain of calls and want to trigger an + * action which will probably lead to your function being re-entered + * recursively if you just call the initiating function the normal + * way. + * + * Most front ends run the queued callbacks by simply calling + * run_toplevel_callbacks() after handling each event in their + * top-level event loop. However, if a front end doesn't have control + * over its own event loop (e.g. because it's using GTK) then it can + * instead request notifications when a callback is available, so that + * it knows to ask its delegate event loop to do the same thing. Also, + * if a front end needs to know whether a callback is pending without + * actually running it (e.g. so as to put a zero timeout on a select() + * call) then it can call toplevel_callback_pending(), which will + * return true if at least one callback is in the queue. + */ +typedef void (*toplevel_callback_fn_t)(void *ctx); +void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); +void run_toplevel_callbacks(void); +int toplevel_callback_pending(void); + +typedef void (*toplevel_callback_notify_fn_t)(void *frontend); +void request_callback_notifications(toplevel_callback_notify_fn_t notify, + void *frontend); + +/* + * Define no-op macros for the jump list functions, on platforms that + * don't support them. (This is a bit of a hack, and it'd be nicer to + * localise even the calls to those functions into the Windows front + * end, but it'll do for the moment.) + */ +#ifndef JUMPLIST_SUPPORTED +#define add_session_to_jumplist(x) ((void)0) +#define remove_session_from_jumplist(x) ((void)0) +#endif + +/* SURROGATE PAIR */ +#define HIGH_SURROGATE_START 0xd800 +#define HIGH_SURROGATE_END 0xdbff +#define LOW_SURROGATE_START 0xdc00 +#define LOW_SURROGATE_END 0xdfff + +/* These macros exist in the Windows API, so the environment may + * provide them. If not, define them in terms of the above. */ +#ifndef IS_HIGH_SURROGATE +#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ + ((wch) <= HIGH_SURROGATE_END)) +#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && \ + ((wch) <= LOW_SURROGATE_END)) +#define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && \ + IS_LOW_SURROGATE(ls)) +#endif + + +#define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ + ((wch) <= LOW_SURROGATE_END)) +#define HIGH_SURROGATE_OF(codept) \ + (HIGH_SURROGATE_START + (((codept) - 0x10000) >> 10)) +#define LOW_SURROGATE_OF(codept) \ + (LOW_SURROGATE_START + (((codept) - 0x10000) & 0x3FF)) +#define FROM_SURROGATES(wch1, wch2) \ + (0x10000 + (((wch1) & 0x3FF) << 10) + ((wch2) & 0x3FF)) + +#ifdef __cplusplus +extern std::recursive_mutex putty_section; +#endif +void putty_initialize(); +void putty_finalize(); +#define MPEXT_PUTTY_SECTION_ENTER putty_section.lock(); +#define MPEXT_PUTTY_SECTION_LEAVE putty_section.unlock(); + +#define MPEXT_BOM "\xEF\xBB\xBF" + +#endif diff --git a/netbox/libs/Putty/puttyexp.h b/netbox/libs/Putty/puttyexp.h new file mode 100644 index 000000000..9fc9b6d49 --- /dev/null +++ b/netbox/libs/Putty/puttyexp.h @@ -0,0 +1,102 @@ +#ifndef PUTTY_PUTTYEXP_H +#define PUTTY_PUTTYEXP_H + +#ifdef __cplusplus +extern "C" { +#endif /* C++ */ + +// from ssh.c + +void ssh_close(void * handle); +int is_ssh(void * handle); +void call_ssh_timer(void * handle); +int get_ssh_version(void * handle); +void * get_ssh_frontend(void * handle); +int get_ssh1_compressing(void * handle); +const struct ssh_cipher * get_cipher(void * handle); +const struct ssh2_cipher * get_cscipher(void * handle); +const struct ssh2_cipher * get_sccipher(void * handle); +const struct ssh_compress * get_cscomp(void * handle); +const struct ssh_compress * get_sccomp(void * handle); +int get_ssh_state(void * handle); +int get_ssh_state_closed(void * handle); +int get_ssh_state_session(void * handle); +int get_ssh_exitcode(void * handle); +const unsigned int * ssh2_remmaxpkt(void * handle); +const unsigned int * ssh2_remwindow(void * handle); +void md5checksum(const char * buffer, int len, unsigned char output[16]); +typedef const struct ssh_signkey * cp_ssh_signkey; +void get_hostkey_algs(int * count, cp_ssh_signkey * SignKeys); + +// from wingss.c + +#ifndef SSH2_GSS_OIDTYPE +#include "sshgss.h" +#endif + +// from portfwd.c + +int is_pfwd(void * handle); +void * get_pfwd_backend(void * handle); + +#ifndef __linux__ +// for winstore.c + +#include "winstuff.h" + +long reg_open_winscp_key(HKEY Key, const char * SubKey, HKEY * Result); +long reg_create_winscp_key(HKEY Key, const char * SubKey, HKEY * Result); +long reg_query_winscp_value_ex(HKEY Key, const char * ValueName, + unsigned long * Reserved, unsigned long * Type, unsigned char * Data, + unsigned long * DataSize); +long reg_set_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long Reserved, + unsigned long Type, const unsigned char * Data, unsigned long DataSize); +long reg_close_winscp_key(HKEY Key); + +// from winnet.c + +int select_result(WPARAM wParam, LPARAM lParam); + +// from winmisc.c + +void win_misc_cleanup(); + +// from winsecur.c + +void win_secur_cleanup(void); +#endif + +// from winstore.c/uxstore.c + +void putty_mungestr(const char *in, char *out); +void putty_unmungestr(const char *in, char *out, int outlen); + +// from sshzlib.c + +extern const struct ssh_compress ssh_zlib; + +// from sshaes.c + +void * call_aes_make_context(); +void call_aes_free_context(void * handle); +void call_aes_setup(void * ctx, int blocklen, unsigned char * key, int keylen); +void call_aes_encrypt(void * ctx, unsigned int * block); +void call_aes_decrypt(void * ctx, unsigned int * block); + +// from sshsha.c + +void call_sha1_key_internal(void * handle, unsigned char * key, int len); + +// from misc.c + +const char * get_putty_version(); + +// from sshecc.c + +void ec_cleanup(void); + +#ifdef __cplusplus +} +#endif /* C++ */ + +#endif diff --git a/netbox/libs/Putty/puttymem.h b/netbox/libs/Putty/puttymem.h new file mode 100644 index 000000000..941aded3f --- /dev/null +++ b/netbox/libs/Putty/puttymem.h @@ -0,0 +1,52 @@ +/* + * PuTTY memory-handling header. + */ + +#ifndef PUTTY_PUTTYMEM_H +#define PUTTY_PUTTYMEM_H + +#include /* for size_t */ +#include /* for memcpy() */ + + +/* #define MALLOC_LOG do this if you suspect putty of leaking memory */ +#ifdef MALLOC_LOG +#define smalloc(z) (mlog(__FILE__,__LINE__), safemalloc(z,1)) +#define snmalloc(z,s) (mlog(__FILE__,__LINE__), safemalloc(z,s)) +#define srealloc(y,z) (mlog(__FILE__,__LINE__), saferealloc(y,z,1)) +#define snrealloc(y,z,s) (mlog(__FILE__,__LINE__), saferealloc(y,z,s)) +#define sfree(z) (mlog(__FILE__,__LINE__), safefree(z)) +void mlog(char *, int); +#else +#define smalloc(z) safemalloc(z,1) +#define snmalloc safemalloc +#define srealloc(y,z) saferealloc(y,z,1) +#define snrealloc saferealloc +#define sfree safefree +#endif + +void *safemalloc(size_t, size_t); +void *saferealloc(void *, size_t, size_t); +void safefree(void *); + +/* + * Direct use of smalloc within the code should be avoided where + * possible, in favour of these type-casting macros which ensure + * you don't mistakenly allocate enough space for one sort of + * structure and assign it to a different sort of pointer. + * + * The nasty trick in sresize with sizeof arranges for the compiler, + * in passing, to type-check the expression ((type *)0 == (ptr)), i.e. + * to type-check that the input pointer is a pointer to the correct + * type. The construction sizeof(stuff) ? (b) : (b) looks like a + * violation of the first principle of safe macros, but in fact it's + * OK - although it _expands_ the macro parameter more than once, it + * only _evaluates_ it once, so it's still side-effect safe. + */ +#define snew(type) ((type *)snmalloc(1, sizeof(type))) +#define snewn(n, type) ((type *)snmalloc((n), sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)snrealloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n), sizeof(type))) + +#endif diff --git a/netbox/libs/Putty/puttyps.h b/netbox/libs/Putty/puttyps.h new file mode 100644 index 000000000..724bf9b8d --- /dev/null +++ b/netbox/libs/Putty/puttyps.h @@ -0,0 +1,22 @@ +/* + * Find the platform-specific header for this platform. + */ + +#ifndef PUTTY_PUTTYPS_H +#define PUTTY_PUTTYPS_H + +#ifdef _WINDOWS + +#include "winstuff.h" + +#elif defined(MACOSX) + +#include "osx.h" + +#else + +#include "unix.h" + +#endif + +#endif diff --git a/netbox/libs/Putty/raw.c b/netbox/libs/Putty/raw.c new file mode 100644 index 000000000..97355e8ad --- /dev/null +++ b/netbox/libs/Putty/raw.c @@ -0,0 +1,345 @@ +/* + * "Raw" backend. + */ + +#include +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define RAW_MAX_BACKLOG 4096 + +typedef struct raw_backend_data { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + int closed_on_socket_error; + int bufsize; + void *frontend; + int sent_console_eof, sent_socket_eof; +} *Raw; + +static void raw_size(void *handle, int width, int height); + +static void c_write(Raw raw, char *buf, int len) +{ + int backlog = from_backend(raw->frontend, 0, buf, len); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static void raw_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Raw raw = (Raw) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(raw->frontend, msg); + sfree(msg); +} + +static void raw_check_close(Raw raw) +{ + /* + * Called after we send EOF on either the socket or the console. + * Its job is to wind up the session once we have sent EOF on both. + */ + if (raw->sent_console_eof && raw->sent_socket_eof) { + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + notify_remote_exit(raw->frontend); + } + } +} + +static int raw_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Raw raw = (Raw) plug; + + if (error_msg) { + /* A socket error has occurred. */ + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + raw->closed_on_socket_error = TRUE; + notify_remote_exit(raw->frontend); + } + logevent(raw->frontend, error_msg); + connection_fatal(raw->frontend, "%s", error_msg); + } else { + /* Otherwise, the remote side closed the connection normally. */ + if (!raw->sent_console_eof && from_backend_eof(raw->frontend)) { + /* + * The front end wants us to close the outgoing side of the + * connection as soon as we see EOF from the far end. + */ + if (!raw->sent_socket_eof) { + if (raw->s) + sk_write_eof(raw->s); + raw->sent_socket_eof= TRUE; + } + } + raw->sent_console_eof = TRUE; + raw_check_close(raw); + } + return 0; +} + +static int raw_receive(Plug plug, int urgent, char *data, int len) +{ + Raw raw = (Raw) plug; + c_write(raw, data, len); + return 1; +} + +static void raw_sent(Plug plug, int bufsize) +{ + Raw raw = (Raw) plug; + raw->bufsize = bufsize; +} + +/* + * Called to set up the raw connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *raw_init(void *frontend_handle, void **backend_handle, + Conf *conf, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + static const struct plug_function_table fn_table = { + raw_log, + raw_closing, + raw_receive, + raw_sent + }; + SockAddr addr; + const char *err; + Raw raw; + int addressfamily; + char *loghost; + + raw = snew(struct raw_backend_data); + raw->fn = &fn_table; + raw->s = NULL; + raw->closed_on_socket_error = FALSE; + *backend_handle = raw; + raw->sent_console_eof = raw->sent_socket_eof = FALSE; + raw->bufsize = 0; + + raw->frontend = frontend_handle; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + { + char *buf; + buf = dupprintf("Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(raw->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive, + (Plug) raw, conf); + if ((err = sk_socket_error(raw->s)) != NULL) + return err; + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void raw_free(void *handle) +{ + Raw raw = (Raw) handle; + + if (raw->s) + sk_close(raw->s); + sfree(raw); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void raw_reconfig(void *handle, Conf *conf) +{ +} + +/* + * Called to send data down the raw connection. + */ +static int raw_send(void *handle, char *buf, int len) +{ + Raw raw = (Raw) handle; + + if (raw->s == NULL) + return 0; + + raw->bufsize = sk_write(raw->s, buf, len); + + return raw->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int raw_sendbuffer(void *handle) +{ + Raw raw = (Raw) handle; + return raw->bufsize; +} + +/* + * Called to set the size of the window + */ +static void raw_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send raw special codes. We only handle outgoing EOF here. + */ +static void raw_special(void *handle, Telnet_Special code) +{ + Raw raw = (Raw) handle; + if (code == TS_EOF && raw->s) { + sk_write_eof(raw->s); + raw->sent_socket_eof= TRUE; + raw_check_close(raw); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *raw_get_specials(void *handle) +{ + return NULL; +} + +static int raw_connected(void *handle) +{ + Raw raw = (Raw) handle; + return raw->s != NULL; +} + +static int raw_sendok(void *handle) +{ + return 1; +} + +static void raw_unthrottle(void *handle, int backlog) +{ + Raw raw = (Raw) handle; + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static int raw_ldisc(void *handle, int option) +{ + if (option == LD_EDIT || option == LD_ECHO) + return 1; + return 0; +} + +static void raw_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void raw_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int raw_exitcode(void *handle) +{ + Raw raw = (Raw) handle; + if (raw->s != NULL) + return -1; /* still connected */ + else if (raw->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Exit codes are a meaningless concept in the Raw protocol */ + return 0; +} + +/* + * cfg_info for Raw does nothing at all. + */ +static int raw_cfg_info(void *handle) +{ + return 0; +} + +Backend raw_backend = { + raw_init, + raw_free, + raw_reconfig, + raw_send, + raw_sendbuffer, + raw_size, + raw_special, + raw_get_specials, + raw_connected, + raw_exitcode, + raw_sendok, + raw_ldisc, + raw_provide_ldisc, + raw_provide_logctx, + raw_unthrottle, + raw_cfg_info, + "raw", + PROT_RAW, + 0 +}; diff --git a/netbox/libs/Putty/resource.h b/netbox/libs/Putty/resource.h new file mode 100644 index 000000000..07d5008ac --- /dev/null +++ b/netbox/libs/Putty/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by win_res.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/netbox/libs/Putty/rlogin.c b/netbox/libs/Putty/rlogin.c new file mode 100644 index 000000000..3c86eee85 --- /dev/null +++ b/netbox/libs/Putty/rlogin.c @@ -0,0 +1,431 @@ +/* + * Rlogin backend. + */ + +#include +#include +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define RLOGIN_MAX_BACKLOG 4096 + +typedef struct rlogin_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + int closed_on_socket_error; + int bufsize; + int firstbyte; + int cansize; + int term_width, term_height; + void *frontend; + + Conf *conf; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; +} *Rlogin; + +static void rlogin_size(void *handle, int width, int height); + +static void c_write(Rlogin rlogin, char *buf, int len) +{ + int backlog = from_backend(rlogin->frontend, 0, buf, len); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static void rlogin_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Rlogin rlogin = (Rlogin) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(rlogin->frontend, msg); + sfree(msg); +} + +static int rlogin_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Rlogin rlogin = (Rlogin) plug; + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (rlogin->s) { + sk_close(rlogin->s); + rlogin->s = NULL; + if (error_msg) + rlogin->closed_on_socket_error = TRUE; + notify_remote_exit(rlogin->frontend); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(rlogin->frontend, error_msg); + connection_fatal(rlogin->frontend, "%s", error_msg); + } /* Otherwise, the remote side closed the connection normally. */ + return 0; +} + +static int rlogin_receive(Plug plug, int urgent, char *data, int len) +{ + Rlogin rlogin = (Rlogin) plug; + if (urgent == 2) { + char c; + + c = *data++; + len--; + if (c == '\x80') { + rlogin->cansize = 1; + rlogin_size(rlogin, rlogin->term_width, rlogin->term_height); + } + /* + * We should flush everything (aka Telnet SYNCH) if we see + * 0x02, and we should turn off and on _local_ flow control + * on 0x10 and 0x20 respectively. I'm not convinced it's + * worth it... + */ + } else { + /* + * Main rlogin protocol. This is really simple: the first + * byte is expected to be NULL and is ignored, and the rest + * is printed. + */ + if (rlogin->firstbyte) { + if (data[0] == '\0') { + data++; + len--; + } + rlogin->firstbyte = 0; + } + if (len > 0) + c_write(rlogin, data, len); + } + return 1; +} + +static void rlogin_sent(Plug plug, int bufsize) +{ + Rlogin rlogin = (Rlogin) plug; + rlogin->bufsize = bufsize; +} + +static void rlogin_startup(Rlogin rlogin, const char *ruser) +{ + char z = 0; + char *p; + + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + + rlogin->prompt = NULL; +} + +/* + * Called to set up the rlogin connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *rlogin_init(void *frontend_handle, void **backend_handle, + Conf *conf, + char *host, int port, char **realhost, + int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + rlogin_log, + rlogin_closing, + rlogin_receive, + rlogin_sent + }; + SockAddr addr; + const char *err; + Rlogin rlogin; + char *ruser; + int addressfamily; + char *loghost; + + rlogin = snew(struct rlogin_tag); + rlogin->fn = &fn_table; + rlogin->s = NULL; + rlogin->closed_on_socket_error = FALSE; + rlogin->frontend = frontend_handle; + rlogin->term_width = conf_get_int(conf, CONF_width); + rlogin->term_height = conf_get_int(conf, CONF_height); + rlogin->firstbyte = 1; + rlogin->cansize = 0; + rlogin->prompt = NULL; + rlogin->conf = conf_copy(conf); + *backend_handle = rlogin; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + { + char *buf; + buf = dupprintf("Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(rlogin->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 513; /* default rlogin port */ + + /* + * Open socket. + */ + rlogin->s = new_connection(addr, *realhost, port, 1, 0, + nodelay, keepalive, (Plug) rlogin, conf); + if ((err = sk_socket_error(rlogin->s)) != NULL) + return err; + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + /* + * Send local username, remote username, terminal type and + * terminal speed - unless we don't have the remote username yet, + * in which case we prompt for it and may end up deferring doing + * anything else until the local prompt mechanism returns. + */ + if ((ruser = get_remote_username(conf)) != NULL) { + rlogin_startup(rlogin, ruser); + sfree(ruser); + } else { + int ret; + + rlogin->prompt = new_prompts(rlogin->frontend); + rlogin->prompt->to_server = TRUE; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE); + ret = get_userpass_input(rlogin->prompt, NULL, 0); + if (ret >= 0) { + rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); + } + } + + return NULL; +} + +static void rlogin_free(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + conf_free(rlogin->conf); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(void *handle, Conf *conf) +{ +} + +/* + * Called to send data down the rlogin connection. + */ +static int rlogin_send(void *handle, char *buf, int len) +{ + Rlogin rlogin = (Rlogin) handle; + + if (rlogin->s == NULL) + return 0; + + if (rlogin->prompt) { + /* + * We're still prompting for a username, and aren't talking + * directly to the network connection yet. + */ + int ret = get_userpass_input(rlogin->prompt, + (unsigned char *)buf, len); + if (ret >= 0) { + rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); + /* that nulls out rlogin->prompt, so then we'll start sending + * data down the wire in the obvious way */ + } + } else { + rlogin->bufsize = sk_write(rlogin->s, buf, len); + } + + return rlogin->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int rlogin_sendbuffer(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + return rlogin->bufsize; +} + +/* + * Called to set the size of the window + */ +static void rlogin_size(void *handle, int width, int height) +{ + Rlogin rlogin = (Rlogin) handle; + char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 }; + + rlogin->term_width = width; + rlogin->term_height = height; + + if (rlogin->s == NULL || !rlogin->cansize) + return; + + b[6] = rlogin->term_width >> 8; + b[7] = rlogin->term_width & 0xFF; + b[4] = rlogin->term_height >> 8; + b[5] = rlogin->term_height & 0xFF; + rlogin->bufsize = sk_write(rlogin->s, b, 12); + return; +} + +/* + * Send rlogin special codes. + */ +static void rlogin_special(void *handle, Telnet_Special code) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *rlogin_get_specials(void *handle) +{ + return NULL; +} + +static int rlogin_connected(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + return rlogin->s != NULL; +} + +static int rlogin_sendok(void *handle) +{ + /* Rlogin rlogin = (Rlogin) handle; */ + return 1; +} + +static void rlogin_unthrottle(void *handle, int backlog) +{ + Rlogin rlogin = (Rlogin) handle; + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static int rlogin_ldisc(void *handle, int option) +{ + /* Rlogin rlogin = (Rlogin) handle; */ + return 0; +} + +static void rlogin_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void rlogin_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int rlogin_exitcode(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + if (rlogin->s != NULL) + return -1; /* still connected */ + else if (rlogin->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* If we ever implement RSH, we'll probably need to do this properly */ + return 0; +} + +/* + * cfg_info for rlogin does nothing at all. + */ +static int rlogin_cfg_info(void *handle) +{ + return 0; +} + +Backend rlogin_backend = { + rlogin_init, + rlogin_free, + rlogin_reconfig, + rlogin_send, + rlogin_sendbuffer, + rlogin_size, + rlogin_special, + rlogin_get_specials, + rlogin_connected, + rlogin_exitcode, + rlogin_sendok, + rlogin_ldisc, + rlogin_provide_ldisc, + rlogin_provide_logctx, + rlogin_unthrottle, + rlogin_cfg_info, + "rlogin", + PROT_RLOGIN, + 513 +}; diff --git a/netbox/libs/Putty/sercfg.c b/netbox/libs/Putty/sercfg.c new file mode 100644 index 000000000..fef910f31 --- /dev/null +++ b/netbox/libs/Putty/sercfg.c @@ -0,0 +1,206 @@ +/* + * sercfg.c - the serial-port specific parts of the PuTTY + * configuration box. Centralised as cross-platform code because + * more than one platform will want to use it, but not part of the + * main configuration. The expectation is that each platform's + * local config function will call out to ser_setup_config_box() if + * it needs to set up the standard serial stuff. (Of course, it can + * then apply local tweaks after ser_setup_config_box() returns, if + * it needs to.) + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void serial_parity_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } parities[] = { + {"None", SER_PAR_NONE}, + {"Odd", SER_PAR_ODD}, + {"Even", SER_PAR_EVEN}, + {"Mark", SER_PAR_MARK}, + {"Space", SER_PAR_SPACE}, + }; + int mask = ctrl->listbox.context.i; + int i, j; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldparity = conf_get_int(conf, CONF_serparity); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(parities); i++) { + if (mask & (1 << i)) + dlg_listbox_addwithid(ctrl, dlg, parities[i].name, + parities[i].val); + } + for (i = j = 0; i < lenof(parities); i++) { + if (mask & (1 << i)) { + if (oldparity == parities[i].val) { + dlg_listbox_select(ctrl, dlg, j); + break; + } + j++; + } + } + if (i == lenof(parities)) { /* an unsupported setting was chosen */ + dlg_listbox_select(ctrl, dlg, 0); + oldparity = SER_PAR_NONE; + } + dlg_update_done(ctrl, dlg); + conf_set_int(conf, CONF_serparity, oldparity); /* restore */ + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_PAR_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, CONF_serparity, i); + } +} + +static void serial_flow_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } flows[] = { + {"None", SER_FLOW_NONE}, + {"XON/XOFF", SER_FLOW_XONXOFF}, + {"RTS/CTS", SER_FLOW_RTSCTS}, + {"DSR/DTR", SER_FLOW_DSRDTR}, + }; + int mask = ctrl->listbox.context.i; + int i, j; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldflow = conf_get_int(conf, CONF_serflow); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(flows); i++) { + if (mask & (1 << i)) + dlg_listbox_addwithid(ctrl, dlg, flows[i].name, flows[i].val); + } + for (i = j = 0; i < lenof(flows); i++) { + if (mask & (1 << i)) { + if (oldflow == flows[i].val) { + dlg_listbox_select(ctrl, dlg, j); + break; + } + j++; + } + } + if (i == lenof(flows)) { /* an unsupported setting was chosen */ + dlg_listbox_select(ctrl, dlg, 0); + oldflow = SER_FLOW_NONE; + } + dlg_update_done(ctrl, dlg); + conf_set_int(conf, CONF_serflow, oldflow);/* restore */ + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_FLOW_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, CONF_serflow, i); + } +} + +void ser_setup_config_box(struct controlbox *b, int midsession, + int parity_mask, int flow_mask) +{ + struct controlset *s; + union control *c; + + if (!midsession) { + int i; + extern void config_protocolbuttons_handler(union control *, void *, + void *, int); + + /* + * Add the serial back end to the protocols list at the + * top of the config box. + */ + s = ctrl_getset(b, "Session", "hostport", + "Specify the destination you want to connect to"); + + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.handler == config_protocolbuttons_handler) { + c->radio.nbuttons++; + c->radio.ncolumns++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Serial"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROT_SERIAL); + if (c->radio.shortcuts) { + c->radio.shortcuts = + sresize(c->radio.shortcuts, c->radio.nbuttons, char); + c->radio.shortcuts[c->radio.nbuttons-1] = 'r'; + } + } + } + } + + /* + * Entirely new Connection/Serial panel for serial port + * configuration. + */ + ctrl_settitle(b, "Connection/Serial", + "Options controlling local serial lines"); + + if (!midsession) { + /* + * We don't permit switching to a different serial port in + * midflight, although we do allow all other + * reconfiguration. + */ + s = ctrl_getset(b, "Connection/Serial", "serline", + "Select a serial line"); + ctrl_editbox(s, "Serial line to connect to", 'l', 40, + HELPCTX(serial_line), + conf_editbox_handler, I(CONF_serline), I(1)); + } + + s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); + ctrl_editbox(s, "Speed (baud)", 's', 40, + HELPCTX(serial_speed), + conf_editbox_handler, I(CONF_serspeed), I(-1)); + ctrl_editbox(s, "Data bits", 'b', 40, + HELPCTX(serial_databits), + conf_editbox_handler, I(CONF_serdatabits), I(-1)); + /* + * Stop bits come in units of one half. + */ + ctrl_editbox(s, "Stop bits", 't', 40, + HELPCTX(serial_stopbits), + conf_editbox_handler, I(CONF_serstopbits), I(-2)); + ctrl_droplist(s, "Parity", 'p', 40, + HELPCTX(serial_parity), + serial_parity_handler, I(parity_mask)); + ctrl_droplist(s, "Flow control", 'f', 40, + HELPCTX(serial_flow), + serial_flow_handler, I(flow_mask)); +} diff --git a/netbox/libs/Putty/settings.c b/netbox/libs/Putty/settings.c new file mode 100644 index 000000000..02d7506b1 --- /dev/null +++ b/netbox/libs/Putty/settings.c @@ -0,0 +1,1095 @@ +/* + * settings.c: read and write saved sessions. (platform-independent) + */ + +#include +#include +#include +#include "putty.h" +#include "storage.h" + +/* The cipher order given here is the default order. */ +static const struct keyvalwhere ciphernames[] = { + { "aes", CIPHER_AES, -1, -1 }, + { "blowfish", CIPHER_BLOWFISH, -1, -1 }, + { "3des", CIPHER_3DES, -1, -1 }, + { "WARN", CIPHER_WARN, -1, -1 }, + { "arcfour", CIPHER_ARCFOUR, -1, -1 }, + { "des", CIPHER_DES, -1, -1 } +}; + +static const struct keyvalwhere kexnames[] = { + { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, + { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 }, + { "rsa", KEX_RSA, KEX_WARN, -1 }, + { "WARN", KEX_WARN, -1, -1 } +}; + +/* + * All the terminal modes that we know about for the "TerminalModes" + * setting. (Also used by config.c for the drop-down list.) + * This is currently precisely the same as the set in ssh.c, but could + * in principle differ if other backends started to support tty modes + * (e.g., the pty backend). + */ +const char *const ttymodes[] = { + "INTR", "QUIT", "ERASE", "KILL", "EOF", + "EOL", "EOL2", "START", "STOP", "SUSP", + "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH", + "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK", + "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL", + "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL", + "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE", + "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN", + "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC", + "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7", + "CS8", "PARENB", "PARODD", NULL +}; + +/* + * Convenience functions to access the backends[] array + * (which is only present in tools that manage settings). + */ + +Backend *backend_from_name(const char *name) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if (!strcmp((*p)->name, name)) + return *p; + return NULL; +} + +Backend *backend_from_proto(int proto) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if ((*p)->protocol == proto) + return *p; + return NULL; +} + +char *get_remote_username(Conf *conf) +{ + char *username = conf_get_str(conf, CONF_username); + if (*username) { + return dupstr(username); + } else if (conf_get_int(conf, CONF_username_from_env)) { + /* Use local username. */ + return get_username(); /* might still be NULL */ + } else { + return NULL; + } +} + +static char *gpps_raw(void *handle, const char *name, const char *def) +{ + char *ret = read_setting_s(handle, name); + if (!ret) + ret = platform_default_s(name); + if (!ret) + ret = def ? dupstr(def) : NULL; /* permit NULL as final fallback */ + return ret; +} + +static void gpps(void *handle, const char *name, const char *def, + Conf *conf, int primary) +{ + char *val = gpps_raw(handle, name, def); + conf_set_str(conf, primary, val); + sfree(val); +} + +/* + * gppfont and gppfile cannot have local defaults, since the very + * format of a Filename or FontSpec is platform-dependent. So the + * platform-dependent functions MUST return some sort of value. + */ +static void gppfont(void *handle, const char *name, Conf *conf, int primary) +{ + FontSpec *result = read_setting_fontspec(handle, name); + if (!result) + result = platform_default_fontspec(name); + conf_set_fontspec(conf, primary, result); + fontspec_free(result); +} +static void gppfile(void *handle, const char *name, Conf *conf, int primary) +{ + Filename *result = read_setting_filename(handle, name); + if (!result) + result = platform_default_filename(name); + conf_set_filename(conf, primary, result); + filename_free(result); +} + +static int gppi_raw(void *handle, char *name, int def) +{ + def = platform_default_i(name, def); + return read_setting_i(handle, name, def); +} + +static void gppi(void *handle, char *name, int def, Conf *conf, int primary) +{ + conf_set_int(conf, primary, gppi_raw(handle, name, def)); +} + +/* + * Read a set of name-value pairs in the format we occasionally use: + * NAME\tVALUE\0NAME\tVALUE\0\0 in memory + * NAME=VALUE,NAME=VALUE, in storage + * If there's no "=VALUE" (e.g. just NAME,NAME,NAME) then those keys + * are mapped to the empty string. + */ +static int gppmap(void *handle, char *name, Conf *conf, int primary) +{ + char *buf, *p, *q, *key, *val; + + /* + * Start by clearing any existing subkeys of this key from conf. + */ + while ((key = conf_get_str_nthstrkey(conf, primary, 0)) != NULL) + conf_del_str_str(conf, primary, key); + + /* + * Now read a serialised list from the settings and unmarshal it + * into its components. + */ + buf = gpps_raw(handle, name, NULL); + if (!buf) + return FALSE; + + p = buf; + while (*p) { + q = buf; + val = NULL; + while (*p && *p != ',') { + int c = *p++; + if (c == '=') + c = '\0'; + if (c == '\\') + c = *p++; + *q++ = c; + if (!c) + val = q; + } + if (*p == ',') + p++; + if (!val) + val = q; + *q = '\0'; + + if (primary == CONF_portfwd && strchr(buf, 'D') != NULL) { + /* + * Backwards-compatibility hack: dynamic forwardings are + * indexed in the data store as a third type letter in the + * key, 'D' alongside 'L' and 'R' - but really, they + * should be filed under 'L' with a special _value_, + * because local and dynamic forwardings both involve + * _listening_ on a local port, and are hence mutually + * exclusive on the same port number. So here we translate + * the legacy storage format into the sensible internal + * form, by finding the D and turning it into a L. + */ + char *newkey = dupstr(buf); + *strchr(newkey, 'D') = 'L'; + conf_set_str_str(conf, primary, newkey, "D"); + sfree(newkey); + } else { + conf_set_str_str(conf, primary, buf, val); + } + } + sfree(buf); + + return TRUE; +} + +/* + * Write a set of name/value pairs in the above format, or just the + * names if include_values is FALSE. + */ +static void wmap(void *handle, char const *outkey, Conf *conf, int primary, + int include_values) +{ + char *buf, *p, *q, *key, *realkey, *val; + int len; + + len = 1; /* allow for NUL */ + + for (val = conf_get_str_strs(conf, primary, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, primary, key, &key)) + len += 2 + 2 * (strlen(key) + strlen(val)); /* allow for escaping */ + + buf = snewn(len, char); + p = buf; + + for (val = conf_get_str_strs(conf, primary, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, primary, key, &key)) { + + if (primary == CONF_portfwd && !strcmp(val, "D")) { + /* + * Backwards-compatibility hack, as above: translate from + * the sensible internal representation of dynamic + * forwardings (key "L", value "D") to the + * conceptually incoherent legacy storage format (key + * "D", value empty). + */ + char *L; + + realkey = key; /* restore it at end of loop */ + val = ""; + key = dupstr(key); + L = strchr(key, 'L'); + if (L) *L = 'D'; + } else { + realkey = NULL; + } + + if (p != buf) + *p++ = ','; + for (q = key; *q; q++) { + if (*q == '=' || *q == ',' || *q == '\\') + *p++ = '\\'; + *p++ = *q; + } + if (include_values) { + *p++ = '='; + for (q = val; *q; q++) { + if (*q == '=' || *q == ',' || *q == '\\') + *p++ = '\\'; + *p++ = *q; + } + } + + if (realkey) { + free(key); + key = realkey; + } + } + *p = '\0'; + write_setting_s(handle, outkey, buf); + sfree(buf); +} + +static int key2val(const struct keyvalwhere *mapping, + int nmaps, char *key) +{ + int i; + for (i = 0; i < nmaps; i++) + if (!strcmp(mapping[i].s, key)) return mapping[i].v; + return -1; +} + +static const char *val2key(const struct keyvalwhere *mapping, + int nmaps, int val) +{ + int i; + for (i = 0; i < nmaps; i++) + if (mapping[i].v == val) return mapping[i].s; + return NULL; +} + +/* + * Helper function to parse a comma-separated list of strings into + * a preference list array of values. Any missing values are added + * to the end and duplicates are weeded. + * XXX: assumes vals in 'mapping' are small +ve integers + */ +static void gprefs(void *sesskey, char *name, char *def, + const struct keyvalwhere *mapping, int nvals, + Conf *conf, int primary) +{ + char *commalist; + char *p, *q; + int i, j, n, v, pos; + unsigned long seen = 0; /* bitmap for weeding dups etc */ + + /* + * Fetch the string which we'll parse as a comma-separated list. + */ + commalist = gpps_raw(sesskey, name, def); + + /* + * Go through that list and convert it into values. + */ + n = 0; + p = commalist; + while (1) { + while (*p && *p == ',') p++; + if (!*p) + break; /* no more words */ + + q = p; + while (*p && *p != ',') p++; + if (*p) *p++ = '\0'; + + v = key2val(mapping, nvals, q); + if (v != -1 && !(seen & (1 << v))) { + seen |= (1 << v); + conf_set_int_int(conf, primary, n, v); + n++; + } + } + + sfree(commalist); + + /* + * Now go through 'mapping' and add values that weren't mentioned + * in the list we fetched. We may have to loop over it multiple + * times so that we add values before other values whose default + * positions depend on them. + */ + while (n < nvals) { + for (i = 0; i < nvals; i++) { + assert(mapping[i].v < 32); + + if (!(seen & (1 << mapping[i].v))) { + /* + * This element needs adding. But can we add it yet? + */ + if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel))) + continue; /* nope */ + + /* + * OK, we can work out where to add this element, so + * do so. + */ + if (mapping[i].vrel == -1) { + pos = (mapping[i].where < 0 ? n : 0); + } else { + for (j = 0; j < n; j++) + if (conf_get_int_int(conf, primary, j) == + mapping[i].vrel) + break; + assert(j < n); /* implied by (seen & (1<= pos; j--) + conf_set_int_int(conf, primary, j+1, + conf_get_int_int(conf, primary, j)); + conf_set_int_int(conf, primary, pos, mapping[i].v); + n++; + } + } + } +} + +/* + * Write out a preference list. + */ +static void wprefs(void *sesskey, char *name, + const struct keyvalwhere *mapping, int nvals, + Conf *conf, int primary) +{ + char *buf, *p; + int i, maxlen; + + for (maxlen = i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, + conf_get_int_int(conf, primary, i)); + if (s) { + maxlen += (maxlen > 0 ? 1 : 0) + strlen(s); + } + } + + buf = snewn(maxlen + 1, char); + p = buf; + + for (i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, + conf_get_int_int(conf, primary, i)); + if (s) { + p += sprintf(p, "%s%s", (p > buf ? "," : ""), s); + } + } + + assert(p - buf == maxlen); + *p = '\0'; + + write_setting_s(sesskey, name, buf); + + sfree(buf); +} + +char *save_settings(char *section, Conf *conf) +{ + void *sesskey; + char *errmsg; + + sesskey = open_settings_w(section, &errmsg); + if (!sesskey) + return errmsg; + save_open_settings(sesskey, conf); + close_settings_w(sesskey); + return NULL; +} + +void save_open_settings(void *sesskey, Conf *conf) +{ + int i; + char *p; + + write_setting_i(sesskey, "Present", 1); + write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host)); + write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename)); + write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype)); + write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr)); + write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush)); + write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass)); + write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata)); + p = "raw"; + { + const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol)); + if (b) + p = b->name; + } + write_setting_s(sesskey, "Protocol", p); + write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port)); + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3); + write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close)); + write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */ + write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */ + write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay)); + write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives)); + write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype)); + write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed)); + wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, TRUE); + + /* Address family selection */ + write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily)); + + /* proxy settings */ + write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list)); + write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3); + write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost)); + write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type)); + write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host)); + write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port)); + write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username)); + write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password)); + write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command)); + wmap(sesskey, "Environment", conf, CONF_environmt, TRUE); + write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username)); + write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env)); + write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername)); + write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty)); + write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression)); + write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent)); + write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd)); + write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd)); + write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username)); + wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); + wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); + write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); + write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth)); + write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner)); + write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth)); + write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth)); + write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth)); +#ifndef NO_GSSAPI + wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); + write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom)); +#endif + write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell)); + write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot)); + write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); + write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc)); + write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); + write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); + write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ)); + write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet)); + write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete)); + write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend)); + write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type)); + write_setting_i(sesskey, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k)); + write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c)); + write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep)); + write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize)); + write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen)); + write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle)); + write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action)); + write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace)); + write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset)); + write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor)); + write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad)); + write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad)); + write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4)); + write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space)); + write_setting_i(sesskey, "AltOnly", conf_get_int(conf, CONF_alt_only)); + write_setting_i(sesskey, "ComposeKey", conf_get_int(conf, CONF_compose_key)); + write_setting_i(sesskey, "CtrlAltKeys", conf_get_int(conf, CONF_ctrlaltkeys)); + write_setting_i(sesskey, "TelnetKey", conf_get_int(conf, CONF_telnet_keyboard)); + write_setting_i(sesskey, "TelnetRet", conf_get_int(conf, CONF_telnet_newline)); + write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho)); + write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit)); + write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback)); + write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop)); + write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter)); + write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr)); + write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge)); + write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border)); + write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type)); + write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur)); + write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep)); + write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind)); + write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile)); + write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl)); + write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n)); + write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t) +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "BellOverloadS", conf_get_int(conf, CONF_bellovl_s) +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines)); + write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om)); + write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode)); + write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr)); + write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf)); + write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping)); + write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi)); + write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always)); + write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle)); + write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width)); + write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height)); + write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font)); + write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality)); + write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode)); + write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour)); + write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette)); + write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour)); + write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour)); + write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1); + + for (i = 0; i < 22; i++) { + char buf[20], buf2[30]; + sprintf(buf, "Colour%d", i); + sprintf(buf2, "%d,%d,%d", + conf_get_int_int(conf, CONF_colours, i*3+0), + conf_get_int_int(conf, CONF_colours, i*3+1), + conf_get_int_int(conf, CONF_colours, i*3+2)); + write_setting_s(sesskey, buf, buf2); + } + write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp)); + write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste)); + write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); + write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select)); + write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override)); + for (i = 0; i < 256; i += 32) { + char buf[20], buf2[256]; + int j; + sprintf(buf, "Wordness%d", i); + *buf2 = '\0'; + for (j = i; j < i + 32; j++) { + sprintf(buf2 + strlen(buf2), "%s%d", + (*buf2 ? "," : ""), + conf_get_int_int(conf, CONF_wordness, j)); + } + write_setting_s(sesskey, buf, buf2); + } + write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); + write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide)); + write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override)); + write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer)); + write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr)); + write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar)); + write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen)); + write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key)); + write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp)); + write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback)); + write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action)); + write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce)); + write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext)); + write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward)); + write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display)); + write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth)); + write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile)); + write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall)); + write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall)); + wmap(sesskey, "PortForwardings", conf, CONF_portfwd, TRUE); + write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1)); + write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1)); + write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1)); + write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2)); + write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2)); + write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2)); + write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2)); + write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2)); + write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2)); + write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2)); + write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2)); + write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); + write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); + write_setting_i(sesskey, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp)); + write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell)); + write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left)); + write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont)); + write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont)); + write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont)); + write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold)); + write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset)); + write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline)); + write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed)); + write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits)); + write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits)); + write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity)); + write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow)); + write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass)); + write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing)); + write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream)); + write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream)); + wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE); +} + +void load_settings(char *section, Conf *conf) +{ + void *sesskey; + + sesskey = open_settings_r(section); + load_open_settings(sesskey, conf); + close_settings_r(sesskey); + + if (conf_launchable(conf)) + add_session_to_jumplist(section); +} + +void load_open_settings(void *sesskey, Conf *conf) +{ + int i; + char *prot; + + conf_set_int(conf, CONF_ssh_subsys, 0); /* FIXME: load this properly */ + conf_set_str(conf, CONF_remote_cmd, ""); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_str(conf, CONF_ssh_nc_host, ""); + + gpps(sesskey, "HostName", "", conf, CONF_host); + gppfile(sesskey, "LogFileName", conf, CONF_logfilename); + gppi(sesskey, "LogType", 0, conf, CONF_logtype); + gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr); + gppi(sesskey, "LogFlush", 1, conf, CONF_logflush); + gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass); + gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata); + + prot = gpps_raw(sesskey, "Protocol", "default"); + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); + { + const Backend *b = backend_from_name(prot); + if (b) { + conf_set_int(conf, CONF_protocol, b->protocol); + gppi(sesskey, "PortNumber", default_port, conf, CONF_port); + } + } + sfree(prot); + + /* Address family selection */ + gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily); + + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3); + gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close); + { + /* This is two values for backward compatibility with 0.50/0.51 */ + int pingmin, pingsec; + pingmin = gppi_raw(sesskey, "PingInterval", 0); + pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0); + conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec); + } + gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay); + gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives); + gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype); + gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed); + if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) { + /* This hardcodes a big set of defaults in any new saved + * sessions. Let's hope we don't change our mind. */ + for (i = 0; ttymodes[i]; i++) + conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], "A"); + } + + /* proxy settings */ + gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list); + i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3); + gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost); + gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type); + if (conf_get_int(conf, CONF_proxy_type) == -1) { + int i; + i = gppi_raw(sesskey, "ProxyType", 0); + if (i == 0) + conf_set_int(conf, CONF_proxy_type, PROXY_NONE); + else if (i == 1) + conf_set_int(conf, CONF_proxy_type, PROXY_HTTP); + else if (i == 3) + conf_set_int(conf, CONF_proxy_type, PROXY_TELNET); + else if (i == 4) + conf_set_int(conf, CONF_proxy_type, PROXY_CMD); + else { + i = gppi_raw(sesskey, "ProxySOCKSVersion", 5); + if (i == 5) + conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5); + else + conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4); + } + } + gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host); + gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port); + gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username); + gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password); + gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", + conf, CONF_proxy_telnet_command); + gppmap(sesskey, "Environment", conf, CONF_environmt); + gpps(sesskey, "UserName", "", conf, CONF_username); + gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env); + gpps(sesskey, "LocalUserName", "", conf, CONF_localusername); + gppi(sesskey, "NoPTY", 0, conf, CONF_nopty); + gppi(sesskey, "Compression", 0, conf, CONF_compression); + gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent); + gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd); + gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username); + gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd); + gprefs(sesskey, "Cipher", "\0", + ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); + { + /* Backward-compatibility: we used to have an option to + * disable gex under the "bugs" panel after one report of + * a server which offered it then choked, but we never got + * a server version string or any other reports. */ + char *default_kexes; + i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); + if (i == FORCE_ON) + default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1"; + else + default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"; + gprefs(sesskey, "KEX", default_kexes, + kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + } + gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); + gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); + /* SSH-2 only by default */ + gppi(sesskey, "SshProt", 3, conf, CONF_sshprot); + gpps(sesskey, "LogHost", "", conf, CONF_loghost); + gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc); + gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth); + gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner); + gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth); + gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth); + gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth); +#ifndef NO_GSSAPI + gprefs(sesskey, "GSSLibs", "\0", + gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); + gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom); +#endif + gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell); + gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); + gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); + gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ); + gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet); + gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete); + gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend); + gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type); + gppi(sesskey, "NoApplicationKeys", 0, conf, CONF_no_applic_k); + gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c); + gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep); + gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize); + gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen); + gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle); + { + /* Backward compatibility */ + int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1); + /* We deliberately interpret the old setting of "no response" as + * "empty string". This changes the behaviour, but hopefully for + * the better; the user can always recover the old behaviour. */ + gppi(sesskey, "RemoteQTitleAction", + no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, + conf, CONF_remote_qtitle_action); + } + gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace); + gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset); + gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor); + gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad); + gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad); + gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4); + gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space); + gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only); + gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key); + gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys); + gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard); + gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline); + gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho); + gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit); + gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback); + gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop); + gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter); + gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr); + gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge); + gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border); + gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); + gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur); + /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */ + gppi(sesskey, "Beep", 1, conf, CONF_beep); + gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind); + gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile); + gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl); + gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n); + i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + ); + conf_set_int(conf, CONF_bellovl_t, i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ); + i = gppi_raw(sesskey, "BellOverloadS", 5*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + ); + conf_set_int(conf, CONF_bellovl_s, i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ); + gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines); + gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om); + gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode); + gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr); + gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf); + gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping); + gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi); + gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always); + gpps(sesskey, "WinTitle", "", conf, CONF_wintitle); + gppi(sesskey, "TermWidth", 80, conf, CONF_width); + gppi(sesskey, "TermHeight", 24, conf, CONF_height); + gppfont(sesskey, "Font", conf, CONF_font); + gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); + gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode); + gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour); + gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette); + gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour); + gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour); + i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1); + + for (i = 0; i < 22; i++) { + static const char *const defaults[] = { + "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0", + "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85", + "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187", + "85,85,255", "187,0,187", "255,85,255", "0,187,187", + "85,255,255", "187,187,187", "255,255,255" + }; + char buf[20], *buf2; + int c0, c1, c2; + sprintf(buf, "Colour%d", i); + buf2 = gpps_raw(sesskey, buf, defaults[i]); + if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) { + conf_set_int_int(conf, CONF_colours, i*3+0, c0); + conf_set_int_int(conf, CONF_colours, i*3+1, c1); + conf_set_int_int(conf, CONF_colours, i*3+2, c2); + } + sfree(buf2); + } + gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp); + gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste); + gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); + gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select); + gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override); + for (i = 0; i < 256; i += 32) { + static const char *const defaults[] = { + "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", + "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2" + }; + char buf[20], *buf2, *p; + int j; + sprintf(buf, "Wordness%d", i); + buf2 = gpps_raw(sesskey, buf, defaults[i / 32]); + p = buf2; + for (j = i; j < i + 32; j++) { + char *q = p; + while (*p && *p != ',') + p++; + if (*p == ',') + *p++ = '\0'; + conf_set_int_int(conf, CONF_wordness, j, atoi(q)); + } + sfree(buf2); + } + /* + * The empty default for LineCodePage will be converted later + * into a plausible default for the locale. + */ + gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage); + gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide); + gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override); + gpps(sesskey, "Printer", "", conf, CONF_printer); + gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr); + gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar); + gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen); + gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key); + gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp); + gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback); + gppi(sesskey, "LockSize", 0, conf, CONF_resize_action); + gppi(sesskey, "BCE", 1, conf, CONF_bce); + gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext); + gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward); + gpps(sesskey, "X11Display", "", conf, CONF_x11_display); + gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth); + gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile); + + gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall); + gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall); + gppmap(sesskey, "PortForwardings", conf, CONF_portfwd); + i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i); + i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i); + i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i); + i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i); + { + int i; + i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i); + if (2-i == AUTO) { + i = gppi_raw(sesskey, "BuggyMAC", 0); + if (i == 1) + conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON); + } + } + i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i); + i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i); + i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i); + i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i); + i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i); + i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i); + i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); + i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); + conf_set_int(conf, CONF_ssh_simple, FALSE); + gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp); + gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell); + gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left); + gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold); + gppfont(sesskey, "BoldFont", conf, CONF_boldfont); + gppfont(sesskey, "WideFont", conf, CONF_widefont); + gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont); + gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset); + gpps(sesskey, "SerialLine", "", conf, CONF_serline); + gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed); + gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits); + gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits); + gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity); + gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow); + gpps(sesskey, "WindowClass", "", conf, CONF_winclass); + gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing); + gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream); + gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream); + gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys); +} + +void do_defaults(char *session, Conf *conf) +{ + load_settings(session, conf); +} + +static int sessioncmp(const void *av, const void *bv) +{ + const char *a = *(const char *const *) av; + const char *b = *(const char *const *) bv; + + /* + * Alphabetical order, except that "Default Settings" is a + * special case and comes first. + */ + if (!strcmp(a, "Default Settings")) + return -1; /* a comes first */ + if (!strcmp(b, "Default Settings")) + return +1; /* b comes first */ + /* + * FIXME: perhaps we should ignore the first & in determining + * sort order. + */ + return strcmp(a, b); /* otherwise, compare normally */ +} + +void get_sesslist(struct sesslist *list, int allocate) +{ + char otherbuf[2048]; + int buflen, bufsize, i; + char *p, *ret; + void *handle; + + if (allocate) { + + buflen = bufsize = 0; + list->buffer = NULL; + if ((handle = enum_settings_start()) != NULL) { + do { + ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf)); + if (ret) { + int len = strlen(otherbuf) + 1; + if (bufsize < buflen + len) { + bufsize = buflen + len + 2048; + list->buffer = sresize(list->buffer, bufsize, char); + } + strcpy(list->buffer + buflen, otherbuf); + buflen += strlen(list->buffer + buflen) + 1; + } + } while (ret); + enum_settings_finish(handle); + } + list->buffer = sresize(list->buffer, buflen + 1, char); + list->buffer[buflen] = '\0'; + + /* + * Now set up the list of sessions. Note that "Default + * Settings" must always be claimed to exist, even if it + * doesn't really. + */ + + p = list->buffer; + list->nsessions = 1; /* "Default Settings" counts as one */ + while (*p) { + if (strcmp(p, "Default Settings")) + list->nsessions++; + while (*p) + p++; + p++; + } + + list->sessions = snewn(list->nsessions + 1, char *); + list->sessions[0] = "Default Settings"; + p = list->buffer; + i = 1; + while (*p) { + if (strcmp(p, "Default Settings")) + list->sessions[i++] = p; + while (*p) + p++; + p++; + } + + qsort(list->sessions, i, sizeof(char *), sessioncmp); + } else { + sfree(list->buffer); + sfree(list->sessions); + list->buffer = NULL; + list->sessions = NULL; + } +} diff --git a/netbox/libs/Putty/sftp.c b/netbox/libs/Putty/sftp.c new file mode 100644 index 000000000..bf75779df --- /dev/null +++ b/netbox/libs/Putty/sftp.c @@ -0,0 +1,1441 @@ +/* + * sftp.c: SFTP generic client code. + */ + +#include +#include +#include +#include +#include + +#include "misc.h" +#include "int64.h" +#include "tree234.h" +#include "sftp.h" + +struct sftp_packet { + char *data; + unsigned length, maxlen; + unsigned savedpos; + int type; +}; + +static const char *fxp_error_message; +static int fxp_errtype; + +static void fxp_internal_error(char *msg); + +/* ---------------------------------------------------------------------- + * SFTP packet construction functions. + */ +static void sftp_pkt_ensure(struct sftp_packet *pkt, int length) +{ + if ((int)pkt->maxlen < length) { + pkt->maxlen = length + 256; + pkt->data = sresize(pkt->data, pkt->maxlen, char); + } +} +static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len) +{ + pkt->length += len; + sftp_pkt_ensure(pkt, pkt->length); + memcpy(pkt->data + pkt->length - len, data, len); +} +static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) +{ + sftp_pkt_adddata(pkt, &byte, 1); +} +static void sftp_pkt_adduint32(struct sftp_packet *pkt, + unsigned long value) +{ + unsigned char x[4]; + PUT_32BIT(x, value); + sftp_pkt_adddata(pkt, x, 4); +} +static struct sftp_packet *sftp_pkt_init(int pkt_type) +{ + struct sftp_packet *pkt; + pkt = snew(struct sftp_packet); + pkt->data = NULL; + pkt->savedpos = -1; + pkt->length = 0; + pkt->maxlen = 0; + sftp_pkt_adduint32(pkt, 0); /* length field will be filled in later */ + sftp_pkt_addbyte(pkt, (unsigned char) pkt_type); + return pkt; +} +/* +static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) +{ + sftp_pkt_adddata(pkt, &value, 1); +} +*/ +static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) +{ + unsigned char x[8]; + PUT_32BIT(x, value.hi); + PUT_32BIT(x + 4, value.lo); + sftp_pkt_adddata(pkt, x, 8); +} +static void sftp_pkt_addstring_start(struct sftp_packet *pkt) +{ + sftp_pkt_adduint32(pkt, 0); + pkt->savedpos = pkt->length; +} +static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data) +{ + sftp_pkt_adddata(pkt, data, strlen(data)); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void sftp_pkt_addstring_data(struct sftp_packet *pkt, + char *data, int len) +{ + sftp_pkt_adddata(pkt, data, len); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data) +{ + sftp_pkt_addstring_start(pkt); + sftp_pkt_addstring_str(pkt, data); +} +static void sftp_pkt_addattrs(struct sftp_packet *pkt, struct fxp_attrs attrs) +{ + sftp_pkt_adduint32(pkt, attrs.flags); + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { + sftp_pkt_adduint32(pkt, attrs.size.hi); + sftp_pkt_adduint32(pkt, attrs.size.lo); + } + if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { + sftp_pkt_adduint32(pkt, attrs.uid); + sftp_pkt_adduint32(pkt, attrs.gid); + } + if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + sftp_pkt_adduint32(pkt, attrs.permissions); + } + if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { + sftp_pkt_adduint32(pkt, attrs.atime); + sftp_pkt_adduint32(pkt, attrs.mtime); + } + if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { + /* + * We currently don't support sending any extended + * attributes. + */ + } +} + +/* ---------------------------------------------------------------------- + * SFTP packet decode functions. + */ + +static int sftp_pkt_getbyte(struct sftp_packet *pkt, unsigned char *ret) +{ + if (pkt->length - pkt->savedpos < 1) + return 0; + *ret = (unsigned char) pkt->data[pkt->savedpos]; + pkt->savedpos++; + return 1; +} +static int sftp_pkt_getuint32(struct sftp_packet *pkt, unsigned long *ret) +{ + if (pkt->length - pkt->savedpos < 4) + return 0; + *ret = GET_32BIT(pkt->data + pkt->savedpos); + pkt->savedpos += 4; + return 1; +} +static int sftp_pkt_getstring(struct sftp_packet *pkt, + char **p, int *length) +{ + *p = NULL; + if (pkt->length - pkt->savedpos < 4) + return 0; + *length = toint(GET_32BIT(pkt->data + pkt->savedpos)); + pkt->savedpos += 4; + if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) { + *length = 0; + return 0; + } + *p = pkt->data + pkt->savedpos; + pkt->savedpos += *length; + return 1; +} +static int sftp_pkt_getattrs(struct sftp_packet *pkt, struct fxp_attrs *ret) +{ + if (!sftp_pkt_getuint32(pkt, &ret->flags)) + return 0; + if (ret->flags & SSH_FILEXFER_ATTR_SIZE) { + unsigned long hi, lo; + if (!sftp_pkt_getuint32(pkt, &hi) || + !sftp_pkt_getuint32(pkt, &lo)) + return 0; + ret->size = uint64_make(hi, lo); + } + if (ret->flags & SSH_FILEXFER_ATTR_UIDGID) { + if (!sftp_pkt_getuint32(pkt, &ret->uid) || + !sftp_pkt_getuint32(pkt, &ret->gid)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (!sftp_pkt_getuint32(pkt, &ret->permissions)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + if (!sftp_pkt_getuint32(pkt, &ret->atime) || + !sftp_pkt_getuint32(pkt, &ret->mtime)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_EXTENDED) { + unsigned long count; + if (!sftp_pkt_getuint32(pkt, &count)) + return 0; + while (count--) { + char *str; + int len; + /* + * We should try to analyse these, if we ever find one + * we recognise. + */ + if (!sftp_pkt_getstring(pkt, &str, &len) || + !sftp_pkt_getstring(pkt, &str, &len)) + return 0; + } + } + return 1; +} +static void sftp_pkt_free(struct sftp_packet *pkt) +{ + if (pkt->data) + sfree(pkt->data); + sfree(pkt); +} + +/* ---------------------------------------------------------------------- + * Send and receive packet functions. + */ +int sftp_send(struct sftp_packet *pkt) +{ + int ret; + PUT_32BIT(pkt->data, pkt->length - 4); + ret = sftp_senddata(pkt->data, pkt->length); + sftp_pkt_free(pkt); + return ret; +} +struct sftp_packet *sftp_recv(void) +{ + struct sftp_packet *pkt; + char x[4]; + unsigned char uc; + + if (!sftp_recvdata(x, 4)) + return NULL; + + pkt = snew(struct sftp_packet); + pkt->savedpos = 0; + pkt->length = pkt->maxlen = GET_32BIT(x); + pkt->data = snewn(pkt->length, char); + + if (!sftp_recvdata(pkt->data, pkt->length)) { + sftp_pkt_free(pkt); + return NULL; + } + + if (!sftp_pkt_getbyte(pkt, &uc)) { + sftp_pkt_free(pkt); + return NULL; + } else { + pkt->type = uc; + } + + return pkt; +} + +/* ---------------------------------------------------------------------- + * Request ID allocation and temporary dispatch routines. + */ + +#define REQUEST_ID_OFFSET 256 + +struct sftp_request { + unsigned id; + int registered; + void *userdata; +}; + +static int sftp_reqcmp(void *av, void *bv) +{ + struct sftp_request *a = (struct sftp_request *)av; + struct sftp_request *b = (struct sftp_request *)bv; + if (a->id < b->id) + return -1; + if (a->id > b->id) + return +1; + return 0; +} +static int sftp_reqfind(void *av, void *bv) +{ + unsigned *a = (unsigned *) av; + struct sftp_request *b = (struct sftp_request *)bv; + if (*a < b->id) + return -1; + if (*a > b->id) + return +1; + return 0; +} + +static tree234 *sftp_requests; + +static struct sftp_request *sftp_alloc_request(void) +{ + unsigned low, high, mid; + int tsize; + struct sftp_request *r; + + if (sftp_requests == NULL) + sftp_requests = newtree234(sftp_reqcmp); + + /* + * First-fit allocation of request IDs: always pick the lowest + * unused one. To do this, binary-search using the counted + * B-tree to find the largest ID which is in a contiguous + * sequence from the beginning. (Precisely everything in that + * sequence must have ID equal to its tree index plus + * REQUEST_ID_OFFSET.) + */ + tsize = count234(sftp_requests); + + low = -1; + high = tsize; + while (high - low > 1) { + mid = (high + low) / 2; + r = index234(sftp_requests, mid); + if (r->id == mid + REQUEST_ID_OFFSET) + low = mid; /* this one is fine */ + else + high = mid; /* this one is past it */ + } + /* + * Now low points to either -1, or the tree index of the + * largest ID in the initial sequence. + */ + { + unsigned i = low + 1 + REQUEST_ID_OFFSET; + assert(NULL == find234(sftp_requests, &i, sftp_reqfind)); + } + + /* + * So the request ID we need to create is + * low + 1 + REQUEST_ID_OFFSET. + */ + r = snew(struct sftp_request); + r->id = low + 1 + REQUEST_ID_OFFSET; + r->registered = 0; + r->userdata = NULL; + add234(sftp_requests, r); + return r; +} + +void sftp_cleanup_request(void) +{ + if (sftp_requests != NULL) { + freetree234(sftp_requests); + sftp_requests = NULL; + } +} + +void sftp_register(struct sftp_request *req) +{ + req->registered = 1; +} + +struct sftp_request *sftp_find_request(struct sftp_packet *pktin) +{ + unsigned long id; + struct sftp_request *req; + + if (!pktin) { + fxp_internal_error("did not receive a valid SFTP packet\n"); + return NULL; + } + + if (!sftp_pkt_getuint32(pktin, &id)) { + fxp_internal_error("did not receive a valid SFTP packet\n"); + return NULL; + } + req = find234(sftp_requests, &id, sftp_reqfind); + + if (!req || !req->registered) { + fxp_internal_error("request ID mismatch\n"); + return NULL; + } + + del234(sftp_requests, req); + + return req; +} + +/* ---------------------------------------------------------------------- + * String handling routines. + */ + +static char *mkstr(char *s, int len) +{ + char *p = snewn(len + 1, char); + memcpy(p, s, len); + p[len] = '\0'; + return p; +} + +/* ---------------------------------------------------------------------- + * SFTP primitives. + */ + +/* + * Deal with (and free) an FXP_STATUS packet. Return 1 if + * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error). + * Also place the status into fxp_errtype. + */ +static int fxp_got_status(struct sftp_packet *pktin) +{ + static const char *const messages[] = { + /* SSH_FX_OK. The only time we will display a _message_ for this + * is if we were expecting something other than FXP_STATUS on + * success, so this is actually an error message! */ + "unexpected OK response", + "end of file", + "no such file or directory", + "permission denied", + "failure", + "bad message", + "no connection", + "connection lost", + "operation unsupported", + }; + + if (pktin->type != SSH_FXP_STATUS) { + fxp_error_message = "expected FXP_STATUS packet"; + fxp_errtype = -1; + } else { + unsigned long ul; + if (!sftp_pkt_getuint32(pktin, &ul)) { + fxp_error_message = "malformed FXP_STATUS packet"; + fxp_errtype = -1; + } else { + fxp_errtype = ul; + if (fxp_errtype < 0 || + fxp_errtype >= sizeof(messages) / sizeof(*messages)) + fxp_error_message = "unknown error code"; + else + fxp_error_message = messages[fxp_errtype]; + } + } + + if (fxp_errtype == SSH_FX_OK) + return 1; + else if (fxp_errtype == SSH_FX_EOF) + return 0; + else + return -1; +} + +static void fxp_internal_error(char *msg) +{ + fxp_error_message = msg; + fxp_errtype = -1; +} + +const char *fxp_error(void) +{ + return fxp_error_message; +} + +int fxp_error_type(void) +{ + return fxp_errtype; +} + +/* + * Perform exchange of init/version packets. Return 0 on failure. + */ +int fxp_init(void) +{ + struct sftp_packet *pktout, *pktin; + unsigned long remotever; + + pktout = sftp_pkt_init(SSH_FXP_INIT); + sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION); + sftp_send(pktout); + + pktin = sftp_recv(); + if (!pktin) { + fxp_internal_error("could not connect"); + return 0; + } + if (pktin->type != SSH_FXP_VERSION) { + fxp_internal_error("did not receive FXP_VERSION"); + sftp_pkt_free(pktin); + return 0; + } + if (!sftp_pkt_getuint32(pktin, &remotever)) { + fxp_internal_error("malformed FXP_VERSION packet"); + sftp_pkt_free(pktin); + return 0; + } + if (remotever > SFTP_PROTO_VERSION) { + fxp_internal_error + ("remote protocol is more advanced than we support"); + sftp_pkt_free(pktin); + return 0; + } + /* + * In principle, this packet might also contain extension- + * string pairs. We should work through them and look for any + * we recognise. In practice we don't currently do so because + * we know we don't recognise _any_. + */ + sftp_pkt_free(pktin); + + return 1; +} + +/* + * Canonify a pathname. + */ +struct sftp_request *fxp_realpath_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REALPATH); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_str(pktout, path); + sftp_send(pktout); + + return req; +} + +char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + + if (pktin->type == SSH_FXP_NAME) { + unsigned long count; + char *path; + int len; + + if (!sftp_pkt_getuint32(pktin, &count) || count != 1) { + fxp_internal_error("REALPATH did not return name count of 1\n"); + sftp_pkt_free(pktin); + return NULL; + } + if (!sftp_pkt_getstring(pktin, &path, &len)) { + fxp_internal_error("REALPATH returned malformed FXP_NAME\n"); + sftp_pkt_free(pktin); + return NULL; + } + path = mkstr(path, len); + sftp_pkt_free(pktin); + return path; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Open a file. + */ +struct sftp_request *fxp_open_send(char *path, int type, + struct fxp_attrs *attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPEN); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_pkt_adduint32(pktout, type); + if (attrs) + sftp_pkt_addattrs(pktout, *attrs); + else + sftp_pkt_adduint32(pktout, 0); /* empty ATTRS structure */ + sftp_send(pktout); + + return req; +} + +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + + if (pktin->type == SSH_FXP_HANDLE) { + char *hstring; + struct fxp_handle *handle; + int len; + + if (!sftp_pkt_getstring(pktin, &hstring, &len)) { + fxp_internal_error("OPEN returned malformed FXP_HANDLE\n"); + sftp_pkt_free(pktin); + return NULL; + } + handle = snew(struct fxp_handle); + handle->hstring = mkstr(hstring, len); + handle->hlen = len; + sftp_pkt_free(pktin); + return handle; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPENDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_send(pktout); + + return req; +} + +struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + if (pktin->type == SSH_FXP_HANDLE) { + char *hstring; + struct fxp_handle *handle; + int len; + + if (!sftp_pkt_getstring(pktin, &hstring, &len)) { + fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n"); + sftp_pkt_free(pktin); + return NULL; + } + handle = snew(struct fxp_handle); + handle->hstring = mkstr(hstring, len); + handle->hlen = len; + sftp_pkt_free(pktin); + return handle; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Close a file/dir. + */ +struct sftp_request *fxp_close_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_CLOSE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + sfree(handle->hstring); + sfree(handle); + + return req; +} + +void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + fxp_got_status(pktin); + sftp_pkt_free(pktin); +} + +struct sftp_request *fxp_mkdir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_MKDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */ + sftp_send(pktout); + + return req; +} + +int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_rmdir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RMDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_send(pktout); + + return req; +} + +int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_remove_send(char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REMOVE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_send(pktout); + + return req; +} + +int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RENAME); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, srcfname); + sftp_pkt_addstring(pktout, dstfname); + sftp_send(pktout); + + return req; +} + +int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +/* + * Retrieve the attributes of a file. We have fxp_stat which works + * on filenames, and fxp_fstat which works on open file handles. + */ +struct sftp_request *fxp_stat_send(char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_STAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_send(pktout); + + return req; +} + +int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + if (!sftp_pkt_getattrs(pktin, attrs)) { + fxp_internal_error("malformed SSH_FXP_ATTRS packet"); + sftp_pkt_free(pktin); + return 0; + } + sftp_pkt_free(pktin); + return 1; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return 0; + } +} + +struct sftp_request *fxp_fstat_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_FSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + return req; +} + +int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + if (!sftp_pkt_getattrs(pktin, attrs)) { + fxp_internal_error("malformed SSH_FXP_ATTRS packet"); + sftp_pkt_free(pktin); + return 0; + } + sftp_pkt_free(pktin); + return 1; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return 0; + } +} + +/* + * Set the attributes of a file. + */ +struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_SETSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_pkt_addattrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, + struct fxp_attrs attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_FSETSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_addattrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +/* + * Read from a file. Returns the number of bytes read, or -1 on an + * error, or possibly 0 if EOF. (I'm not entirely sure whether it + * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the + * error indicator. It might even depend on the SFTP server.) + */ +struct sftp_request *fxp_read_send(struct fxp_handle *handle, + uint64 offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_READ); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_adduint64(pktout, offset); + sftp_pkt_adduint32(pktout, len); + sftp_send(pktout); + + return req; +} + +int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, + char *buffer, int len) +{ + sfree(req); + if (pktin->type == SSH_FXP_DATA) { + char *str; + int rlen; + + if (!sftp_pkt_getstring(pktin, &str, &rlen)) { + fxp_internal_error("READ returned malformed SSH_FXP_DATA packet"); + sftp_pkt_free(pktin); + return -1; + } + + if (rlen > len || rlen < 0) { + fxp_internal_error("READ returned more bytes than requested"); + sftp_pkt_free(pktin); + return -1; + } + + memcpy(buffer, str, rlen); + sftp_pkt_free(pktin); + return rlen; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return -1; + } +} + +/* + * Read from a directory. + */ +struct sftp_request *fxp_readdir_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_READDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + return req; +} + +struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + if (pktin->type == SSH_FXP_NAME) { + struct fxp_names *ret; + unsigned long i; + + /* + * Sanity-check the number of names. Minimum is obviously + * zero. Maximum is the remaining space in the packet + * divided by the very minimum length of a name, which is + * 12 bytes (4 for an empty filename, 4 for an empty + * longname, 4 for a set of attribute flags indicating that + * no other attributes are supplied). + */ + if (!sftp_pkt_getuint32(pktin, &i) || + i > (pktin->length-pktin->savedpos)/12) { + fxp_internal_error("malformed FXP_NAME packet"); + sftp_pkt_free(pktin); + return NULL; + } + + /* + * Ensure the implicit multiplication in the snewn() call + * doesn't suffer integer overflow and cause us to malloc + * too little space. + */ + if (i > INT_MAX / sizeof(struct fxp_name)) { + fxp_internal_error("unreasonably large FXP_NAME packet"); + sftp_pkt_free(pktin); + return NULL; + } + + ret = snew(struct fxp_names); + ret->nnames = i; + ret->names = snewn(ret->nnames, struct fxp_name); + for (i = 0; i < (unsigned long)ret->nnames; i++) { + char *str1, *str2; + int len1, len2; + if (!sftp_pkt_getstring(pktin, &str1, &len1) || + !sftp_pkt_getstring(pktin, &str2, &len2) || + !sftp_pkt_getattrs(pktin, &ret->names[i].attrs)) { + fxp_internal_error("malformed FXP_NAME packet"); + while (i--) { + sfree(ret->names[i].filename); + sfree(ret->names[i].longname); + } + sfree(ret->names); + sfree(ret); + sfree(pktin); + return NULL; + } + ret->names[i].filename = mkstr(str1, len1); + ret->names[i].longname = mkstr(str2, len2); + } + sftp_pkt_free(pktin); + return ret; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Write to a file. Returns 0 on error, 1 on OK. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + char *buffer, uint64 offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_WRITE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_adduint64(pktout, offset); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, buffer, len); + sftp_send(pktout); + + return req; +} + +int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return fxp_errtype == SSH_FX_OK; +} + +/* + * Free up an fxp_names structure. + */ +void fxp_free_names(struct fxp_names *names) +{ + int i; + + for (i = 0; i < names->nnames; i++) { + sfree(names->names[i].filename); + sfree(names->names[i].longname); + } + sfree(names->names); + sfree(names); +} + +/* + * Duplicate an fxp_name structure. + */ +struct fxp_name *fxp_dup_name(struct fxp_name *name) +{ + struct fxp_name *ret; + ret = snew(struct fxp_name); + ret->filename = dupstr(name->filename); + ret->longname = dupstr(name->longname); + ret->attrs = name->attrs; /* structure copy */ + return ret; +} + +/* + * Free up an fxp_name structure. + */ +void fxp_free_name(struct fxp_name *name) +{ + sfree(name->filename); + sfree(name->longname); + sfree(name); +} + +/* + * Store user data in an sftp_request structure. + */ +void *fxp_get_userdata(struct sftp_request *req) +{ + return req->userdata; +} + +void fxp_set_userdata(struct sftp_request *req, void *data) +{ + req->userdata = data; +} + +/* + * A wrapper to go round fxp_read_* and fxp_write_*, which manages + * the queueing of multiple read/write requests. + */ + +struct req { + char *buffer; + int len, retlen, complete; + uint64 offset; + struct req *next, *prev; +}; + +struct fxp_xfer { + uint64 offset, furthestdata, filesize; + int req_totalsize, req_maxsize, eof, err; + struct fxp_handle *fh; + struct req *head, *tail; +}; + +static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = snew(struct fxp_xfer); + + xfer->fh = fh; + xfer->offset = offset; + xfer->head = xfer->tail = NULL; + xfer->req_totalsize = 0; + xfer->req_maxsize = 1048576; + xfer->err = 0; + xfer->filesize = uint64_make(ULONG_MAX, ULONG_MAX); + xfer->furthestdata = uint64_make(0, 0); + + return xfer; +} + +int xfer_done(struct fxp_xfer *xfer) +{ + /* + * We're finished if we've seen EOF _and_ there are no + * outstanding requests. + */ + return (xfer->eof || xfer->err) && !xfer->head; +} + +void xfer_download_queue(struct fxp_xfer *xfer) +{ + while (xfer->req_totalsize < xfer->req_maxsize && + !xfer->eof && !xfer->err) { + /* + * Queue a new read request. + */ + struct req *rr; + struct sftp_request *req; + + rr = snew(struct req); + rr->offset = xfer->offset; + rr->complete = 0; + if (xfer->tail) { + xfer->tail->next = rr; + rr->prev = xfer->tail; + } else { + xfer->head = rr; + rr->prev = NULL; + } + xfer->tail = rr; + rr->next = NULL; + + rr->len = 32768; + rr->buffer = snewn(rr->len, char); + sftp_register(req = fxp_read_send(xfer->fh, rr->offset, rr->len)); + fxp_set_userdata(req, rr); + + xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_DOWNLOAD + { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing read request %p at %s\n", rr, buf); } +#endif + } +} + +struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = xfer_init(fh, offset); + + xfer->eof = FALSE; + xfer_download_queue(xfer); + + return xfer; +} + +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_read_recv and hence has not freed pktin. + */ +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr; + + rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) { + fxp_internal_error("request ID is not part of the current download"); + return INT_MIN; /* this packet isn't ours */ + } + rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len); +#ifdef DEBUG_DOWNLOAD + printf("read request %p has returned [%d]\n", rr, rr->retlen); +#endif + + if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) { + xfer->eof = TRUE; + rr->complete = -1; +#ifdef DEBUG_DOWNLOAD + printf("setting eof\n"); +#endif + } else if (rr->retlen < 0) { + /* some error other than EOF; signal it back to caller */ + xfer_set_error(xfer); + rr->complete = -1; + return -1; + } + + rr->complete = 1; + + /* + * Special case: if we have received fewer bytes than we + * actually read, we should do something. For the moment I'll + * just throw an ersatz FXP error to signal this; the SFTP + * draft I've got says that it can't happen except on special + * files, in which case seeking probably has very little + * meaning and so queueing an additional read request to fill + * up the gap sounds like the wrong answer. I'm not sure what I + * should be doing here - if it _was_ a special file, I suspect + * I simply shouldn't have been queueing multiple requests in + * the first place... + */ + if (rr->retlen > 0 && uint64_compare(xfer->furthestdata, rr->offset) < 0) { + xfer->furthestdata = rr->offset; +#ifdef DEBUG_DOWNLOAD + { char buf[40]; + uint64_decimal(xfer->furthestdata, buf); + printf("setting furthestdata = %s\n", buf); } +#endif + } + + if (rr->retlen < rr->len) { + uint64 filesize = uint64_add32(rr->offset, + (rr->retlen < 0 ? 0 : rr->retlen)); +#ifdef DEBUG_DOWNLOAD + { char buf[40]; + uint64_decimal(filesize, buf); + printf("short block! trying filesize = %s\n", buf); } +#endif + if (uint64_compare(xfer->filesize, filesize) > 0) { + xfer->filesize = filesize; +#ifdef DEBUG_DOWNLOAD + printf("actually changing filesize\n"); +#endif + } + } + + if (uint64_compare(xfer->furthestdata, xfer->filesize) > 0) { + fxp_error_message = "received a short buffer from FXP_READ, but not" + " at EOF"; + fxp_errtype = -1; + xfer_set_error(xfer); + return -1; + } + + return 1; +} + +void xfer_set_error(struct fxp_xfer *xfer) +{ + xfer->err = 1; +} + +int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len) +{ + void *retbuf = NULL; + int retlen = 0; + + /* + * Discard anything at the head of the rr queue with complete < + * 0; return the first thing with complete > 0. + */ + while (xfer->head && xfer->head->complete && !retbuf) { + struct req *rr = xfer->head; + + if (rr->complete > 0) { + retbuf = rr->buffer; + retlen = rr->retlen; +#ifdef DEBUG_DOWNLOAD + printf("handing back data from read request %p\n", rr); +#endif + } +#ifdef DEBUG_DOWNLOAD + else + printf("skipping failed read request %p\n", rr); +#endif + + xfer->head = xfer->head->next; + if (xfer->head) + xfer->head->prev = NULL; + else + xfer->tail = NULL; + xfer->req_totalsize -= rr->len; + sfree(rr); + } + + if (retbuf) { + *buf = retbuf; + *len = retlen; + return 1; + } else + return 0; +} + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = xfer_init(fh, offset); + + /* + * We set `eof' to 1 because this will cause xfer_done() to + * return true iff there are no outstanding requests. During an + * upload, our caller will be responsible for working out + * whether all the data has been sent, so all it needs to know + * from us is whether the outstanding requests have been + * handled once that's done. + */ + xfer->eof = 1; + + return xfer; +} + +int xfer_upload_ready(struct fxp_xfer *xfer) +{ + if (xfer->req_totalsize < xfer->req_maxsize) + return 1; + else + return 0; +} + +void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len) +{ + struct req *rr; + struct sftp_request *req; + + rr = snew(struct req); + rr->offset = xfer->offset; + rr->complete = 0; + if (xfer->tail) { + xfer->tail->next = rr; + rr->prev = xfer->tail; + } else { + xfer->head = rr; + rr->prev = NULL; + } + xfer->tail = rr; + rr->next = NULL; + + rr->len = len; + rr->buffer = NULL; + sftp_register(req = fxp_write_send(xfer->fh, buffer, rr->offset, len)); + fxp_set_userdata(req, rr); + + xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_UPLOAD + { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing write request %p at %s [len %d]\n", rr, buf, len); } +#endif +} + +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_write_recv and hence has not freed pktin. + */ +int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr, *prev, *next; + int ret; + + rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) { + fxp_internal_error("request ID is not part of the current upload"); + return INT_MIN; /* this packet isn't ours */ + } + ret = fxp_write_recv(pktin, rreq); +#ifdef DEBUG_UPLOAD + printf("write request %p has returned [%d]\n", rr, ret); +#endif + + /* + * Remove this one from the queue. + */ + prev = rr->prev; + next = rr->next; + if (prev) + prev->next = next; + else + xfer->head = next; + if (next) + next->prev = prev; + else + xfer->tail = prev; + xfer->req_totalsize -= rr->len; + sfree(rr); + + if (!ret) + return -1; + + return 1; +} + +void xfer_cleanup(struct fxp_xfer *xfer) +{ + struct req *rr; + while (xfer->head) { + rr = xfer->head; + xfer->head = xfer->head->next; + sfree(rr->buffer); + sfree(rr); + } + sfree(xfer); +} diff --git a/netbox/libs/Putty/sftp.h b/netbox/libs/Putty/sftp.h new file mode 100644 index 000000000..c3167a57a --- /dev/null +++ b/netbox/libs/Putty/sftp.h @@ -0,0 +1,263 @@ +/* + * sftp.h: definitions for SFTP and the sftp.c routines. + */ + +#include "int64.h" + +#define SSH_FXP_INIT 1 /* 0x1 */ +#define SSH_FXP_VERSION 2 /* 0x2 */ +#define SSH_FXP_OPEN 3 /* 0x3 */ +#define SSH_FXP_CLOSE 4 /* 0x4 */ +#define SSH_FXP_READ 5 /* 0x5 */ +#define SSH_FXP_WRITE 6 /* 0x6 */ +#define SSH_FXP_LSTAT 7 /* 0x7 */ +#define SSH_FXP_FSTAT 8 /* 0x8 */ +#define SSH_FXP_SETSTAT 9 /* 0x9 */ +#define SSH_FXP_FSETSTAT 10 /* 0xa */ +#define SSH_FXP_OPENDIR 11 /* 0xb */ +#define SSH_FXP_READDIR 12 /* 0xc */ +#define SSH_FXP_REMOVE 13 /* 0xd */ +#define SSH_FXP_MKDIR 14 /* 0xe */ +#define SSH_FXP_RMDIR 15 /* 0xf */ +#define SSH_FXP_REALPATH 16 /* 0x10 */ +#define SSH_FXP_STAT 17 /* 0x11 */ +#define SSH_FXP_RENAME 18 /* 0x12 */ +#define SSH_FXP_STATUS 101 /* 0x65 */ +#define SSH_FXP_HANDLE 102 /* 0x66 */ +#define SSH_FXP_DATA 103 /* 0x67 */ +#define SSH_FXP_NAME 104 /* 0x68 */ +#define SSH_FXP_ATTRS 105 /* 0x69 */ +#define SSH_FXP_EXTENDED 200 /* 0xc8 */ +#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */ + +#define SSH_FX_OK 0 +#define SSH_FX_EOF 1 +#define SSH_FX_NO_SUCH_FILE 2 +#define SSH_FX_PERMISSION_DENIED 3 +#define SSH_FX_FAILURE 4 +#define SSH_FX_BAD_MESSAGE 5 +#define SSH_FX_NO_CONNECTION 6 +#define SSH_FX_CONNECTION_LOST 7 +#define SSH_FX_OP_UNSUPPORTED 8 + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 + +#define SSH_FXF_READ 0x00000001 +#define SSH_FXF_WRITE 0x00000002 +#define SSH_FXF_APPEND 0x00000004 +#define SSH_FXF_CREAT 0x00000008 +#define SSH_FXF_TRUNC 0x00000010 +#define SSH_FXF_EXCL 0x00000020 + +#define SFTP_PROTO_VERSION 3 + +/* + * External references. The sftp client module sftp.c expects to be + * able to get at these functions. + * + * sftp_recvdata must never return less than len. It either blocks + * until len is available, or it returns failure. + * + * Both functions return 1 on success, 0 on failure. + */ +int sftp_senddata(char *data, int len); +int sftp_recvdata(char *data, int len); + +/* + * Free sftp_requests + */ +void sftp_cleanup_request(void); + +struct fxp_attrs { + unsigned long flags; + uint64 size; + unsigned long uid; + unsigned long gid; + unsigned long permissions; + unsigned long atime; + unsigned long mtime; +}; + +/* + * Copy between the possibly-unused permissions field in an fxp_attrs + * and a possibly-negative integer containing the same permissions. + */ +#define PUT_PERMISSIONS(attrs, perms) \ + ((perms) >= 0 ? \ + ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \ + (attrs).permissions = (perms)) : \ + ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS)) +#define GET_PERMISSIONS(attrs) \ + ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ + (attrs).permissions : -1) + +struct fxp_handle { + char *hstring; + int hlen; +}; + +struct fxp_name { + char *filename, *longname; + struct fxp_attrs attrs; +}; + +struct fxp_names { + int nnames; + struct fxp_name *names; +}; + +struct sftp_request; +struct sftp_packet; + +const char *fxp_error(void); +int fxp_error_type(void); + +/* + * Perform exchange of init/version packets. Return 0 on failure. + */ +int fxp_init(void); + +/* + * Canonify a pathname. Concatenate the two given path elements + * with a separating slash, unless the second is NULL. + */ +struct sftp_request *fxp_realpath_send(char *path); +char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Open a file. 'attrs' contains attributes to be applied to the file + * if it's being created. + */ +struct sftp_request *fxp_open_send(char *path, int type, + struct fxp_attrs *attrs); +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(char *path); +struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Close a file/dir. + */ +struct sftp_request *fxp_close_send(struct fxp_handle *handle); +void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Make a directory. + */ +struct sftp_request *fxp_mkdir_send(char *path); +int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a directory. + */ +struct sftp_request *fxp_rmdir_send(char *path); +int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a file. + */ +struct sftp_request *fxp_remove_send(char *fname); +int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Rename a file. + */ +struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname); +int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Return file attributes. + */ +struct sftp_request *fxp_stat_send(char *fname); +int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs); +struct sftp_request *fxp_fstat_send(struct fxp_handle *handle); +int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs); + +/* + * Set file attributes. + */ +struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs); +int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req); +struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, + struct fxp_attrs attrs); +int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Read from a file. + */ +struct sftp_request *fxp_read_send(struct fxp_handle *handle, + uint64 offset, int len); +int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, + char *buffer, int len); + +/* + * Write to a file. Returns 0 on error, 1 on OK. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + char *buffer, uint64 offset, int len); +int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Read from a directory. + */ +struct sftp_request *fxp_readdir_send(struct fxp_handle *handle); +struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Free up an fxp_names structure. + */ +void fxp_free_names(struct fxp_names *names); + +/* + * Duplicate and free fxp_name structures. + */ +struct fxp_name *fxp_dup_name(struct fxp_name *name); +void fxp_free_name(struct fxp_name *name); + +/* + * Store user data in an sftp_request structure. + */ +void *fxp_get_userdata(struct sftp_request *req); +void fxp_set_userdata(struct sftp_request *req, void *data); + +/* + * These functions might well be temporary placeholders to be + * replaced with more useful similar functions later. They form the + * main dispatch loop for processing incoming SFTP responses. + */ +void sftp_register(struct sftp_request *req); +struct sftp_request *sftp_find_request(struct sftp_packet *pktin); +struct sftp_packet *sftp_recv(void); + +/* + * A wrapper to go round fxp_read_* and fxp_write_*, which manages + * the queueing of multiple read/write requests. + */ + +struct fxp_xfer; + +struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset); +void xfer_download_queue(struct fxp_xfer *xfer); +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); +int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset); +int xfer_upload_ready(struct fxp_xfer *xfer); +void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len); +int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); + +int xfer_done(struct fxp_xfer *xfer); +void xfer_set_error(struct fxp_xfer *xfer); +void xfer_cleanup(struct fxp_xfer *xfer); diff --git a/netbox/libs/Putty/ssh.c b/netbox/libs/Putty/ssh.c new file mode 100644 index 000000000..c65dcbc0b --- /dev/null +++ b/netbox/libs/Putty/ssh.c @@ -0,0 +1,11507 @@ +/* + * SSH backend. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* + * Packet type contexts, so that ssh2_pkt_type can correctly decode + * the ambiguous type numbers back into the correct type strings. + */ +typedef enum { + SSH2_PKTCTX_NOKEX, + SSH2_PKTCTX_DHGROUP, + SSH2_PKTCTX_DHGEX, + SSH2_PKTCTX_RSAKEX +} Pkt_KCtx; +typedef enum { + SSH2_PKTCTX_NOAUTH, + SSH2_PKTCTX_PUBLICKEY, + SSH2_PKTCTX_PASSWORD, + SSH2_PKTCTX_GSSAPI, + SSH2_PKTCTX_KBDINTER +} Pkt_ACtx; + +static const char *const ssh2_disconnect_reasons[] = { + NULL, + "host not allowed to connect", + "protocol error", + "key exchange failed", + "host authentication failed", + "MAC error", + "compression error", + "service not available", + "protocol version not supported", + "host key not verifiable", + "connection lost", + "by application", + "too many connections", + "auth cancelled by user", + "no more auth methods available", + "illegal user name", +}; + +/* + * Various remote-bug flags. + */ +#define BUG_CHOKES_ON_SSH1_IGNORE 1 +#define BUG_SSH2_HMAC 2 +#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 +#define BUG_CHOKES_ON_RSA 8 +#define BUG_SSH2_RSA_PADDING 16 +#define BUG_SSH2_DERIVEKEY 32 +#define BUG_SSH2_REKEY 64 +#define BUG_SSH2_PK_SESSIONID 128 +#define BUG_SSH2_MAXPKT 256 +#define BUG_CHOKES_ON_SSH2_IGNORE 512 +#define BUG_CHOKES_ON_WINADJ 1024 +#define BUG_SENDS_LATE_REQUEST_REPLY 2048 +#define BUG_SSH2_OLDGEX 4096 + +#define DH_MIN_SIZE 1024 +#define DH_MAX_SIZE 8192 + +/* + * Codes for terminal modes. + * Most of these are the same in SSH-1 and SSH-2. + * This list is derived from RFC 4254 and + * SSH-1 RFC-1.2.31. + */ +static const struct { + const char* const mode; + int opcode; + enum { TTY_OP_CHAR, TTY_OP_BOOL } type; +} ssh_ttymodes[] = { + /* "V" prefix discarded for special characters relative to SSH specs */ + { "INTR", 1, TTY_OP_CHAR }, + { "QUIT", 2, TTY_OP_CHAR }, + { "ERASE", 3, TTY_OP_CHAR }, + { "KILL", 4, TTY_OP_CHAR }, + { "EOF", 5, TTY_OP_CHAR }, + { "EOL", 6, TTY_OP_CHAR }, + { "EOL2", 7, TTY_OP_CHAR }, + { "START", 8, TTY_OP_CHAR }, + { "STOP", 9, TTY_OP_CHAR }, + { "SUSP", 10, TTY_OP_CHAR }, + { "DSUSP", 11, TTY_OP_CHAR }, + { "REPRINT", 12, TTY_OP_CHAR }, + { "WERASE", 13, TTY_OP_CHAR }, + { "LNEXT", 14, TTY_OP_CHAR }, + { "FLUSH", 15, TTY_OP_CHAR }, + { "SWTCH", 16, TTY_OP_CHAR }, + { "STATUS", 17, TTY_OP_CHAR }, + { "DISCARD", 18, TTY_OP_CHAR }, + { "IGNPAR", 30, TTY_OP_BOOL }, + { "PARMRK", 31, TTY_OP_BOOL }, + { "INPCK", 32, TTY_OP_BOOL }, + { "ISTRIP", 33, TTY_OP_BOOL }, + { "INLCR", 34, TTY_OP_BOOL }, + { "IGNCR", 35, TTY_OP_BOOL }, + { "ICRNL", 36, TTY_OP_BOOL }, + { "IUCLC", 37, TTY_OP_BOOL }, + { "IXON", 38, TTY_OP_BOOL }, + { "IXANY", 39, TTY_OP_BOOL }, + { "IXOFF", 40, TTY_OP_BOOL }, + { "IMAXBEL", 41, TTY_OP_BOOL }, + { "ISIG", 50, TTY_OP_BOOL }, + { "ICANON", 51, TTY_OP_BOOL }, + { "XCASE", 52, TTY_OP_BOOL }, + { "ECHO", 53, TTY_OP_BOOL }, + { "ECHOE", 54, TTY_OP_BOOL }, + { "ECHOK", 55, TTY_OP_BOOL }, + { "ECHONL", 56, TTY_OP_BOOL }, + { "NOFLSH", 57, TTY_OP_BOOL }, + { "TOSTOP", 58, TTY_OP_BOOL }, + { "IEXTEN", 59, TTY_OP_BOOL }, + { "ECHOCTL", 60, TTY_OP_BOOL }, + { "ECHOKE", 61, TTY_OP_BOOL }, + { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */ + { "OPOST", 70, TTY_OP_BOOL }, + { "OLCUC", 71, TTY_OP_BOOL }, + { "ONLCR", 72, TTY_OP_BOOL }, + { "OCRNL", 73, TTY_OP_BOOL }, + { "ONOCR", 74, TTY_OP_BOOL }, + { "ONLRET", 75, TTY_OP_BOOL }, + { "CS7", 90, TTY_OP_BOOL }, + { "CS8", 91, TTY_OP_BOOL }, + { "PARENB", 92, TTY_OP_BOOL }, + { "PARODD", 93, TTY_OP_BOOL } +}; + +/* Miscellaneous other tty-related constants. */ +#define SSH_TTY_OP_END 0 +/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */ +#define SSH1_TTY_OP_ISPEED 192 +#define SSH1_TTY_OP_OSPEED 193 +#define SSH2_TTY_OP_ISPEED 128 +#define SSH2_TTY_OP_OSPEED 129 + +/* Helper functions for parsing tty-related config. */ +static unsigned int ssh_tty_parse_specchar(char *s) +{ + unsigned int ret; + if (*s) { + char *next = NULL; + ret = ctrlparse(s, &next); + if (!next) ret = s[0]; + } else { + ret = 255; /* special value meaning "don't set" */ + } + return ret; +} +static unsigned int ssh_tty_parse_boolean(char *s) +{ + if (stricmp(s, "yes") == 0 || + stricmp(s, "on") == 0 || + stricmp(s, "true") == 0 || + stricmp(s, "+") == 0) + return 1; /* true */ + else if (stricmp(s, "no") == 0 || + stricmp(s, "off") == 0 || + stricmp(s, "false") == 0 || + stricmp(s, "-") == 0) + return 0; /* false */ + else + return (atoi(s) != 0); +} + +#define translate(x) if (type == x) return #x +#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x +#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x +static char *ssh1_pkt_type(int type) +{ + translate(SSH1_MSG_DISCONNECT); + translate(SSH1_SMSG_PUBLIC_KEY); + translate(SSH1_CMSG_SESSION_KEY); + translate(SSH1_CMSG_USER); + translate(SSH1_CMSG_AUTH_RSA); + translate(SSH1_SMSG_AUTH_RSA_CHALLENGE); + translate(SSH1_CMSG_AUTH_RSA_RESPONSE); + translate(SSH1_CMSG_AUTH_PASSWORD); + translate(SSH1_CMSG_REQUEST_PTY); + translate(SSH1_CMSG_WINDOW_SIZE); + translate(SSH1_CMSG_EXEC_SHELL); + translate(SSH1_CMSG_EXEC_CMD); + translate(SSH1_SMSG_SUCCESS); + translate(SSH1_SMSG_FAILURE); + translate(SSH1_CMSG_STDIN_DATA); + translate(SSH1_SMSG_STDOUT_DATA); + translate(SSH1_SMSG_STDERR_DATA); + translate(SSH1_CMSG_EOF); + translate(SSH1_SMSG_EXIT_STATUS); + translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH1_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH1_MSG_CHANNEL_DATA); + translate(SSH1_MSG_CHANNEL_CLOSE); + translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); + translate(SSH1_SMSG_X11_OPEN); + translate(SSH1_CMSG_PORT_FORWARD_REQUEST); + translate(SSH1_MSG_PORT_OPEN); + translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING); + translate(SSH1_SMSG_AGENT_OPEN); + translate(SSH1_MSG_IGNORE); + translate(SSH1_CMSG_EXIT_CONFIRMATION); + translate(SSH1_CMSG_X11_REQUEST_FORWARDING); + translate(SSH1_CMSG_AUTH_RHOSTS_RSA); + translate(SSH1_MSG_DEBUG); + translate(SSH1_CMSG_REQUEST_COMPRESSION); + translate(SSH1_CMSG_AUTH_TIS); + translate(SSH1_SMSG_AUTH_TIS_CHALLENGE); + translate(SSH1_CMSG_AUTH_TIS_RESPONSE); + translate(SSH1_CMSG_AUTH_CCARD); + translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE); + translate(SSH1_CMSG_AUTH_CCARD_RESPONSE); + return "unknown"; +} +static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) +{ + translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI); + translate(SSH2_MSG_DISCONNECT); + translate(SSH2_MSG_IGNORE); + translate(SSH2_MSG_UNIMPLEMENTED); + translate(SSH2_MSG_DEBUG); + translate(SSH2_MSG_SERVICE_REQUEST); + translate(SSH2_MSG_SERVICE_ACCEPT); + translate(SSH2_MSG_KEXINIT); + translate(SSH2_MSG_NEWKEYS); + translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP); + translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP); + translatek(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); + translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); + translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); + translate(SSH2_MSG_USERAUTH_REQUEST); + translate(SSH2_MSG_USERAUTH_FAILURE); + translate(SSH2_MSG_USERAUTH_SUCCESS); + translate(SSH2_MSG_USERAUTH_BANNER); + translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY); + translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD); + translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER); + translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER); + translate(SSH2_MSG_GLOBAL_REQUEST); + translate(SSH2_MSG_REQUEST_SUCCESS); + translate(SSH2_MSG_REQUEST_FAILURE); + translate(SSH2_MSG_CHANNEL_OPEN); + translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH2_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + translate(SSH2_MSG_CHANNEL_DATA); + translate(SSH2_MSG_CHANNEL_EXTENDED_DATA); + translate(SSH2_MSG_CHANNEL_EOF); + translate(SSH2_MSG_CHANNEL_CLOSE); + translate(SSH2_MSG_CHANNEL_REQUEST); + translate(SSH2_MSG_CHANNEL_SUCCESS); + translate(SSH2_MSG_CHANNEL_FAILURE); + return "unknown"; +} +#undef translate +#undef translatec + +/* Enumeration values for fields in SSH-1 packets */ +enum { + PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM, +}; + +/* + * Coroutine mechanics for the sillier bits of the code. If these + * macros look impenetrable to you, you might find it helpful to + * read + * + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + * + * which explains the theory behind these macros. + * + * In particular, if you are getting `case expression not constant' + * errors when building with MS Visual Studio, this is because MS's + * Edit and Continue debugging feature causes their compiler to + * violate ANSI C. To disable Edit and Continue debugging: + * + * - right-click ssh.c in the FileView + * - click Settings + * - select the C/C++ tab and the General category + * - under `Debug info:', select anything _other_ than `Program + * Database for Edit and Continue'. + */ +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crBeginState crBegin(s->crLine) +#define crStateP(t, v) \ + struct t *s; \ + if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \ + s = (v); +#define crState(t) crStateP(t, ssh->t) +#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } +#define crFinishFree(z) } sfree(s); return (z); } +#define crFinishFreeV } sfree(s); return; } +#define crReturn(z) \ + do {\ + *crLine =__LINE__; return (z); case __LINE__:;\ + } while (0) +#define crReturnV \ + do {\ + *crLine=__LINE__; return; case __LINE__:;\ + } while (0) +#define crStop(z) do{ *crLine = 0; return (z); }while(0) +#define crStopV do{ *crLine = 0; return; }while(0) +#define crWaitUntil(c) do { crReturn(0); } while (!(c)) +#define crWaitUntilV(c) do { crReturnV; } while (!(c)) + +struct Packet; + +static struct Packet *ssh1_pkt_init(int pkt_type); +static struct Packet *ssh2_pkt_init(int pkt_type); +static void ssh_pkt_ensure(struct Packet *, int length); +static void ssh_pkt_adddata(struct Packet *, const void *data, int len); +static void ssh_pkt_addbyte(struct Packet *, unsigned char value); +static void ssh2_pkt_addbool(struct Packet *, unsigned char value); +static void ssh_pkt_adduint32(struct Packet *, unsigned long value); +static void ssh_pkt_addstring_start(struct Packet *); +static void ssh_pkt_addstring_str(struct Packet *, const char *data); +static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len); +static void ssh_pkt_addstring(struct Packet *, const char *data); +static unsigned char *ssh2_mpint_fmt(Bignum b, int *len); +static void ssh1_pkt_addmp(struct Packet *, Bignum b); +static void ssh2_pkt_addmp(struct Packet *, Bignum b); +static int ssh2_pkt_construct(Ssh, struct Packet *); +static void ssh2_pkt_send(Ssh, struct Packet *); +static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); +static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin); +static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin); +static void ssh2_channel_check_close(struct ssh_channel *c); +static void ssh_channel_destroy(struct ssh_channel *c); +static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin); + +/* + * Buffer management constants. There are several of these for + * various different purposes: + * + * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up + * on a local data stream before we throttle the whole SSH + * connection (in SSH-1 only). Throttling the whole connection is + * pretty drastic so we set this high in the hope it won't + * happen very often. + * + * - SSH_MAX_BACKLOG is the amount of backlog that must build up + * on the SSH connection itself before we defensively throttle + * _all_ local data streams. This is pretty drastic too (though + * thankfully unlikely in SSH-2 since the window mechanism should + * ensure that the server never has any need to throttle its end + * of the connection), so we set this high as well. + * + * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2 + * channels. + * + * - OUR_V2_BIGWIN is the window size we advertise for the only + * channel in a simple connection. It must be <= INT_MAX. + * + * - OUR_V2_MAXPKT is the official "maximum packet size" we send + * to the remote side. This actually has nothing to do with the + * size of the _packet_, but is instead a limit on the amount + * of data we're willing to receive in a single SSH2 channel + * data message. + * + * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH + * _packet_ we're prepared to cope with. It must be a multiple + * of the cipher block size, and must be at least 35000. + */ + +#define SSH1_BUFFER_LIMIT 32768 +#define SSH_MAX_BACKLOG 32768 +#define OUR_V2_WINSIZE 16384 +#define OUR_V2_BIGWIN 0x7fffffff +#define OUR_V2_MAXPKT 0x4000UL +#define OUR_V2_PACKETLIMIT 0x9000UL + +const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss }; + +const static struct ssh_mac *macs[] = { + &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 +}; +const static struct ssh_mac *buggymacs[] = { + &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 +}; + +static void *ssh_comp_none_init(void) +{ + return NULL; +} +static void ssh_comp_none_cleanup(void *handle) +{ +} +static int ssh_comp_none_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + return 0; +} +static int ssh_comp_none_disable(void *handle) +{ + return 0; +} +const static struct ssh_compress ssh_comp_none = { + "none", NULL, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_comp_none_disable, NULL +}; +extern const struct ssh_compress ssh_zlib; +const static struct ssh_compress *compressions[] = { + &ssh_zlib, &ssh_comp_none +}; + +enum { /* channel types */ + CHAN_MAINSESSION, + CHAN_X11, + CHAN_AGENT, + CHAN_SOCKDATA, + CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */ + /* + * CHAN_SHARING indicates a channel which is tracked here on + * behalf of a connection-sharing downstream. We do almost nothing + * with these channels ourselves: all messages relating to them + * get thrown straight to sshshare.c and passed on almost + * unmodified to downstream. + */ + CHAN_SHARING, + /* + * CHAN_ZOMBIE is used to indicate a channel for which we've + * already destroyed the local data source: for instance, if a + * forwarded port experiences a socket error on the local side, we + * immediately destroy its local socket and turn the SSH channel + * into CHAN_ZOMBIE. + */ + CHAN_ZOMBIE +}; + +typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); +typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx); +typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *); + +/* + * Each channel has a queue of outstanding CHANNEL_REQUESTS and their + * handlers. + */ +struct outstanding_channel_request { + cchandler_fn_t handler; + void *ctx; + struct outstanding_channel_request *next; +}; + +/* + * 2-3-4 tree storing channels. + */ +struct ssh_channel { + Ssh ssh; /* pointer back to main context */ + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + int halfopen; + /* + * In SSH-1, this value contains four bits: + * + * 1 We have sent SSH1_MSG_CHANNEL_CLOSE. + * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * 4 We have received SSH1_MSG_CHANNEL_CLOSE. + * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * + * A channel is completely finished with when all four bits are set. + * + * In SSH-2, the four bits mean: + * + * 1 We have sent SSH2_MSG_CHANNEL_EOF. + * 2 We have sent SSH2_MSG_CHANNEL_CLOSE. + * 4 We have received SSH2_MSG_CHANNEL_EOF. + * 8 We have received SSH2_MSG_CHANNEL_CLOSE. + * + * A channel is completely finished with when we have both sent + * and received CLOSE. + * + * The symbolic constants below use the SSH-2 terminology, which + * is a bit confusing in SSH-1, but we have to use _something_. + */ +#define CLOSES_SENT_EOF 1 +#define CLOSES_SENT_CLOSE 2 +#define CLOSES_RCVD_EOF 4 +#define CLOSES_RCVD_CLOSE 8 + int closes; + + /* + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. + */ + int pending_eof; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + int throttling_conn; + union { + struct ssh2_data_channel { + bufchain outbuffer; + unsigned remwindow, remmaxpkt; + /* locwindow is signed so we can cope with excess data. */ + int locwindow, locmaxwin; + /* + * remlocwin is the amount of local window that we think + * the remote end had available to it after it sent the + * last data packet or window adjust ack. + */ + int remlocwin; + /* + * These store the list of channel requests that haven't + * been acked. + */ + struct outstanding_channel_request *chanreq_head, *chanreq_tail; + enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; + } v2; + } v; + union { + struct ssh_agent_channel { + unsigned char *message; + unsigned char msglen[4]; + unsigned lensofar, totallen; + int outstanding_requests; + } a; + struct ssh_x11_channel { + struct X11Connection *xconn; + int initial; + } x11; + struct ssh_pfd_channel { + struct PortForwarding *pf; + } pfd; + struct ssh_sharing_channel { + void *ctx; + } sharing; + } u; +}; + +/* + * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2 + * use this structure in different ways, reflecting SSH-2's + * altogether saner approach to port forwarding. + * + * In SSH-1, you arrange a remote forwarding by sending the server + * the remote port number, and the local destination host:port. + * When a connection comes in, the server sends you back that + * host:port pair, and you connect to it. This is a ready-made + * security hole if you're not on the ball: a malicious server + * could send you back _any_ host:port pair, so if you trustingly + * connect to the address it gives you then you've just opened the + * entire inside of your corporate network just by connecting + * through it to a dodgy SSH server. Hence, we must store a list of + * host:port pairs we _are_ trying to forward to, and reject a + * connection request from the server if it's not in the list. + * + * In SSH-2, each side of the connection minds its own business and + * doesn't send unnecessary information to the other. You arrange a + * remote forwarding by sending the server just the remote port + * number. When a connection comes in, the server tells you which + * of its ports was connected to; and _you_ have to remember what + * local host:port pair went with that port number. + * + * Hence, in SSH-1 this structure is indexed by destination + * host:port pair, whereas in SSH-2 it is indexed by source port. + */ +struct ssh_portfwd; /* forward declaration */ + +struct ssh_rportfwd { + unsigned sport, dport; + char *shost, *dhost; + char *sportdesc; + void *share_ctx; + struct ssh_portfwd *pfrec; +}; + +static void free_rportfwd(struct ssh_rportfwd *pf) +{ + if (pf) { + sfree(pf->sportdesc); + sfree(pf->shost); + sfree(pf->dhost); + sfree(pf); + } +} + +/* + * Separately to the rportfwd tree (which is for looking up port + * open requests from the server), a tree of _these_ structures is + * used to keep track of all the currently open port forwardings, + * so that we can reconfigure in mid-session if the user requests + * it. + */ +struct ssh_portfwd { + enum { DESTROY, KEEP, CREATE } status; + int type; + unsigned sport, dport; + char *saddr, *daddr; + char *sserv, *dserv; + struct ssh_rportfwd *remote; + int addressfamily; + struct PortListener *local; +}; +#define free_portfwd(pf) ( \ + ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \ + sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) ) + +struct Packet { + long length; /* length of packet: see below */ + long forcepad; /* SSH-2: force padding to at least this length */ + int type; /* only used for incoming packets */ + unsigned long sequence; /* SSH-2 incoming sequence number */ + unsigned char *data; /* allocated storage */ + unsigned char *body; /* offset of payload within `data' */ + long savedpos; /* dual-purpose saved packet position: see below */ + long maxlen; /* amount of storage allocated for `data' */ + long encrypted_len; /* for SSH-2 total-size counting */ + + /* + * A note on the 'length' and 'savedpos' fields above. + * + * Incoming packets are set up so that pkt->length is measured + * relative to pkt->body, which itself points to a few bytes after + * pkt->data (skipping some uninteresting header fields including + * the packet type code). The ssh_pkt_get* functions all expect + * this setup, and they also use pkt->savedpos to indicate how far + * through the packet being decoded they've got - and that, too, + * is an offset from pkt->body rather than pkt->data. + * + * During construction of an outgoing packet, however, pkt->length + * is measured relative to the base pointer pkt->data, and + * pkt->body is not really used for anything until the packet is + * ready for sending. In this mode, pkt->savedpos is reused as a + * temporary variable by the addstring functions, which write out + * a string length field and then keep going back and updating it + * as more data is appended to the subsequent string data field; + * pkt->savedpos stores the offset (again relative to pkt->data) + * of the start of the string data field. + */ + + /* Extra metadata used in SSH packet logging mode, allowing us to + * log in the packet header line that the packet came from a + * connection-sharing downstream and what if anything unusual was + * done to it. The additional_log_text field is expected to be a + * static string - it will not be freed. */ + unsigned downstream_id; + const char *additional_log_text; +}; + +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh1_protocol_setup(Ssh ssh); +static void ssh2_protocol_setup(Ssh ssh); +static void ssh2_bare_connection_protocol_setup(Ssh ssh); +static void ssh_size(void *handle, int width, int height); +static void ssh_special(void *handle, Telnet_Special); +static int ssh2_try_send(struct ssh_channel *c); +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len); +static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); +static void ssh2_set_window(struct ssh_channel *c, int newwin); +static int ssh_sendbuffer(void *handle); +static int ssh_do_close(Ssh ssh, int notify_exit); +static unsigned long ssh_pkt_getuint32(struct Packet *pkt); +static int ssh2_pkt_getbool(struct Packet *pkt); +static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); +static void ssh2_timer(void *ctx, unsigned long now); +static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin); + +struct rdpkt1_state_tag { + long len, pad, biglen, to_read; + unsigned long realcrc, gotcrc; + unsigned char *p; + int i; + int chunk; + struct Packet *pktin; +}; + +struct rdpkt2_state_tag { + long len, pad, payload, packetlen, maclen; + int i; + int cipherblk; + unsigned long incoming_sequence; + struct Packet *pktin; +}; + +struct rdpkt2_bare_state_tag { + char length[4]; + long packetlen; + int i; + unsigned long incoming_sequence; + struct Packet *pktin; +}; + +struct queued_handler; +struct queued_handler { + int msg1, msg2; + chandler_fn_t handler; + void *ctx; + struct queued_handler *next; +}; + +struct ssh_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + char *v_c, *v_s; + void *exhash; + + Socket s; + + void *ldisc; + void *logctx; + + unsigned char session_key[32]; + int v1_compressing; + int v1_remote_protoflags; + int v1_local_protoflags; + int agentfwd_enabled; + int X11_fwd_enabled; + int remote_bugs; + const struct ssh_cipher *cipher; + void *v1_cipher_ctx; + void *crcda_ctx; + const struct ssh2_cipher *cscipher, *sccipher; + void *cs_cipher_ctx, *sc_cipher_ctx; + const struct ssh_mac *csmac, *scmac; + void *cs_mac_ctx, *sc_mac_ctx; + const struct ssh_compress *cscomp, *sccomp; + void *cs_comp_ctx, *sc_comp_ctx; + const struct ssh_kex *kex; + const struct ssh_signkey *hostkey; + char *hostkey_str; /* string representation, for easy checking in rekeys */ + unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; + int v2_session_id_len; + void *kex_ctx; + + int bare_connection; + int attempting_connshare; + void *connshare; + + char *savedhost; + int savedport; + int send_ok; + int echoing, editing; + + void *frontend; + + int ospeed, ispeed; /* temporaries */ + int term_width, term_height; + + tree234 *channels; /* indexed by local id */ + struct ssh_channel *mainchan; /* primary session channel */ + int ncmode; /* is primary channel direct-tcpip? */ + int exitcode; + int close_expected; + int clean_exit; + + tree234 *rportfwds, *portfwds; + + enum { + SSH_STATE_PREPACKET, + SSH_STATE_BEFORE_SIZE, + SSH_STATE_INTERMED, + SSH_STATE_SESSION, + SSH_STATE_CLOSED + } state; + + int size_needed, eof_needed; + int sent_console_eof; + int got_pty; /* affects EOF behaviour on main channel */ + + struct Packet **queue; + int queuelen, queuesize; + int queueing; + unsigned char *deferred_send_data; + int deferred_len, deferred_size; + + /* + * Gross hack: pscp will try to start SFTP but fall back to + * scp1 if that fails. This variable is the means by which + * scp.c can reach into the SSH code and find out which one it + * got. + */ + int fallback_cmd; + + bufchain banner; /* accumulates banners during do_ssh2_authconn */ + + Pkt_KCtx pkt_kctx; + Pkt_ACtx pkt_actx; + + struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + tree234 *x11authtree; + + int version; + int conn_throttle_count; + int overall_bufsize; + int throttled_all; + int v1_stdout_throttling; + unsigned long v2_outgoing_sequence; + + int ssh1_rdpkt_crstate; + int ssh2_rdpkt_crstate; + int ssh2_bare_rdpkt_crstate; + int ssh_gotdata_crstate; + int do_ssh1_connection_crstate; + + void *do_ssh_init_state; + void *do_ssh1_login_state; + void *do_ssh2_transport_state; + void *do_ssh2_authconn_state; + void *do_ssh_connection_init_state; + + struct rdpkt1_state_tag rdpkt1_state; + struct rdpkt2_state_tag rdpkt2_state; + struct rdpkt2_bare_state_tag rdpkt2_bare_state; + + /* SSH-1 and SSH-2 use this for different things, but both use it */ + int protocol_initial_phase_done; + + void (*protocol) (Ssh ssh, void *vin, int inlen, + struct Packet *pkt); + struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); + int (*do_ssh_init)(Ssh ssh, unsigned char c); + + /* + * We maintain our own copy of a Conf structure here. That way, + * when we're passed a new one for reconfiguration, we can check + * the differences and potentially reconfigure port forwardings + * etc in mid-session. + */ + Conf *conf; + + /* + * Values cached out of conf so as to avoid the tree234 lookup + * cost every time they're used. + */ + int logomitdata; + + /* + * Dynamically allocated username string created during SSH + * login. Stored in here rather than in the coroutine state so + * that it'll be reliably freed if we shut down the SSH session + * at some unexpected moment. + */ + char *username; + + /* + * Used to transfer data back from async callbacks. + */ + void *agent_response; + int agent_response_len; + int user_response; + + /* + * The SSH connection can be set as `frozen', meaning we are + * not currently accepting incoming data from the network. This + * is slightly more serious than setting the _socket_ as + * frozen, because we may already have had data passed to us + * from the network which we need to delay processing until + * after the freeze is lifted, so we also need a bufchain to + * store that data. + */ + int frozen; + bufchain queued_incoming_data; + + /* + * Dispatch table for packet types that we may have to deal + * with at any time. + */ + handler_fn_t packet_dispatch[256]; + + /* + * Queues of one-off handler functions for success/failure + * indications from a request. + */ + struct queued_handler *qhead, *qtail; + handler_fn_t q_saved_handler1, q_saved_handler2; + + /* + * This module deals with sending keepalives. + */ + Pinger pinger; + + /* + * Track incoming and outgoing data sizes and time, for + * size-based rekeys. + */ + unsigned long incoming_data_size, outgoing_data_size, deferred_data_size; + unsigned long max_data_size; + int kex_in_progress; + unsigned long next_rekey, last_rekey; + char *deferred_rekey_reason; /* points to STATIC string; don't free */ + + /* + * Fully qualified host name, which we need if doing GSSAPI. + */ + char *fullhostname; + +#ifndef NO_GSSAPI + /* + * GSSAPI libraries for this session. + */ + struct ssh_gss_liblist *gsslibs; +#endif +}; + +#define logevent(s) logevent(ssh->frontend, s) + +/* logevent, only printf-formatted. */ +static void logeventf(Ssh ssh, const char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + va_end(ap); + logevent(buf); + sfree(buf); +} + +static void bomb_out(Ssh ssh, char *text) +{ + ssh_do_close(ssh, FALSE); + logevent(text); + connection_fatal(ssh->frontend, "%s", text); + sfree(text); +} + +#define bombout(msg) bomb_out(ssh, dupprintf msg) + +/* Helper function for common bits of parsing ttymodes. */ +static void parse_ttymodes(Ssh ssh, + void (*do_mode)(void *data, char *mode, char *val), + void *data) +{ + char *key, *val; + + for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) { + /* + * val[0] is either 'V', indicating that an explicit value + * follows it, or 'A' indicating that we should pass the + * value through from the local environment via get_ttymode. + */ + if (val[0] == 'A') { + val = get_ttymode(ssh->frontend, key); + if (val) { + do_mode(data, key, val); + sfree(val); + } + } else + do_mode(data, key, val + 1); /* skip the 'V' */ + } +} + +static int ssh_channelcmp(void *av, void *bv) +{ + struct ssh_channel *a = (struct ssh_channel *) av; + struct ssh_channel *b = (struct ssh_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} +static int ssh_channelfind(void *av, void *bv) +{ + unsigned *a = (unsigned *) av; + struct ssh_channel *b = (struct ssh_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +static int ssh_rportcmp_ssh1(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->dhost, b->dhost)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + return 0; +} + +static int ssh_rportcmp_ssh2(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->shost, b->shost)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + return 0; +} + +/* + * Special form of strcmp which can cope with NULL inputs. NULL is + * defined to sort before even the empty string. + */ +static int nullstrcmp(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return +1; + return strcmp(a, b); +} + +static int ssh_portcmp(void *av, void *bv) +{ + struct ssh_portfwd *a = (struct ssh_portfwd *) av; + struct ssh_portfwd *b = (struct ssh_portfwd *) bv; + int i; + if (a->type > b->type) + return +1; + if (a->type < b->type) + return -1; + if (a->addressfamily > b->addressfamily) + return +1; + if (a->addressfamily < b->addressfamily) + return -1; + if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + if (a->type != 'D') { + if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + } + return 0; +} + +static int alloc_channel_id(Ssh ssh) +{ + const unsigned CHANNEL_NUMBER_OFFSET = 256; + unsigned low, high, mid; + int tsize; + struct ssh_channel *c; + + /* + * First-fit allocation of channel numbers: always pick the + * lowest unused one. To do this, binary-search using the + * counted B-tree to find the largest channel ID which is in a + * contiguous sequence from the beginning. (Precisely + * everything in that sequence must have ID equal to its tree + * index plus CHANNEL_NUMBER_OFFSET.) + */ + tsize = count234(ssh->channels); + + low = -1; + high = tsize; + while (high - low > 1) { + mid = (high + low) / 2; + c = index234(ssh->channels, mid); + if (c->localid == mid + CHANNEL_NUMBER_OFFSET) + low = mid; /* this one is fine */ + else + high = mid; /* this one is past it */ + } + /* + * Now low points to either -1, or the tree index of the + * largest ID in the initial sequence. + */ + { + unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET; + assert(NULL == find234(ssh->channels, &i, ssh_channelfind)); + } + return low + 1 + CHANNEL_NUMBER_OFFSET; +} + +static void c_write_stderr(int trusted, const char *buf, int len) +{ + int i; + for (i = 0; i < len; i++) + if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60))) + fputc(buf[i], stderr); +} + +static void c_write(Ssh ssh, const char *buf, int len) +{ + if (flags & FLAG_STDERR) + c_write_stderr(1, buf, len); + else + from_backend(ssh->frontend, 1, buf, len); +} + +static void c_write_untrusted(Ssh ssh, const char *buf, int len) +{ + if (flags & FLAG_STDERR) + c_write_stderr(0, buf, len); + else + from_backend_untrusted(ssh->frontend, buf, len); +} + +static void c_write_str(Ssh ssh, const char *buf) +{ + c_write(ssh, buf, strlen(buf)); +} + +static void ssh_free_packet(struct Packet *pkt) +{ + sfree(pkt->data); + sfree(pkt); +} +static struct Packet *ssh_new_packet(void) +{ + struct Packet *pkt = snew(struct Packet); + + pkt->body = pkt->data = NULL; + pkt->maxlen = 0; + + return pkt; +} + +static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH1_SMSG_STDOUT_DATA || + pkt->type == SSH1_SMSG_STDERR_DATA || + pkt->type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (pkt->type == SSH1_MSG_CHANNEL_DATA) + ssh_pkt_getuint32(pkt); /* skip channel id */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + log_packet(ssh->logctx, PKT_INCOMING, pkt->type, + ssh1_pkt_type(pkt->type), + pkt->body, pkt->length, nblanks, blanks, NULL, + 0, NULL); +} + +static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + /* + * For outgoing packets, pkt->length represents the length of the + * whole packet starting at pkt->data (including some header), and + * pkt->body refers to the point within that where the log-worthy + * payload begins. However, incoming packets expect pkt->length to + * represent only the payload length (that is, it's measured from + * pkt->body not from pkt->data). Temporarily adjust our outgoing + * packet to conform to the incoming-packet semantics, so that we + * can analyse it with the ssh_pkt_get functions. + */ + pkt->length -= (pkt->body - pkt->data); + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH1_CMSG_STDIN_DATA || + pkt->type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (pkt->type == SSH1_MSG_CHANNEL_DATA) + ssh_pkt_getuint32(pkt); /* skip channel id */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD || + pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE || + pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a password or similar packet, blank the password(s). */ + blanks[nblanks].offset = 0; + blanks[nblanks].len = pkt->length; + blanks[nblanks].type = PKTLOG_BLANK; + nblanks++; + } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* + * If this is an X forwarding request packet, blank the fake + * auth data. + * + * Note that while we blank the X authentication data here, we + * don't take any special action to blank the start of an X11 + * channel, so using MIT-MAGIC-COOKIE-1 and actually opening + * an X connection without having session blanking enabled is + * likely to leak your cookie into the log. + */ + pkt->savedpos = 0; + ssh_pkt_getstring(pkt, &str, &slen); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } + } + + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], + ssh1_pkt_type(pkt->data[12]), + pkt->body, pkt->length, + nblanks, blanks, NULL, 0, NULL); + + /* + * Undo the above adjustment of pkt->length, to put the packet + * back in the state we found it. + */ + pkt->length += (pkt->body - pkt->data); +} + +/* + * Collect incoming data in the incoming packet buffer. + * Decipher and verify the packet when it is completely read. + * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets. + * Update the *data and *datalen variables. + * Return a Packet structure when a packet is completed. + */ +static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +{ + struct rdpkt1_state_tag *st = &ssh->rdpkt1_state; + + crBegin(ssh->ssh1_rdpkt_crstate); + + st->pktin = ssh_new_packet(); + + st->pktin->type = 0; + st->pktin->length = 0; + + for (st->i = st->len = 0; st->i < 4; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->len = (st->len << 8) + **data; + (*data)++, (*datalen)--; + } + + st->pad = 8 - (st->len % 8); + st->biglen = st->len + st->pad; + st->pktin->length = st->len - 5; + + if (st->biglen < 0) { + bombout(("Extremely large packet length from server suggests" + " data stream corruption")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + st->pktin->maxlen = st->biglen; + st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char); + + st->to_read = st->biglen; + st->p = st->pktin->data; + while (st->to_read > 0) { + st->chunk = st->to_read; + while ((*datalen) == 0) + crReturn(NULL); + if (st->chunk > (*datalen)) + st->chunk = (*datalen); + memcpy(st->p, *data, st->chunk); + *data += st->chunk; + *datalen -= st->chunk; + st->p += st->chunk; + st->to_read -= st->chunk; + } + + if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data, + st->biglen, NULL)) { + bombout(("Network attack (CRC compensation) detected!")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + if (ssh->cipher) + ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen); + + st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4); + st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4); + if (st->gotcrc != st->realcrc) { + bombout(("Incorrect CRC received on packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + st->pktin->body = st->pktin->data + st->pad + 1; + + if (ssh->v1_compressing) { + unsigned char *decompblk; + int decomplen; + if (!zlib_decompress_block(ssh->sc_comp_ctx, + st->pktin->body - 1, st->pktin->length + 1, + &decompblk, &decomplen)) { + bombout(("Zlib decompression encountered invalid data")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + if (st->pktin->maxlen < st->pad + decomplen) { + st->pktin->maxlen = st->pad + decomplen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + st->pktin->body = st->pktin->data + st->pad + 1; + } + + memcpy(st->pktin->body - 1, decompblk, decomplen); + sfree(decompblk); + st->pktin->length = decomplen - 1; + } + + st->pktin->type = st->pktin->body[-1]; + + /* + * Now pktin->body and pktin->length identify the semantic content + * of the packet, excluding the initial type byte. + */ + + if (ssh->logctx) + ssh1_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; + + crFinish(st->pktin); +} + +static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH2_MSG_CHANNEL_DATA || + pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + ssh_pkt_getuint32(pkt); /* skip channel id */ + if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + ssh_pkt_getuint32(pkt); /* skip extended data type */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + log_packet(ssh->logctx, PKT_INCOMING, pkt->type, + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type), + pkt->body, pkt->length, nblanks, blanks, &pkt->sequence, + 0, NULL); +} + +static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + /* + * For outgoing packets, pkt->length represents the length of the + * whole packet starting at pkt->data (including some header), and + * pkt->body refers to the point within that where the log-worthy + * payload begins. However, incoming packets expect pkt->length to + * represent only the payload length (that is, it's measured from + * pkt->body not from pkt->data). Temporarily adjust our outgoing + * packet to conform to the incoming-packet semantics, so that we + * can analyse it with the ssh_pkt_get functions. + */ + pkt->length -= (pkt->body - pkt->data); + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH2_MSG_CHANNEL_DATA || + pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + ssh_pkt_getuint32(pkt); /* skip channel id */ + if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + ssh_pkt_getuint32(pkt); /* skip extended data type */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + if (pkt->type == SSH2_MSG_USERAUTH_REQUEST && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a password packet, blank the password(s). */ + pkt->savedpos = 0; + ssh_pkt_getstring(pkt, &str, &slen); + ssh_pkt_getstring(pkt, &str, &slen); + ssh_pkt_getstring(pkt, &str, &slen); + if (slen == 8 && !memcmp(str, "password", 8)) { + ssh2_pkt_getbool(pkt); + /* Blank the password field. */ + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + /* If there's another password field beyond it (change of + * password), blank that too. */ + ssh_pkt_getstring(pkt, &str, &slen); + if (str) + blanks[nblanks-1].len = + pkt->savedpos - blanks[nblanks].offset; + } + } + } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER && + pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a keyboard-interactive response packet, blank + * the responses. */ + pkt->savedpos = 0; + ssh_pkt_getuint32(pkt); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + while (1) { + ssh_pkt_getstring(pkt, &str, &slen); + if (!str) + break; + } + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* + * If this is an X forwarding request packet, blank the fake + * auth data. + * + * Note that while we blank the X authentication data here, we + * don't take any special action to blank the start of an X11 + * channel, so using MIT-MAGIC-COOKIE-1 and actually opening + * an X connection without having session blanking enabled is + * likely to leak your cookie into the log. + */ + pkt->savedpos = 0; + ssh_pkt_getuint32(pkt); + ssh_pkt_getstring(pkt, &str, &slen); + if (slen == 7 && !memcmp(str, "x11-req", 0)) { + ssh2_pkt_getbool(pkt); + ssh2_pkt_getbool(pkt); + ssh_pkt_getstring(pkt, &str, &slen); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } + } + } + + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), + pkt->body, pkt->length, nblanks, blanks, + &ssh->v2_outgoing_sequence, + pkt->downstream_id, pkt->additional_log_text); + + /* + * Undo the above adjustment of pkt->length, to put the packet + * back in the state we found it. + */ + pkt->length += (pkt->body - pkt->data); +} + +static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +{ + struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; + + crBegin(ssh->ssh2_rdpkt_crstate); + + st->pktin = ssh_new_packet(); + + st->pktin->type = 0; + st->pktin->length = 0; + if (ssh->sccipher) + st->cipherblk = ssh->sccipher->blksize; + else + st->cipherblk = 8; + if (st->cipherblk < 8) + st->cipherblk = 8; + st->maclen = ssh->scmac ? ssh->scmac->len : 0; + + if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) && + ssh->scmac) { + /* + * When dealing with a CBC-mode cipher, we want to avoid the + * possibility of an attacker's tweaking the ciphertext stream + * so as to cause us to feed the same block to the block + * cipher more than once and thus leak information + * (VU#958563). The way we do this is not to take any + * decisions on the basis of anything we've decrypted until + * we've verified it with a MAC. That includes the packet + * length, so we just read data and check the MAC repeatedly, + * and when the MAC passes, see if the length we've got is + * plausible. + */ + + /* May as well allocate the whole lot now. */ + st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA, + unsigned char); + + /* Read an amount corresponding to the MAC. */ + for (st->i = 0; st->i < st->maclen; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + st->packetlen = 0; + { + unsigned char seq[4]; + ssh->scmac->start(ssh->sc_mac_ctx); + PUT_32BIT(seq, st->incoming_sequence); + ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4); + } + + for (;;) { /* Once around this loop per cipher block. */ + /* Read another cipher-block's worth, and tack it onto the end. */ + for (st->i = 0; st->i < st->cipherblk; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++; + (*datalen)--; + } + /* Decrypt one more block (a little further back in the stream). */ + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data + st->packetlen, + st->cipherblk); + /* Feed that block to the MAC. */ + ssh->scmac->bytes(ssh->sc_mac_ctx, + st->pktin->data + st->packetlen, st->cipherblk); + st->packetlen += st->cipherblk; + /* See if that gives us a valid packet. */ + if (ssh->scmac->verresult(ssh->sc_mac_ctx, + st->pktin->data + st->packetlen) && + ((st->len = toint(GET_32BIT(st->pktin->data))) == + st->packetlen-4)) + break; + if (st->packetlen >= OUR_V2_PACKETLIMIT) { + bombout(("No valid incoming packet found")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + } + st->pktin->maxlen = st->packetlen + st->maclen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + } else { + st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char); + + /* + * Acquire and decrypt the first block of the packet. This will + * contain the length and padding details. + */ + for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + if (ssh->sccipher) + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data, st->cipherblk); + + /* + * Now get the length figure. + */ + st->len = toint(GET_32BIT(st->pktin->data)); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT || + (st->len + 4) % st->cipherblk != 0) { + bombout(("Incoming packet was garbled on decryption")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + /* + * So now we can work out the total packet length. + */ + st->packetlen = st->len + 4; + + /* + * Allocate memory for the rest of the packet. + */ + st->pktin->maxlen = st->packetlen + st->maclen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + + /* + * Read and decrypt the remainder of the packet. + */ + for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen; + st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + /* Decrypt everything _except_ the MAC. */ + if (ssh->sccipher) + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data + st->cipherblk, + st->packetlen - st->cipherblk); + + /* + * Check the MAC. + */ + if (ssh->scmac + && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data, + st->len + 4, st->incoming_sequence)) { + bombout(("Incorrect MAC received on packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + } + /* Get and sanity-check the amount of random padding. */ + st->pad = st->pktin->data[4]; + if (st->pad < 4 || st->len - st->pad < 1) { + bombout(("Invalid padding length on received packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + /* + * This enables us to deduce the payload length. + */ + st->payload = st->len - st->pad - 1; + + st->pktin->length = st->payload + 5; + st->pktin->encrypted_len = st->packetlen; + + st->pktin->sequence = st->incoming_sequence++; + + st->pktin->length = st->packetlen - st->pad; + assert(st->pktin->length >= 0); + + /* + * Decompress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (ssh->sccomp && + ssh->sccomp->decompress(ssh->sc_comp_ctx, + st->pktin->data + 5, st->pktin->length - 5, + &newpayload, &newlen)) { + if (st->pktin->maxlen < newlen + 5) { + st->pktin->maxlen = newlen + 5; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + } + st->pktin->length = 5 + newlen; + memcpy(st->pktin->data + 5, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * RFC 4253 doesn't explicitly say that completely empty packets + * with no type byte are forbidden, so treat them as deserving + * an SSH_MSG_UNIMPLEMENTED. + */ + if (st->pktin->length <= 5) { /* == 5 we hope, but robustness */ + ssh2_msg_something_unimplemented(ssh, st->pktin); + crStop(NULL); + } + /* + * pktin->body and pktin->length should identify the semantic + * content of the packet, excluding the initial type byte. + */ + st->pktin->type = st->pktin->data[5]; + st->pktin->body = st->pktin->data + 6; + st->pktin->length -= 6; + assert(st->pktin->length >= 0); /* one last double-check */ + + if (ssh->logctx) + ssh2_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; + + crFinish(st->pktin); +} + +static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data, + int *datalen) +{ + struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state; + + crBegin(ssh->ssh2_bare_rdpkt_crstate); + + /* + * Read the packet length field. + */ + for (st->i = 0; st->i < 4; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->length[st->i] = *(*data)++; + (*datalen)--; + } + + st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length)); + if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) { + bombout(("Invalid packet length received")); + crStop(NULL); + } + + st->pktin = ssh_new_packet(); + st->pktin->data = snewn(st->packetlen, unsigned char); + + st->pktin->encrypted_len = st->packetlen; + + st->pktin->sequence = st->incoming_sequence++; + + /* + * Read the remainder of the packet. + */ + for (st->i = 0; st->i < st->packetlen; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + /* + * pktin->body and pktin->length should identify the semantic + * content of the packet, excluding the initial type byte. + */ + st->pktin->type = st->pktin->data[0]; + st->pktin->body = st->pktin->data + 1; + st->pktin->length = st->packetlen - 1; + + /* + * Log incoming packet, possibly omitting sensitive fields. + */ + if (ssh->logctx) + ssh2_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; + + crFinish(st->pktin); +} + +static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p) +{ + int pad, biglen, i, pktoffs; + unsigned long crc; +#ifdef __SC__ + /* + * XXX various versions of SC (including 8.8.4) screw up the + * register allocation in this function and use the same register + * (D6) for len and as a temporary, with predictable results. The + * following sledgehammer prevents this. + */ + volatile +#endif + int len; + + if (ssh->logctx) + ssh1_log_outgoing_packet(ssh, pkt); + + if (ssh->v1_compressing) { + unsigned char *compblk; + int complen; + zlib_compress_block(ssh->cs_comp_ctx, + pkt->data + 12, pkt->length - 12, + &compblk, &complen); + ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */ + memcpy(pkt->data + 12, compblk, complen); + sfree(compblk); + pkt->length = complen + 12; + } + + ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */ + pkt->length += 4; + len = pkt->length - 4 - 8; /* len(type+data+CRC) */ + pad = 8 - (len % 8); + pktoffs = 8 - pad; + biglen = len + pad; /* len(padding+type+data+CRC) */ + + for (i = pktoffs; i < 4+8; i++) + pkt->data[i] = random_byte(); + crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */ + PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); + PUT_32BIT(pkt->data + pktoffs, len); + + if (ssh->cipher) + ssh->cipher->encrypt(ssh->v1_cipher_ctx, + pkt->data + pktoffs + 4, biglen); + + if (offset_p) *offset_p = pktoffs; + return biglen + 4; /* len(length+padding+type+data+CRC) */ +} + +static int s_write(Ssh ssh, void *data, int len) +{ + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + if (!ssh->s) + return 0; + return sk_write(ssh->s, (char *)data, len); +} + +static void s_wrpkt(Ssh ssh, struct Packet *pkt) +{ + int len, backlog, offset; + len = s_wrpkt_prepare(ssh, pkt, &offset); + backlog = s_write(ssh, pkt->data + offset, len); + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + ssh_free_packet(pkt); +} + +static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt) +{ + int len, offset; + len = s_wrpkt_prepare(ssh, pkt, &offset); + if (ssh->deferred_len + len > ssh->deferred_size) { + ssh->deferred_size = ssh->deferred_len + len + 128; + ssh->deferred_send_data = sresize(ssh->deferred_send_data, + ssh->deferred_size, + unsigned char); + } + memcpy(ssh->deferred_send_data + ssh->deferred_len, + pkt->data + offset, len); + ssh->deferred_len += len; + ssh_free_packet(pkt); +} + +/* + * Construct a SSH-1 packet with the specified contents. + * (This all-at-once interface used to be the only one, but now SSH-1 + * packets can also be constructed incrementally.) + */ +static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap) +{ + int argtype; + Bignum bn; + struct Packet *pkt; + + pkt = ssh1_pkt_init(pkttype); + + while ((argtype = va_arg(ap, int)) != PKT_END) { + unsigned char *argp, argchar; + char *sargp; + unsigned long argint; + int arglen; + switch (argtype) { + /* Actual fields in the packet */ + case PKT_INT: + argint = va_arg(ap, int); + ssh_pkt_adduint32(pkt, argint); + break; + case PKT_CHAR: + argchar = (unsigned char) va_arg(ap, int); + ssh_pkt_addbyte(pkt, argchar); + break; + case PKT_DATA: + argp = va_arg(ap, unsigned char *); + arglen = va_arg(ap, int); + ssh_pkt_adddata(pkt, argp, arglen); + break; + case PKT_STR: + sargp = va_arg(ap, char *); + ssh_pkt_addstring(pkt, sargp); + break; + case PKT_BIGNUM: + bn = va_arg(ap, Bignum); + ssh1_pkt_addmp(pkt, bn); + break; + } + } + + return pkt; +} + +static void send_packet(Ssh ssh, int pkttype, ...) +{ + struct Packet *pkt; + va_list ap; + va_start(ap, pkttype); + pkt = construct_packet(ssh, pkttype, ap); + va_end(ap); + s_wrpkt(ssh, pkt); +} + +static void defer_packet(Ssh ssh, int pkttype, ...) +{ + struct Packet *pkt; + va_list ap; + va_start(ap, pkttype); + pkt = construct_packet(ssh, pkttype, ap); + va_end(ap); + s_wrpkt_defer(ssh, pkt); +} + +static int ssh_versioncmp(char *a, char *b) +{ + char *ae, *be; + unsigned long av, bv; + + av = strtoul(a, &ae, 10); + bv = strtoul(b, &be, 10); + if (av != bv) + return (av < bv ? -1 : +1); + if (*ae == '.') + ae++; + if (*be == '.') + be++; + av = strtoul(ae, &ae, 10); + bv = strtoul(be, &be, 10); + if (av != bv) + return (av < bv ? -1 : +1); + return 0; +} + +/* + * Utility routines for putting an SSH-protocol `string' and + * `uint32' into a hash state. + */ +static void hash_string(const struct ssh_hash *h, void *s, void *str, int len) +{ + unsigned char lenblk[4]; + PUT_32BIT(lenblk, len); + h->bytes(s, lenblk, 4); + h->bytes(s, str, len); +} + +static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i) +{ + unsigned char intblk[4]; + PUT_32BIT(intblk, i); + h->bytes(s, intblk, 4); +} + +/* + * Packet construction functions. Mostly shared between SSH-1 and SSH-2. + */ +static void ssh_pkt_ensure(struct Packet *pkt, int length) +{ + if (pkt->maxlen < length) { + unsigned char *body = pkt->body; + int offset = body ? body - pkt->data : 0; + pkt->maxlen = length + 256; + pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char); + if (body) pkt->body = pkt->data + offset; + } +} +static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len) +{ + pkt->length += len; + ssh_pkt_ensure(pkt, pkt->length); + memcpy(pkt->data + pkt->length - len, data, len); +} +static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte) +{ + ssh_pkt_adddata(pkt, &byte, 1); +} +static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value) +{ + ssh_pkt_adddata(pkt, &value, 1); +} +static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value) +{ + unsigned char x[4]; + PUT_32BIT(x, value); + ssh_pkt_adddata(pkt, x, 4); +} +static void ssh_pkt_addstring_start(struct Packet *pkt) +{ + ssh_pkt_adduint32(pkt, 0); + pkt->savedpos = pkt->length; +} +static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data) +{ + ssh_pkt_adddata(pkt, data, strlen(data)); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data, + int len) +{ + ssh_pkt_adddata(pkt, data, len); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void ssh_pkt_addstring(struct Packet *pkt, const char *data) +{ + ssh_pkt_addstring_start(pkt); + ssh_pkt_addstring_str(pkt, data); +} +static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b) +{ + int len = ssh1_bignum_length(b); + unsigned char *data = snewn(len, unsigned char); + (void) ssh1_write_bignum(data, b); + ssh_pkt_adddata(pkt, data, len); + sfree(data); +} +static unsigned char *ssh2_mpint_fmt(Bignum b, int *len) +{ + unsigned char *p; + int i, n = (bignum_bitcount(b) + 7) / 8; + p = snewn(n + 1, unsigned char); + p[0] = 0; + for (i = 1; i <= n; i++) + p[i] = bignum_byte(b, n - i); + i = 0; + while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0) + i++; + memmove(p, p + i, n + 1 - i); + *len = n + 1 - i; + return p; +} +static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b) +{ + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + ssh_pkt_addstring_start(pkt); + ssh_pkt_addstring_data(pkt, (char *)p, len); + sfree(p); +} + +static struct Packet *ssh1_pkt_init(int pkt_type) +{ + struct Packet *pkt = ssh_new_packet(); + pkt->length = 4 + 8; /* space for length + max padding */ + ssh_pkt_addbyte(pkt, pkt_type); + pkt->body = pkt->data + pkt->length; + pkt->type = pkt_type; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + return pkt; +} + +/* For legacy code (SSH-1 and -2 packet construction used to be separate) */ +#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length) +#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len) +#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte) +#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value) +#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt) +#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data) +#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len) +#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data) + +static struct Packet *ssh2_pkt_init(int pkt_type) +{ + struct Packet *pkt = ssh_new_packet(); + pkt->length = 5; /* space for packet length + padding length */ + pkt->forcepad = 0; + pkt->type = pkt_type; + ssh_pkt_addbyte(pkt, (unsigned char) pkt_type); + pkt->body = pkt->data + pkt->length; /* after packet type */ + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + return pkt; +} + +/* + * Construct an SSH-2 final-form packet: compress it, encrypt it, + * put the MAC on it. Final packet, ready to be sent, is stored in + * pkt->data. Total length is returned. + */ +static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) +{ + int cipherblk, maclen, padding, i; + + if (ssh->logctx) + ssh2_log_outgoing_packet(ssh, pkt); + + if (ssh->bare_connection) { + /* + * Trivial packet construction for the bare connection + * protocol. + */ + PUT_32BIT(pkt->data + 1, pkt->length - 5); + pkt->body = pkt->data + 1; + ssh->v2_outgoing_sequence++; /* only for diagnostics, really */ + return pkt->length - 1; + } + + /* + * Compress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (ssh->cscomp && + ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5, + pkt->length - 5, + &newpayload, &newlen)) { + pkt->length = 5; + ssh2_pkt_adddata(pkt, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Add padding. At least four bytes, and must also bring total + * length (minus MAC) up to a multiple of the block size. + * If pkt->forcepad is set, make sure the packet is at least that size + * after padding. + */ + cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */ + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + padding = 4; + if (pkt->length + padding < pkt->forcepad) + padding = pkt->forcepad - pkt->length; + padding += + (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk; + assert(padding <= 255); + maclen = ssh->csmac ? ssh->csmac->len : 0; + ssh2_pkt_ensure(pkt, pkt->length + padding + maclen); + pkt->data[4] = padding; + for (i = 0; i < padding; i++) + pkt->data[pkt->length + i] = random_byte(); + PUT_32BIT(pkt->data, pkt->length + padding - 4); + if (ssh->csmac) + ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, + pkt->length + padding, + ssh->v2_outgoing_sequence); + ssh->v2_outgoing_sequence++; /* whether or not we MACed */ + + if (ssh->cscipher) + ssh->cscipher->encrypt(ssh->cs_cipher_ctx, + pkt->data, pkt->length + padding); + + pkt->encrypted_len = pkt->length + padding; + + /* Ready-to-send packet starts at pkt->data. We return length. */ + pkt->body = pkt->data; + return pkt->length + padding + maclen; +} + +/* + * Routines called from the main SSH code to send packets. There + * are quite a few of these, because we have two separate + * mechanisms for delaying the sending of packets: + * + * - In order to send an IGNORE message and a password message in + * a single fixed-length blob, we require the ability to + * concatenate the encrypted forms of those two packets _into_ a + * single blob and then pass it to our transport + * layer in one go. Hence, there's a deferment mechanism which + * works after packet encryption. + * + * - In order to avoid sending any connection-layer messages + * during repeat key exchange, we have to queue up any such + * outgoing messages _before_ they are encrypted (and in + * particular before they're allocated sequence numbers), and + * then send them once we've finished. + * + * I call these mechanisms `defer' and `queue' respectively, so as + * to distinguish them reasonably easily. + * + * The functions send_noqueue() and defer_noqueue() free the packet + * structure they are passed. Every outgoing packet goes through + * precisely one of these functions in its life; packets passed to + * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of + * these or get queued, and then when the queue is later emptied + * the packets are all passed to defer_noqueue(). + * + * When using a CBC-mode cipher, it's necessary to ensure that an + * attacker can't provide data to be encrypted using an IV that they + * know. We ensure this by prefixing each packet that might contain + * user data with an SSH_MSG_IGNORE. This is done using the deferral + * mechanism, so in this case send_noqueue() ends up redirecting to + * defer_noqueue(). If you don't like this inefficiency, don't use + * CBC. + */ + +static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int); +static void ssh_pkt_defersend(Ssh); + +/* + * Send an SSH-2 packet immediately, without queuing or deferring. + */ +static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) +{ + int len; + int backlog; + if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) { + /* We need to send two packets, so use the deferral mechanism. */ + ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); + ssh_pkt_defersend(ssh); + return; + } + len = ssh2_pkt_construct(ssh, pkt); + backlog = s_write(ssh, pkt->body, len); + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + + ssh->outgoing_data_size += pkt->encrypted_len; + if (!ssh->kex_in_progress && + !ssh->bare_connection && + ssh->max_data_size != 0 && + ssh->outgoing_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data sent", -1, NULL); + + ssh_free_packet(pkt); +} + +/* + * Defer an SSH-2 packet. + */ +static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore) +{ + int len; + if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) && + ssh->deferred_len == 0 && !noignore && + !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + /* + * Interpose an SSH_MSG_IGNORE to ensure that user data don't + * get encrypted with a known IV. + */ + struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(ipkt); + ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE); + } + len = ssh2_pkt_construct(ssh, pkt); + if (ssh->deferred_len + len > ssh->deferred_size) { + ssh->deferred_size = ssh->deferred_len + len + 128; + ssh->deferred_send_data = sresize(ssh->deferred_send_data, + ssh->deferred_size, + unsigned char); + } + memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len); + ssh->deferred_len += len; + ssh->deferred_data_size += pkt->encrypted_len; + ssh_free_packet(pkt); +} + +/* + * Queue an SSH-2 packet. + */ +static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt) +{ + assert(ssh->queueing); + + if (ssh->queuelen >= ssh->queuesize) { + ssh->queuesize = ssh->queuelen + 32; + ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *); + } + + ssh->queue[ssh->queuelen++] = pkt; +} + +/* + * Either queue or send a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_send_noqueue(ssh, pkt); +} + +/* + * Either queue or defer a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); +} + +/* + * Send the whole deferred data block constructed by + * ssh2_pkt_defer() or SSH-1's defer_packet(). + * + * The expected use of the defer mechanism is that you call + * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If + * not currently queueing, this simply sets up deferred_send_data + * and then sends it. If we _are_ currently queueing, the calls to + * ssh2_pkt_defer() put the deferred packets on to the queue + * instead, and therefore ssh_pkt_defersend() has no deferred data + * to send. Hence, there's no need to make it conditional on + * ssh->queueing. + */ +static void ssh_pkt_defersend(Ssh ssh) +{ + int backlog; + backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len); + ssh->deferred_len = ssh->deferred_size = 0; + sfree(ssh->deferred_send_data); + ssh->deferred_send_data = NULL; + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + + ssh->outgoing_data_size += ssh->deferred_data_size; + if (!ssh->kex_in_progress && + !ssh->bare_connection && + ssh->max_data_size != 0 && + ssh->outgoing_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data sent", -1, NULL); + ssh->deferred_data_size = 0; +} + +/* + * Send a packet whose length needs to be disguised (typically + * passwords or keyboard-interactive responses). + */ +static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt, + int padsize) +{ +#if 0 + if (0) { + /* + * The simplest way to do this is to adjust the + * variable-length padding field in the outgoing packet. + * + * Currently compiled out, because some Cisco SSH servers + * don't like excessively padded packets (bah, why's it + * always Cisco?) + */ + pkt->forcepad = padsize; + ssh2_pkt_send(ssh, pkt); + } else +#endif + { + /* + * If we can't do that, however, an alternative approach is + * to use the pkt_defer mechanism to bundle the packet + * tightly together with an SSH_MSG_IGNORE such that their + * combined length is a constant. So first we construct the + * final form of this packet and defer its sending. + */ + ssh2_pkt_defer(ssh, pkt); + + /* + * Now construct an SSH_MSG_IGNORE which includes a string + * that's an exact multiple of the cipher block size. (If + * the cipher is NULL so that the block size is + * unavailable, we don't do this trick at all, because we + * gain nothing by it.) + */ + if (ssh->cscipher && + !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + int stringlen, i; + + stringlen = (256 - ssh->deferred_len); + stringlen += ssh->cscipher->blksize - 1; + stringlen -= (stringlen % ssh->cscipher->blksize); + if (ssh->cscomp) { + /* + * Temporarily disable actual compression, so we + * can guarantee to get this string exactly the + * length we want it. The compression-disabling + * routine should return an integer indicating how + * many bytes we should adjust our string length + * by. + */ + stringlen -= + ssh->cscomp->disable_compression(ssh->cs_comp_ctx); + } + pkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(pkt); + for (i = 0; i < stringlen; i++) { + char c = (char) random_byte(); + ssh2_pkt_addstring_data(pkt, &c, 1); + } + ssh2_pkt_defer(ssh, pkt); + } + ssh_pkt_defersend(ssh); + } +} + +/* + * Send all queued SSH-2 packets. We send them by means of + * ssh2_pkt_defer_noqueue(), in case they included a pair of + * packets that needed to be lumped together. + */ +static void ssh2_pkt_queuesend(Ssh ssh) +{ + int i; + + assert(!ssh->queueing); + + for (i = 0; i < ssh->queuelen; i++) + ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE); + ssh->queuelen = 0; + + ssh_pkt_defersend(ssh); +} + +#if 0 +void bndebug(char *string, Bignum b) +{ + unsigned char *p; + int i, len; + p = ssh2_mpint_fmt(b, &len); + debug(("%s", string)); + for (i = 0; i < len; i++) + debug((" %02x", p[i])); + debug(("\n")); + sfree(p); +} +#endif + +static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b) +{ + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + hash_string(h, s, p, len); + sfree(p); +} + +/* + * Packet decode functions for both SSH-1 and SSH-2. + */ +static unsigned long ssh_pkt_getuint32(struct Packet *pkt) +{ + unsigned long value; + if (pkt->length - pkt->savedpos < 4) + return 0; /* arrgh, no way to decline (FIXME?) */ + value = GET_32BIT(pkt->body + pkt->savedpos); + pkt->savedpos += 4; + return value; +} +static int ssh2_pkt_getbool(struct Packet *pkt) +{ + unsigned long value; + if (pkt->length - pkt->savedpos < 1) + return 0; /* arrgh, no way to decline (FIXME?) */ + value = pkt->body[pkt->savedpos] != 0; + pkt->savedpos++; + return value; +} +static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length) +{ + int len; + *p = NULL; + *length = 0; + if (pkt->length - pkt->savedpos < 4) + return; + len = toint(GET_32BIT(pkt->body + pkt->savedpos)); + if (len < 0) + return; + *length = len; + pkt->savedpos += 4; + if (pkt->length - pkt->savedpos < *length) + return; + *p = (char *)(pkt->body + pkt->savedpos); + pkt->savedpos += *length; +} +static void *ssh_pkt_getdata(struct Packet *pkt, int length) +{ + if (pkt->length - pkt->savedpos < length) + return NULL; + pkt->savedpos += length; + return pkt->body + (pkt->savedpos - length); +} +static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key, + unsigned char **keystr) +{ + int j; + + j = makekey(pkt->body + pkt->savedpos, + pkt->length - pkt->savedpos, + key, keystr, 0); + + if (j < 0) + return FALSE; + + pkt->savedpos += j; + assert(pkt->savedpos < pkt->length); + + return TRUE; +} +static Bignum ssh1_pkt_getmp(struct Packet *pkt) +{ + int j; + Bignum b; + + j = ssh1_read_bignum(pkt->body + pkt->savedpos, + pkt->length - pkt->savedpos, &b); + + if (j < 0) + return NULL; + + pkt->savedpos += j; + return b; +} +static Bignum ssh2_pkt_getmp(struct Packet *pkt) +{ + char *p; + int length; + Bignum b; + + ssh_pkt_getstring(pkt, &p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + return NULL; + b = bignum_from_bytes((unsigned char *)p, length); + return b; +} + +/* + * Helper function to add an SSH-2 signature blob to a packet. + * Expects to be shown the public key blob as well as the signature + * blob. Normally works just like ssh2_pkt_addstring, but will + * fiddle with the signature packet if necessary for + * BUG_SSH2_RSA_PADDING. + */ +static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt, + void *pkblob_v, int pkblob_len, + void *sigblob_v, int sigblob_len) +{ + unsigned char *pkblob = (unsigned char *)pkblob_v; + unsigned char *sigblob = (unsigned char *)sigblob_v; + + /* dmemdump(pkblob, pkblob_len); */ + /* dmemdump(sigblob, sigblob_len); */ + + /* + * See if this is in fact an ssh-rsa signature and a buggy + * server; otherwise we can just do this the easy way. + */ + if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 && + (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) { + int pos, len, siglen; + + /* + * Find the byte length of the modulus. + */ + + pos = 4+7; /* skip over "ssh-rsa" */ + len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */ + if (len < 0 || len > pkblob_len - pos - 4) + goto give_up; + pos += 4 + len; /* skip over exponent */ + if (pkblob_len - pos < 4) + goto give_up; + len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */ + if (len < 0 || len > pkblob_len - pos - 4) + goto give_up; + pos += 4; /* find modulus itself */ + while (len > 0 && pkblob[pos] == 0) + len--, pos++; + /* debug(("modulus length is %d\n", len)); */ + + /* + * Now find the signature integer. + */ + pos = 4+7; /* skip over "ssh-rsa" */ + if (sigblob_len < pos+4) + goto give_up; + siglen = toint(GET_32BIT(sigblob+pos)); + if (siglen != sigblob_len - pos - 4) + goto give_up; + /* debug(("signature length is %d\n", siglen)); */ + + if (len != siglen) { + unsigned char newlen[4]; + ssh2_pkt_addstring_start(pkt); + ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos); + /* dmemdump(sigblob, pos); */ + pos += 4; /* point to start of actual sig */ + PUT_32BIT(newlen, len); + ssh2_pkt_addstring_data(pkt, (char *)newlen, 4); + /* dmemdump(newlen, 4); */ + newlen[0] = 0; + while (len-- > siglen) { + ssh2_pkt_addstring_data(pkt, (char *)newlen, 1); + /* dmemdump(newlen, 1); */ + } + ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen); + /* dmemdump(sigblob+pos, siglen); */ + return; + } + + /* Otherwise fall through and do it the easy way. We also come + * here as a fallback if we discover above that the key blob + * is misformatted in some way. */ + give_up:; + } + + ssh2_pkt_addstring_start(pkt); + ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len); +} + +/* + * Examine the remote side's version string and compare it against + * a list of known buggy implementations. + */ +static void ssh_detect_bugs(Ssh ssh, char *vstring) +{ + char *imp; /* pointer to implementation part */ + imp = vstring; + imp += strcspn(imp, "-"); + if (*imp) imp++; + imp += strcspn(imp, "-"); + if (*imp) imp++; + + ssh->remote_bugs = 0; + + /* + * General notes on server version strings: + * - Not all servers reporting "Cisco-1.25" have all the bugs listed + * here -- in particular, we've heard of one that's perfectly happy + * with SSH1_MSG_IGNOREs -- but this string never seems to change, + * so we can't distinguish them. + */ + if (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == AUTO && + (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || + !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || + !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || + !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { + /* + * These versions don't support SSH1_MSG_IGNORE, so we have + * to use a different defence against password length + * sniffing. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; + logevent("We believe remote version has SSH-1 ignore bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == AUTO && + (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { + /* + * These versions need a plain password sent; they can't + * handle having a null and a random length of data after + * the password. + */ + ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; + logevent("We believe remote version needs a plain SSH-1 password"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == AUTO && + (!strcmp(imp, "Cisco-1.25")))) { + /* + * These versions apparently have no clue whatever about + * RSA authentication and will panic and die if they see + * an AUTH_RSA message. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_RSA; + logevent("We believe remote version can't handle SSH-1 RSA authentication"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || + wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || + wc_match("2.1 *", imp)))) { + /* + * These versions have the HMAC bug. + */ + ssh->remote_bugs |= BUG_SSH2_HMAC; + logevent("We believe remote version has SSH-2 HMAC bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { + /* + * These versions have the key-derivation bug (failing to + * include the literal shared secret in the hashes that + * generate the keys). + */ + ssh->remote_bugs |= BUG_SSH2_DERIVEKEY; + logevent("We believe remote version has SSH-2 key-derivation bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO && + (wc_match("OpenSSH_2.[5-9]*", imp) || + wc_match("OpenSSH_3.[0-2]*", imp) || + wc_match("mod_sftp/0.[0-8]*", imp) || + wc_match("mod_sftp/0.9.[0-8]", imp)))) { + /* + * These versions have the SSH-2 RSA padding bug. + */ + ssh->remote_bugs |= BUG_SSH2_RSA_PADDING; + logevent("We believe remote version has SSH-2 RSA padding bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == AUTO && + wc_match("OpenSSH_2.[0-2]*", imp))) { + /* + * These versions have the SSH-2 session-ID bug in + * public-key authentication. + */ + ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID; + logevent("We believe remote version has SSH-2 public-key-session-ID bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == AUTO && + (wc_match("DigiSSH_2.0", imp) || + wc_match("OpenSSH_2.[0-4]*", imp) || + wc_match("OpenSSH_2.5.[0-3]*", imp) || + wc_match("Sun_SSH_1.0", imp) || + wc_match("Sun_SSH_1.0.1", imp) || + /* All versions <= 1.2.6 (they changed their format in 1.2.7) */ + wc_match("WeOnlyDo-*", imp)))) { + /* + * These versions have the SSH-2 rekey bug. + */ + ssh->remote_bugs |= BUG_SSH2_REKEY; + logevent("We believe remote version has SSH-2 rekey bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == AUTO && + (wc_match("1.36_sshlib GlobalSCAPE", imp) || + wc_match("1.36 sshlib: GlobalScape", imp)))) { + /* + * This version ignores our makpkt and needs to be throttled. + */ + ssh->remote_bugs |= BUG_SSH2_MAXPKT; + logevent("We believe remote version ignores SSH-2 maximum packet size"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_ignore2) == FORCE_ON) { + /* + * Servers that don't support SSH2_MSG_IGNORE. Currently, + * none detected automatically. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; + logevent("We believe remote version has SSH-2 ignore bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == AUTO && + (wc_match("OpenSSH_2.[235]*", imp)))) { + /* + * These versions only support the original (pre-RFC4419) + * SSH-2 GEX request. + */ + ssh->remote_bugs |= BUG_SSH2_OLDGEX; + logevent("We believe remote version has outdated SSH-2 GEX"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) { + /* + * Servers that don't support our winadj request for one + * reason or another. Currently, none detected automatically. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ; + logevent("We believe remote version has winadj bug"); + } + + if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == AUTO && + (wc_match("OpenSSH_[2-5].*", imp) || + wc_match("OpenSSH_6.[0-6]*", imp) || + wc_match("dropbear_0.[2-4][0-9]*", imp) || + wc_match("dropbear_0.5[01]*", imp)))) { + /* + * These versions have the SSH-2 channel request bug. + * OpenSSH 6.7 and above do not: + * https://bugzilla.mindrot.org/show_bug.cgi?id=1818 + * dropbear_0.52 and above do not: + * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c + */ + ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; + logevent("We believe remote version has SSH-2 channel request bug"); + } +} + +/* + * The `software version' part of an SSH version string is required + * to contain no spaces or minus signs. + */ +static void ssh_fix_verstring(char *str) +{ + /* Eat "-". */ + while (*str && *str != '-') str++; + assert(*str == '-'); str++; + + /* Convert minus signs and spaces in the remaining string into + * underscores. */ + while (*str) { + if (*str == '-' || *str == ' ') + *str = '_'; + str++; + } +} + +/* + * Send an appropriate SSH version string. + */ +static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers) +{ + char *verstring; + + if (ssh->version == 2) { + /* + * Construct a v2 version string. + */ + verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver); + } else { + /* + * Construct a v1 version string. + */ + assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */ + verstring = dupprintf("SSH-%s-%s\012", + (ssh_versioncmp(svers, "1.5") <= 0 ? + svers : "1.5"), + sshver); + } + + ssh_fix_verstring(verstring + strlen(protoname)); + + if (ssh->version == 2) { + size_t len; + /* + * Record our version string. + */ + len = strcspn(verstring, "\015\012"); + ssh->v_c = snewn(len + 1, char); + memcpy(ssh->v_c, verstring, len); + ssh->v_c[len] = 0; + } + + logeventf(ssh, "We claim version: %.*s", + strcspn(verstring, "\015\012"), verstring); + s_write(ssh, verstring, strlen(verstring)); + sfree(verstring); +} + +static int do_ssh_init(Ssh ssh, unsigned char c) +{ + static const char protoname[] = "SSH-"; + + struct do_ssh_init_state { + int crLine; + int vslen; + char version[10]; + char *vstring; + int vstrsize; + int i; + int proto1, proto2; + }; + crState(do_ssh_init_state); + + crBeginState; + + /* Search for a line beginning with the protocol name prefix in + * the input. */ + for (;;) { + for (s->i = 0; protoname[s->i]; s->i++) { + if ((char)c != protoname[s->i]) goto no; + crReturn(1); + } + break; + no: + while (c != '\012') + crReturn(1); + crReturn(1); + } + + s->vstrsize = sizeof(protoname) + 16; + s->vstring = snewn(s->vstrsize, char); + strcpy(s->vstring, protoname); + s->vslen = strlen(protoname); + s->i = 0; + while (1) { + if (s->vslen >= s->vstrsize - 1) { + s->vstrsize += 16; + s->vstring = sresize(s->vstring, s->vstrsize, char); + } + s->vstring[s->vslen++] = c; + if (s->i >= 0) { + if (c == '-') { + s->version[s->i] = '\0'; + s->i = -1; + } else if (s->i < sizeof(s->version) - 1) + s->version[s->i++] = c; + } else if (c == '\012') + break; + crReturn(1); /* get another char */ + } + + ssh->agentfwd_enabled = FALSE; + ssh->rdpkt2_state.incoming_sequence = 0; + + s->vstring[s->vslen] = 0; + s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ + logeventf(ssh, "Server version: %s", s->vstring); + ssh_detect_bugs(ssh, s->vstring); + + /* + * Decide which SSH protocol version to support. + */ + + /* Anything strictly below "2.0" means protocol 1 is supported. */ + s->proto1 = ssh_versioncmp(s->version, "2.0") < 0; + /* Anything greater or equal to "1.99" means protocol 2 is supported. */ + s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0; + + if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) { + bombout(("SSH protocol version 1 required by configuration but " + "not provided by server")); + crStop(0); + } + if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) { + bombout(("SSH protocol version 2 required by configuration but " + "not provided by server")); + crStop(0); + } + + if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1)) + ssh->version = 2; + else + ssh->version = 1; + + logeventf(ssh, "Using SSH protocol version %d", ssh->version); + + /* Send the version string, if we haven't already */ + if (conf_get_int(ssh->conf, CONF_sshprot) != 3) + ssh_send_verstring(ssh, protoname, s->version); + + if (ssh->version == 2) { + size_t len; + /* + * Record their version string. + */ + len = strcspn(s->vstring, "\015\012"); + ssh->v_s = snewn(len + 1, char); + memcpy(ssh->v_s, s->vstring, len); + ssh->v_s[len] = 0; + + /* + * Initialise SSH-2 protocol. + */ + ssh->protocol = ssh2_protocol; + ssh2_protocol_setup(ssh); + ssh->s_rdpkt = ssh2_rdpkt; + } else { + /* + * Initialise SSH-1 protocol. + */ + ssh->protocol = ssh1_protocol; + ssh1_protocol_setup(ssh); + ssh->s_rdpkt = ssh1_rdpkt; + } + if (ssh->version == 2) + do_ssh2_transport(ssh, NULL, -1, NULL); + + update_specials_menu(ssh->frontend); + ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); + + sfree(s->vstring); + + crFinish(0); +} + +static int do_ssh_connection_init(Ssh ssh, unsigned char c) +{ + /* + * Ordinary SSH begins with the banner "SSH-x.y-...". This is just + * the ssh-connection part, extracted and given a trivial binary + * packet protocol, so we replace 'SSH-' at the start with a new + * name. In proper SSH style (though of course this part of the + * proper SSH protocol _isn't_ subject to this kind of + * DNS-domain-based extension), we define the new name in our + * extension space. + */ + static const char protoname[] = + "SSHCONNECTION@putty.projects.tartarus.org-"; + + struct do_ssh_connection_init_state { + int crLine; + int vslen; + char version[10]; + char *vstring; + int vstrsize; + int i; + }; + crState(do_ssh_connection_init_state); + + crBeginState; + + /* Search for a line beginning with the protocol name prefix in + * the input. */ + for (;;) { + for (s->i = 0; protoname[s->i]; s->i++) { + if ((char)c != protoname[s->i]) goto no; + crReturn(1); + } + break; + no: + while (c != '\012') + crReturn(1); + crReturn(1); + } + + s->vstrsize = sizeof(protoname) + 16; + s->vstring = snewn(s->vstrsize, char); + strcpy(s->vstring, protoname); + s->vslen = strlen(protoname); + s->i = 0; + while (1) { + if (s->vslen >= s->vstrsize - 1) { + s->vstrsize += 16; + s->vstring = sresize(s->vstring, s->vstrsize, char); + } + s->vstring[s->vslen++] = c; + if (s->i >= 0) { + if (c == '-') { + s->version[s->i] = '\0'; + s->i = -1; + } else if (s->i < sizeof(s->version) - 1) + s->version[s->i++] = c; + } else if (c == '\012') + break; + crReturn(1); /* get another char */ + } + + ssh->agentfwd_enabled = FALSE; + ssh->rdpkt2_bare_state.incoming_sequence = 0; + + s->vstring[s->vslen] = 0; + s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ + logeventf(ssh, "Server version: %s", s->vstring); + ssh_detect_bugs(ssh, s->vstring); + + /* + * Decide which SSH protocol version to support. This is easy in + * bare ssh-connection mode: only 2.0 is legal. + */ + if (ssh_versioncmp(s->version, "2.0") < 0) { + bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol")); + crStop(0); + } + if (conf_get_int(ssh->conf, CONF_sshprot) == 0) { + bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode")); + crStop(0); + } + + ssh->version = 2; + + logeventf(ssh, "Using bare ssh-connection protocol"); + + /* Send the version string, if we haven't already */ + ssh_send_verstring(ssh, protoname, s->version); + + /* + * Initialise bare connection protocol. + */ + ssh->protocol = ssh2_bare_connection_protocol; + ssh2_bare_connection_protocol_setup(ssh); + ssh->s_rdpkt = ssh2_bare_connection_rdpkt; + + update_specials_menu(ssh->frontend); + ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); + + /* + * Get authconn (really just conn) under way. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); + + sfree(s->vstring); + + crFinish(0); +} + +static void ssh_process_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + struct Packet *pktin; + + pktin = ssh->s_rdpkt(ssh, data, datalen); + if (pktin) { + ssh->protocol(ssh, NULL, 0, pktin); + ssh_free_packet(pktin); + } +} + +static void ssh_queue_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + bufchain_add(&ssh->queued_incoming_data, *data, *datalen); + *data += *datalen; + *datalen = 0; +} + +static void ssh_process_queued_incoming_data(Ssh ssh) +{ + void *vdata; + unsigned char *data; + int len, origlen; + + while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) { + bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len); + data = vdata; + origlen = len; + + while (!ssh->frozen && len > 0) + ssh_process_incoming_data(ssh, &data, &len); + + if (origlen > len) + bufchain_consume(&ssh->queued_incoming_data, origlen - len); + } +} + +static void ssh_set_frozen(Ssh ssh, int frozen) +{ + if (ssh->s) + sk_set_frozen(ssh->s, frozen); + ssh->frozen = frozen; +} + +static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) +{ + /* Log raw data, if we're in that mode. */ + if (ssh->logctx) + log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, + 0, NULL, NULL, 0, NULL); + + crBegin(ssh->ssh_gotdata_crstate); + + /* + * To begin with, feed the characters one by one to the + * protocol initialisation / selection function do_ssh_init(). + * When that returns 0, we're done with the initial greeting + * exchange and can move on to packet discipline. + */ + while (1) { + int ret; /* need not be kept across crReturn */ + if (datalen == 0) + crReturnV; /* more data please */ + ret = ssh->do_ssh_init(ssh, *data); + data++; + datalen--; + if (ret == 0) + break; + } + + /* + * We emerge from that loop when the initial negotiation is + * over and we have selected an s_rdpkt function. Now pass + * everything to s_rdpkt, and then pass the resulting packets + * to the proper protocol handler. + */ + + while (1) { + while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) { + if (ssh->frozen) { + ssh_queue_incoming_data(ssh, &data, &datalen); + /* This uses up all data and cannot cause anything interesting + * to happen; indeed, for anything to happen at all, we must + * return, so break out. */ + break; + } else if (bufchain_size(&ssh->queued_incoming_data) > 0) { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_queued_incoming_data(ssh); + } else { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_incoming_data(ssh, &data, &datalen); + } + /* FIXME this is probably EBW. */ + if (ssh->state == SSH_STATE_CLOSED) + return; + } + /* We're out of data. Go and get some more. */ + crReturnV; + } + crFinishV; +} + +static int ssh_do_close(Ssh ssh, int notify_exit) +{ + int ret = 0; + struct ssh_channel *c; + + ssh->state = SSH_STATE_CLOSED; + expire_timer_context(ssh); + if (ssh->s) { + sk_close(ssh->s); + ssh->s = NULL; + if (notify_exit) + notify_remote_exit(ssh->frontend); + else + ret = 1; + } + /* + * Now we must shut down any port- and X-forwarded channels going + * through this connection. + */ + if (ssh->channels) { + while (NULL != (c = index234(ssh->channels, 0))) { + switch (c->type) { + case CHAN_X11: + x11_close(c->u.x11.xconn); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + pfd_close(c->u.pfd.pf); + break; + } + del234(ssh->channels, c); /* moving next one to index 0 */ + if (ssh->version == 2) + bufchain_clear(&c->v.v2.outbuffer); + sfree(c); + } + } + /* + * Go through port-forwardings, and close any associated + * listening sockets. + */ + if (ssh->portfwds) { + struct ssh_portfwd *pf; + while (NULL != (pf = index234(ssh->portfwds, 0))) { + /* Dispose of any listening socket. */ + if (pf->local) + pfl_terminate(pf->local); + del234(ssh->portfwds, pf); /* moving next one to index 0 */ + free_portfwd(pf); + } + freetree234(ssh->portfwds); + ssh->portfwds = NULL; + } + + /* + * Also stop attempting to connection-share. + */ + if (ssh->connshare) { + sharestate_free(ssh->connshare); + ssh->connshare = NULL; + } + + return ret; +} + +static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Ssh ssh = (Ssh) plug; + char addrbuf[256], *msg; + + if (ssh->attempting_connshare) { + /* + * While we're attempting connection sharing, don't loudly log + * everything that happens. Real TCP connections need to be + * logged when we _start_ trying to connect, because it might + * be ages before they respond if something goes wrong; but + * connection sharing is local and quick to respond, and it's + * sufficient to simply wait and see whether it worked + * afterwards. + */ + } else { + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) { + if (sk_addr_needs_port(addr)) { + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + } else { + msg = dupprintf("Connecting to %s", addrbuf); + } + } else { + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + } + + logevent(msg); + sfree(msg); + } +} + +void ssh_connshare_log(Ssh ssh, int event, const char *logtext, + const char *ds_err, const char *us_err) +{ + if (event == SHARE_NONE) { + /* In this case, 'logtext' is an error message indicating a + * reason why connection sharing couldn't be set up _at all_. + * Failing that, ds_err and us_err indicate why we couldn't be + * a downstream and an upstream respectively. */ + if (logtext) { + logeventf(ssh, "Could not set up connection sharing: %s", logtext); + } else { + if (ds_err) + logeventf(ssh, "Could not set up connection sharing" + " as downstream: %s", ds_err); + if (us_err) + logeventf(ssh, "Could not set up connection sharing" + " as upstream: %s", us_err); + } + } else if (event == SHARE_DOWNSTREAM) { + /* In this case, 'logtext' is a local endpoint address */ + logeventf(ssh, "Using existing shared connection at %s", logtext); + /* Also we should mention this in the console window to avoid + * confusing users as to why this window doesn't behave the + * usual way. */ + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { + c_write_str(ssh,"Reusing a shared connection to this server.\r\n"); + } + } else if (event == SHARE_UPSTREAM) { + /* In this case, 'logtext' is a local endpoint address too */ + logeventf(ssh, "Sharing this connection at %s", logtext); + } +} + +static int ssh_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Ssh ssh = (Ssh) plug; + int need_notify = ssh_do_close(ssh, FALSE); + + if (!error_msg) { + if (!ssh->close_expected) + error_msg = "Server unexpectedly closed network connection"; + else + error_msg = "Server closed network connection"; + } + + if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0) + ssh->exitcode = 0; + + if (need_notify) + notify_remote_exit(ssh->frontend); + + if (error_msg) + logevent(error_msg); + if (!ssh->close_expected || !ssh->clean_exit) + connection_fatal(ssh->frontend, "%s", error_msg); + return 0; +} + +static int ssh_receive(Plug plug, int urgent, char *data, int len) +{ + Ssh ssh = (Ssh) plug; + ssh_gotdata(ssh, (unsigned char *)data, len); + if (ssh->state == SSH_STATE_CLOSED) { + ssh_do_close(ssh, TRUE); + return 0; + } + return 1; +} + +static void ssh_sent(Plug plug, int bufsize) +{ + Ssh ssh = (Ssh) plug; + /* + * If the send backlog on the SSH socket itself clears, we + * should unthrottle the whole world if it was throttled. + */ + if (bufsize < SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 0, bufsize); +} + +/* + * Connect to specified host and port. + * Returns an error message, or NULL on success. + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *connect_to_host(Ssh ssh, char *host, int port, + char **realhost, int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + ssh_socket_log, + ssh_closing, + ssh_receive, + ssh_sent, + NULL + }; + + SockAddr addr; + const char *err; + char *loghost; + int addressfamily, sshprot; + + loghost = conf_get_str(ssh->conf, CONF_loghost); + if (*loghost) { + char *tmphost; + char *colon; + + tmphost = dupstr(loghost); + ssh->savedport = 22; /* default ssh port */ + + /* + * A colon suffix on the hostname string also lets us affect + * savedport. (Unless there are multiple colons, in which case + * we assume this is an unbracketed IPv6 literal.) + */ + colon = host_strrchr(tmphost, ':'); + if (colon && colon == host_strchr(tmphost, ':')) { + *colon++ = '\0'; + if (*colon) + ssh->savedport = atoi(colon); + } + + ssh->savedhost = host_strduptrim(tmphost); + sfree(tmphost); + } else { + ssh->savedhost = host_strduptrim(host); + if (port < 0) + port = 22; /* default ssh port */ + ssh->savedport = port; + } + + ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */ + + /* + * Try connection-sharing, in case that means we don't open a + * socket after all. ssh_connection_sharing_init will connect to a + * previously established upstream if it can, and failing that, + * establish a listening socket for _us_ to be the upstream. In + * the latter case it will return NULL just as if it had done + * nothing, because here we only need to care if we're a + * downstream and need to do our connection setup differently. + */ + ssh->connshare = NULL; + ssh->attempting_connshare = TRUE; /* affects socket logging behaviour */ + ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport, + ssh->conf, ssh, &ssh->connshare); + ssh->attempting_connshare = FALSE; + if (ssh->s != NULL) { + /* + * We are a downstream. + */ + ssh->bare_connection = TRUE; + ssh->do_ssh_init = do_ssh_connection_init; + ssh->fullhostname = NULL; + *realhost = dupstr(host); /* best we can do */ + } else { + /* + * We're not a downstream, so open a normal socket. + */ + ssh->do_ssh_init = do_ssh_init; + + /* + * Try to find host. + */ + addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); + logeventf(ssh, "Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); + addr = name_lookup(host, port, realhost, ssh->conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ + + ssh->s = new_connection(addr, *realhost, port, + 0, 1, nodelay, keepalive, + (Plug) ssh, ssh->conf); + if ((err = sk_socket_error(ssh->s)) != NULL) { + ssh->s = NULL; + notify_remote_exit(ssh->frontend); + return err; + } + } + + /* + * If the SSH version number's fixed, set it now, and if it's SSH-2, + * send the version string too. + */ + sshprot = conf_get_int(ssh->conf, CONF_sshprot); + if (sshprot == 0) + ssh->version = 1; + if (sshprot == 3 && !ssh->bare_connection) { + ssh->version = 2; + ssh_send_verstring(ssh, "SSH-", NULL); + } + + /* + * loghost, if configured, overrides realhost. + */ + if (*loghost) { + sfree(*realhost); + *realhost = dupstr(loghost); + } + + return NULL; +} + +/* + * Throttle or unthrottle the SSH connection. + */ +static void ssh_throttle_conn(Ssh ssh, int adjust) +{ + int old_count = ssh->conn_throttle_count; + ssh->conn_throttle_count += adjust; + assert(ssh->conn_throttle_count >= 0); + if (ssh->conn_throttle_count && !old_count) { + ssh_set_frozen(ssh, 1); + } else if (!ssh->conn_throttle_count && old_count) { + ssh_set_frozen(ssh, 0); + } +} + +/* + * Throttle or unthrottle _all_ local data streams (for when sends + * on the SSH connection itself back up). + */ +static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) +{ + int i; + struct ssh_channel *c; + + if (enable == ssh->throttled_all) + return; + ssh->throttled_all = enable; + ssh->overall_bufsize = bufsize; + if (!ssh->channels) + return; + for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) { + switch (c->type) { + case CHAN_MAINSESSION: + /* + * This is treated separately, outside the switch. + */ + break; + case CHAN_X11: + x11_override_throttle(c->u.x11.xconn, enable); + break; + case CHAN_AGENT: + /* Agent channels require no buffer management. */ + break; + case CHAN_SOCKDATA: + pfd_override_throttle(c->u.pfd.pf, enable); + break; + } + } +} + +static void ssh_agent_callback(void *sshv, void *reply, int replylen) +{ + Ssh ssh = (Ssh) sshv; + + ssh->agent_response = reply; + ssh->agent_response_len = replylen; + + if (ssh->version == 1) + do_ssh1_login(ssh, NULL, -1, NULL); + else + do_ssh2_authconn(ssh, NULL, -1, NULL); +} + +static void ssh_dialog_callback(void *sshv, int ret) +{ + Ssh ssh = (Ssh) sshv; + + ssh->user_response = ret; + + if (ssh->version == 1) + do_ssh1_login(ssh, NULL, -1, NULL); + else + do_ssh2_transport(ssh, NULL, -1, NULL); + + /* + * This may have unfrozen the SSH connection, so do a + * queued-data run. + */ + ssh_process_queued_incoming_data(ssh); +} + +static void ssh_agentf_callback(void *cv, void *reply, int replylen) +{ + struct ssh_channel *c = (struct ssh_channel *)cv; + Ssh ssh = c->ssh; + void *sentreply = reply; + + c->u.a.outstanding_requests--; + if (!sentreply) { + /* Fake SSH_AGENT_FAILURE. */ + sentreply = "\0\0\0\1\5"; + replylen = 5; + } + if (ssh->version == 2) { + ssh2_add_channel_data(c, sentreply, replylen); + ssh2_try_send(c); + } else { + send_packet(ssh, SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, replylen, + PKT_DATA, sentreply, replylen, + PKT_END); + } + if (reply) + sfree(reply); + /* + * If we've already seen an incoming EOF but haven't sent an + * outgoing one, this may be the moment to send it. + */ + if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF)) + sshfwd_write_eof(c); +} + +/* + * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' + * non-NULL, otherwise just close the connection. `client_reason' == NULL + * => log `wire_reason'. + */ +static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason, + int code, int clean_exit) +{ + char *error; + if (!client_reason) + client_reason = wire_reason; + if (client_reason) + error = dupprintf("Disconnected: %s", client_reason); + else + error = dupstr("Disconnected"); + if (wire_reason) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason, + PKT_END); + } else if (ssh->version == 2) { + struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); + ssh2_pkt_adduint32(pktout, code); + ssh2_pkt_addstring(pktout, wire_reason); + ssh2_pkt_addstring(pktout, "en"); /* language tag */ + ssh2_pkt_send_noqueue(ssh, pktout); + } + } + ssh->close_expected = TRUE; + ssh->clean_exit = clean_exit; + ssh_closing((Plug)ssh, error, 0, 0); + sfree(error); +} + +int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint, + const struct ssh_signkey *ssh2keytype, + void *ssh2keydata) +{ + if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) { + return -1; /* no manual keys configured */ + } + + if (fingerprint) { + /* + * The fingerprint string we've been given will have things + * like 'ssh-rsa 2048' at the front of it. Strip those off and + * narrow down to just the colon-separated hex block at the + * end of the string. + */ + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + /* Quick sanity checks, including making sure it's in lowercase */ + assert(strlen(fingerprint) == 16*3 - 1); + assert(fingerprint[2] == ':'); + assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); + + if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, + fingerprint)) + return 1; /* success */ + } + + if (ssh2keydata) { + /* + * Construct the base64-encoded public key blob and see if + * that's listed. + */ + unsigned char *binblob; + char *base64blob; + int binlen, atoms, i; + binblob = ssh2keytype->public_blob(ssh2keydata, &binlen); + atoms = (binlen + 2) / 3; + base64blob = snewn(atoms * 4 + 1, char); + for (i = 0; i < atoms; i++) + base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i); + base64blob[atoms * 4] = '\0'; + sfree(binblob); + if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, + base64blob)) { + sfree(base64blob); + return 1; /* success */ + } + sfree(base64blob); + } + + return 0; +} + +/* + * Handle the key exchange and user authentication phases. + */ +static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + int i, j, ret; + unsigned char cookie[8], *ptr; + struct MD5Context md5c; + struct do_ssh1_login_state { + int crLine; + int len; + unsigned char *rsabuf, *keystr1, *keystr2; + unsigned long supported_ciphers_mask, supported_auths_mask; + int tried_publickey, tried_agent; + int tis_auth_refused, ccard_auth_refused; + unsigned char session_id[16]; + int cipher_type; + void *publickey_blob; + int publickey_bloblen; + char *publickey_comment; + int publickey_encrypted; + prompts_t *cur_prompt; + char c; + int pwpkt_type; + unsigned char request[5], *response, *p; + int responselen; + int keyi, nkeys; + int authed; + struct RSAKey key; + Bignum challenge; + char *commentp; + int commentlen; + int dlgret; + Filename *keyfile; + struct RSAKey servkey, hostkey; + }; + crState(do_ssh1_login_state); + + crBeginState; + + if (!pktin) + crWaitUntil(pktin); + + if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { + bombout(("Public key packet not received")); + crStop(0); + } + + logevent("Received public keys"); + + ptr = ssh_pkt_getdata(pktin, 8); + if (!ptr) { + bombout(("SSH-1 public key packet stopped before random cookie")); + crStop(0); + } + memcpy(cookie, ptr, 8); + + if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) || + !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) { + bombout(("Failed to read SSH-1 public keys from public key packet")); + crStop(0); + } + + /* + * Log the host key fingerprint. + */ + { + char logmsg[80]; + logevent("Host key fingerprint is:"); + strcpy(logmsg, " "); + s->hostkey.comment = NULL; + rsa_fingerprint(logmsg + strlen(logmsg), + sizeof(logmsg) - strlen(logmsg), &s->hostkey); + logevent(logmsg); + } + + ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin); + s->supported_ciphers_mask = ssh_pkt_getuint32(pktin); + s->supported_auths_mask = ssh_pkt_getuint32(pktin); + if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) + s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); + + ssh->v1_local_protoflags = + ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; + ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; + + MD5Init(&md5c); + MD5Update(&md5c, s->keystr2, s->hostkey.bytes); + MD5Update(&md5c, s->keystr1, s->servkey.bytes); + MD5Update(&md5c, cookie, 8); + MD5Final(s->session_id, &md5c); + + for (i = 0; i < 32; i++) + ssh->session_key[i] = random_byte(); + + /* + * Verify that the `bits' and `bytes' parameters match. + */ + if (s->hostkey.bits > s->hostkey.bytes * 8 || + s->servkey.bits > s->servkey.bytes * 8) { + bombout(("SSH-1 public keys were badly formatted")); + crStop(0); + } + + s->len = (s->hostkey.bytes > s->servkey.bytes ? + s->hostkey.bytes : s->servkey.bytes); + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + int len = rsastr_len(&s->hostkey); + char fingerprint[100]; + char *keystr = snewn(len, char); + rsastr_fmt(keystr, &s->hostkey); + rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL); + if (s->dlgret == 0) { /* did not match */ + bombout(("Host key did not appear in manually configured list")); + sfree(keystr); + crStop(0); + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + "rsa", keystr, fingerprint, + ssh_dialog_callback, ssh); + sfree(keystr); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at host key verification", + NULL, 0, TRUE); + crStop(0); + } + } else { + sfree(keystr); + } + } + + for (i = 0; i < 32; i++) { + s->rsabuf[i] = ssh->session_key[i]; + if (i < 16) + s->rsabuf[i] ^= s->session_id[i]; + } + + if (s->hostkey.bytes > s->servkey.bytes) { + ret = rsaencrypt(s->rsabuf, 32, &s->servkey); + if (ret) + ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey); + } else { + ret = rsaencrypt(s->rsabuf, 32, &s->hostkey); + if (ret) + ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey); + } + if (!ret) { + bombout(("SSH-1 public key encryptions failed due to bad formatting")); + crStop(0); + } + + logevent("Encrypted session key"); + + { + int cipher_chosen = 0, warn = 0; + char *cipher_string = NULL; + int i; + for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { + int next_cipher = conf_get_int_int(ssh->conf, + CONF_ssh_cipherlist, i); + if (next_cipher == CIPHER_WARN) { + /* If/when we choose a cipher, warn about it */ + warn = 1; + } else if (next_cipher == CIPHER_AES) { + /* XXX Probably don't need to mention this. */ + logevent("AES not supported in SSH-1, skipping"); + } else { + switch (next_cipher) { + case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = 1; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) + bombout(("Server violates SSH-1 protocol by not " + "supporting 3DES encryption")); + else + /* shouldn't happen */ + bombout(("No supported ciphers found")); + crStop(0); + } + + /* Warn about chosen cipher if necessary. */ + if (warn) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "cipher", cipher_string, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStop(0); + } + } + } + + switch (s->cipher_type) { + case SSH_CIPHER_3DES: + logevent("Using 3DES encryption"); + break; + case SSH_CIPHER_DES: + logevent("Using single-DES encryption"); + break; + case SSH_CIPHER_BLOWFISH: + logevent("Using Blowfish encryption"); + break; + } + + send_packet(ssh, SSH1_CMSG_SESSION_KEY, + PKT_CHAR, s->cipher_type, + PKT_DATA, cookie, 8, + PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF, + PKT_DATA, s->rsabuf, s->len, + PKT_INT, ssh->v1_local_protoflags, PKT_END); + + logevent("Trying to enable encryption..."); + + sfree(s->rsabuf); + + ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH_CIPHER_DES ? &ssh_des : + &ssh_3des); + ssh->v1_cipher_ctx = ssh->cipher->make_context(); + ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key); + logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name); + + ssh->crcda_ctx = crcda_make_context(); + logevent("Installing CRC compensation attack detector"); + + if (s->servkey.modulus) { + sfree(s->servkey.modulus); + s->servkey.modulus = NULL; + } + if (s->servkey.exponent) { + sfree(s->servkey.exponent); + s->servkey.exponent = NULL; + } + if (s->hostkey.modulus) { + sfree(s->hostkey.modulus); + s->hostkey.modulus = NULL; + } + if (s->hostkey.exponent) { + sfree(s->hostkey.exponent); + s->hostkey.exponent = NULL; + } + crWaitUntil(pktin); + + if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Encryption not successfully enabled")); + crStop(0); + } + + logevent("Successfully started encryption"); + + fflush(stdout); /* FIXME eh? */ + { + if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get a username. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStop(0); + } + ssh->username = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } + + send_packet(ssh, SSH1_CMSG_USER, PKT_STR, ssh->username, PKT_END); + { + char *userlog = dupprintf("Sent username \"%s\"", ssh->username); + logevent(userlog); + if (flags & FLAG_INTERACTIVE && + (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { + c_write_str(ssh, userlog); + c_write_str(ssh, "\r\n"); + } + sfree(userlog); + } + } + + crWaitUntil(pktin); + + if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + s->tried_publickey = s->tried_agent = 1; + } else { + s->tried_publickey = s->tried_agent = 0; + } + s->tis_auth_refused = s->ccard_auth_refused = 0; + /* + * Load the public half of any configured keyfile for later use. + */ + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH1) { + const char *error; + if (rsakey_pubblob(s->keyfile, + &s->publickey_blob, &s->publickey_bloblen, + &s->publickey_comment, &error)) { + s->publickey_encrypted = rsakey_encrypted(s->keyfile, + NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(s->keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } else + s->publickey_blob = NULL; + + while (pktin->type == SSH1_SMSG_FAILURE) { + s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + + if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) { + /* + * Attempt RSA authentication using Pageant. + */ + void *r; + + s->authed = FALSE; + s->tried_agent = 1; + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(s->request, 1); + s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; + if (!agent_query(s->request, 5, &r, &s->responselen, + ssh_agent_callback, ssh)) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for agent response")); + crStop(0); + } + } while (pktin || inlen > 0); + r = ssh->agent_response; + s->responselen = ssh->agent_response_len; + } + s->response = (unsigned char *) r; + if (s->response && s->responselen >= 5 && + s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + s->p = s->response + 5; + s->nkeys = toint(GET_32BIT(s->p)); + if (s->nkeys < 0) { + logeventf(ssh, "Pageant reported negative key count %d", + s->nkeys); + s->nkeys = 0; + } + s->p += 4; + logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); + for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { + unsigned char *pkblob = s->p; + s->p += 4; + { + int n, ok = FALSE; + do { /* do while (0) to make breaking easy */ + n = ssh1_read_bignum + (s->p, toint(s->responselen-(s->p-s->response)), + &s->key.exponent); + if (n < 0) + break; + s->p += n; + n = ssh1_read_bignum + (s->p, toint(s->responselen-(s->p-s->response)), + &s->key.modulus); + if (n < 0) + break; + s->p += n; + if (s->responselen - (s->p-s->response) < 4) + break; + s->commentlen = toint(GET_32BIT(s->p)); + s->p += 4; + if (s->commentlen < 0 || + toint(s->responselen - (s->p-s->response)) < + s->commentlen) + break; + s->commentp = (char *)s->p; + s->p += s->commentlen; + ok = TRUE; + } while (0); + if (!ok) { + logevent("Pageant key list packet was truncated"); + break; + } + } + if (s->publickey_blob) { + if (!memcmp(pkblob, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", s->keyi); + s->tried_publickey = 1; + } else + /* Skip non-configured key */ + continue; + } + logeventf(ssh, "Trying Pageant key #%d", s->keyi); + send_packet(ssh, SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, s->key.modulus, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + logevent("Key refused"); + continue; + } + logevent("Received RSA challenge"); + if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) { + bombout(("Server's RSA challenge was badly formatted")); + crStop(0); + } + + { + char *agentreq, *q, *ret; + void *vret; + int len, retlen; + len = 1 + 4; /* message type, bit count */ + len += ssh1_bignum_length(s->key.exponent); + len += ssh1_bignum_length(s->key.modulus); + len += ssh1_bignum_length(s->challenge); + len += 16; /* session id */ + len += 4; /* response format */ + agentreq = snewn(4 + len, char); + PUT_32BIT(agentreq, len); + q = agentreq + 4; + *q++ = SSH1_AGENTC_RSA_CHALLENGE; + PUT_32BIT(q, bignum_bitcount(s->key.modulus)); + q += 4; + q += ssh1_write_bignum(q, s->key.exponent); + q += ssh1_write_bignum(q, s->key.modulus); + q += ssh1_write_bignum(q, s->challenge); + memcpy(q, s->session_id, 16); + q += 16; + PUT_32BIT(q, 1); /* response format */ + if (!agent_query(agentreq, len + 4, &vret, &retlen, + ssh_agent_callback, ssh)) { + sfree(agentreq); + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server" + " while waiting for agent" + " response")); + crStop(0); + } + } while (pktin || inlen > 0); + vret = ssh->agent_response; + retlen = ssh->agent_response_len; + } else + sfree(agentreq); + ret = vret; + if (ret) { + if (ret[4] == SSH1_AGENT_RSA_RESPONSE) { + logevent("Sending Pageant's response"); + send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, ret + 5, 16, + PKT_END); + sfree(ret); + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_SUCCESS) { + logevent + ("Pageant's response accepted"); + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticated using" + " RSA key \""); + c_write(ssh, s->commentp, + s->commentlen); + c_write_str(ssh, "\" from agent\r\n"); + } + s->authed = TRUE; + } else + logevent + ("Pageant's response not accepted"); + } else { + logevent + ("Pageant failed to answer challenge"); + sfree(ret); + } + } else { + logevent("No reply received from Pageant"); + } + } + freebn(s->key.exponent); + freebn(s->key.modulus); + freebn(s->challenge); + if (s->authed) + break; + } + sfree(s->response); + if (s->publickey_blob && !s->tried_publickey) + logevent("Configured key file not in Pageant"); + } else { + logevent("Failed to get reply from Pageant"); + } + if (s->authed) + break; + } + if (s->publickey_blob && !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + int got_passphrase; /* need not be kept over crReturn */ + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Trying public key authentication.\r\n"); + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + logeventf(ssh, "Trying public key \"%s\"", + filename_to_str(s->keyfile)); + s->tried_publickey = 1; + got_passphrase = FALSE; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->publickey_encrypted) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "No passphrase required.\r\n"); + passphrase = NULL; + } else { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), FALSE); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + 0, TRUE); + crStop(0); + } + passphrase = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } + /* + * Try decrypting key with passphrase. + */ + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + ret = loadrsakey(s->keyfile, &s->key, passphrase, + &error); + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (ret == 1) { + /* Correct passphrase. */ + got_passphrase = TRUE; + } else if (ret == 0) { + c_write_str(ssh, "Couldn't load private key from "); + c_write_str(ssh, filename_to_str(s->keyfile)); + c_write_str(ssh, " ("); + c_write_str(ssh, error); + c_write_str(ssh, ").\r\n"); + got_passphrase = FALSE; + break; /* go and try something else */ + } else if (ret == -1) { + c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ + got_passphrase = FALSE; + /* and try again */ + } else { + assert(0 && "unexpected return from loadrsakey()"); + got_passphrase = FALSE; /* placate optimisers */ + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + send_packet(ssh, SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, s->key.modulus, PKT_END); + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused our public key.\r\n"); + continue; /* go and try something else */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + bombout(("Bizarre response to offer of public key")); + crStop(0); + } + + { + int i; + unsigned char buffer[32]; + Bignum challenge, response; + + if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) { + bombout(("Server's RSA challenge was badly formatted")); + crStop(0); + } + response = rsadecrypt(challenge, &s->key); + freebn(s->key.private_exponent);/* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31 - i); + } + + MD5Init(&md5c); + MD5Update(&md5c, buffer, 32); + MD5Update(&md5c, s->session_id, 16); + MD5Final(buffer, &md5c); + + send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, buffer, 16, PKT_END); + + freebn(challenge); + freebn(response); + } + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Failed to authenticate with" + " our public key.\r\n"); + continue; /* go and try something else */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Bizarre response to RSA authentication response")); + crStop(0); + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(ssh->frontend); + + if (conf_get_int(ssh->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + logevent("Requested TIS authentication"); + send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) { + logevent("TIS authentication declined"); + if (flags & FLAG_INTERACTIVE) + c_write_str(ssh, "TIS authentication refused.\r\n"); + s->tis_auth_refused = 1; + continue; + } else { + char *challenge; + int challengelen; + char *instr_suf, *prompt; + + ssh_pkt_getstring(pktin, &challenge, &challengelen); + if (!challenge) { + bombout(("TIS challenge packet was badly formed")); + crStop(0); + } + logevent("Received TIS challenge"); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH TIS authentication"); + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using TIS authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE); + sfree(instr_suf); + } + } + if (conf_get_int(ssh->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; + logevent("Requested CryptoCard authentication"); + send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) { + logevent("CryptoCard authentication declined"); + c_write_str(ssh, "CryptoCard authentication refused.\r\n"); + s->ccard_auth_refused = 1; + continue; + } else { + char *challenge; + int challengelen; + char *instr_suf, *prompt; + + ssh_pkt_getstring(pktin, &challenge, &challengelen); + if (!challenge) { + bombout(("CryptoCard challenge packet was badly formed")); + crStop(0); + } + logevent("Received CryptoCard challenge"); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + s->cur_prompt->name_reqd = FALSE; + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using CryptoCard authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE); + sfree(instr_suf); + } + } + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { + bombout(("No supported authentication methods available")); + crStop(0); + } + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + ssh->username, ssh->savedhost), + FALSE); + } + + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ + { + int ret; /* need not be kept over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); + crStop(0); + } + } + + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + /* + * Defence against traffic analysis: we send a + * whole bunch of packets containing strings of + * different lengths. One of these strings is the + * password, in a SSH1_CMSG_AUTH_PASSWORD packet. + * The others are all random data in + * SSH1_MSG_IGNORE packets. This way a passive + * listener can't tell which is the password, and + * hence can't deduce the password length. + * + * Anybody with a password length greater than 16 + * bytes is going to have enough entropy in their + * password that a listener won't find it _that_ + * much help to know how long it is. So what we'll + * do is: + * + * - if password length < 16, we send 15 packets + * containing string lengths 1 through 15 + * + * - otherwise, we let N be the nearest multiple + * of 8 below the password length, and send 8 + * packets containing string lengths N through + * N+7. This won't obscure the order of + * magnitude of the password length, but it will + * introduce a bit of extra uncertainty. + * + * A few servers can't deal with SSH1_MSG_IGNORE, at + * least in this context. For these servers, we need + * an alternative defence. We make use of the fact + * that the password is interpreted as a C string: + * so we can append a NUL, then some random data. + * + * A few servers can deal with neither SSH1_MSG_IGNORE + * here _nor_ a padded password string. + * For these servers we are left with no defences + * against password length sniffing. + */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. + */ + int bottom, top, pwlen, i; + char *randomstr; + + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + randomstr = snewn(top + 1, char); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + defer_packet(ssh, s->pwpkt_type, + PKT_STR,s->cur_prompt->prompts[0]->result, + PKT_END); + } else { + for (j = 0; j < i; j++) { + do { + randomstr[j] = random_byte(); + } while (randomstr[j] == '\0'); + } + randomstr[i] = '\0'; + defer_packet(ssh, SSH1_MSG_IGNORE, + PKT_STR, randomstr, PKT_END); + } + } + logevent("Sending password with camouflage packets"); + ssh_pkt_defersend(ssh); + sfree(randomstr); + } + else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + char string[64]; + char *ss; + int len; + + len = strlen(s->cur_prompt->prompts[0]->result); + if (len < sizeof(string)) { + ss = string; + strcpy(string, s->cur_prompt->prompts[0]->result); + len++; /* cover the zero byte */ + while (len < sizeof(string)) { + string[len++] = (char) random_byte(); + } + } else { + ss = s->cur_prompt->prompts[0]->result; + } + logevent("Sending length-padded password"); + send_packet(ssh, s->pwpkt_type, + PKT_INT, len, PKT_DATA, ss, len, + PKT_END); + } else { + /* + * The server is believed unable to cope with + * any of our password camouflage methods. + */ + int len; + len = strlen(s->cur_prompt->prompts[0]->result); + logevent("Sending unpadded password"); + send_packet(ssh, s->pwpkt_type, + PKT_INT, len, + PKT_DATA, s->cur_prompt->prompts[0]->result, len, + PKT_END); + } + } else { + send_packet(ssh, s->pwpkt_type, + PKT_STR, s->cur_prompt->prompts[0]->result, + PKT_END); + } + logevent("Sent password"); + free_prompts(s->cur_prompt); + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Access denied\r\n"); + logevent("Authentication refused"); + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Strange packet received, type %d", pktin->type)); + crStop(0); + } + } + + /* Clear up */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + + logevent("Authentication successful"); + + crFinish(1); +} + +static void ssh_channel_try_eof(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0) + return; /* can't send EOF: pending outgoing data */ + + c->pending_eof = FALSE; /* we're about to send it */ + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } else { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_EOF; + ssh2_channel_check_close(c); + } +} + +Conf *sshfwd_get_conf(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + return ssh->conf; +} + +void sshfwd_write_eof(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (c->closes & CLOSES_SENT_EOF) + return; + + c->pending_eof = TRUE; + ssh_channel_try_eof(c); +} + +void sshfwd_unclean_close(struct ssh_channel *c, const char *err) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + switch (c->type) { + case CHAN_X11: + x11_close(c->u.x11.xconn); + logeventf(ssh, "Forwarded X11 connection terminated due to local " + "error: %s", err); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + pfd_close(c->u.pfd.pf); + logeventf(ssh, "Forwarded port closed due to local error: %s", err); + break; + } + c->type = CHAN_ZOMBIE; + c->pending_eof = FALSE; /* this will confuse a zombie channel */ + + ssh2_channel_check_close(c); +} + +int sshfwd_write(struct ssh_channel *c, char *buf, int len) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return 0; + + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, len, PKT_DATA, buf, len, + PKT_END); + /* + * In SSH-1 we can return 0 here - implying that forwarded + * connections are never individually throttled - because + * the only circumstance that can cause throttling will be + * the whole SSH connection backing up, in which case + * _everything_ will be throttled as a whole. + */ + return 0; + } else { + ssh2_add_channel_data(c, buf, len); + return ssh2_try_send(c); + } +} + +void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) +{ + Ssh ssh = c->ssh; + int buflimit; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (ssh->version == 1) { + buflimit = SSH1_BUFFER_LIMIT; + } else { + buflimit = c->v.v2.locmaxwin; + ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0); + } + if (c->throttling_conn && bufsize <= buflimit) { + c->throttling_conn = 0; + ssh_throttle_conn(ssh, -1); + } +} + +static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin) +{ + struct queued_handler *qh = ssh->qhead; + + assert(qh != NULL); + + assert(pktin->type == qh->msg1 || pktin->type == qh->msg2); + + if (qh->msg1 > 0) { + assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler); + ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1; + } + if (qh->msg2 > 0) { + assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler); + ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2; + } + + if (qh->next) { + ssh->qhead = qh->next; + + if (ssh->qhead->msg1 > 0) { + ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; + ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler; + } + if (ssh->qhead->msg2 > 0) { + ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; + ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler; + } + } else { + ssh->qhead = ssh->qtail = NULL; + } + + qh->handler(ssh, pktin, qh->ctx); + + sfree(qh); +} + +static void ssh_queue_handler(Ssh ssh, int msg1, int msg2, + chandler_fn_t handler, void *ctx) +{ + struct queued_handler *qh; + + qh = snew(struct queued_handler); + qh->msg1 = msg1; + qh->msg2 = msg2; + qh->handler = handler; + qh->ctx = ctx; + qh->next = NULL; + + if (ssh->qtail == NULL) { + ssh->qhead = qh; + + if (qh->msg1 > 0) { + ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; + ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler; + } + if (qh->msg2 > 0) { + ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; + ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler; + } + } else { + ssh->qtail->next = qh; + } + ssh->qtail = qh; +} + +static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx) +{ + struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx; + + if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS : + SSH2_MSG_REQUEST_SUCCESS)) { + logeventf(ssh, "Remote port forwarding from %s enabled", + pf->sportdesc); + } else { + logeventf(ssh, "Remote port forwarding from %s refused", + pf->sportdesc); + + rpf = del234(ssh->rportfwds, pf); + assert(rpf == pf); + pf->pfrec->remote = NULL; + free_rportfwd(pf); + } +} + +int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, + void *share_ctx) +{ + struct ssh_rportfwd *pf = snew(struct ssh_rportfwd); + pf->dhost = NULL; + pf->dport = 0; + pf->share_ctx = share_ctx; + pf->shost = dupstr(shost); + pf->sport = sport; + pf->sportdesc = NULL; + if (!ssh->rportfwds) { + assert(ssh->version == 2); + ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); + } + if (add234(ssh->rportfwds, pf) != pf) { + sfree(pf->shost); + sfree(pf); + return FALSE; + } + return TRUE; +} + +static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin, + void *ctx) +{ + share_got_pkt_from_server(ctx, pktin->type, + pktin->body, pktin->length); +} + +void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx) +{ + ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE, + ssh_sharing_global_request_response, share_ctx); +} + +static void ssh_setup_portfwd(Ssh ssh, Conf *conf) +{ + struct ssh_portfwd *epf; + int i; + char *key, *val; + + if (!ssh->portfwds) { + ssh->portfwds = newtree234(ssh_portcmp); + } else { + /* + * Go through the existing port forwardings and tag them + * with status==DESTROY. Any that we want to keep will be + * re-enabled (status==KEEP) as we go through the + * configuration and find out which bits are the same as + * they were before. + */ + struct ssh_portfwd *epf; + int i; + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + epf->status = DESTROY; + } + + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *kp, *kp2, *vp, *vp2; + char address_family, type; + int sport,dport,sserv,dserv; + char *sports, *dports, *saddr, *host; + + kp = key; + + address_family = 'A'; + type = 'L'; + if (*kp == 'A' || *kp == '4' || *kp == '6') + address_family = *kp++; + if (*kp == 'L' || *kp == 'R') + type = *kp++; + + if ((kp2 = host_strchr(kp, ':')) != NULL) { + /* + * There's a colon in the middle of the source port + * string, which means that the part before it is + * actually a source address. + */ + char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp); + saddr = host_strduptrim(saddr_tmp); + sfree(saddr_tmp); + sports = kp2+1; + } else { + saddr = NULL; + sports = kp; + } + sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + sport = net_service_lookup(sports); + if (!sport) { + logeventf(ssh, "Service lookup failed for source" + " port \"%s\"", sports); + } + } + + if (type == 'L' && !strcmp(val, "D")) { + /* dynamic forwarding */ + host = NULL; + dports = NULL; + dport = -1; + dserv = 0; + type = 'D'; + } else { + /* ordinary forwarding */ + vp = val; + vp2 = vp + host_strcspn(vp, ":"); + host = dupprintf("%.*s", (int)(vp2 - vp), vp); + if (*vp2) + vp2++; + dports = vp2; + dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + dport = net_service_lookup(dports); + if (!dport) { + logeventf(ssh, "Service lookup failed for destination" + " port \"%s\"", dports); + } + } + } + + if (sport && dport) { + /* Set up a description of the source port. */ + struct ssh_portfwd *pfrec, *epfrec; + + pfrec = snew(struct ssh_portfwd); + pfrec->type = type; + pfrec->saddr = saddr; + pfrec->sserv = sserv ? dupstr(sports) : NULL; + pfrec->sport = sport; + pfrec->daddr = host; + pfrec->dserv = dserv ? dupstr(dports) : NULL; + pfrec->dport = dport; + pfrec->local = NULL; + pfrec->remote = NULL; + pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : + address_family == '6' ? ADDRTYPE_IPV6 : + ADDRTYPE_UNSPEC); + + epfrec = add234(ssh->portfwds, pfrec); + if (epfrec != pfrec) { + if (epfrec->status == DESTROY) { + /* + * We already have a port forwarding up and running + * with precisely these parameters. Hence, no need + * to do anything; simply re-tag the existing one + * as KEEP. + */ + epfrec->status = KEEP; + } + /* + * Anything else indicates that there was a duplicate + * in our input, which we'll silently ignore. + */ + free_portfwd(pfrec); + } else { + pfrec->status = CREATE; + } + } else { + sfree(saddr); + sfree(host); + } + } + + /* + * Now go through and destroy any port forwardings which were + * not re-enabled. + */ + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + if (epf->status == DESTROY) { + char *message; + + message = dupprintf("%s port forwarding from %s%s%d", + epf->type == 'L' ? "local" : + epf->type == 'R' ? "remote" : "dynamic", + epf->saddr ? epf->saddr : "", + epf->saddr ? ":" : "", + epf->sport); + + if (epf->type != 'D') { + char *msg2 = dupprintf("%s to %s:%d", message, + epf->daddr, epf->dport); + sfree(message); + message = msg2; + } + + logeventf(ssh, "Cancelling %s", message); + sfree(message); + + /* epf->remote or epf->local may be NULL if setting up a + * forwarding failed. */ + if (epf->remote) { + struct ssh_rportfwd *rpf = epf->remote; + struct Packet *pktout; + + /* + * Cancel the port forwarding at the server + * end. + */ + if (ssh->version == 1) { + /* + * We cannot cancel listening ports on the + * server side in SSH-1! There's no message + * to support it. Instead, we simply remove + * the rportfwd record from the local end + * so that any connections the server tries + * to make on it are rejected. + */ + } else { + pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring(pktout, "cancel-tcpip-forward"); + ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */ + if (epf->saddr) { + ssh2_pkt_addstring(pktout, epf->saddr); + } else if (conf_get_int(conf, CONF_rport_acceptall)) { + /* XXX: rport_acceptall may not represent + * what was used to open the original connection, + * since it's reconfigurable. */ + ssh2_pkt_addstring(pktout, ""); + } else { + ssh2_pkt_addstring(pktout, "localhost"); + } + ssh2_pkt_adduint32(pktout, epf->sport); + ssh2_pkt_send(ssh, pktout); + } + + del234(ssh->rportfwds, rpf); + free_rportfwd(rpf); + } else if (epf->local) { + pfl_terminate(epf->local); + } + + delpos234(ssh->portfwds, i); + free_portfwd(epf); + i--; /* so we don't skip one in the list */ + } + + /* + * And finally, set up any new port forwardings (status==CREATE). + */ + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + if (epf->status == CREATE) { + char *sportdesc, *dportdesc; + sportdesc = dupprintf("%s%s%s%s%d%s", + epf->saddr ? epf->saddr : "", + epf->saddr ? ":" : "", + epf->sserv ? epf->sserv : "", + epf->sserv ? "(" : "", + epf->sport, + epf->sserv ? ")" : ""); + if (epf->type == 'D') { + dportdesc = NULL; + } else { + dportdesc = dupprintf("%s:%s%s%d%s", + epf->daddr, + epf->dserv ? epf->dserv : "", + epf->dserv ? "(" : "", + epf->dport, + epf->dserv ? ")" : ""); + } + + if (epf->type == 'L') { + char *err = pfl_listen(epf->daddr, epf->dport, + epf->saddr, epf->sport, + ssh, conf, &epf->local, + epf->addressfamily); + + logeventf(ssh, "Local %sport %s forwarding to %s%s%s", + epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, dportdesc, + err ? " failed: " : "", err ? err : ""); + if (err) + sfree(err); + } else if (epf->type == 'D') { + char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport, + ssh, conf, &epf->local, + epf->addressfamily); + + logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s", + epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, + err ? " failed: " : "", err ? err : ""); + + if (err) + sfree(err); + } else { + struct ssh_rportfwd *pf; + + /* + * Ensure the remote port forwardings tree exists. + */ + if (!ssh->rportfwds) { + if (ssh->version == 1) + ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); + else + ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); + } + + pf = snew(struct ssh_rportfwd); + pf->share_ctx = NULL; + pf->dhost = dupstr(epf->daddr); + pf->dport = epf->dport; + if (epf->saddr) { + pf->shost = dupstr(epf->saddr); + } else if (conf_get_int(conf, CONF_rport_acceptall)) { + pf->shost = dupstr(""); + } else { + pf->shost = dupstr("localhost"); + } + pf->sport = epf->sport; + if (add234(ssh->rportfwds, pf) != pf) { + logeventf(ssh, "Duplicate remote port forwarding to %s:%d", + epf->daddr, epf->dport); + sfree(pf); + } else { + logeventf(ssh, "Requesting remote port %s" + " forward to %s", sportdesc, dportdesc); + + pf->sportdesc = sportdesc; + sportdesc = NULL; + epf->remote = pf; + pf->pfrec = epf; + + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST, + PKT_INT, epf->sport, + PKT_STR, epf->daddr, + PKT_INT, epf->dport, + PKT_END); + ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, + SSH1_SMSG_FAILURE, + ssh_rportfwd_succfail, pf); + } else { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring(pktout, "tcpip-forward"); + ssh2_pkt_addbool(pktout, 1);/* want reply */ + ssh2_pkt_addstring(pktout, pf->shost); + ssh2_pkt_adduint32(pktout, pf->sport); + ssh2_pkt_send(ssh, pktout); + + ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, + SSH2_MSG_REQUEST_FAILURE, + ssh_rportfwd_succfail, pf); + } + } + } + sfree(sportdesc); + sfree(dportdesc); + } +} + +static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin) +{ + char *string; + int stringlen, bufsize; + + ssh_pkt_getstring(pktin, &string, &stringlen); + if (string == NULL) { + bombout(("Incoming terminal data packet was badly formed")); + return; + } + + bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA, + string, stringlen); + if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { + ssh->v1_stdout_throttling = 1; + ssh_throttle_conn(ssh, +1); + } +} + +static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to our + * X-Server. Give them back a local channel number. */ + struct ssh_channel *c; + int remoteid = ssh_pkt_getuint32(pktin); + + logevent("Received X11 connect request"); + /* Refuse if X11 forwarding is disabled. */ + if (!ssh->X11_fwd_enabled) { + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + logevent("Rejected X11 connect request"); + } else { + c = snew(struct ssh_channel); + c->ssh = ssh; + + c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1); + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = 0; + c->type = CHAN_X11; /* identify channel type */ + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Opened X11 forward channel"); + } +} + +static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to our + * agent. Give them back a local channel number. */ + struct ssh_channel *c; + int remoteid = ssh_pkt_getuint32(pktin); + + /* Refuse if agent forwarding is disabled. */ + if (!ssh->agentfwd_enabled) { + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + c = snew(struct ssh_channel); + c->ssh = ssh; + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = 0; + c->type = CHAN_AGENT; /* identify channel type */ + c->u.a.lensofar = 0; + c->u.a.message = NULL; + c->u.a.outstanding_requests = 0; + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, c->localid, + PKT_END); + } +} + +static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to a + * forwarded port. Give them back a local channel number. */ + struct ssh_rportfwd pf, *pfp; + int remoteid; + int hostsize, port; + char *host; + char *err; + + remoteid = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &host, &hostsize); + port = ssh_pkt_getuint32(pktin); + + pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host)); + pf.dport = port; + pfp = find234(ssh->rportfwds, &pf, NULL); + + if (pfp == NULL) { + logeventf(ssh, "Rejected remote port open request for %s:%d", + pf.dhost, port); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + struct ssh_channel *c = snew(struct ssh_channel); + c->ssh = ssh; + + logeventf(ssh, "Received remote port open request for %s:%d", + pf.dhost, port); + err = pfd_connect(&c->u.pfd.pf, pf.dhost, port, + c, ssh->conf, pfp->pfrec->addressfamily); + if (err != NULL) { + logeventf(ssh, "Port open failed: %s", err); + sfree(err); + sfree(c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = 0; + c->type = CHAN_SOCKDATA; /* identify channel type */ + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Forwarded port opened successfully"); + } + } + + sfree(pf.dhost); +} + +static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) +{ + unsigned int remoteid = ssh_pkt_getuint32(pktin); + unsigned int localid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &remoteid, ssh_channelfind); + if (c && c->type == CHAN_SOCKDATA_DORMANT) { + c->remoteid = localid; + c->halfopen = FALSE; + c->type = CHAN_SOCKDATA; + c->throttling_conn = 0; + pfd_confirm(c->u.pfd.pf); + } + + if (c && c->pending_eof) { + /* + * We have a pending close on this channel, + * which we decided on before the server acked + * the channel open. So now we know the + * remoteid, we can close it again. + */ + ssh_channel_try_eof(c); + } +} + +static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +{ + unsigned int remoteid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &remoteid, ssh_channelfind); + if (c && c->type == CHAN_SOCKDATA_DORMANT) { + logevent("Forwarded connection refused by server"); + pfd_close(c->u.pfd.pf); + del234(ssh->channels, c); + sfree(c); + } +} + +static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) +{ + /* Remote side closes a channel. */ + unsigned i = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + c = find234(ssh->channels, &i, ssh_channelfind); + if (c && !c->halfopen) { + + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE && + !(c->closes & CLOSES_RCVD_EOF)) { + /* + * Received CHANNEL_CLOSE, which we translate into + * outgoing EOF. + */ + int send_close = FALSE; + + c->closes |= CLOSES_RCVD_EOF; + + switch (c->type) { + case CHAN_X11: + if (c->u.x11.xconn) + x11_send_eof(c->u.x11.xconn); + else + send_close = TRUE; + break; + case CHAN_SOCKDATA: + if (c->u.pfd.pf) + pfd_send_eof(c->u.pfd.pf); + else + send_close = TRUE; + break; + case CHAN_AGENT: + send_close = TRUE; + break; + } + + if (send_close && !(c->closes & CLOSES_SENT_EOF)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } + } + + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION && + !(c->closes & CLOSES_RCVD_CLOSE)) { + + if (!(c->closes & CLOSES_SENT_EOF)) { + bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d" + " for which we never sent CHANNEL_CLOSE\n", i)); + } + + c->closes |= CLOSES_RCVD_CLOSE; + } + + if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) && + !(c->closes & CLOSES_SENT_CLOSE)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, + PKT_INT, c->remoteid, PKT_END); + c->closes |= CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) + ssh_channel_destroy(c); + } else { + bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n", + pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" : + "_CONFIRMATION", c ? "half-open" : "nonexistent", + i)); + } +} + +static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) +{ + /* Data sent down one of our channels. */ + int i = ssh_pkt_getuint32(pktin); + char *p; + int len; + struct ssh_channel *c; + + ssh_pkt_getstring(pktin, &p, &len); + + c = find234(ssh->channels, &i, ssh_channelfind); + if (c) { + int bufsize = 0; + switch (c->type) { + case CHAN_X11: + bufsize = x11_send(c->u.x11.xconn, p, len); + break; + case CHAN_SOCKDATA: + bufsize = pfd_send(c->u.pfd.pf, p, len); + break; + case CHAN_AGENT: + /* Data for an agent message. Buffer it. */ + while (len > 0) { + if (c->u.a.lensofar < 4) { + unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len); + memcpy(c->u.a.msglen + c->u.a.lensofar, p, + l); + p += l; + len -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == 4) { + c->u.a.totallen = + 4 + GET_32BIT(c->u.a.msglen); + c->u.a.message = snewn(c->u.a.totallen, + unsigned char); + memcpy(c->u.a.message, c->u.a.msglen, 4); + } + if (c->u.a.lensofar >= 4 && len > 0) { + unsigned int l = + min(c->u.a.totallen - c->u.a.lensofar, + (unsigned)len); + memcpy(c->u.a.message + c->u.a.lensofar, p, + l); + p += l; + len -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == c->u.a.totallen) { + void *reply; + int replylen; + c->u.a.outstanding_requests++; + if (agent_query(c->u.a.message, + c->u.a.totallen, + &reply, &replylen, + ssh_agentf_callback, c)) + ssh_agentf_callback(c, reply, replylen); + sfree(c->u.a.message); + c->u.a.lensofar = 0; + } + } + bufsize = 0; /* agent channels never back up */ + break; + } + if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { + c->throttling_conn = 1; + ssh_throttle_conn(ssh, +1); + } + } +} + +static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin) +{ + ssh->exitcode = ssh_pkt_getuint32(pktin); + logeventf(ssh, "Server sent command exit status %d", ssh->exitcode); + send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); + /* + * In case `helpful' firewalls or proxies tack + * extra human-readable text on the end of the + * session which we might mistake for another + * encrypted packet, we close the session once + * we've sent EXIT_CONFIRMATION. + */ + ssh_disconnect(ssh, NULL, NULL, 0, TRUE); +} + +/* Helper function to deal with sending tty modes for REQUEST_PTY */ +static void ssh1_send_ttymode(void *data, char *mode, char *val) +{ + struct Packet *pktout = (struct Packet *)data; + int i = 0; + unsigned int arg = 0; + while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++; + if (i == lenof(ssh_ttymodes)) return; + switch (ssh_ttymodes[i].type) { + case TTY_OP_CHAR: + arg = ssh_tty_parse_specchar(val); + break; + case TTY_OP_BOOL: + arg = ssh_tty_parse_boolean(val); + break; + } + ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); + ssh2_pkt_addbyte(pktout, arg); +} + +int ssh_agent_forwarding_permitted(Ssh ssh) +{ + return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists(); +} + +static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + crBegin(ssh->do_ssh1_connection_crstate); + + ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] = + ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] = + ssh1_smsg_stdout_stderr_data; + + ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] = + ssh1_msg_channel_open_confirmation; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] = + ssh1_msg_channel_open_failure; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] = + ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] = + ssh1_msg_channel_close; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data; + ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status; + + if (ssh_agent_forwarding_permitted(ssh)) { + logevent("Requesting agent forwarding"); + send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END); + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + logevent("Agent forwarding refused"); + } else { + logevent("Agent forwarding enabled"); + ssh->agentfwd_enabled = TRUE; + ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open; + } + } + + if (conf_get_int(ssh->conf, CONF_x11_forward)) { + ssh->x11disp = + x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), + ssh->conf); + if (!ssh->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + logevent("X11 forwarding not enabled: unable to" + " initialise X display"); + } else { + ssh->x11auth = x11_invent_fake_auth + (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); + ssh->x11auth->disp = ssh->x11disp; + + logevent("Requesting X11 forwarding"); + if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11auth->protoname, + PKT_STR, ssh->x11auth->datastring, + PKT_INT, ssh->x11disp->screennum, + PKT_END); + } else { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11auth->protoname, + PKT_STR, ssh->x11auth->datastring, + PKT_END); + } + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; + } + } + } + + ssh_setup_portfwd(ssh, ssh->conf); + ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; + + if (!conf_get_int(ssh->conf, CONF_nopty)) { + struct Packet *pkt; + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); + /* Send the pty request. */ + pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY); + ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype)); + ssh_pkt_adduint32(pkt, ssh->term_height); + ssh_pkt_adduint32(pkt, ssh->term_width); + ssh_pkt_adduint32(pkt, 0); /* width in pixels */ + ssh_pkt_adduint32(pkt, 0); /* height in pixels */ + parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt); + ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED); + ssh_pkt_adduint32(pkt, ssh->ispeed); + ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED); + ssh_pkt_adduint32(pkt, ssh->ospeed); + ssh_pkt_addbyte(pkt, SSH_TTY_OP_END); + s_wrpkt(ssh, pkt); + ssh->state = SSH_STATE_INTERMED; + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused to allocate pty\r\n"); + ssh->editing = ssh->echoing = 1; + } else { + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + ssh->got_pty = TRUE; + } + } else { + ssh->editing = ssh->echoing = 1; + } + + if (conf_get_int(ssh->conf, CONF_compression)) { + send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END); + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused to compress\r\n"); + } + logevent("Started compression"); + ssh->v1_compressing = TRUE; + ssh->cs_comp_ctx = zlib_compress_init(); + logevent("Initialised zlib (RFC1950) compression"); + ssh->sc_comp_ctx = zlib_decompress_init(); + logevent("Initialised zlib (RFC1950) decompression"); + } + + /* + * Start the shell or command. + * + * Special case: if the first-choice command is an SSH-2 + * subsystem (hence not usable here) and the second choice + * exists, we fall straight back to that. + */ + { + char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd); + + if (conf_get_int(ssh->conf, CONF_ssh_subsys) && + conf_get_str(ssh->conf, CONF_remote_cmd2)) { + cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); + ssh->fallback_cmd = TRUE; + } + if (*cmd) + send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END); + else + send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END); + logevent("Started session"); + } + + ssh->state = SSH_STATE_SESSION; + if (ssh->size_needed) + ssh_size(ssh, ssh->term_width, ssh->term_height); + if (ssh->eof_needed) + ssh_special(ssh, TS_EOF); + + if (ssh->ldisc) + ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + ssh->send_ok = 1; + ssh->channels = newtree234(ssh_channelcmp); + while (1) { + + /* + * By this point, most incoming packets are already being + * handled by the dispatch table, and we need only pay + * attention to the unusual ones. + */ + + crReturnV; + if (pktin) { + if (pktin->type == SSH1_SMSG_SUCCESS) { + /* may be from EXEC_SHELL on some servers */ + } else if (pktin->type == SSH1_SMSG_FAILURE) { + /* may be from EXEC_SHELL on some servers + * if no pty is available or in other odd cases. Ignore */ + } else { + bombout(("Strange packet received: type %d", pktin->type)); + crStopV; + } + } else { + while (inlen > 0) { + int len = min(inlen, 512); + send_packet(ssh, SSH1_CMSG_STDIN_DATA, + PKT_INT, len, PKT_DATA, in, len, + PKT_END); + in += len; + inlen -= len; + } + } + } + + crFinishV; +} + +/* + * Handle the top-level SSH-2 protocol. + */ +static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) +{ + char *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); +} + +static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + bombout(("Server sent disconnect message:\n\"%.*s\"", + msglen, NULLTOEMPTY(msg))); +} + +static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) +{ + /* Do nothing, because we're ignoring it! Duhh. */ +} + +static void ssh1_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages are handled by the coroutines. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = NULL; + + /* + * These special message types we install handlers for. + */ + ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect; + ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore; + ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; +} + +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in=(unsigned char*)vin; + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin && ssh->packet_dispatch[pktin->type]) { + ssh->packet_dispatch[pktin->type](ssh, pktin); + return; + } + + if (!ssh->protocol_initial_phase_done) { + if (do_ssh1_login(ssh, in, inlen, pktin)) + ssh->protocol_initial_phase_done = TRUE; + else + return; + } + + do_ssh1_connection(ssh, in, inlen, pktin); +} + +/* + * Utility routine for decoding comma-separated strings in KEXINIT. + */ +static int in_commasep_string(char *needle, char *haystack, int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return 0; + needlen = strlen(needle); + while (1) { + /* + * Is it at the start of the string? + */ + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return 1; + /* + * If not, search for the next comma and resume after that. + * If no comma found, terminate. + */ + while (haylen > 0 && *haystack != ',') + haylen--, haystack++; + if (haylen == 0) + return 0; + haylen--, haystack++; /* skip over comma itself */ + } +} + +/* + * Similar routine for checking whether we have the first string in a list. + */ +static int first_in_commasep_string(char *needle, char *haystack, int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return 0; + needlen = strlen(needle); + /* + * Is it at the start of the string? + */ + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return 1; + return 0; +} + + +/* + * SSH-2 key creation method. + * (Currently assumes 2 lots of any hash are sufficient to generate + * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.) + */ +#define SSH2_MKKEY_ITERS (2) +static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr, + unsigned char *keyspace) +{ + const struct ssh_hash *h = ssh->kex->hash; + void *s; + /* First hlen bytes. */ + s = h->init(); + if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) + hash_mpint(h, s, K); + h->bytes(s, H, h->hlen); + h->bytes(s, &chr, 1); + h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len); + h->final(s, keyspace); + /* Next hlen bytes. */ + s = h->init(); + if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) + hash_mpint(h, s, K); + h->bytes(s, H, h->hlen); + h->bytes(s, keyspace, h->hlen); + h->final(s, keyspace + h->hlen); +} + +/* + * Handle the SSH-2 transport layer. + */ +static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + struct do_ssh2_transport_state { + int crLine; + int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; + Bignum p, g, e, f, K; + void *our_kexinit; + int our_kexinitlen; + int kex_init_value, kex_reply_value; + const struct ssh_mac **maclist; + int nmacs; + const struct ssh2_cipher *cscipher_tobe; + const struct ssh2_cipher *sccipher_tobe; + const struct ssh_mac *csmac_tobe; + const struct ssh_mac *scmac_tobe; + const struct ssh_compress *cscomp_tobe; + const struct ssh_compress *sccomp_tobe; + char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; + int hostkeylen, siglen, rsakeylen; + void *hkey; /* actual host key */ + void *rsakey; /* for RSA kex */ + unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; + int n_preferred_kex; + const struct ssh_kexes *preferred_kex[KEX_MAX]; + int n_preferred_ciphers; + const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; + const struct ssh_compress *preferred_comp; + int userauth_succeeded; /* for delayed compression */ + int pending_compression; + int got_session_id, activated_authconn; + struct Packet *pktout; + int dlgret; + int guessok; + int ignorepkt; + }; + crState(do_ssh2_transport_state); + + assert(!ssh->bare_connection); + + crBeginState; + + s->cscipher_tobe = s->sccipher_tobe = NULL; + s->csmac_tobe = s->scmac_tobe = NULL; + s->cscomp_tobe = s->sccomp_tobe = NULL; + + s->got_session_id = s->activated_authconn = FALSE; + s->userauth_succeeded = FALSE; + s->pending_compression = FALSE; + + /* + * Be prepared to work around the buggy MAC problem. + */ + if (ssh->remote_bugs & BUG_SSH2_HMAC) + s->maclist = buggymacs, s->nmacs = lenof(buggymacs); + else + s->maclist = macs, s->nmacs = lenof(macs); + + begin_key_exchange: + ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; + { + int i, j, k, commalist_started; + + /* + * Set up the preferred key exchange. (NULL => warn below here) + */ + s->n_preferred_kex = 0; + for (i = 0; i < KEX_MAX; i++) { + switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) { + case KEX_DHGEX: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_gex; + break; + case KEX_DHGROUP14: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group14; + break; + case KEX_DHGROUP1: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group1; + break; + case KEX_RSA: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_rsa_kex; + break; + case KEX_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < KEX_MAX - 1) { + s->preferred_kex[s->n_preferred_kex++] = NULL; + } + break; + } + } + + /* + * Set up the preferred ciphers. (NULL => warn below here) + */ + s->n_preferred_ciphers = 0; + for (i = 0; i < CIPHER_MAX; i++) { + switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) { + case CIPHER_BLOWFISH: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; + break; + case CIPHER_DES: + if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) { + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; + } + break; + case CIPHER_3DES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; + break; + case CIPHER_AES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes; + break; + case CIPHER_ARCFOUR: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; + break; + case CIPHER_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < CIPHER_MAX - 1) { + s->preferred_ciphers[s->n_preferred_ciphers++] = NULL; + } + break; + } + } + + /* + * Set up preferred compression. + */ + if (conf_get_int(ssh->conf, CONF_compression)) + s->preferred_comp = &ssh_zlib; + else + s->preferred_comp = &ssh_comp_none; + + /* + * Enable queueing of outgoing auth- or connection-layer + * packets while we are in the middle of a key exchange. + */ + ssh->queueing = TRUE; + + /* + * Flag that KEX is in progress. + */ + ssh->kex_in_progress = TRUE; + + /* + * Construct and send our key exchange packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT); + for (i = 0; i < 16; i++) + ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte()); + /* List key exchange algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_kex; i++) { + const struct ssh_kexes *k = s->preferred_kex[i]; + if (!k) continue; /* warning flag */ + for (j = 0; j < k->nkexes; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, k->list[j]->name); + commalist_started = 1; + } + } + /* List server host key algorithms. */ + if (!s->got_session_id) { + /* + * In the first key exchange, we list all the algorithms + * we're prepared to cope with. + */ + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < lenof(hostkey_algs); i++) { + ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name); + if (i < lenof(hostkey_algs) - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + } else { + /* + * In subsequent key exchanges, we list only the kex + * algorithm that was selected in the first key exchange, + * so that we keep getting the same host key and hence + * don't have to interrupt the user's session to ask for + * reverification. + */ + assert(ssh->kex); + ssh2_pkt_addstring(s->pktout, ssh->hostkey->name); + } + /* List encryption algorithms (client->server then server->client). */ + for (k = 0; k < 2; k++) { + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) continue; /* warning flag */ + for (j = 0; j < c->nciphers; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); + commalist_started = 1; + } + } + } + /* List MAC algorithms (client->server then server->client). */ + for (j = 0; j < 2; j++) { + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < s->nmacs; i++) { + ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); + if (i < s->nmacs - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + } + /* List client->server compression algorithms, + * then server->client compression algorithms. (We use the + * same set twice.) */ + for (j = 0; j < 2; j++) { + ssh2_pkt_addstring_start(s->pktout); + assert(lenof(compressions) > 1); + /* Prefer non-delayed versions */ + ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); + /* We don't even list delayed versions of algorithms until + * they're allowed to be used, to avoid a race. See the end of + * this function. */ + if (s->userauth_succeeded && s->preferred_comp->delayed_name) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, + s->preferred_comp->delayed_name); + } + for (i = 0; i < lenof(compressions); i++) { + const struct ssh_compress *c = compressions[i]; + if (c != s->preferred_comp) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->name); + if (s->userauth_succeeded && c->delayed_name) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->delayed_name); + } + } + } + } + /* List client->server languages. Empty list. */ + ssh2_pkt_addstring_start(s->pktout); + /* List server->client languages. Empty list. */ + ssh2_pkt_addstring_start(s->pktout); + /* First KEX packet does _not_ follow, because we're not that brave. */ + ssh2_pkt_addbool(s->pktout, FALSE); + /* Reserved. */ + ssh2_pkt_adduint32(s->pktout, 0); + } + + s->our_kexinitlen = s->pktout->length - 5; + s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); + memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); + + ssh2_pkt_send_noqueue(ssh, s->pktout); + + if (!pktin) + crWaitUntilV(pktin); + + /* + * Now examine the other side's KEXINIT to see what we're up + * to. + */ + { + char *str, *preferred; + int i, j, len; + + if (pktin->type != SSH2_MSG_KEXINIT) { + bombout(("expected key exchange packet from server")); + crStopV; + } + ssh->kex = NULL; + ssh->hostkey = NULL; + s->cscipher_tobe = NULL; + s->sccipher_tobe = NULL; + s->csmac_tobe = NULL; + s->scmac_tobe = NULL; + s->cscomp_tobe = NULL; + s->sccomp_tobe = NULL; + s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE; + + pktin->savedpos += 16; /* skip garbage cookie */ + ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + + preferred = NULL; + for (i = 0; i < s->n_preferred_kex; i++) { + const struct ssh_kexes *k = s->preferred_kex[i]; + if (!k) { + s->warn_kex = TRUE; + } else { + for (j = 0; j < k->nkexes; j++) { + if (!preferred) preferred = k->list[j]->name; + if (in_commasep_string(k->list[j]->name, str, len)) { + ssh->kex = k->list[j]; + break; + } + } + } + if (ssh->kex) + break; + } + if (!ssh->kex) { + bombout(("Couldn't agree a key exchange algorithm" + " (available: %.*s)", len, str)); + crStopV; + } + /* + * Note that the server's guess is considered wrong if it doesn't match + * the first algorithm in our list, even if it's still the algorithm + * we end up using. + */ + s->guessok = first_in_commasep_string(preferred, str, len); + ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < lenof(hostkey_algs); i++) { + if (in_commasep_string(hostkey_algs[i]->name, str, len)) { + ssh->hostkey = hostkey_algs[i]; + break; + } + } + if (!ssh->hostkey) { + bombout(("Couldn't agree a host key algorithm" + " (available: %.*s)", len, str)); + crStopV; + } + + s->guessok = s->guessok && + first_in_commasep_string(hostkey_algs[0]->name, str, len); + ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) { + s->warn_cscipher = TRUE; + } else { + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + s->cscipher_tobe = c->list[j]; + break; + } + } + } + if (s->cscipher_tobe) + break; + } + if (!s->cscipher_tobe) { + bombout(("Couldn't agree a client-to-server cipher" + " (available: %.*s)", len, str)); + crStopV; + } + + ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) { + s->warn_sccipher = TRUE; + } else { + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + s->sccipher_tobe = c->list[j]; + break; + } + } + } + if (s->sccipher_tobe) + break; + } + if (!s->sccipher_tobe) { + bombout(("Couldn't agree a server-to-client cipher" + " (available: %.*s)", len, str)); + crStopV; + } + + ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < s->nmacs; i++) { + if (in_commasep_string(s->maclist[i]->name, str, len)) { + s->csmac_tobe = s->maclist[i]; + break; + } + } + ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < s->nmacs; i++) { + if (in_commasep_string(s->maclist[i]->name, str, len)) { + s->scmac_tobe = s->maclist[i]; + break; + } + } + ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < lenof(compressions) + 1; i++) { + const struct ssh_compress *c = + i == 0 ? s->preferred_comp : compressions[i - 1]; + if (in_commasep_string(c->name, str, len)) { + s->cscomp_tobe = c; + break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->cscomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } + } + } + ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } + for (i = 0; i < lenof(compressions) + 1; i++) { + const struct ssh_compress *c = + i == 0 ? s->preferred_comp : compressions[i - 1]; + if (in_commasep_string(c->name, str, len)) { + s->sccomp_tobe = c; + break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->sccomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } + } + } + if (s->pending_compression) { + logevent("Server supports delayed compression; " + "will try this later"); + } + ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ + ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ + s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; + + ssh->exhash = ssh->kex->hash->init(); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c)); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s)); + hash_string(ssh->kex->hash, ssh->exhash, + s->our_kexinit, s->our_kexinitlen); + sfree(s->our_kexinit); + /* Include the type byte in the hash of server's KEXINIT */ + hash_string(ssh->kex->hash, ssh->exhash, + pktin->body - 1, pktin->length + 1); + + if (s->warn_kex) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", + ssh->kex->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at kex warning", NULL, + 0, TRUE); + crStopV; + } + } + + if (s->warn_cscipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "client-to-server cipher", + s->cscipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStopV; + } + } + + if (s->warn_sccipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "server-to-client cipher", + s->sccipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStopV; + } + } + + if (s->ignorepkt) /* first_kex_packet_follows */ + crWaitUntilV(pktin); /* Ignore packet */ + } + + if (ssh->kex->main_type == KEXTYPE_DH) { + /* + * Work out the number of bits of key we will need from the + * key exchange. We start with the maximum key length of + * either cipher... + */ + { + int csbits, scbits; + + csbits = s->cscipher_tobe->keylen; + scbits = s->sccipher_tobe->keylen; + s->nbits = (csbits > scbits ? csbits : scbits); + } + /* The keys only have hlen-bit entropy, since they're based on + * a hash. So cap the key size at hlen bits. */ + if (s->nbits > ssh->kex->hash->hlen * 8) + s->nbits = ssh->kex->hash->hlen * 8; + + /* + * If we're doing Diffie-Hellman group exchange, start by + * requesting a group. + */ + if (!ssh->kex->pdata) { + logevent("Doing Diffie-Hellman group exchange"); + ssh->pkt_kctx = SSH2_PKTCTX_DHGEX; + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + if (s->pbits < DH_MIN_SIZE) + s->pbits = DH_MIN_SIZE; + if (s->pbits > DH_MAX_SIZE) + s->pbits = DH_MAX_SIZE; + if ((ssh->remote_bugs & BUG_SSH2_OLDGEX)) { + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); + ssh2_pkt_adduint32(s->pktout, s->pbits); + } else { + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); + ssh2_pkt_adduint32(s->pktout, DH_MIN_SIZE); + ssh2_pkt_adduint32(s->pktout, s->pbits); + ssh2_pkt_adduint32(s->pktout, DH_MAX_SIZE); + } + ssh2_pkt_send_noqueue(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { + bombout(("expected key exchange group packet from server")); + crStopV; + } + s->p = ssh2_pkt_getmp(pktin); + s->g = ssh2_pkt_getmp(pktin); + if (!s->p || !s->g) { + bombout(("unable to read mp-ints from incoming group packet")); + crStopV; + } + ssh->kex_ctx = dh_setup_gex(s->p, s->g); + s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; + s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; + } else { + ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP; + ssh->kex_ctx = dh_setup_group(ssh->kex); + s->kex_init_value = SSH2_MSG_KEXDH_INIT; + s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; + logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"", + ssh->kex->groupname); + } + + logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s", + ssh->kex->hash->text_name); + /* + * Now generate and send e for Diffie-Hellman. + */ + set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ + s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); + s->pktout = ssh2_pkt_init(s->kex_init_value); + ssh2_pkt_addmp(s->pktout, s->e); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ + crWaitUntilV(pktin); + if (pktin->type != s->kex_reply_value) { + bombout(("expected key exchange reply packet from server")); + crStopV; + } + set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (!s->hostkeydata) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + s->f = ssh2_pkt_getmp(pktin); + if (!s->f) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + if (!s->sigdata) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } + + { + const char *err = dh_validate_f(ssh->kex_ctx, s->f); + if (err) { + bombout(("key exchange reply failed validation: %s", err)); + crStopV; + } + } + s->K = dh_find_K(ssh->kex_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(ssh->frontend, BUSY_NOT); + + hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); + if (!ssh->kex->pdata) { + if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) + hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE); + hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); + if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) + hash_uint32(ssh->kex->hash, ssh->exhash, DH_MAX_SIZE); + hash_mpint(ssh->kex->hash, ssh->exhash, s->p); + hash_mpint(ssh->kex->hash, ssh->exhash, s->g); + } + hash_mpint(ssh->kex->hash, ssh->exhash, s->e); + hash_mpint(ssh->kex->hash, ssh->exhash, s->f); + + dh_cleanup(ssh->kex_ctx); + freebn(s->f); + if (!ssh->kex->pdata) { + freebn(s->g); + freebn(s->p); + } + } else { + logeventf(ssh, "Doing RSA key exchange with hash %s", + ssh->kex->hash->text_name); + ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + bombout(("expected RSA public key packet from server")); + crStopV; + } + + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (!s->hostkeydata) { + bombout(("unable to parse RSA public key packet")); + crStopV; + } + hash_string(ssh->kex->hash, ssh->exhash, + s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + + { + char *keydata; + ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); + if (!keydata) { + bombout(("unable to parse RSA public key packet")); + crStopV; + } + s->rsakeydata = snewn(s->rsakeylen, char); + memcpy(s->rsakeydata, keydata, s->rsakeylen); + } + + s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); + if (!s->rsakey) { + sfree(s->rsakeydata); + bombout(("unable to parse RSA public key from server")); + crStopV; + } + + hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); + + /* + * Next, set up a shared secret K, of precisely KLEN - + * 2*HLEN - 49 bits, where KLEN is the bit length of the + * RSA key modulus and HLEN is the bit length of the hash + * we're using. + */ + { + int klen = ssh_rsakex_klen(s->rsakey); + int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); + int i, byte = 0; + unsigned char *kstr1, *kstr2, *outstr; + int kstr1len, kstr2len, outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + kstr1 = ssh2_mpint_fmt(s->K, &kstr1len); + kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char); + PUT_32BIT(kstr2, kstr1len); + memcpy(kstr2 + 4, kstr1, kstr1len); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len, + outstr, outstrlen, s->rsakey); + + /* + * And send it off in a return packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen); + + sfree(kstr2); + sfree(kstr1); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsakey); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + sfree(s->rsakeydata); + bombout(("expected signature packet from server")); + crStopV; + } + + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + if (!s->sigdata) { + bombout(("unable to parse signature packet")); + crStopV; + } + + sfree(s->rsakeydata); + } + + hash_mpint(ssh->kex->hash, ssh->exhash, s->K); + assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); + ssh->kex->hash->final(ssh->exhash, s->exchange_hash); + + ssh->kex_ctx = NULL; + +#if 0 + debug(("Exchange hash is:\n")); + dmemdump(s->exchange_hash, ssh->kex->hash->hlen); +#endif + + if (!s->hkey || + !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, + (char *)s->exchange_hash, + ssh->kex->hash->hlen)) { + bombout(("Server's host key did not match the signature supplied")); + crStopV; + } + + s->keystr = ssh->hostkey->fmtkey(s->hkey); + if (!s->got_session_id) { + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + s->fingerprint = ssh->hostkey->fingerprint(s->hkey); + logevent("Host key fingerprint is:"); + logevent(s->fingerprint); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint, + ssh->hostkey, s->hkey); + if (s->dlgret == 0) { /* did not match */ + bombout(("Host key did not appear in manually configured list")); + crStopV; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + ssh->hostkey->keytype, s->keystr, + s->fingerprint, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "Aborted at host key verification", NULL, + 0, TRUE); + crStopV; + } + } + sfree(s->fingerprint); + /* + * Save this host key, to check against the one presented in + * subsequent rekeys. + */ + ssh->hostkey_str = s->keystr; + } else { + /* + * In a rekey, we never present an interactive host key + * verification request to the user. Instead, we simply + * enforce that the key we're seeing this time is identical to + * the one we saw before. + */ + if (strcmp(ssh->hostkey_str, s->keystr)) { + bombout(("Host key was different in repeat key exchange")); + crStopV; + } + sfree(s->keystr); + } + ssh->hostkey->freekey(s->hkey); + + /* + * The exchange hash from the very first key exchange is also + * the session id, used in session key construction and + * authentication. + */ + if (!s->got_session_id) { + assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id)); + memcpy(ssh->v2_session_id, s->exchange_hash, + sizeof(s->exchange_hash)); + ssh->v2_session_id_len = ssh->kex->hash->hlen; + assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id)); + s->got_session_id = TRUE; + } + + /* + * Send SSH2_MSG_NEWKEYS. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS); + ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh->outgoing_data_size = 0; /* start counting from here */ + + /* + * We've sent client NEWKEYS, so create and initialise + * client-to-server session keys. + */ + if (ssh->cs_cipher_ctx) + ssh->cscipher->free_context(ssh->cs_cipher_ctx); + ssh->cscipher = s->cscipher_tobe; + ssh->cs_cipher_ctx = ssh->cscipher->make_context(); + + if (ssh->cs_mac_ctx) + ssh->csmac->free_context(ssh->cs_mac_ctx); + ssh->csmac = s->csmac_tobe; + ssh->cs_mac_ctx = ssh->csmac->make_context(); + + if (ssh->cs_comp_ctx) + ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); + ssh->cscomp = s->cscomp_tobe; + ssh->cs_comp_ctx = ssh->cscomp->compress_init(); + + /* + * Set IVs on client-to-server keys. Here we use the exchange + * hash from the _first_ key exchange. + */ + { + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace); + assert((ssh->cscipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace); + assert(ssh->cscipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace); + assert(ssh->csmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace); + smemclr(keyspace, sizeof(keyspace)); + } + + logeventf(ssh, "Initialised %.200s client->server encryption", + ssh->cscipher->text_name); + logeventf(ssh, "Initialised %.200s client->server MAC algorithm", + ssh->csmac->text_name); + if (ssh->cscomp->text_name) + logeventf(ssh, "Initialised %s compression", + ssh->cscomp->text_name); + + /* + * Now our end of the key exchange is complete, we can send all + * our queued higher-layer packets. + */ + ssh->queueing = FALSE; + ssh2_pkt_queuesend(ssh); + + /* + * Expect SSH2_MSG_NEWKEYS from server. + */ + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_NEWKEYS) { + bombout(("expected new-keys packet from server")); + crStopV; + } + ssh->incoming_data_size = 0; /* start counting from here */ + + /* + * We've seen server NEWKEYS, so create and initialise + * server-to-client session keys. + */ + if (ssh->sc_cipher_ctx) + ssh->sccipher->free_context(ssh->sc_cipher_ctx); + ssh->sccipher = s->sccipher_tobe; + ssh->sc_cipher_ctx = ssh->sccipher->make_context(); + + if (ssh->sc_mac_ctx) + ssh->scmac->free_context(ssh->sc_mac_ctx); + ssh->scmac = s->scmac_tobe; + ssh->sc_mac_ctx = ssh->scmac->make_context(); + + if (ssh->sc_comp_ctx) + ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); + ssh->sccomp = s->sccomp_tobe; + ssh->sc_comp_ctx = ssh->sccomp->decompress_init(); + + /* + * Set IVs on server-to-client keys. Here we use the exchange + * hash from the _first_ key exchange. + */ + { + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace); + assert((ssh->sccipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace); + assert(ssh->sccipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace); + assert(ssh->scmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace); + smemclr(keyspace, sizeof(keyspace)); + } + logeventf(ssh, "Initialised %.200s server->client encryption", + ssh->sccipher->text_name); + logeventf(ssh, "Initialised %.200s server->client MAC algorithm", + ssh->scmac->text_name); + if (ssh->sccomp->text_name) + logeventf(ssh, "Initialised %s decompression", + ssh->sccomp->text_name); + + /* + * Free shared secret. + */ + freebn(s->K); + + /* + * Key exchange is over. Loop straight back round if we have a + * deferred rekey reason. + */ + if (ssh->deferred_rekey_reason) { + logevent(ssh->deferred_rekey_reason); + pktin = NULL; + ssh->deferred_rekey_reason = NULL; + goto begin_key_exchange; + } + + /* + * Otherwise, schedule a timer for our next rekey. + */ + ssh->kex_in_progress = FALSE; + ssh->last_rekey = GETTICKCOUNT(); + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) + ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, + ssh2_timer, ssh); + + /* + * Now we're encrypting. Begin returning 1 to the protocol main + * function so that other things can run on top of the + * transport. If we ever see a KEXINIT, we must go back to the + * start. + * + * We _also_ go back to the start if we see pktin==NULL and + * inlen negative, because this is a special signal meaning + * `initiate client-driven rekey', and `in' contains a message + * giving the reason for the rekey. + * + * inlen==-1 means always initiate a rekey; + * inlen==-2 means that userauth has completed successfully and + * we should consider rekeying (for delayed compression). + */ + while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || + (!pktin && inlen < 0))) { + wait_for_rekey: + if (!ssh->protocol_initial_phase_done) { + ssh->protocol_initial_phase_done = TRUE; + /* + * Allow authconn to initialise itself. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); + } + crReturnV; + } + if (pktin) { + logevent("Server initiated key re-exchange"); + } else { + if (inlen == -2) { + /* + * authconn has seen a USERAUTH_SUCCEEDED. Time to enable + * delayed compression, if it's available. + * + * draft-miller-secsh-compression-delayed-00 says that you + * negotiate delayed compression in the first key exchange, and + * both sides start compressing when the server has sent + * USERAUTH_SUCCESS. This has a race condition -- the server + * can't know when the client has seen it, and thus which incoming + * packets it should treat as compressed. + * + * Instead, we do the initial key exchange without offering the + * delayed methods, but note if the server offers them; when we + * get here, if a delayed method was available that was higher + * on our list than what we got, we initiate a rekey in which we + * _do_ list the delayed methods (and hopefully get it as a + * result). Subsequent rekeys will do the same. + */ + assert(!s->userauth_succeeded); /* should only happen once */ + s->userauth_succeeded = TRUE; + if (!s->pending_compression) + /* Can't see any point rekeying. */ + goto wait_for_rekey; /* this is utterly horrid */ + /* else fall through to rekey... */ + s->pending_compression = FALSE; + } + /* + * Now we've decided to rekey. + * + * Special case: if the server bug is set that doesn't + * allow rekeying, we give a different log message and + * continue waiting. (If such a server _initiates_ a rekey, + * we process it anyway!) + */ + if ((ssh->remote_bugs & BUG_SSH2_REKEY)) { + logeventf(ssh, "Server bug prevents key re-exchange (%s)", + (char *)in); + /* Reset the counters, so that at least this message doesn't + * hit the event log _too_ often. */ + ssh->outgoing_data_size = 0; + ssh->incoming_data_size = 0; + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) { + ssh->next_rekey = + schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, + ssh2_timer, ssh); + } + goto wait_for_rekey; /* this is still utterly horrid */ + } else { + logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); + } + } + goto begin_key_exchange; + + crFinishV; +} + +/* + * Add data to an SSH-2 channel output buffer. + */ +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, + int len) +{ + bufchain_add(&c->v.v2.outbuffer, buf, len); +} + +/* + * Attempt to send data on an SSH-2 channel. + */ +static int ssh2_try_send(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + struct Packet *pktout; + int ret; + + while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { + int len; + void *data; + bufchain_prefix(&c->v.v2.outbuffer, &data, &len); + if ((unsigned)len > c->v.v2.remwindow) + len = c->v.v2.remwindow; + if ((unsigned)len > c->v.v2.remmaxpkt) + len = c->v.v2.remmaxpkt; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_addstring_start(pktout); + ssh2_pkt_addstring_data(pktout, data, len); + ssh2_pkt_send(ssh, pktout); + bufchain_consume(&c->v.v2.outbuffer, len); + c->v.v2.remwindow -= len; + } + + /* + * After having sent as much data as we can, return the amount + * still buffered. + */ + ret = bufchain_size(&c->v.v2.outbuffer); + + /* + * And if there's no data pending but we need to send an EOF, send + * it. + */ + if (!ret && c->pending_eof) + ssh_channel_try_eof(c); + + return ret; +} + +static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) +{ + int bufsize; + if (c->closes & CLOSES_SENT_EOF) + return; /* don't send on channels we've EOFed */ + bufsize = ssh2_try_send(c); + if (bufsize == 0) { + switch (c->type) { + case CHAN_MAINSESSION: + /* stdin need not receive an unthrottle + * notification since it will be polled */ + break; + case CHAN_X11: + x11_unthrottle(c->u.x11.xconn); + break; + case CHAN_AGENT: + /* agent sockets are request/response and need no + * buffer management */ + break; + case CHAN_SOCKDATA: + pfd_unthrottle(c->u.pfd.pf); + break; + } + } +} + +static int ssh_is_simple(Ssh ssh) +{ + /* + * We use the 'simple' variant of the SSH protocol if we're asked + * to, except not if we're also doing connection-sharing (either + * tunnelling our packets over an upstream or expecting to be + * tunnelled over ourselves), since then the assumption that we + * have only one channel to worry about is not true after all. + */ + return (conf_get_int(ssh->conf, CONF_ssh_simple) && + !ssh->bare_connection && !ssh->connshare); +} + +/* + * Set up most of a new ssh_channel for SSH-2. + */ +static void ssh2_channel_init(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = FALSE; + c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = + ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; + c->v.v2.chanreq_head = NULL; + c->v.v2.throttle_state = UNTHROTTLED; + bufchain_init(&c->v.v2.outbuffer); +} + +/* + * Construct the common parts of a CHANNEL_OPEN. + */ +static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type) +{ + struct Packet *pktout; + + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(pktout, type); + ssh2_pkt_adduint32(pktout, c->localid); + ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + return pktout; +} + +/* + * CHANNEL_FAILURE doesn't come with any indication of what message + * caused it, so we have to keep track of the outstanding + * CHANNEL_REQUESTs ourselves. + */ +static void ssh2_queue_chanreq_handler(struct ssh_channel *c, + cchandler_fn_t handler, void *ctx) +{ + struct outstanding_channel_request *ocr = + snew(struct outstanding_channel_request); + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + ocr->handler = handler; + ocr->ctx = ctx; + ocr->next = NULL; + if (!c->v.v2.chanreq_head) + c->v.v2.chanreq_head = ocr; + else + c->v.v2.chanreq_tail->next = ocr; + c->v.v2.chanreq_tail = ocr; +} + +/* + * Construct the common parts of a CHANNEL_REQUEST. If handler is not + * NULL then a reply will be requested and the handler will be called + * when it arrives. The returned packet is ready to have any + * request-specific data added and be sent. Note that if a handler is + * provided, it's essential that the request actually be sent. + * + * The handler will usually be passed the response packet in pktin. If + * pktin is NULL, this means that no reply will ever be forthcoming + * (e.g. because the entire connection is being destroyed, or because + * the server initiated channel closure before we saw the response) + * and the handler should free any storage it's holding. + */ +static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type, + cchandler_fn_t handler, void *ctx) +{ + struct Packet *pktout; + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_addstring(pktout, type); + ssh2_pkt_addbool(pktout, handler != NULL); + if (handler != NULL) + ssh2_queue_chanreq_handler(c, handler, ctx); + return pktout; +} + +/* + * Potentially enlarge the window on an SSH-2 channel. + */ +static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *, + void *); +static void ssh2_set_window(struct ssh_channel *c, int newwin) +{ + Ssh ssh = c->ssh; + + /* + * Never send WINDOW_ADJUST for a channel that the remote side has + * already sent EOF on; there's no point, since it won't be + * sending any more data anyway. Ditto if _we've_ already sent + * CLOSE. + */ + if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) + return; + + /* + * Also, never widen the window for an X11 channel when we're + * still waiting to see its initial auth and may yet hand it off + * to a downstream. + */ + if (c->type == CHAN_X11 && c->u.x11.initial) + return; + + /* + * If the remote end has a habit of ignoring maxpkt, limit the + * window so that it has no choice (assuming it doesn't ignore the + * window as well). + */ + if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) + newwin = OUR_V2_MAXPKT; + + /* + * Only send a WINDOW_ADJUST if there's significantly more window + * available than the other end thinks there is. This saves us + * sending a WINDOW_ADJUST for every character in a shell session. + * + * "Significant" is arbitrarily defined as half the window size. + */ + if (newwin / 2 >= c->v.v2.locwindow) { + struct Packet *pktout; + unsigned *up; + + /* + * In order to keep track of how much window the client + * actually has available, we'd like it to acknowledge each + * WINDOW_ADJUST. We can't do that directly, so we accompany + * it with a CHANNEL_REQUEST that has to be acknowledged. + * + * This is only necessary if we're opening the window wide. + * If we're not, then throughput is being constrained by + * something other than the maximum window size anyway. + */ + if (newwin == c->v.v2.locmaxwin && + !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) { + up = snew(unsigned); + *up = newwin - c->v.v2.locwindow; + pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", + ssh2_handle_winadj_response, up); + ssh2_pkt_send(ssh, pktout); + + if (c->v.v2.throttle_state != UNTHROTTLED) + c->v.v2.throttle_state = UNTHROTTLING; + } else { + /* Pretend the WINDOW_ADJUST was acked immediately. */ + c->v.v2.remlocwin = newwin; + c->v.v2.throttle_state = THROTTLED; + } + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow); + ssh2_pkt_send(ssh, pktout); + c->v.v2.locwindow = newwin; + } +} + +/* + * Find the channel associated with a message. If there's no channel, + * or it's not properly open, make a noise about it and return NULL. + */ +static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin) +{ + unsigned localid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &localid, ssh_channelfind); + if (!c || + (c->type != CHAN_SHARING && c->halfopen && + pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && + pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) { + char *buf = dupprintf("Received %s for %s channel %u", + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + pktin->type), + c ? "half-open" : "nonexistent", localid); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); + return NULL; + } + return c; +} + +static void ssh2_handle_winadj_response(struct ssh_channel *c, + struct Packet *pktin, void *ctx) +{ + unsigned *sizep = ctx; + + /* + * Winadj responses should always be failures. However, at least + * one server ("boks_sshd") is known to return SUCCESS for channel + * requests it's never heard of, such as "winadj@putty". Raised + * with foxt.com as bug 090916-090424, but for the sake of a quiet + * life, we don't worry about what kind of response we got. + */ + + c->v.v2.remlocwin += *sizep; + sfree(sizep); + /* + * winadj messages are only sent when the window is fully open, so + * if we get an ack of one, we know any pending unthrottle is + * complete. + */ + if (c->v.v2.throttle_state == UNTHROTTLING) + c->v.v2.throttle_state = UNTHROTTLED; +} + +static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c = ssh2_channel_msg(ssh, pktin); + struct outstanding_channel_request *ocr; + + if (!c) return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + ocr = c->v.v2.chanreq_head; + if (!ocr) { + ssh2_msg_unexpected(ssh, pktin); + return; + } + ocr->handler(c, pktin, ocr->ctx); + c->v.v2.chanreq_head = ocr->next; + sfree(ocr); + /* + * We may now initiate channel-closing procedures, if that + * CHANNEL_REQUEST was the last thing outstanding before we send + * CHANNEL_CLOSE. + */ + ssh2_channel_check_close(c); +} + +static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + if (!(c->closes & CLOSES_SENT_EOF)) { + c->v.v2.remwindow += ssh_pkt_getuint32(pktin); + ssh2_try_send_and_unthrottle(ssh, c); + } +} + +static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) +{ + char *data; + int length; + struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA && + ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR) + return; /* extended but not stderr */ + ssh_pkt_getstring(pktin, &data, &length); + if (data) { + int bufsize = 0; + c->v.v2.locwindow -= length; + c->v.v2.remlocwin -= length; + switch (c->type) { + case CHAN_MAINSESSION: + bufsize = + from_backend(ssh->frontend, pktin->type == + SSH2_MSG_CHANNEL_EXTENDED_DATA, + data, length); + break; + case CHAN_X11: + bufsize = x11_send(c->u.x11.xconn, data, length); + break; + case CHAN_SOCKDATA: + bufsize = pfd_send(c->u.pfd.pf, data, length); + break; + case CHAN_AGENT: + while (length > 0) { + if (c->u.a.lensofar < 4) { + unsigned int l = min(4 - c->u.a.lensofar, + (unsigned)length); + memcpy(c->u.a.msglen + c->u.a.lensofar, + data, l); + data += l; + length -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == 4) { + c->u.a.totallen = + 4 + GET_32BIT(c->u.a.msglen); + c->u.a.message = snewn(c->u.a.totallen, + unsigned char); + memcpy(c->u.a.message, c->u.a.msglen, 4); + } + if (c->u.a.lensofar >= 4 && length > 0) { + unsigned int l = + min(c->u.a.totallen - c->u.a.lensofar, + (unsigned)length); + memcpy(c->u.a.message + c->u.a.lensofar, + data, l); + data += l; + length -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == c->u.a.totallen) { + void *reply; + int replylen; + c->u.a.outstanding_requests++; + if (agent_query(c->u.a.message, + c->u.a.totallen, + &reply, &replylen, + ssh_agentf_callback, c)) + ssh_agentf_callback(c, reply, replylen); + sfree(c->u.a.message); + c->u.a.message = NULL; + c->u.a.lensofar = 0; + } + } + bufsize = 0; + break; + } + /* + * If it looks like the remote end hit the end of its window, + * and we didn't want it to do that, think about using a + * larger window. + */ + if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED && + c->v.v2.locmaxwin < 0x40000000) + c->v.v2.locmaxwin += OUR_V2_WINSIZE; + /* + * If we are not buffering too much data, + * enlarge the window again at the remote side. + * If we are buffering too much, we may still + * need to adjust the window if the server's + * sent excess data. + */ + ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ? + c->v.v2.locmaxwin - bufsize : 0); + /* + * If we're either buffering way too much data, or if we're + * buffering anything at all and we're in "simple" mode, + * throttle the whole channel. + */ + if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0)) + && !c->throttling_conn) { + c->throttling_conn = 1; + ssh_throttle_conn(ssh, +1); + } + } +} + +static void ssh_check_termination(Ssh ssh) +{ + if (ssh->version == 2 && + !conf_get_int(ssh->conf, CONF_ssh_no_shell) && + (ssh->channels && count234(ssh->channels) == 0) && + !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) { + /* + * We used to send SSH_MSG_DISCONNECT here, because I'd + * believed that _every_ conforming SSH-2 connection had to + * end with a disconnect being sent by at least one side; + * apparently I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut when you're done, + * and indeed OpenSSH feels this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + } +} + +void ssh_sharing_downstream_connected(Ssh ssh, unsigned id, + const char *peerinfo) +{ + if (peerinfo) + logeventf(ssh, "Connection sharing downstream #%u connected from %s", + id, peerinfo); + else + logeventf(ssh, "Connection sharing downstream #%u connected", id); +} + +void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id) +{ + logeventf(ssh, "Connection sharing downstream #%u disconnected", id); + ssh_check_termination(ssh); +} + +void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + if (id) + logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf); + else + logeventf(ssh, "Connection sharing: %s", buf); + sfree(buf); +} + +static void ssh_channel_destroy(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + + switch (c->type) { + case CHAN_MAINSESSION: + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; + case CHAN_X11: + if (c->u.x11.xconn != NULL) + x11_close(c->u.x11.xconn); + logevent("Forwarded X11 connection terminated"); + break; + case CHAN_AGENT: + sfree(c->u.a.message); + break; + case CHAN_SOCKDATA: + if (c->u.pfd.pf != NULL) + pfd_close(c->u.pfd.pf); + logevent("Forwarded port closed"); + break; + } + + del234(ssh->channels, c); + if (ssh->version == 2) { + bufchain_clear(&c->v.v2.outbuffer); + assert(c->v.v2.chanreq_head == NULL); + } + sfree(c); + + /* + * If that was the last channel left open, we might need to + * terminate. + */ + ssh_check_termination(ssh); +} + +static void ssh2_channel_check_close(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + struct Packet *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || + c->type == CHAN_ZOMBIE) && + !c->v.v2.chanreq_head && + !(c->closes & CLOSES_SENT_CLOSE)) { + /* + * We have both sent and received EOF (or the channel is a + * zombie), and we have no outstanding channel requests, which + * means the channel is in final wind-up. But we haven't sent + * CLOSE, so let's do so now. + */ + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { + assert(c->v.v2.chanreq_head == NULL); + /* + * We have both sent and received CLOSE, which means we're + * completely done with the channel. + */ + ssh_channel_destroy(c); + } +} + +static void ssh2_channel_got_eof(struct ssh_channel *c) +{ + if (c->closes & CLOSES_RCVD_EOF) + return; /* already seen EOF */ + c->closes |= CLOSES_RCVD_EOF; + + if (c->type == CHAN_X11) { + x11_send_eof(c->u.x11.xconn); + } else if (c->type == CHAN_AGENT) { + if (c->u.a.outstanding_requests == 0) { + /* Manufacture an outgoing EOF in response to the incoming one. */ + sshfwd_write_eof(c); + } + } else if (c->type == CHAN_SOCKDATA) { + pfd_send_eof(c->u.pfd.pf); + } else if (c->type == CHAN_MAINSESSION) { + Ssh ssh = c->ssh; + + if (!ssh->sent_console_eof && + (from_backend_eof(ssh->frontend) || ssh->got_pty)) { + /* + * Either from_backend_eof told us that the front end + * wants us to close the outgoing side of the connection + * as soon as we see EOF from the far end, or else we've + * unilaterally decided to do that because we've allocated + * a remote pty and hence EOF isn't a particularly + * meaningful concept. + */ + sshfwd_write_eof(c); + } + ssh->sent_console_eof = TRUE; + } + + ssh2_channel_check_close(c); +} + +static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + ssh2_channel_got_eof(c); +} + +static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + + /* + * When we receive CLOSE on a channel, we assume it comes with an + * implied EOF if we haven't seen EOF yet. + */ + ssh2_channel_got_eof(c); + + if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { + /* + * It also means we stop expecting to see replies to any + * outstanding channel requests, so clean those up too. + * (ssh_chanreq_init will enforce by assertion that we don't + * subsequently put anything back on this list.) + */ + while (c->v.v2.chanreq_head) { + struct outstanding_channel_request *ocr = c->v.v2.chanreq_head; + ocr->handler(c, NULL, ocr->ctx); + c->v.v2.chanreq_head = ocr->next; + sfree(ocr); + } + } + + /* + * And we also send an outgoing EOF, if we haven't already, on the + * assumption that CLOSE is a pretty forceful announcement that + * the remote side is doing away with the entire channel. (If it + * had wanted to send us EOF and continue receiving data from us, + * it would have just sent CHANNEL_EOF.) + */ + if (!(c->closes & CLOSES_SENT_EOF)) { + /* + * Make sure we don't read any more from whatever our local + * data source is for this channel. + */ + switch (c->type) { + case CHAN_MAINSESSION: + ssh->send_ok = 0; /* stop trying to read from stdin */ + break; + case CHAN_X11: + x11_override_throttle(c->u.x11.xconn, 1); + break; + case CHAN_SOCKDATA: + pfd_override_throttle(c->u.pfd.pf, 1); + break; + } + + /* + * Abandon any buffered data we still wanted to send to this + * channel. Receiving a CHANNEL_CLOSE is an indication that + * the server really wants to get on and _destroy_ this + * channel, and it isn't going to send us any further + * WINDOW_ADJUSTs to permit us to send pending stuff. + */ + bufchain_clear(&c->v.v2.outbuffer); + + /* + * Send outgoing EOF. + */ + sshfwd_write_eof(c); + } + + /* + * Now process the actual close. + */ + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + ssh2_channel_check_close(c); + } +} + +static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + assert(c->halfopen); /* ssh2_channel_msg will have enforced this */ + c->remoteid = ssh_pkt_getuint32(pktin); + c->halfopen = FALSE; + c->v.v2.remwindow = ssh_pkt_getuint32(pktin); + c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + + if (c->type == CHAN_SOCKDATA_DORMANT) { + c->type = CHAN_SOCKDATA; + if (c->u.pfd.pf) + pfd_confirm(c->u.pfd.pf); + } else if (c->type == CHAN_ZOMBIE) { + /* + * This case can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. In this case, all we can do is + * immediately initiate close proceedings now that we know the + * server's id to put in the close message. + */ + ssh2_channel_check_close(c); + } else { + /* + * We never expect to receive OPEN_CONFIRMATION for any + * *other* channel type (since only local-to-remote port + * forwardings cause us to send CHANNEL_OPEN after the main + * channel is live - all other auxiliary channel types are + * initiated from the server end). It's safe to enforce this + * by assertion rather than by ssh_disconnect, because the + * real point is that we never constructed a half-open channel + * structure in the first place with any type other than the + * above. + */ + assert(!"Funny channel type in ssh2_msg_channel_open_confirmation"); + } + + if (c->pending_eof) + ssh_channel_try_eof(c); /* in case we had a pending EOF */ +} + +static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +{ + static const char *const reasons[] = { + "", + "Administratively prohibited", + "Connect failed", + "Unknown channel type", + "Resource shortage", + }; + unsigned reason_code; + char *reason_string; + int reason_length; + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + assert(c->halfopen); /* ssh2_channel_msg will have enforced this */ + + if (c->type == CHAN_SOCKDATA_DORMANT) { + reason_code = ssh_pkt_getuint32(pktin); + if (reason_code >= lenof(reasons)) + reason_code = 0; /* ensure reasons[reason_code] in range */ + ssh_pkt_getstring(pktin, &reason_string, &reason_length); + logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", + reasons[reason_code], reason_length, + NULLTOEMPTY(reason_string)); + + pfd_close(c->u.pfd.pf); + } else if (c->type == CHAN_ZOMBIE) { + /* + * This case can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_FAILURE. In this case, we need do nothing except allow + * the code below to throw the half-open channel away. + */ + } else { + /* + * We never expect to receive OPEN_FAILURE for any *other* + * channel type (since only local-to-remote port forwardings + * cause us to send CHANNEL_OPEN after the main channel is + * live - all other auxiliary channel types are initiated from + * the server end). It's safe to enforce this by assertion + * rather than by ssh_disconnect, because the real point is + * that we never constructed a half-open channel structure in + * the first place with any type other than the above. + */ + assert(!"Funny channel type in ssh2_msg_channel_open_failure"); + } + + del234(ssh->channels, c); + sfree(c); +} + +static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen, want_reply; + int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */ + struct ssh_channel *c; + struct Packet *pktout; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + ssh_pkt_getstring(pktin, &type, &typelen); + want_reply = ssh2_pkt_getbool(pktin); + + if (c->closes & CLOSES_SENT_CLOSE) { + /* + * We don't reply to channel requests after we've sent + * CHANNEL_CLOSE for the channel, because our reply might + * cross in the network with the other side's CHANNEL_CLOSE + * and arrive after they have wound the channel up completely. + */ + want_reply = FALSE; + } + + /* + * Having got the channel number, we now look at + * the request type string to see if it's something + * we recognise. + */ + if (c == ssh->mainchan) { + /* + * We recognise "exit-status" and "exit-signal" on + * the primary channel. + */ + if (typelen == 11 && + !memcmp(type, "exit-status", 11)) { + + ssh->exitcode = ssh_pkt_getuint32(pktin); + logeventf(ssh, "Server sent command exit status %d", + ssh->exitcode); + reply = SSH2_MSG_CHANNEL_SUCCESS; + + } else if (typelen == 11 && + !memcmp(type, "exit-signal", 11)) { + + int is_plausible = TRUE, is_int = FALSE; + char *fmt_sig = "", *fmt_msg = ""; + char *msg; + int msglen = 0, core = FALSE; + /* ICK: older versions of OpenSSH (e.g. 3.4p1) + * provide an `int' for the signal, despite its + * having been a `string' in the drafts of RFC 4254 since at + * least 2001. (Fixed in session.c 1.147.) Try to + * infer which we can safely parse it as. */ + { + unsigned char *p = pktin->body + + pktin->savedpos; + long len = pktin->length - pktin->savedpos; + unsigned long num = GET_32BIT(p); /* what is it? */ + /* If it's 0, it hardly matters; assume string */ + if (num == 0) { + is_int = FALSE; + } else { + int maybe_int = FALSE, maybe_str = FALSE; +#define CHECK_HYPOTHESIS(offset, result) \ + do \ + { \ + int q = toint(offset); \ + if (q >= 0 && q+4 <= len) { \ + q = toint(q + 4 + GET_32BIT(p+q)); \ + if (q >= 0 && q+4 <= len && \ + ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \ + q == len) \ + result = TRUE; \ + } \ + } while(0) + CHECK_HYPOTHESIS(4+1, maybe_int); + CHECK_HYPOTHESIS(4+num+1, maybe_str); +#undef CHECK_HYPOTHESIS + if (maybe_int && !maybe_str) + is_int = TRUE; + else if (!maybe_int && maybe_str) + is_int = FALSE; + else + /* Crikey. Either or neither. Panic. */ + is_plausible = FALSE; + } + } + ssh->exitcode = 128; /* means `unknown signal' */ + if (is_plausible) { + if (is_int) { + /* Old non-standard OpenSSH. */ + int signum = ssh_pkt_getuint32(pktin); + fmt_sig = dupprintf(" %d", signum); + ssh->exitcode = 128 + signum; + } else { + /* As per RFC 4254. */ + char *sig; + int siglen; + ssh_pkt_getstring(pktin, &sig, &siglen); + /* Signal name isn't supposed to be blank, but + * let's cope gracefully if it is. */ + if (siglen) { + fmt_sig = dupprintf(" \"%.*s\"", + siglen, sig); + } + + /* + * Really hideous method of translating the + * signal description back into a locally + * meaningful number. + */ + + if (0) + ; +#define TRANSLATE_SIGNAL(s) \ + else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \ + ssh->exitcode = 128 + SIG ## s +#ifdef SIGABRT + TRANSLATE_SIGNAL(ABRT); +#endif +#ifdef SIGALRM + TRANSLATE_SIGNAL(ALRM); +#endif +#ifdef SIGFPE + TRANSLATE_SIGNAL(FPE); +#endif +#ifdef SIGHUP + TRANSLATE_SIGNAL(HUP); +#endif +#ifdef SIGILL + TRANSLATE_SIGNAL(ILL); +#endif +#ifdef SIGINT + TRANSLATE_SIGNAL(INT); +#endif +#ifdef SIGKILL + TRANSLATE_SIGNAL(KILL); +#endif +#ifdef SIGPIPE + TRANSLATE_SIGNAL(PIPE); +#endif +#ifdef SIGQUIT + TRANSLATE_SIGNAL(QUIT); +#endif +#ifdef SIGSEGV + TRANSLATE_SIGNAL(SEGV); +#endif +#ifdef SIGTERM + TRANSLATE_SIGNAL(TERM); +#endif +#ifdef SIGUSR1 + TRANSLATE_SIGNAL(USR1); +#endif +#ifdef SIGUSR2 + TRANSLATE_SIGNAL(USR2); +#endif +#undef TRANSLATE_SIGNAL + else + ssh->exitcode = 128; + } + core = ssh2_pkt_getbool(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + if (msglen) { + fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg); + } + /* ignore lang tag */ + } /* else don't attempt to parse */ + logeventf(ssh, "Server exited on signal%s%s%s", + fmt_sig, core ? " (core dumped)" : "", + fmt_msg); + if (*fmt_sig) sfree(fmt_sig); + if (*fmt_msg) sfree(fmt_msg); + reply = SSH2_MSG_CHANNEL_SUCCESS; + + } + } else { + /* + * This is a channel request we don't know + * about, so we now either ignore the request + * or respond with CHANNEL_FAILURE, depending + * on want_reply. + */ + reply = SSH2_MSG_CHANNEL_FAILURE; + } + if (want_reply) { + pktout = ssh2_pkt_init(reply); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + } +} + +static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen, want_reply; + struct Packet *pktout; + + ssh_pkt_getstring(pktin, &type, &typelen); + want_reply = ssh2_pkt_getbool(pktin); + + /* + * We currently don't support any global requests + * at all, so we either ignore the request or + * respond with REQUEST_FAILURE, depending on + * want_reply. + */ + if (want_reply) { + pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); + ssh2_pkt_send(ssh, pktout); + } +} + +struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, + void *share_cs, + void *share_chan) +{ + struct X11FakeAuth *auth; + + /* + * Make up a new set of fake X11 auth data, and add it to the tree + * of currently valid ones with an indication of the sharing + * context that it's relevant to. + */ + auth = x11_invent_fake_auth(ssh->x11authtree, authtype); + auth->share_cs = share_cs; + auth->share_chan = share_chan; + + return auth; +} + +void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth) +{ + del234(ssh->x11authtree, auth); + x11_free_fake_auth(auth); +} + +static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen; + char *peeraddr; + int peeraddrlen; + int peerport; + char *error = NULL; + struct ssh_channel *c; + unsigned remid, winsize, pktsize; + unsigned our_winsize_override = 0; + struct Packet *pktout; + + ssh_pkt_getstring(pktin, &type, &typelen); + c = snew(struct ssh_channel); + c->ssh = ssh; + + remid = ssh_pkt_getuint32(pktin); + winsize = ssh_pkt_getuint32(pktin); + pktsize = ssh_pkt_getuint32(pktin); + + if (typelen == 3 && !memcmp(type, "x11", 3)) { + char *addrstr; + + ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); + addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr)); + peerport = ssh_pkt_getuint32(pktin); + + logeventf(ssh, "Received X11 connect request from %s:%d", + addrstr, peerport); + + if (!ssh->X11_fwd_enabled && !ssh->connshare) + error = "X11 forwarding is not enabled"; + else { + c->u.x11.xconn = x11_init(ssh->x11authtree, c, + addrstr, peerport); + c->type = CHAN_X11; + c->u.x11.initial = TRUE; + + /* + * If we are a connection-sharing upstream, then we should + * initially present a very small window, adequate to take + * the X11 initial authorisation packet but not much more. + * Downstream will then present us a larger window (by + * fiat of the connection-sharing protocol) and we can + * guarantee to send a positive-valued WINDOW_ADJUST. + */ + if (ssh->connshare) + our_winsize_override = 128; + + logevent("Opened X11 forward channel"); + } + + sfree(addrstr); + } else if (typelen == 15 && + !memcmp(type, "forwarded-tcpip", 15)) { + struct ssh_rportfwd pf, *realpf; + char *shost; + int shostlen; + ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */ + pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost)); + pf.sport = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); + peerport = ssh_pkt_getuint32(pktin); + realpf = find234(ssh->rportfwds, &pf, NULL); + logeventf(ssh, "Received remote port %s:%d open request " + "from %.*s:%d", pf.shost, pf.sport, + peeraddrlen, NULLTOEMPTY(peeraddr), peerport); + sfree(pf.shost); + + if (realpf == NULL) { + error = "Remote port is not recognised"; + } else { + char *err; + + if (realpf->share_ctx) { + /* + * This port forwarding is on behalf of a + * connection-sharing downstream, so abandon our own + * channel-open procedure and just pass the message on + * to sshshare.c. + */ + share_got_pkt_from_server(realpf->share_ctx, pktin->type, + pktin->body, pktin->length); + sfree(c); + return; + } + + err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport, + c, ssh->conf, realpf->pfrec->addressfamily); + logeventf(ssh, "Attempting to forward remote port to " + "%s:%d", realpf->dhost, realpf->dport); + if (err != NULL) { + logeventf(ssh, "Port open failed: %s", err); + sfree(err); + error = "Port open failed"; + } else { + logevent("Forwarded port opened successfully"); + c->type = CHAN_SOCKDATA; + } + } + } else if (typelen == 22 && + !memcmp(type, "auth-agent@openssh.com", 22)) { + if (!ssh->agentfwd_enabled) + error = "Agent forwarding is not enabled"; + else { + c->type = CHAN_AGENT; /* identify channel type */ + c->u.a.lensofar = 0; + c->u.a.message = NULL; + c->u.a.outstanding_requests = 0; + } + } else { + error = "Unsupported channel type requested"; + } + + c->remoteid = remid; + c->halfopen = FALSE; + if (error) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED); + ssh2_pkt_addstring(pktout, error); + ssh2_pkt_addstring(pktout, "en"); /* language tag */ + ssh2_pkt_send(ssh, pktout); + logeventf(ssh, "Rejected channel open: %s", error); + sfree(c); + } else { + ssh2_channel_init(c); + c->v.v2.remwindow = winsize; + c->v.v2.remmaxpkt = pktsize; + if (our_winsize_override) { + c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = + our_winsize_override; + } + add234(ssh->channels, c); + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, c->localid); + ssh2_pkt_adduint32(pktout, c->v.v2.locwindow); + ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_send(ssh, pktout); + } +} + +void sshfwd_x11_sharing_handover(struct ssh_channel *c, + void *share_cs, void *share_chan, + const char *peer_addr, int peer_port, + int endian, int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + /* + * This function is called when we've just discovered that an X + * forwarding channel on which we'd been handling the initial auth + * ourselves turns out to be destined for a connection-sharing + * downstream. So we turn the channel into a CHAN_SHARING, meaning + * that we completely stop tracking windows and buffering data and + * just pass more or less unmodified SSH messages back and forth. + */ + c->type = CHAN_SHARING; + c->u.sharing.ctx = share_cs; + share_setup_x11_channel(share_cs, share_chan, + c->localid, c->remoteid, c->v.v2.remwindow, + c->v.v2.remmaxpkt, c->v.v2.locwindow, + peer_addr, peer_port, endian, + protomajor, protominor, + initial_data, initial_len); +} + +void sshfwd_x11_is_local(struct ssh_channel *c) +{ + /* + * This function is called when we've just discovered that an X + * forwarding channel is _not_ destined for a connection-sharing + * downstream but we're going to handle it ourselves. We stop + * presenting a cautiously small window and go into ordinary data + * exchange mode. + */ + c->u.x11.initial = FALSE; + ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); +} + +/* + * Buffer banner messages for later display at some convenient point, + * if we're going to display them. + */ +static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) +{ + /* Arbitrary limit to prevent unbounded inflation of buffer */ + if (conf_get_int(ssh->conf, CONF_ssh_show_banner) && + bufchain_size(&ssh->banner) <= 131072) { + char *banner = NULL; + int size = 0; + ssh_pkt_getstring(pktin, &banner, &size); + if (banner) + bufchain_add(&ssh->banner, banner, size); + } +} + +/* Helper function to deal with sending tty modes for "pty-req" */ +static void ssh2_send_ttymode(void *data, char *mode, char *val) +{ + struct Packet *pktout = (struct Packet *)data; + int i = 0; + unsigned int arg = 0; + while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++; + if (i == lenof(ssh_ttymodes)) return; + switch (ssh_ttymodes[i].type) { + case TTY_OP_CHAR: + arg = ssh_tty_parse_specchar(val); + break; + case TTY_OP_BOOL: + arg = ssh_tty_parse_boolean(val); + break; + } + ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); + ssh2_pkt_adduint32(pktout, arg); +} + +static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_x11_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_x11_state, ctx); + + crBeginState; + + logevent("Requesting X11 forwarding"); + pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req", + ssh2_setup_x11, s); + ssh2_pkt_addbool(pktout, 0); /* many connections */ + ssh2_pkt_addstring(pktout, ssh->x11auth->protoname); + ssh2_pkt_addstring(pktout, ssh->x11auth->datastring); + ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum); + ssh2_pkt_send(ssh, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + } else + logevent("X11 forwarding refused"); + } + + crFinishFreeV; +} + +static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_agent_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_agent_state, ctx); + + crBeginState; + + logevent("Requesting OpenSSH-style agent forwarding"); + pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com", + ssh2_setup_agent, s); + ssh2_pkt_send(ssh, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logevent("Agent forwarding enabled"); + ssh->agentfwd_enabled = TRUE; + } else + logevent("Agent forwarding refused"); + } + + crFinishFreeV; +} + +static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_pty_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_pty_state, ctx); + + crBeginState; + + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); + /* Build the pty request. */ + pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req", + ssh2_setup_pty, s); + ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype)); + ssh2_pkt_adduint32(pktout, ssh->term_width); + ssh2_pkt_adduint32(pktout, ssh->term_height); + ssh2_pkt_adduint32(pktout, 0); /* pixel width */ + ssh2_pkt_adduint32(pktout, 0); /* pixel height */ + ssh2_pkt_addstring_start(pktout); + parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout); + ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED); + ssh2_pkt_adduint32(pktout, ssh->ispeed); + ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED); + ssh2_pkt_adduint32(pktout, ssh->ospeed); + ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */ + ssh2_pkt_send(ssh, pktout); + ssh->state = SSH_STATE_INTERMED; + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + ssh->got_pty = TRUE; + } else { + c_write_str(ssh, "Server refused to allocate pty\r\n"); + ssh->editing = ssh->echoing = 1; + } + } + + crFinishFreeV; +} + +static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_env_state { + int crLine; + int num_env, env_left, env_ok; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_env_state, ctx); + + crBeginState; + + /* + * Send environment variables. + * + * Simplest thing here is to send all the requests at once, and + * then wait for a whole bunch of successes or failures. + */ + s->num_env = 0; + { + char *key, *val; + + for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) { + pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s); + ssh2_pkt_addstring(pktout, key); + ssh2_pkt_addstring(pktout, val); + ssh2_pkt_send(ssh, pktout); + + s->num_env++; + } + if (s->num_env) + logeventf(ssh, "Sent %d environment variables", s->num_env); + } + + if (s->num_env) { + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + /* Wait to be called back with either a response packet, + * or NULL meaning clean up and free our data */ + crReturnV; + if (!pktin) goto out; + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) + s->env_ok++; + s->env_left--; + } + + if (s->env_ok == s->num_env) { + logevent("All environment variables successfully set"); + } else if (s->env_ok == 0) { + logevent("All environment variables refused"); + c_write_str(ssh, "Server refused to set environment variables\r\n"); + } else { + logeventf(ssh, "%d environment variables refused", + s->num_env - s->env_ok); + c_write_str(ssh, "Server refused to set all environment variables\r\n"); + } + } + out:; + crFinishFreeV; +} + +/* + * Handle the SSH-2 userauth and connection layers. + */ +static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin) +{ + do_ssh2_authconn(ssh, NULL, 0, pktin); +} + +static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + if (pktin) + do_ssh2_authconn(c->ssh, NULL, 0, pktin); +} + +static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + struct do_ssh2_authconn_state { + int crLine; + enum { + AUTH_TYPE_NONE, + AUTH_TYPE_PUBLICKEY, + AUTH_TYPE_PUBLICKEY_OFFER_LOUD, + AUTH_TYPE_PUBLICKEY_OFFER_QUIET, + AUTH_TYPE_PASSWORD, + AUTH_TYPE_GSSAPI, /* always QUIET */ + AUTH_TYPE_KEYBOARD_INTERACTIVE, + AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET + } type; + int done_service_req; + int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; + int tried_pubkey_config, done_agent; +#ifndef NO_GSSAPI + int can_gssapi; + int tried_gssapi; +#endif + int kbd_inter_refused; + int we_are_in, userauth_success; + prompts_t *cur_prompt; + int num_prompts; + char *username; + char *password; + int got_username; + void *publickey_blob; + int publickey_bloblen; + int publickey_encrypted; + char *publickey_algorithm; + char *publickey_comment; + unsigned char agent_request[5], *agent_response, *agentp; + int agent_responselen; + unsigned char *pkblob_in_agent; + int keyi, nkeys; + char *pkblob, *alg, *commentp; + int pklen, alglen, commentlen; + int siglen, retlen, len; + char *q, *agentreq, *ret; + int try_send; + struct Packet *pktout; + Filename *keyfile; +#ifndef NO_GSSAPI + struct ssh_gss_library *gsslib; + Ssh_gss_ctx gss_ctx; + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_name gss_srv_name; + Ssh_gss_stat gss_stat; +#endif + }; + crState(do_ssh2_authconn_state); + + crBeginState; + + /* Register as a handler for all the messages this coroutine handles. */ + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn; + + s->done_service_req = FALSE; + s->we_are_in = s->userauth_success = FALSE; + s->agent_response = NULL; +#ifndef NO_GSSAPI + s->tried_gssapi = FALSE; +#endif + + if (!ssh->bare_connection) { + if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) { + /* + * Request userauth protocol, and await a response to it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-userauth"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) + s->done_service_req = TRUE; + } + if (!s->done_service_req) { + /* + * Request connection protocol directly, without authentication. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { + s->we_are_in = TRUE; /* no auth required */ + } else { + bombout(("Server refused service request")); + crStopV; + } + } + } else { + s->we_are_in = TRUE; + } + + /* Arrange to be able to deal with any BANNERs that come in. + * (We do this now as packets may come in during the next bit.) */ + bufchain_init(&ssh->banner); + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = + ssh2_msg_userauth_banner; + + /* + * Misc one-time setup for authentication. + */ + s->publickey_blob = NULL; + if (!s->we_are_in) { + + /* + * Load the public half of any configured public key file + * for later use. + */ + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH2) { + const char *error; + s->publickey_blob = + ssh2_userkey_loadpub(s->keyfile, + &s->publickey_algorithm, + &s->publickey_bloblen, + &s->publickey_comment, &error); + if (s->publickey_blob) { + s->publickey_encrypted = + ssh2_userkey_encrypted(s->keyfile, NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", + error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(s->keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } + + /* + * Find out about any keys Pageant has (but if there's a + * public key configured, filter out all others). + */ + s->nkeys = 0; + s->agent_response = NULL; + s->pkblob_in_agent = NULL; + if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) { + + void *r; + + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(s->agent_request, 1); + s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; + if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for agent response")); + crStopV; + } + } while (pktin || inlen > 0); + r = ssh->agent_response; + s->agent_responselen = ssh->agent_response_len; + } + s->agent_response = (unsigned char *) r; + if (s->agent_response && s->agent_responselen >= 5 && + s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { + int keyi; + unsigned char *p; + p = s->agent_response + 5; + s->nkeys = toint(GET_32BIT(p)); + + /* + * Vet the Pageant response to ensure that the key + * count and blob lengths make sense. + */ + if (s->nkeys < 0) { + logeventf(ssh, "Pageant response contained a negative" + " key count %d", s->nkeys); + s->nkeys = 0; + goto done_agent_query; + } else { + unsigned char *q = p + 4; + int lenleft = s->agent_responselen - 5 - 4; + + for (keyi = 0; keyi < s->nkeys; keyi++) { + int bloblen, commentlen; + if (lenleft < 4) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + bloblen = toint(GET_32BIT(q)); + if (bloblen < 0 || bloblen > lenleft) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + lenleft -= 4 + bloblen; + q += 4 + bloblen; + commentlen = toint(GET_32BIT(q)); + if (commentlen < 0 || commentlen > lenleft) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + lenleft -= 4 + commentlen; + q += 4 + commentlen; + } + } + + p += 4; + logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); + if (s->publickey_blob) { + /* See if configured key is in agent. */ + for (keyi = 0; keyi < s->nkeys; keyi++) { + s->pklen = toint(GET_32BIT(p)); + if (s->pklen == s->publickey_bloblen && + !memcmp(p+4, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", keyi); + s->keyi = keyi; + s->pkblob_in_agent = p; + break; + } + p += 4 + s->pklen; + p += toint(GET_32BIT(p)) + 4; /* comment */ + } + if (!s->pkblob_in_agent) { + logevent("Configured key file not in Pageant"); + s->nkeys = 0; + } + } + } else { + logevent("Failed to get reply from Pageant"); + } + done_agent_query:; + } + + } + + /* + * We repeat this whole loop, including the username prompt, + * until we manage a successful authentication. If the user + * types the wrong _password_, they can be sent back to the + * beginning to try another username, if this is configured on. + * (If they specify a username in the config, they are never + * asked, even if they do give a wrong password.) + * + * I think this best serves the needs of + * + * - the people who have no configuration, no keys, and just + * want to try repeated (username,password) pairs until they + * type both correctly + * + * - people who have keys and configuration but occasionally + * need to fall back to passwords + * + * - people with a key held in Pageant, who might not have + * logged in to a particular machine before; so they want to + * type a username, and then _either_ their key will be + * accepted, _or_ they will type a password. If they mistype + * the username they will want to be able to get back and + * retype it! + */ + s->got_username = FALSE; + while (!s->we_are_in) { + /* + * Get a username. + */ + if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) { + /* + * We got a username last time round this loop, and + * with change_username turned off we don't try to get + * it again. + */ + } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStopV; + } + ssh->username = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + char *stuff; + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { + stuff = dupprintf("Using username \"%s\".\r\n", ssh->username); + c_write_str(ssh, stuff); + sfree(stuff); + } + } + s->got_username = TRUE; + + /* + * Send an authentication request using method "none": (a) + * just in case it succeeds, and (b) so that we know what + * authentication methods we can usefully try next. + */ + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */ + ssh2_pkt_addstring(s->pktout, "none"); /* method */ + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_NONE; + s->gotit = FALSE; + s->we_are_in = FALSE; + + s->tried_pubkey_config = FALSE; + s->kbd_inter_refused = FALSE; + + /* Reset agent request state. */ + s->done_agent = FALSE; + if (s->agent_response) { + if (s->pkblob_in_agent) { + s->agentp = s->pkblob_in_agent; + } else { + s->agentp = s->agent_response + 5 + 4; + s->keyi = 0; + } + } + + while (1) { + char *methods = NULL; + int methlen = 0; + + /* + * Wait for the result of the last authentication request. + */ + if (!s->gotit) + crWaitUntilV(pktin); + /* + * Now is a convenient point to spew any banner material + * that we've accumulated. (This should ensure that when + * we exit the auth loop, we haven't any left to deal + * with.) + */ + { + int size = bufchain_size(&ssh->banner); + /* + * Don't show the banner if we're operating in + * non-verbose non-interactive mode. (It's probably + * a script, which means nobody will read the + * banner _anyway_, and moreover the printing of + * the banner will screw up processing on the + * output of (say) plink.) + */ + if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { + char *banner = snewn(size, char); + bufchain_fetch(&ssh->banner, banner, size); + c_write_untrusted(ssh, banner, size); + sfree(banner); + } + bufchain_clear(&ssh->banner); + } + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + logevent("Access granted"); + s->we_are_in = s->userauth_success = TRUE; + break; + } + + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) { + bombout(("Strange packet received during authentication: " + "type %d", pktin->type)); + crStopV; + } + + s->gotit = FALSE; + + /* + * OK, we're now sitting on a USERAUTH_FAILURE message, so + * we can look at the string in it and know what we can + * helpfully try next. + */ + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + ssh_pkt_getstring(pktin, &methods, &methlen); + if (!ssh2_pkt_getbool(pktin)) { + /* + * We have received an unequivocal Access + * Denied. This can translate to a variety of + * messages, or no message at all. + * + * For forms of authentication which are attempted + * implicitly, by which I mean without printing + * anything in the window indicating that we're + * trying them, we should never print 'Access + * denied'. + * + * If we do print a message saying that we're + * attempting some kind of authentication, it's OK + * to print a followup message saying it failed - + * but the message may sometimes be more specific + * than simply 'Access denied'. + * + * Additionally, if we'd just tried password + * authentication, we should break out of this + * whole loop so as to go back to the username + * prompt (iff we're configured to allow + * username change attempts). + */ + if (s->type == AUTH_TYPE_NONE) { + /* do nothing */ + } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD || + s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) { + if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD) + c_write_str(ssh, "Server refused our key\r\n"); + logevent("Server refused our key"); + } else if (s->type == AUTH_TYPE_PUBLICKEY) { + /* This _shouldn't_ happen except by a + * protocol bug causing client and server to + * disagree on what is a correct signature. */ + c_write_str(ssh, "Server refused public-key signature" + " despite accepting key!\r\n"); + logevent("Server refused public-key signature" + " despite accepting key!"); + } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { + /* quiet, so no c_write */ + logevent("Server refused keyboard-interactive authentication"); + } else if (s->type==AUTH_TYPE_GSSAPI) { + /* always quiet, so no c_write */ + /* also, the code down in the GSSAPI block has + * already logged this in the Event Log */ + } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { + logevent("Keyboard-interactive authentication failed"); + c_write_str(ssh, "Access denied\r\n"); + } else { + assert(s->type == AUTH_TYPE_PASSWORD); + logevent("Password authentication failed"); + c_write_str(ssh, "Access denied\r\n"); + + if (conf_get_int(ssh->conf, CONF_change_username)) { + /* XXX perhaps we should allow + * keyboard-interactive to do this too? */ + s->we_are_in = FALSE; + break; + } + } + } else { + c_write_str(ssh, "Further authentication required\r\n"); + logevent("Further authentication required"); + } + + s->can_pubkey = + in_commasep_string("publickey", methods, methlen); + s->can_passwd = + in_commasep_string("password", methods, methlen); + s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) && + in_commasep_string("keyboard-interactive", methods, methlen); +#ifndef NO_GSSAPI + if (conf_get_int(ssh->conf, CONF_try_gssapi_auth) && + in_commasep_string("gssapi-with-mic", methods, methlen)) { + /* Try loading the GSS libraries and see if we + * have any. */ + if (!ssh->gsslibs) + ssh->gsslibs = ssh_gss_setup(ssh->conf); + s->can_gssapi = (ssh->gsslibs->nlibraries > 0); + } else { + /* No point in even bothering to try to load the + * GSS libraries, if the user configuration and + * server aren't both prepared to attempt GSSAPI + * auth in the first place. */ + s->can_gssapi = FALSE; + } +#endif + } + + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + + if (s->can_pubkey && !s->done_agent && s->nkeys) { + + /* + * Attempt public-key authentication using a key from Pageant. + */ + + ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + + logeventf(ssh, "Trying Pageant key #%d", s->keyi); + + /* Unpack key from agent response */ + s->pklen = toint(GET_32BIT(s->agentp)); + s->agentp += 4; + s->pkblob = (char *)s->agentp; + s->agentp += s->pklen; + s->alglen = toint(GET_32BIT(s->pkblob)); + s->alg = s->pkblob + 4; + s->commentlen = toint(GET_32BIT(s->agentp)); + s->agentp += 4; + s->commentp = (char *)s->agentp; + s->agentp += s->commentlen; + /* s->agentp now points at next key, if any */ + + /* See if server will accept it */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + + /* Offer of key refused. */ + s->gotit = TRUE; + + } else { + + void *vret; + + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with " + "public key \""); + c_write(ssh, s->commentp, s->commentlen); + c_write_str(ssh, "\" from agent\r\n"); + } + + /* + * Server is willing to accept the key. + * Construct a SIGN_REQUEST. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); + + /* Ask agent for signature. */ + s->siglen = s->pktout->length - 5 + 4 + + ssh->v2_session_id_len; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + s->siglen -= 4; + s->len = 1; /* message type */ + s->len += 4 + s->pklen; /* key blob */ + s->len += 4 + s->siglen; /* data to sign */ + s->len += 4; /* flags */ + s->agentreq = snewn(4 + s->len, char); + PUT_32BIT(s->agentreq, s->len); + s->q = s->agentreq + 4; + *s->q++ = SSH2_AGENTC_SIGN_REQUEST; + PUT_32BIT(s->q, s->pklen); + s->q += 4; + memcpy(s->q, s->pkblob, s->pklen); + s->q += s->pklen; + PUT_32BIT(s->q, s->siglen); + s->q += 4; + /* Now the data to be signed... */ + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(s->q, ssh->v2_session_id_len); + s->q += 4; + } + memcpy(s->q, ssh->v2_session_id, + ssh->v2_session_id_len); + s->q += ssh->v2_session_id_len; + memcpy(s->q, s->pktout->data + 5, + s->pktout->length - 5); + s->q += s->pktout->length - 5; + /* And finally the (zero) flags word. */ + PUT_32BIT(s->q, 0); + if (!agent_query(s->agentreq, s->len + 4, + &vret, &s->retlen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server" + " while waiting for agent" + " response")); + crStopV; + } + } while (pktin || inlen > 0); + vret = ssh->agent_response; + s->retlen = ssh->agent_response_len; + } + s->ret = vret; + sfree(s->agentreq); + if (s->ret) { + if (s->retlen >= 9 && + s->ret[4] == SSH2_AGENT_SIGN_RESPONSE && + GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) { + logevent("Sending Pageant's response"); + ssh2_add_sigblob(ssh, s->pktout, + s->pkblob, s->pklen, + s->ret + 9, + GET_32BIT(s->ret + 5)); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + /* FIXME: less drastic response */ + bombout(("Pageant failed to answer challenge")); + crStopV; + } + } + } + + /* Do we have any keys left to try? */ + if (s->pkblob_in_agent) { + s->done_agent = TRUE; + s->tried_pubkey_config = TRUE; + } else { + s->keyi++; + if (s->keyi >= s->nkeys) + s->done_agent = TRUE; + } + + } else if (s->can_pubkey && s->publickey_blob && + !s->tried_pubkey_config) { + + struct ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ + + ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + + s->tried_pubkey_config = TRUE; + + /* + * Try the public key supplied in the configuration. + * + * First, offer the public blob to see if the server is + * willing to accept it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); + /* no signature included */ + ssh2_pkt_addstring(s->pktout, s->publickey_algorithm); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, + (char *)s->publickey_blob, + s->publickey_bloblen); + ssh2_pkt_send(ssh, s->pktout); + logevent("Offered public key"); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + /* Key refused. Give up. */ + s->gotit = TRUE; /* reconsider message next loop */ + s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; + continue; /* process this new message */ + } + logevent("Offer of public key accepted"); + + /* + * Actually attempt a serious authentication using + * the key. + */ + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with public key \""); + c_write_str(ssh, s->publickey_comment); + c_write_str(ssh, "\"\r\n"); + } + key = NULL; + while (!key) { + const char *error; /* not live over crReturn */ + if (s->publickey_encrypted) { + /* + * Get a passphrase from the user. + */ + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, + in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, + "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + passphrase = + dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + key = ssh2_load_userkey(s->keyfile, passphrase, &error); + if (passphrase) { + /* burn the evidence */ + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { + if (passphrase && + (key == SSH2_WRONG_PASSPHRASE)) { + c_write_str(ssh, "Wrong passphrase\r\n"); + key = NULL; + /* and loop again */ + } else { + c_write_str(ssh, "Unable to load private key ("); + c_write_str(ssh, error); + c_write_str(ssh, ")\r\n"); + key = NULL; + break; /* try something else */ + } + } + } + + if (key) { + unsigned char *pkblob, *sigblob, *sigdata; + int pkblob_len, sigblob_len, sigdata_len; + int p; + + /* + * We have loaded the private key and the server + * has announced that it's willing to accept it. + * Hallelujah. Generate a signature and send it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, TRUE); + /* signature follows */ + ssh2_pkt_addstring(s->pktout, key->alg->name); + pkblob = key->alg->public_blob(key->data, + &pkblob_len); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, + pkblob_len); + + /* + * The data to be signed is: + * + * string session-id + * + * followed by everything so far placed in the + * outgoing packet. + */ + sigdata_len = s->pktout->length - 5 + 4 + + ssh->v2_session_id_len; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + sigdata_len -= 4; + sigdata = snewn(sigdata_len, unsigned char); + p = 0; + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(sigdata+p, ssh->v2_session_id_len); + p += 4; + } + memcpy(sigdata+p, ssh->v2_session_id, + ssh->v2_session_id_len); + p += ssh->v2_session_id_len; + memcpy(sigdata+p, s->pktout->data + 5, + s->pktout->length - 5); + p += s->pktout->length - 5; + assert(p == sigdata_len); + sigblob = key->alg->sign(key->data, (char *)sigdata, + sigdata_len, &sigblob_len); + ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len, + sigblob, sigblob_len); + sfree(pkblob); + sfree(sigblob); + sfree(sigdata); + + ssh2_pkt_send(ssh, s->pktout); + logevent("Sent public key signature"); + s->type = AUTH_TYPE_PUBLICKEY; + key->alg->freekey(key->data); + sfree(key->comment); + sfree(key); + } + +#ifndef NO_GSSAPI + } else if (s->can_gssapi && !s->tried_gssapi) { + + /* GSSAPI Authentication */ + + int micoffset, len; + char *data; + Ssh_gss_buf mic; + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi = TRUE; + s->gotit = TRUE; + ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; + + /* + * Pick the highest GSS library on the preference + * list. + */ + { + int i, j; + s->gsslib = NULL; + for (i = 0; i < ngsslibs; i++) { + int want_id = conf_get_int_int(ssh->conf, + CONF_ssh_gsslist, i); + for (j = 0; j < ssh->gsslibs->nlibraries; j++) + if (ssh->gsslibs->libraries[j].id == want_id) { + s->gsslib = &ssh->gsslibs->libraries[j]; + goto got_gsslib; /* double break */ + } + } + got_gsslib: + /* + * We always expect to have found something in + * the above loop: we only came here if there + * was at least one viable GSS library, and the + * preference list should always mention + * everything and only change the order. + */ + assert(s->gsslib); + } + + if (s->gsslib->gsslogmsg) + logevent(s->gsslib->gsslogmsg); + + /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_addstring(s->pktout, "gssapi-with-mic"); + logevent("Attempting GSSAPI authentication"); + + /* add mechanism info */ + s->gsslib->indicate_mech(s->gsslib, &s->gss_buf); + + /* number of GSSAPI mechanisms */ + ssh2_pkt_adduint32(s->pktout,1); + + /* length of OID + 2 */ + ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2); + ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE); + + /* length of OID */ + ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length); + + ssh_pkt_adddata(s->pktout, s->gss_buf.value, + s->gss_buf.length); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { + logevent("GSSAPI authentication request refused"); + continue; + } + + /* check returned packet ... */ + + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + if (s->gss_rcvtok.length != s->gss_buf.length + 2 || + ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || + ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || + memcmp((char *)s->gss_rcvtok.value + 2, + s->gss_buf.value,s->gss_buf.length) ) { + logevent("GSSAPI authentication - wrong response from server"); + continue; + } + + /* now start running */ + s->gss_stat = s->gsslib->import_name(s->gsslib, + ssh->fullhostname, + &s->gss_srv_name); + if (s->gss_stat != SSH_GSS_OK) { + if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) + logevent("GSSAPI import name failed - Bad service name"); + else + logevent("GSSAPI import name failed"); + continue; + } + + /* fetch TGT into GSS engine */ + s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx); + + if (s->gss_stat != SSH_GSS_OK) { + logevent("GSSAPI authentication failed to get credentials"); + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + continue; + } + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + + /* now enter the loop */ + do { + s->gss_stat = s->gsslib->init_sec_context + (s->gsslib, + &s->gss_ctx, + s->gss_srv_name, + conf_get_int(ssh->conf, CONF_gssapifwd), + &s->gss_rcvtok, + &s->gss_sndtok); + + if (s->gss_stat!=SSH_GSS_S_COMPLETE && + s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { + logevent("GSSAPI authentication initialisation failed"); + + if (s->gsslib->display_status(s->gsslib, s->gss_ctx, + &s->gss_buf) == SSH_GSS_OK) { + logevent(s->gss_buf.value); + sfree(s->gss_buf.value); + } + + break; + } + logevent("GSSAPI authentication initialised"); + + /* Client and server now exchange tokens until GSSAPI + * no longer says CONTINUE_NEEDED */ + + if (s->gss_sndtok.length != 0) { + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length); + ssh2_pkt_send(ssh, s->pktout); + s->gsslib->free_tok(s->gsslib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { + logevent("GSSAPI authentication - bad server response"); + s->gss_stat = SSH_GSS_FAILURE; + break; + } + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + } + } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (s->gss_stat != SSH_GSS_OK) { + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + continue; + } + logevent("GSSAPI authentication loop finished OK"); + + /* Now send the MIC */ + + s->pktout = ssh2_pkt_init(0); + micoffset = s->pktout->length; + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len); + ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST); + ssh_pkt_addstring(s->pktout, ssh->username); + ssh_pkt_addstring(s->pktout, "ssh-connection"); + ssh_pkt_addstring(s->pktout, "gssapi-with-mic"); + + s->gss_buf.value = (char *)s->pktout->data + micoffset; + s->gss_buf.length = s->pktout->length - micoffset; + + s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic); + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, mic.value, mic.length); + ssh2_pkt_send(ssh, s->pktout); + s->gsslib->free_mic(s->gsslib, &mic); + + s->gotit = FALSE; + + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + continue; +#endif + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + + /* + * Keyboard-interactive authentication. + */ + + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + ssh->pkt_actx = SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); + /* method */ + ssh2_pkt_addstring(s->pktout, ""); /* lang */ + ssh2_pkt_addstring(s->pktout, ""); /* submethods */ + ssh2_pkt_send(ssh, s->pktout); + + logevent("Attempting keyboard-interactive authentication"); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + /* Server is not willing to do keyboard-interactive + * at all (or, bizarrely but legally, accepts the + * user without actually issuing any prompts). + * Give up on it entirely. */ + s->gotit = TRUE; + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ + continue; + } + + /* + * Loop while the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + ssh_pkt_getstring(pktin, &name, &name_len); + ssh_pkt_getstring(pktin, &inst, &inst_len); + ssh_pkt_getstring(pktin, &lang, &lang_len); + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = ssh_pkt_getuint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + char *prompt; + int prompt_len; + int echo; + static char noprompt[] = + ": "; + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + echo = ssh2_pkt_getbool(pktin); + if (!prompt_len) { + prompt = noprompt; + prompt_len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, + dupprintf("%.*s", prompt_len, prompt), + echo); + } + + if (name_len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", name_len, name); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; + } + /* We add a prefix to try to make it clear that a prompt + * has come from the server. + * FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + /* Special case: for reasons best known to themselves, + * some servers send k-i requests with no prompts and + * nothing to display. Keep quiet in this case. */ + if (s->num_prompts || name_len || inst_len) { + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; + } else { + s->cur_prompt->instr_reqd = FALSE; + } + + /* + * Display any instructions, and get the user's + * response(s). + */ + { + int ret; /* not live over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + } + + /* + * Send the response(s) to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_pkt_adduint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[i]->result); + } + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + + /* + * Free the prompts structure from this iteration. + * If there's another, a new one will be allocated + * when we return to the top of this while loop. + */ + free_prompts(s->cur_prompt); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crWaitUntilV(pktin); + + } + + /* + * We should have SUCCESS or FAILURE now. + */ + s->gotit = TRUE; + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + int ret; /* not live over crReturn */ + int changereq_first_time; /* not live over crReturn */ + + ssh->pkt_actx = SSH2_PKTCTX_PASSWORD; + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + ssh->username, + ssh->savedhost), + FALSE); + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + + /* + * Send the password packet. + * + * We pad out the password packet to 256 bytes to make + * it harder for an attacker to find the length of the + * user's password. + * + * Anyone using a password longer than 256 bytes + * probably doesn't have much to worry about from + * people who find out how long their password is! + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, FALSE); + ssh2_pkt_addstring(s->pktout, s->password); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + logevent("Sent password"); + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crWaitUntilV(pktin); + changereq_first_time = TRUE; + + while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + + /* + * We're being asked for a new password + * (perhaps not for the first time). + * Loop until the server accepts it. + */ + + int got_new = FALSE; /* not live over crReturn */ + char *prompt; /* not live over crReturn */ + int prompt_len; /* not live over crReturn */ + + { + char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + logevent(msg); + c_write_str(ssh, msg); + c_write_str(ssh, "\r\n"); + } + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = + dupprintf("%.*s", prompt_len, NULLTOEMPTY(prompt)); + s->cur_prompt->instr_reqd = TRUE; + /* + * There's no explicit requirement in the protocol + * for the "old" passwords in the original and + * password-change messages to be the same, and + * apparently some Cisco kit supports password change + * by the user entering a blank password originally + * and the real password subsequently, so, + * reluctantly, we prompt for the old password again. + * + * (On the other hand, some servers don't even bother + * to check this field.) + */ + add_prompt(s->cur_prompt, + dupstr("Current password (blank for previously entered password): "), + FALSE); + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + FALSE); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + FALSE); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + smemclr(s->password, strlen(s->password)); + sfree(s->password); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + + /* + * If the user specified a new original password + * (IYSWIM), overwrite any previously specified + * one. + * (A side effect is that the user doesn't have to + * re-enter it if they louse up the new password.) + */ + if (s->cur_prompt->prompts[0]->result[0]) { + smemclr(s->password, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = + dupstr(s->cur_prompt->prompts[0]->result); + } + + /* + * Check the two new passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[1]->result, + s->cur_prompt->prompts[2]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + c_write_str(ssh, "Passwords do not match\r\n"); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, ssh->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, TRUE); + ssh2_pkt_addstring(s->pktout, s->password); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[1]->result); + free_prompts(s->cur_prompt); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + logevent("Sent new password"); + + /* + * Now see what the server has to say about it. + * (If it's CHANGEREQ again, it's not happy with the + * new password.) + */ + crWaitUntilV(pktin); + changereq_first_time = FALSE; + + } + + /* + * We need to reexamine the current pktin at the top + * of the loop. Either: + * - we weren't asked to change password at all, in + * which case it's a SUCCESS or FAILURE with the + * usual meaning + * - we sent a new password, and the server was + * either OK with it (SUCCESS or FAILURE w/partial + * success) or unhappy with the _old_ password + * (FAILURE w/o partial success) + * In any of these cases, we go back to the top of + * the loop and start again. + */ + s->gotit = TRUE; + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + smemclr(s->password, strlen(s->password)); + sfree(s->password); + + } else { + char *str = dupprintf("No supported authentication methods available" + " (server sent: %.*s)", + methlen, methods); + + ssh_disconnect(ssh, str, + "No supported authentication methods available", + SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + FALSE); + sfree(str); + + crStopV; + + } + + } + } + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; + + /* Clear up various bits and pieces from authentication. */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + if (s->agent_response) + sfree(s->agent_response); + + if (s->userauth_success && !ssh->bare_connection) { + /* + * We've just received USERAUTH_SUCCESS, and we haven't sent any + * packets since. Signal the transport layer to consider enacting + * delayed compression. + * + * (Relying on we_are_in is not sufficient, as + * draft-miller-secsh-compression-delayed is quite clear that it + * triggers on USERAUTH_SUCCESS specifically, and we_are_in can + * become set for other reasons.) + */ + do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); + } + + ssh->channels = newtree234(ssh_channelcmp); + + /* + * Set up handlers for some connection protocol messages, so we + * don't have to handle them repeatedly in this coroutine. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = + ssh2_msg_channel_window_adjust; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = + ssh2_msg_global_request; + + /* + * Create the main session channel. + */ + if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) { + ssh->mainchan = NULL; + } else { + ssh->mainchan = snew(struct ssh_channel); + ssh->mainchan->ssh = ssh; + ssh2_channel_init(ssh->mainchan); + + if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { + /* + * Just start a direct-tcpip channel and use it as the main + * channel. + */ + ssh_send_port_open(ssh->mainchan, + conf_get_str(ssh->conf, CONF_ssh_nc_host), + conf_get_int(ssh->conf, CONF_ssh_nc_port), + "main channel"); + ssh->ncmode = TRUE; + } else { + s->pktout = ssh2_chanopen_init(ssh->mainchan, "session"); + logevent("Opening session as main channel"); + ssh2_pkt_send(ssh, s->pktout); + ssh->ncmode = FALSE; + } + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + bombout(("Server refused to open channel")); + crStopV; + /* FIXME: error data comes back in FAILURE packet */ + } + if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { + bombout(("Server's channel confirmation cited wrong channel")); + crStopV; + } + ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); + ssh->mainchan->halfopen = FALSE; + ssh->mainchan->type = CHAN_MAINSESSION; + ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); + ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); + logevent("Opened main channel"); + } + + /* + * Now we have a channel, make dispatch table entries for + * general channel-based messages. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = + ssh2_msg_channel_data; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = + ssh2_msg_channel_open_confirmation; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = + ssh2_msg_channel_open_failure; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = + ssh2_msg_channel_request; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = + ssh2_msg_channel_open; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response; + + /* + * Now the connection protocol is properly up and running, with + * all those dispatch table entries, so it's safe to let + * downstreams start trying to open extra channels through us. + */ + if (ssh->connshare) + share_activate(ssh->connshare, ssh->v_s); + + if (ssh->mainchan && ssh_is_simple(ssh)) { + /* + * This message indicates to the server that we promise + * not to try to run any other channel in parallel with + * this one, so it's safe for it to advertise a very large + * window and leave the flow control to TCP. + */ + s->pktout = ssh2_chanreq_init(ssh->mainchan, + "simple@putty.projects.tartarus.org", + NULL, NULL); + ssh2_pkt_send(ssh, s->pktout); + } + + /* + * Enable port forwardings. + */ + ssh_setup_portfwd(ssh, ssh->conf); + + if (ssh->mainchan && !ssh->ncmode) { + /* + * Send the CHANNEL_REQUESTS for the main session channel. + * Each one is handled by its own little asynchronous + * co-routine. + */ + + /* Potentially enable X11 forwarding. */ + if (conf_get_int(ssh->conf, CONF_x11_forward)) { + ssh->x11disp = + x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), + ssh->conf); + if (!ssh->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + logevent("X11 forwarding not enabled: unable to" + " initialise X display"); + } else { + ssh->x11auth = x11_invent_fake_auth + (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); + ssh->x11auth->disp = ssh->x11disp; + + ssh2_setup_x11(ssh->mainchan, NULL, NULL); + } + } + + /* Potentially enable agent forwarding. */ + if (ssh_agent_forwarding_permitted(ssh)) + ssh2_setup_agent(ssh->mainchan, NULL, NULL); + + /* Now allocate a pty for the session. */ + if (!conf_get_int(ssh->conf, CONF_nopty)) + ssh2_setup_pty(ssh->mainchan, NULL, NULL); + + /* Send environment variables. */ + ssh2_setup_env(ssh->mainchan, NULL, NULL); + + /* + * Start a shell or a remote command. We may have to attempt + * this twice if the config data has provided a second choice + * of command. + */ + while (1) { + int subsys; + char *cmd; + + if (ssh->fallback_cmd) { + subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2); + cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); + } else { + subsys = conf_get_int(ssh->conf, CONF_ssh_subsys); + cmd = conf_get_str(ssh->conf, CONF_remote_cmd); + } + + if (subsys) { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem", + ssh2_response_authconn, NULL); + ssh2_pkt_addstring(s->pktout, cmd); + } else if (*cmd) { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec", + ssh2_response_authconn, NULL); + ssh2_pkt_addstring(s->pktout, cmd); + } else { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell", + ssh2_response_authconn, NULL); + } + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to shell/command request:" + " packet type %d", pktin->type)); + crStopV; + } + /* + * We failed to start the command. If this is the + * fallback command, we really are finished; if it's + * not, and if the fallback command exists, try falling + * back to it before complaining. + */ + if (!ssh->fallback_cmd && + *conf_get_str(ssh->conf, CONF_remote_cmd2)) { + logevent("Primary command failed; attempting fallback"); + ssh->fallback_cmd = TRUE; + continue; + } + bombout(("Server refused to start a shell/command")); + crStopV; + } else { + logevent("Started a shell/command"); + } + break; + } + } else { + ssh->editing = ssh->echoing = TRUE; + } + + ssh->state = SSH_STATE_SESSION; + if (ssh->size_needed) + ssh_size(ssh, ssh->term_width, ssh->term_height); + if (ssh->eof_needed) + ssh_special(ssh, TS_EOF); + + /* + * Transfer data! + */ + if (ssh->ldisc) + ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + if (ssh->mainchan) + ssh->send_ok = 1; + while (1) { + crReturnV; + s->try_send = FALSE; + if (pktin) { + + /* + * _All_ the connection-layer packets we expect to + * receive are now handled by the dispatch table. + * Anything that reaches here must be bogus. + */ + + bombout(("Strange packet received: type %d", pktin->type)); + crStopV; + } else if (ssh->mainchan) { + /* + * We have spare data. Add it to the channel buffer. + */ + ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen); + s->try_send = TRUE; + } + if (s->try_send) { + int i; + struct ssh_channel *c; + /* + * Try to send data on all channels if we can. + */ + for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) + if (c->type != CHAN_SHARING) + ssh2_try_send_and_unthrottle(ssh, c); + } + } + + crFinishV; +} + +/* + * Handlers for SSH-2 messages that might arrive at any moment. + */ +static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *buf, *msg; + int reason, msglen; + + reason = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { + buf = dupprintf("Received disconnect message (%s)", + ssh2_disconnect_reasons[reason]); + } else { + buf = dupprintf("Received disconnect message (unknown" + " type %d)", reason); + } + logevent(buf); + sfree(buf); + buf = dupprintf("Disconnection message text: %.*s", + msglen, NULLTOEMPTY(msg)); + logevent(buf); + bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"", + reason, + (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown", + msglen, NULLTOEMPTY(msg))); + sfree(buf); +} + +static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) +{ + /* log the debug message */ + char *msg; + int msglen; + + /* XXX maybe we should actually take notice of the return value */ + ssh2_pkt_getbool(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); +} + +static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin) +{ + do_ssh2_transport(ssh, NULL, 0, pktin); +} + +/* + * Called if we receive a packet that isn't allowed by the protocol. + * This only applies to packets whose meaning PuTTY understands. + * Entirely unknown packets are handled below. + */ +static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin) +{ + char *buf = dupprintf("Server protocol violation: unexpected %s packet", + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + pktin->type)); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); +} + +static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) +{ + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); + ssh2_pkt_adduint32(pktout, pktin->sequence); + /* + * UNIMPLEMENTED messages MUST appear in the same order as the + * messages they respond to. Hence, never queue them. + */ + ssh2_pkt_send_noqueue(ssh, pktout); +} + +/* + * Handle the top-level SSH-2 protocol. + */ +static void ssh2_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages cause SSH2_MSG_UNIMPLEMENTED. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + + /* + * Initially, we only accept transport messages (and a few generic + * ones). do_ssh2_authconn will add more when it starts. + * Messages that are understood but not currently acceptable go to + * ssh2_msg_unexpected. + */ + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport; + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; + + /* + * These messages have a special handler from the start. + */ + ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; + ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */ + ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; +} + +static void ssh2_bare_connection_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages cause SSH2_MSG_UNIMPLEMENTED. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + + /* + * Initially, we set all ssh-connection messages to 'unexpected'; + * do_ssh2_authconn will fill things in properly. We also handle a + * couple of messages from the transport protocol which aren't + * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG, + * DISCONNECT). + */ + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; + + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; + + /* + * These messages have a special handler from the start. + */ + ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; + ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; + ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; +} + +static void ssh2_timer(void *ctx, unsigned long now) +{ + Ssh ssh = (Ssh)ctx; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (!ssh->kex_in_progress && !ssh->bare_connection && + conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 && + now == ssh->next_rekey) { + do_ssh2_transport(ssh, "timeout", -1, NULL); + } +} + +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin) { + ssh->incoming_data_size += pktin->encrypted_len; + if (!ssh->kex_in_progress && + ssh->max_data_size != 0 && + ssh->incoming_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data received", -1, NULL); + } + + if (pktin) + ssh->packet_dispatch[pktin->type](ssh, pktin); + else if (!ssh->protocol_initial_phase_done) + do_ssh2_transport(ssh, in, inlen, pktin); + else + do_ssh2_authconn(ssh, in, inlen, pktin); +} + +static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin) + ssh->packet_dispatch[pktin->type](ssh, pktin); + else + do_ssh2_authconn(ssh, in, inlen, pktin); +} + +static void ssh_cache_conf_values(Ssh ssh) +{ + ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata); +} + +/* + * Called to set up the connection. + * + * Returns an error message, or NULL on success. + */ +static const char *ssh_init(void *frontend_handle, void **backend_handle, + Conf *conf, char *host, int port, char **realhost, + int nodelay, int keepalive) +{ + const char *p; + Ssh ssh; + + ssh = snew(struct ssh_tag); + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); + ssh->version = 0; /* when not ready yet */ + ssh->s = NULL; + ssh->cipher = NULL; + ssh->v1_cipher_ctx = NULL; + ssh->crcda_ctx = NULL; + ssh->cscipher = NULL; + ssh->cs_cipher_ctx = NULL; + ssh->sccipher = NULL; + ssh->sc_cipher_ctx = NULL; + ssh->csmac = NULL; + ssh->cs_mac_ctx = NULL; + ssh->scmac = NULL; + ssh->sc_mac_ctx = NULL; + ssh->cscomp = NULL; + ssh->cs_comp_ctx = NULL; + ssh->sccomp = NULL; + ssh->sc_comp_ctx = NULL; + ssh->kex = NULL; + ssh->kex_ctx = NULL; + ssh->hostkey = NULL; + ssh->hostkey_str = NULL; + ssh->exitcode = -1; + ssh->close_expected = FALSE; + ssh->clean_exit = FALSE; + ssh->state = SSH_STATE_PREPACKET; + ssh->size_needed = FALSE; + ssh->eof_needed = FALSE; + ssh->ldisc = NULL; + ssh->logctx = NULL; + ssh->deferred_send_data = NULL; + ssh->deferred_len = 0; + ssh->deferred_size = 0; + ssh->fallback_cmd = 0; + ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + ssh->x11disp = NULL; + ssh->x11auth = NULL; + ssh->x11authtree = newtree234(x11_authcmp); + ssh->v1_compressing = FALSE; + ssh->v2_outgoing_sequence = 0; + ssh->ssh1_rdpkt_crstate = 0; + ssh->ssh2_rdpkt_crstate = 0; + ssh->ssh2_bare_rdpkt_crstate = 0; + ssh->ssh_gotdata_crstate = 0; + ssh->do_ssh1_connection_crstate = 0; + ssh->do_ssh_init_state = NULL; + ssh->do_ssh_connection_init_state = NULL; + ssh->do_ssh1_login_state = NULL; + ssh->do_ssh2_transport_state = NULL; + ssh->do_ssh2_authconn_state = NULL; + ssh->v_c = NULL; + ssh->v_s = NULL; + ssh->mainchan = NULL; + ssh->throttled_all = 0; + ssh->v1_stdout_throttling = 0; + ssh->queue = NULL; + ssh->queuelen = ssh->queuesize = 0; + ssh->queueing = FALSE; + ssh->qhead = ssh->qtail = NULL; + ssh->deferred_rekey_reason = NULL; + bufchain_init(&ssh->queued_incoming_data); + ssh->frozen = FALSE; + ssh->username = NULL; + ssh->sent_console_eof = FALSE; + ssh->got_pty = FALSE; + ssh->bare_connection = FALSE; + ssh->X11_fwd_enabled = FALSE; + ssh->connshare = NULL; + ssh->attempting_connshare = FALSE; + + *backend_handle = ssh; + +#ifdef MSCRYPTOAPI + if (crypto_startup() == 0) + return "Microsoft high encryption pack not installed!"; +#endif + + ssh->frontend = frontend_handle; + ssh->term_width = conf_get_int(ssh->conf, CONF_width); + ssh->term_height = conf_get_int(ssh->conf, CONF_height); + + ssh->channels = NULL; + ssh->rportfwds = NULL; + ssh->portfwds = NULL; + + ssh->send_ok = 0; + ssh->editing = 0; + ssh->echoing = 0; + ssh->conn_throttle_count = 0; + ssh->overall_bufsize = 0; + ssh->fallback_cmd = 0; + + ssh->protocol = NULL; + + ssh->protocol_initial_phase_done = FALSE; + + ssh->pinger = NULL; + + ssh->incoming_data_size = ssh->outgoing_data_size = + ssh->deferred_data_size = 0L; + ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, + CONF_ssh_rekey_data)); + ssh->kex_in_progress = FALSE; + +#ifndef NO_GSSAPI + ssh->gsslibs = NULL; +#endif + + random_ref(); /* do this now - may be needed by sharing setup code */ + + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); + if (p != NULL) { + random_unref(); + return p; + } + + return NULL; +} + +static void ssh_free(void *handle) +{ + Ssh ssh = (Ssh) handle; + struct ssh_channel *c; + struct ssh_rportfwd *pf; + struct X11FakeAuth *auth; + + if (ssh->v1_cipher_ctx) + ssh->cipher->free_context(ssh->v1_cipher_ctx); + if (ssh->cs_cipher_ctx) + ssh->cscipher->free_context(ssh->cs_cipher_ctx); + if (ssh->sc_cipher_ctx) + ssh->sccipher->free_context(ssh->sc_cipher_ctx); + if (ssh->cs_mac_ctx) + ssh->csmac->free_context(ssh->cs_mac_ctx); + if (ssh->sc_mac_ctx) + ssh->scmac->free_context(ssh->sc_mac_ctx); + if (ssh->cs_comp_ctx) { + if (ssh->cscomp) + ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); + else + zlib_compress_cleanup(ssh->cs_comp_ctx); + } + if (ssh->sc_comp_ctx) { + if (ssh->sccomp) + ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); + else + zlib_decompress_cleanup(ssh->sc_comp_ctx); + } + if (ssh->kex_ctx) + dh_cleanup(ssh->kex_ctx); + sfree(ssh->savedhost); + + while (ssh->queuelen-- > 0) + ssh_free_packet(ssh->queue[ssh->queuelen]); + sfree(ssh->queue); + + while (ssh->qhead) { + struct queued_handler *qh = ssh->qhead; + ssh->qhead = qh->next; + sfree(qh); + } + ssh->qhead = ssh->qtail = NULL; + + if (ssh->channels) { + while ((c = delpos234(ssh->channels, 0)) != NULL) { + switch (c->type) { + case CHAN_X11: + if (c->u.x11.xconn != NULL) + x11_close(c->u.x11.xconn); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + if (c->u.pfd.pf != NULL) + pfd_close(c->u.pfd.pf); + break; + } + if (ssh->version == 2) { + struct outstanding_channel_request *ocr, *nocr; + ocr = c->v.v2.chanreq_head; + while (ocr) { + ocr->handler(c, NULL, ocr->ctx); + nocr = ocr->next; + sfree(ocr); + ocr = nocr; + } + bufchain_clear(&c->v.v2.outbuffer); + } + sfree(c); + } + freetree234(ssh->channels); + ssh->channels = NULL; + } + + if (ssh->connshare) + sharestate_free(ssh->connshare); + + if (ssh->rportfwds) { + while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) + free_rportfwd(pf); + freetree234(ssh->rportfwds); + ssh->rportfwds = NULL; + } + sfree(ssh->deferred_send_data); + if (ssh->x11disp) + x11_free_display(ssh->x11disp); + while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) + x11_free_fake_auth(auth); + freetree234(ssh->x11authtree); + sfree(ssh->do_ssh_init_state); + sfree(ssh->do_ssh1_login_state); + sfree(ssh->do_ssh2_transport_state); + sfree(ssh->do_ssh2_authconn_state); + sfree(ssh->v_c); + sfree(ssh->v_s); + sfree(ssh->fullhostname); + sfree(ssh->hostkey_str); + if (ssh->crcda_ctx) { + crcda_free_context(ssh->crcda_ctx); + ssh->crcda_ctx = NULL; + } + if (ssh->s) + ssh_do_close(ssh, TRUE); + expire_timer_context(ssh); + if (ssh->pinger) + pinger_free(ssh->pinger); + bufchain_clear(&ssh->queued_incoming_data); + sfree(ssh->username); + conf_free(ssh->conf); +#ifndef NO_GSSAPI + if (ssh->gsslibs) + ssh_gss_cleanup(ssh->gsslibs); +#endif + sfree(ssh); + + random_unref(); +} + +/* + * Reconfigure the SSH backend. + */ +static void ssh_reconfig(void *handle, Conf *conf) +{ + Ssh ssh = (Ssh) handle; + char *rekeying = NULL, rekey_mandatory = FALSE; + unsigned long old_max_data_size; + int i, rekey_time; + + pinger_reconfig(ssh->pinger, ssh->conf, conf); + if (ssh->portfwds) + ssh_setup_portfwd(ssh, conf); + + rekey_time = conf_get_int(conf, CONF_ssh_rekey_time); + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time && + rekey_time != 0) { + unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC; + unsigned long now = GETTICKCOUNT(); + + if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) { + rekeying = "timeout shortened"; + } else { + ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); + } + } + + old_max_data_size = ssh->max_data_size; + ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, + CONF_ssh_rekey_data)); + if (old_max_data_size != ssh->max_data_size && + ssh->max_data_size != 0) { + if (ssh->outgoing_data_size > ssh->max_data_size || + ssh->incoming_data_size > ssh->max_data_size) + rekeying = "data limit lowered"; + } + + if (conf_get_int(ssh->conf, CONF_compression) != + conf_get_int(conf, CONF_compression)) { + rekeying = "compression setting changed"; + rekey_mandatory = TRUE; + } + + for (i = 0; i < CIPHER_MAX; i++) + if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) != + conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + rekeying = "cipher settings changed"; + rekey_mandatory = TRUE; + } + if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) != + conf_get_int(conf, CONF_ssh2_des_cbc)) { + rekeying = "cipher settings changed"; + rekey_mandatory = TRUE; + } + + conf_free(ssh->conf); + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); + + if (!ssh->bare_connection && rekeying) { + if (!ssh->kex_in_progress) { + do_ssh2_transport(ssh, rekeying, -1, NULL); + } else if (rekey_mandatory) { + ssh->deferred_rekey_reason = rekeying; + } + } +} + +/* + * Called to send data down the SSH connection. + */ +static int ssh_send(void *handle, char *buf, int len) +{ + Ssh ssh = (Ssh) handle; + + if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + return 0; + + ssh->protocol(ssh, (unsigned char *)buf, len, 0); + + return ssh_sendbuffer(ssh); +} + +/* + * Called to query the current amount of buffered stdin data. + */ +static int ssh_sendbuffer(void *handle) +{ + Ssh ssh = (Ssh) handle; + int override_value; + + if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + return 0; + + /* + * If the SSH socket itself has backed up, add the total backup + * size on that to any individual buffer on the stdin channel. + */ + override_value = 0; + if (ssh->throttled_all) + override_value = ssh->overall_bufsize; + + if (ssh->version == 1) { + return override_value; + } else if (ssh->version == 2) { + if (!ssh->mainchan) + return override_value; + else + return (override_value + + bufchain_size(&ssh->mainchan->v.v2.outbuffer)); + } + + return 0; +} + +/* + * Called to set the size of the window from SSH's POV. + */ +static void ssh_size(void *handle, int width, int height) +{ + Ssh ssh = (Ssh) handle; + struct Packet *pktout; + + ssh->term_width = width; + ssh->term_height = height; + + switch (ssh->state) { + case SSH_STATE_BEFORE_SIZE: + case SSH_STATE_PREPACKET: + case SSH_STATE_CLOSED: + break; /* do nothing */ + case SSH_STATE_INTERMED: + ssh->size_needed = TRUE; /* buffer for later */ + break; + case SSH_STATE_SESSION: + if (!conf_get_int(ssh->conf, CONF_nopty)) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_WINDOW_SIZE, + PKT_INT, ssh->term_height, + PKT_INT, ssh->term_width, + PKT_INT, 0, PKT_INT, 0, PKT_END); + } else if (ssh->mainchan) { + pktout = ssh2_chanreq_init(ssh->mainchan, "window-change", + NULL, NULL); + ssh2_pkt_adduint32(pktout, ssh->term_width); + ssh2_pkt_adduint32(pktout, ssh->term_height); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_send(ssh, pktout); + } + } + break; + } +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *ssh_get_specials(void *handle) +{ + static const struct telnet_special ssh1_ignore_special[] = { + {"IGNORE message", TS_NOP} + }; + static const struct telnet_special ssh2_ignore_special[] = { + {"IGNORE message", TS_NOP}, + }; + static const struct telnet_special ssh2_rekey_special[] = { + {"Repeat key exchange", TS_REKEY}, + }; + static const struct telnet_special ssh2_session_specials[] = { + {NULL, TS_SEP}, + {"Break", TS_BRK}, + /* These are the signal names defined by RFC 4254. + * They include all the ISO C signals, but are a subset of the POSIX + * required signals. */ + {"SIGINT (Interrupt)", TS_SIGINT}, + {"SIGTERM (Terminate)", TS_SIGTERM}, + {"SIGKILL (Kill)", TS_SIGKILL}, + {"SIGQUIT (Quit)", TS_SIGQUIT}, + {"SIGHUP (Hangup)", TS_SIGHUP}, + {"More signals", TS_SUBMENU}, + {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM}, + {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL}, + {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV}, + {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2}, + {NULL, TS_EXITMENU} + }; + static const struct telnet_special specials_end[] = { + {NULL, TS_EXITMENU} + }; + /* XXX review this length for any changes: */ + static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) + + lenof(ssh2_rekey_special) + + lenof(ssh2_session_specials) + + lenof(specials_end)]; + Ssh ssh = (Ssh) handle; + int i = 0; +#define ADD_SPECIALS(name) \ + do { \ + assert((i + lenof(name)) <= lenof(ssh_specials)); \ + memcpy(&ssh_specials[i], name, sizeof name); \ + i += lenof(name); \ + } while(0) + + if (ssh->version == 1) { + /* Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + ADD_SPECIALS(ssh1_ignore_special); + } else if (ssh->version == 2) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) + ADD_SPECIALS(ssh2_ignore_special); + if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection) + ADD_SPECIALS(ssh2_rekey_special); + if (ssh->mainchan) + ADD_SPECIALS(ssh2_session_specials); + } /* else we're not ready yet */ + + if (i) { + ADD_SPECIALS(specials_end); + return ssh_specials; + } else { + return NULL; + } +#undef ADD_SPECIALS +} + +/* + * Send special codes. TS_EOF is useful for `plink', so you + * can send an EOF and collect resulting output (e.g. `plink + * hostname sort'). + */ +static void ssh_special(void *handle, Telnet_Special code) +{ + Ssh ssh = (Ssh) handle; + struct Packet *pktout; + + if (code == TS_EOF) { + if (ssh->state != SSH_STATE_SESSION) { + /* + * Buffer the EOF in case we are pre-SESSION, so we can + * send it as soon as we reach SESSION. + */ + if (code == TS_EOF) + ssh->eof_needed = TRUE; + return; + } + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_EOF, PKT_END); + } else if (ssh->mainchan) { + sshfwd_write_eof(ssh->mainchan); + ssh->send_ok = 0; /* now stop trying to read from stdin */ + } + logevent("Sent EOF message"); + } else if (code == TS_PING || code == TS_NOP) { + if (ssh->state == SSH_STATE_CLOSED + || ssh->state == SSH_STATE_PREPACKET) return; + if (ssh->version == 1) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); + } else { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + pktout = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(pktout); + ssh2_pkt_send_noqueue(ssh, pktout); + } + } + } else if (code == TS_REKEY) { + if (!ssh->kex_in_progress && !ssh->bare_connection && + ssh->version == 2) { + do_ssh2_transport(ssh, "at user request", -1, NULL); + } + } else if (code == TS_BRK) { + if (ssh->state == SSH_STATE_CLOSED + || ssh->state == SSH_STATE_PREPACKET) return; + if (ssh->version == 1) { + logevent("Unable to send BREAK signal in SSH-1"); + } else if (ssh->mainchan) { + pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL); + ssh2_pkt_adduint32(pktout, 0); /* default break length */ + ssh2_pkt_send(ssh, pktout); + } + } else { + /* Is is a POSIX signal? */ + char *signame = NULL; + if (code == TS_SIGABRT) signame = "ABRT"; + if (code == TS_SIGALRM) signame = "ALRM"; + if (code == TS_SIGFPE) signame = "FPE"; + if (code == TS_SIGHUP) signame = "HUP"; + if (code == TS_SIGILL) signame = "ILL"; + if (code == TS_SIGINT) signame = "INT"; + if (code == TS_SIGKILL) signame = "KILL"; + if (code == TS_SIGPIPE) signame = "PIPE"; + if (code == TS_SIGQUIT) signame = "QUIT"; + if (code == TS_SIGSEGV) signame = "SEGV"; + if (code == TS_SIGTERM) signame = "TERM"; + if (code == TS_SIGUSR1) signame = "USR1"; + if (code == TS_SIGUSR2) signame = "USR2"; + /* The SSH-2 protocol does in principle support arbitrary named + * signals, including signame@domain, but we don't support those. */ + if (signame) { + /* It's a signal. */ + if (ssh->version == 2 && ssh->mainchan) { + pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL); + ssh2_pkt_addstring(pktout, signame); + ssh2_pkt_send(ssh, pktout); + logeventf(ssh, "Sent signal SIG%s", signame); + } + } else { + /* Never heard of it. Do nothing */ + } + } +} + +void *new_sock_channel(void *handle, struct PortForwarding *pf) +{ + Ssh ssh = (Ssh) handle; + struct ssh_channel *c; + c = snew(struct ssh_channel); + + c->ssh = ssh; + ssh2_channel_init(c); + c->halfopen = TRUE; + c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */ + c->u.pfd.pf = pf; + add234(ssh->channels, c); + return c; +} + +unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx) +{ + struct ssh_channel *c; + c = snew(struct ssh_channel); + + c->ssh = ssh; + ssh2_channel_init(c); + c->type = CHAN_SHARING; + c->u.sharing.ctx = sharing_ctx; + add234(ssh->channels, c); + return c->localid; +} + +void ssh_delete_sharing_channel(Ssh ssh, unsigned localid) +{ + struct ssh_channel *c; + + c = find234(ssh->channels, &localid, ssh_channelfind); + if (c) + ssh_channel_destroy(c); +} + +void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, + const void *data, int datalen, + const char *additional_log_text) +{ + struct Packet *pkt; + + pkt = ssh2_pkt_init(type); + pkt->downstream_id = id; + pkt->additional_log_text = additional_log_text; + ssh2_pkt_adddata(pkt, data, datalen); + ssh2_pkt_send(ssh, pkt); +} + +/* + * This is called when stdout/stderr (the entity to which + * from_backend sends data) manages to clear some backlog. + */ +static void ssh_unthrottle(void *handle, int bufsize) +{ + Ssh ssh = (Ssh) handle; + int buflimit; + + if (ssh->version == 1) { + if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { + ssh->v1_stdout_throttling = 0; + ssh_throttle_conn(ssh, -1); + } + } else { + if (ssh->mainchan) { + ssh2_set_window(ssh->mainchan, + bufsize < ssh->mainchan->v.v2.locmaxwin ? + ssh->mainchan->v.v2.locmaxwin - bufsize : 0); + if (ssh_is_simple(ssh)) + buflimit = 0; + else + buflimit = ssh->mainchan->v.v2.locmaxwin; + if (ssh->mainchan->throttling_conn && bufsize <= buflimit) { + ssh->mainchan->throttling_conn = 0; + ssh_throttle_conn(ssh, -1); + } + } + } + + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + ssh_process_queued_incoming_data(ssh); +} + +void ssh_send_port_open(void *channel, char *hostname, int port, char *org) +{ + struct ssh_channel *c = (struct ssh_channel *)channel; + Ssh ssh = c->ssh; + struct Packet *pktout; + + logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org); + + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_PORT_OPEN, + PKT_INT, c->localid, + PKT_STR, hostname, + PKT_INT, port, + /* PKT_STR, , */ + PKT_END); + } else { + pktout = ssh2_chanopen_init(c, "direct-tcpip"); + { + char *trimmed_host = host_strduptrim(hostname); + ssh2_pkt_addstring(pktout, trimmed_host); + sfree(trimmed_host); + } + ssh2_pkt_adduint32(pktout, port); + /* + * We make up values for the originator data; partly it's + * too much hassle to keep track, and partly I'm not + * convinced the server should be told details like that + * about my local network configuration. + * The "originator IP address" is syntactically a numeric + * IP address, and some servers (e.g., Tectia) get upset + * if it doesn't match this syntax. + */ + ssh2_pkt_addstring(pktout, "0.0.0.0"); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_send(ssh, pktout); + } +} + +static int ssh_connected(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->s != NULL; +} + +static int ssh_sendok(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->send_ok; +} + +static int ssh_ldisc(void *handle, int option) +{ + Ssh ssh = (Ssh) handle; + if (option == LD_ECHO) + return ssh->echoing; + if (option == LD_EDIT) + return ssh->editing; + return FALSE; +} + +static void ssh_provide_ldisc(void *handle, void *ldisc) +{ + Ssh ssh = (Ssh) handle; + ssh->ldisc = ldisc; +} + +static void ssh_provide_logctx(void *handle, void *logctx) +{ + Ssh ssh = (Ssh) handle; + ssh->logctx = logctx; +} + +static int ssh_return_exitcode(void *handle) +{ + Ssh ssh = (Ssh) handle; + if (ssh->s != NULL) + return -1; + else + return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); +} + +/* + * cfg_info for SSH is the protocol running in this session. + * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare + * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.) + */ +static int ssh_cfg_info(void *handle) +{ + Ssh ssh = (Ssh) handle; + if (ssh->version == 0) + return 0; /* don't know yet */ + else if (ssh->bare_connection) + return -1; + else + return ssh->version; +} + +/* + * Gross hack: pscp will try to start SFTP but fall back to scp1 if + * that fails. This variable is the means by which scp.c can reach + * into the SSH code and find out which one it got. + */ +extern int ssh_fallback_cmd(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->fallback_cmd; +} + +Backend ssh_backend = { + ssh_init, + ssh_free, + ssh_reconfig, + ssh_send, + ssh_sendbuffer, + ssh_size, + ssh_special, + ssh_get_specials, + ssh_connected, + ssh_return_exitcode, + ssh_sendok, + ssh_ldisc, + ssh_provide_ldisc, + ssh_provide_logctx, + ssh_unthrottle, + ssh_cfg_info, + "ssh", + PROT_SSH, + 22 +}; + +#ifdef MPEXT + +#include "puttyexp.h" + +void ssh_close(void * handle) +{ + ssh_do_close((Ssh)handle, FALSE); +} + +int is_ssh(void * handle) +{ + Plug fn = (Plug)handle; + return (*fn)->closing == ssh_closing; +} + +void call_ssh_timer(void * handle) +{ + if (((Ssh)handle)->version == 2) + { + ssh2_timer(handle, GETTICKCOUNT()); + } +} + +int get_ssh_version(void * handle) +{ + return ((Ssh)handle)->version; +} + +void * get_ssh_frontend(void * handle) +{ + return ((Ssh)handle)->frontend; +} + +int get_ssh1_compressing(void * handle) +{ + return ((Ssh)handle)->v1_compressing; +} + +const struct ssh_cipher * get_cipher(void * handle) +{ + return ((Ssh)handle)->cipher; +} + +const struct ssh2_cipher * get_cscipher(void * handle) +{ + return ((Ssh)handle)->cscipher; +} + +const struct ssh2_cipher * get_sccipher(void * handle) +{ + return ((Ssh)handle)->sccipher; +} + +const struct ssh_compress * get_cscomp(void * handle) +{ + return ((Ssh)handle)->cscomp; +} + +const struct ssh_compress * get_sccomp(void * handle) +{ + return ((Ssh)handle)->sccomp; +} + +int get_ssh_state(void * handle) +{ + return ((Ssh)handle)->state; +} + +int get_ssh_state_closed(void * handle) +{ + return ((Ssh)handle)->state == SSH_STATE_CLOSED; +} + +int get_ssh_state_session(void * handle) +{ + return ((Ssh)handle)->state == SSH_STATE_SESSION; +} + +int get_ssh_exitcode(void * handle) +{ + return ssh_return_exitcode(handle); +} + +const unsigned int * ssh2_remmaxpkt(void * handle) +{ + return &((Ssh)handle)->mainchan->v.v2.remmaxpkt; +} + +const unsigned int * ssh2_remwindow(void * handle) +{ + return &((Ssh)handle)->mainchan->v.v2.remwindow; +} + +void md5checksum(const char * buffer, int len, unsigned char output[16]) +{ + struct MD5Context md5c; + MD5Init(&md5c); + MD5Update(&md5c, buffer, len); + MD5Final(output, &md5c); +} + +void get_hostkey_algs(int * count, cp_ssh_signkey * SignKeys) +{ + int i; + assert(lenof(hostkey_algs) <= *count); + *count = lenof(hostkey_algs); + for (i = 0; i < *count; i++) + { + *(SignKeys + i) = hostkey_algs[i]; + } +} + +#endif diff --git a/netbox/libs/Putty/ssh.h b/netbox/libs/Putty/ssh.h new file mode 100644 index 000000000..01f79198b --- /dev/null +++ b/netbox/libs/Putty/ssh.h @@ -0,0 +1,902 @@ +#include +#include + +#include "puttymem.h" +#include "tree234.h" +#include "network.h" +#include "int64.h" +#include "misc.h" + +struct ssh_channel; +typedef struct ssh_tag *Ssh; + +extern int sshfwd_write(struct ssh_channel *c, char *, int); +extern void sshfwd_write_eof(struct ssh_channel *c); +extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err); +extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize); +Conf *sshfwd_get_conf(struct ssh_channel *c); +void sshfwd_x11_sharing_handover(struct ssh_channel *c, + void *share_cs, void *share_chan, + const char *peer_addr, int peer_port, + int endian, int protomajor, int protominor, + const void *initial_data, int initial_len); +void sshfwd_x11_is_local(struct ssh_channel *c); + +extern Socket ssh_connection_sharing_init(const char *host, int port, + Conf *conf, Ssh ssh, void **state); +void share_got_pkt_from_server(void *ctx, int type, + unsigned char *pkt, int pktlen); +void share_activate(void *state, const char *server_verstring); +void sharestate_free(void *state); +int share_ndownstreams(void *state); + +void ssh_connshare_log(Ssh ssh, int event, const char *logtext, + const char *ds_err, const char *us_err); +unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx); +void ssh_delete_sharing_channel(Ssh ssh, unsigned localid); +int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, + void *share_ctx); +void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx); +struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, + void *share_cs, + void *share_chan); +void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth); +void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, + const void *pkt, int pktlen, + const char *additional_log_text); +void ssh_sharing_downstream_connected(Ssh ssh, unsigned id, + const char *peerinfo); +void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id); +void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...); +int ssh_agent_forwarding_permitted(Ssh ssh); +void share_setup_x11_channel(void *csv, void *chanv, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len); + +/* + * Useful thing. + */ +#ifndef lenof +#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) +#endif + +#define SSH_CIPHER_IDEA 1 +#define SSH_CIPHER_DES 2 +#define SSH_CIPHER_3DES 3 +#define SSH_CIPHER_BLOWFISH 6 + +#ifdef MSCRYPTOAPI +#define APIEXTRA 8 +#else +#define APIEXTRA 0 +#endif + +#ifndef BIGNUM_INTERNAL +typedef void *Bignum; +#endif + +struct RSAKey { + int bits; + int bytes; +#ifdef MSCRYPTOAPI + unsigned long exponent; + unsigned char *modulus; +#else + Bignum modulus; + Bignum exponent; + Bignum private_exponent; + Bignum p; + Bignum q; + Bignum iqmp; +#endif + char *comment; +}; + +struct dss_key { + Bignum p, q, g, y, x; +}; + +int makekey(unsigned char *data, int len, struct RSAKey *result, + unsigned char **keystr, int order); +int makeprivate(unsigned char *data, int len, struct RSAKey *result); +int rsaencrypt(unsigned char *data, int length, struct RSAKey *key); +Bignum rsadecrypt(Bignum input, struct RSAKey *key); +void rsasign(unsigned char *data, int length, struct RSAKey *key); +void rsasanitise(struct RSAKey *key); +int rsastr_len(struct RSAKey *key); +void rsastr_fmt(char *str, struct RSAKey *key); +void rsa_fingerprint(char *str, int len, struct RSAKey *key); +int rsa_verify(struct RSAKey *key); +unsigned char *rsa_public_blob(struct RSAKey *key, int *len); +int rsa_public_blob_len(void *data, int maxlen); +void freersakey(struct RSAKey *key); + +#ifndef PUTTY_UINT32_DEFINED +/* This makes assumptions about the int type. */ +typedef unsigned int uint32; +#define PUTTY_UINT32_DEFINED +#endif +typedef uint32 word32; + +unsigned long crc32_compute(const void *s, size_t len); +unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len); + +/* SSH CRC compensation attack detector */ +void *crcda_make_context(void); +void crcda_free_context(void *handle); +int detect_attack(void *handle, unsigned char *buf, uint32 len, + unsigned char *IV); + +/* + * SSH2 RSA key exchange functions + */ +struct ssh_hash; +void *ssh_rsakex_newkey(char *data, int len); +void ssh_rsakex_freekey(void *key); +int ssh_rsakex_klen(void *key); +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key); + +typedef struct { + uint32 h[4]; +} MD5_Core_State; + +struct MD5Context { +#ifdef MSCRYPTOAPI + unsigned long hHash; +#else + MD5_Core_State core; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +#endif +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Simple(void const *p, unsigned len, unsigned char output[16]); + +void *hmacmd5_make_context(void); +void hmacmd5_free_context(void *handle); +void hmacmd5_key(void *handle, void const *key, int len); +void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, + unsigned char *hmac); + +typedef struct { + uint32 h[5]; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +} SHA_State; +void putty_SHA_Init(SHA_State * s); +void putty_SHA_Bytes(SHA_State * s, const void *p, int len); +void putty_SHA_Final(SHA_State * s, unsigned char *output); +void putty_SHA_Simple(const void *p, int len, unsigned char *output); + +void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, + unsigned char *output); +typedef struct { + uint32 h[8]; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +} SHA256_State; +void putty_SHA256_Init(SHA256_State * s); +void putty_SHA256_Bytes(SHA256_State * s, const void *p, int len); +void putty_SHA256_Final(SHA256_State * s, unsigned char *output); +void putty_SHA256_Simple(const void *p, int len, unsigned char *output); + +typedef struct { + uint64 h[8]; + unsigned char block[128]; + int blkused; + uint32 len[4]; +} SHA512_State; +void putty_SHA512_Init(SHA512_State * s); +void putty_SHA512_Bytes(SHA512_State * s, const void *p, int len); +void putty_SHA512_Final(SHA512_State * s, unsigned char *output); +void putty_SHA512_Simple(const void *p, int len, unsigned char *output); + +struct ssh_cipher { + void *(*make_context)(void); + void (*free_context)(void *); + void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */ + void (*encrypt) (void *, unsigned char *blk, int len); + void (*decrypt) (void *, unsigned char *blk, int len); + int blksize; + char *text_name; +}; + +struct ssh2_cipher { + void *(*make_context)(void); + void (*free_context)(void *); + void (*setiv) (void *, unsigned char *key); /* for SSH-2 */ + void (*setkey) (void *, unsigned char *key);/* for SSH-2 */ + void (*encrypt) (void *, unsigned char *blk, int len); + void (*decrypt) (void *, unsigned char *blk, int len); + char *name; + int blksize; + int keylen; + unsigned int flags; +#define SSH_CIPHER_IS_CBC 1 + char *text_name; +}; + +struct ssh2_ciphers { + int nciphers; + const struct ssh2_cipher *const *list; +}; + +struct ssh_mac { + void *(*make_context)(void); + void (*free_context)(void *); + void (*setkey) (void *, unsigned char *key); + /* whole-packet operations */ + void (*generate) (void *, unsigned char *blk, int len, unsigned long seq); + int (*verify) (void *, unsigned char *blk, int len, unsigned long seq); + /* partial-packet operations */ + void (*start) (void *); + void (*bytes) (void *, unsigned char const *, int); + void (*genresult) (void *, unsigned char *); + int (*verresult) (void *, unsigned char const *); + char *name; + int len; + char *text_name; +}; + +struct ssh_hash { + void *(*init)(void); /* also allocates context */ + void (*bytes)(void *, void *, int); + void (*final)(void *, unsigned char *); /* also frees context */ + int hlen; /* output length in bytes */ + char *text_name; +}; + +struct ssh_kex { + char *name, *groupname; + enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH } main_type; + /* For DH */ + const unsigned char *pdata, *gdata; /* NULL means group exchange */ + int plen, glen; + const struct ssh_hash *hash; + const void *extra; /* private to the kex methods */ +}; + +struct ssh_kexes { + int nkexes; + const struct ssh_kex *const *list; +}; + +struct ssh_signkey { + void *(*newkey) (char *data, int len); + void (*freekey) (void *key); + char *(*fmtkey) (void *key); + unsigned char *(*public_blob) (void *key, int *len); + unsigned char *(*private_blob) (void *key, int *len); + void *(*createkey) (unsigned char *pub_blob, int pub_len, + unsigned char *priv_blob, int priv_len); + void *(*openssh_createkey) (unsigned char **blob, int *len); + int (*openssh_fmtkey) (void *key, unsigned char *blob, int len); + int (*pubkey_bits) (void *blob, int len); + char *(*fingerprint) (void *key); + int (*verifysig) (void *key, char *sig, int siglen, + char *data, int datalen); + unsigned char *(*sign) (void *key, char *data, int datalen, + int *siglen); + char *name; + char *keytype; /* for host key cache */ + const void *extra; /* private to the public key methods */ +}; + +struct ssh_compress { + char *name; + /* For zlib@openssh.com: if non-NULL, this name will be considered once + * userauth has completed successfully. */ + char *delayed_name; + void *(*compress_init) (void); + void (*compress_cleanup) (void *); + int (*compress) (void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + void *(*decompress_init) (void); + void (*decompress_cleanup) (void *); + int (*decompress) (void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + int (*disable_compression) (void *); + char *text_name; +}; + +struct ssh2_userkey { + const struct ssh_signkey *alg; /* the key algorithm */ + void *data; /* the key data */ + char *comment; /* the key comment */ +}; + +/* The maximum length of any hash algorithm used in kex. (bytes) */ +#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */ + +extern const struct ssh_cipher ssh_3des; +extern const struct ssh_cipher ssh_des; +extern const struct ssh_cipher ssh_blowfish_ssh1; +extern const struct ssh2_ciphers ssh2_3des; +extern const struct ssh2_ciphers ssh2_des; +extern const struct ssh2_ciphers ssh2_aes; +extern const struct ssh2_ciphers ssh2_blowfish; +extern const struct ssh2_ciphers ssh2_arcfour; +extern const struct ssh2_ciphers ssh2_ccp; +extern const struct ssh_hash ssh_sha1; +extern const struct ssh_hash ssh_sha256; +extern const struct ssh_hash ssh_sha384; +extern const struct ssh_hash ssh_sha512; +extern const struct ssh_kexes ssh_diffiehellman_group1; +extern const struct ssh_kexes ssh_diffiehellman_group14; +extern const struct ssh_kexes ssh_diffiehellman_gex; +extern const struct ssh_kexes ssh_rsa_kex; +extern const struct ssh_kexes ssh_ecdh_kex; +extern const struct ssh_signkey ssh_dss; +extern const struct ssh_signkey ssh_rsa; +extern const struct ssh_signkey ssh_ecdsa_ed25519; +extern const struct ssh_signkey ssh_ecdsa_nistp256; +extern const struct ssh_signkey ssh_ecdsa_nistp384; +extern const struct ssh_signkey ssh_ecdsa_nistp521; +extern const struct ssh_mac ssh_hmac_md5; +extern const struct ssh_mac ssh_hmac_sha1; +extern const struct ssh_mac ssh_hmac_sha1_buggy; +extern const struct ssh_mac ssh_hmac_sha1_96; +extern const struct ssh_mac ssh_hmac_sha1_96_buggy; +extern const struct ssh_mac ssh_hmac_sha256; + +void *aes_make_context(void); +void aes_free_context(void *handle); +void aes128_key(void *handle, unsigned char *key); +void aes192_key(void *handle, unsigned char *key); +void aes256_key(void *handle, unsigned char *key); +void aes_iv(void *handle, unsigned char *iv); +void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len); +void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len); + +/* + * PuTTY version number formatted as an SSH version string. + */ +extern char sshver[]; + +/* + * Gross hack: pscp will try to start SFTP but fall back to scp1 if + * that fails. This variable is the means by which scp.c can reach + * into the SSH code and find out which one it got. + */ +extern int ssh_fallback_cmd(void *handle); + +#ifndef MSCRYPTOAPI +void SHATransform(word32 * digest, word32 * data); +#endif + +int random_byte(void); +void random_add_noise(void *noise, int length); +void random_add_heavynoise(void *noise, int length); + +void logevent(void *, const char *); + +struct PortForwarding; + +/* Allocate and register a new channel for port forwarding */ +void *new_sock_channel(void *handle, struct PortForwarding *pf); +void ssh_send_port_open(void *channel, char *hostname, int port, char *org); + +/* Exports from portfwd.c */ +extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port, + void *c, Conf *conf, int addressfamily); +extern void pfd_close(struct PortForwarding *); +extern int pfd_send(struct PortForwarding *, char *data, int len); +extern void pfd_send_eof(struct PortForwarding *); +extern void pfd_confirm(struct PortForwarding *); +extern void pfd_unthrottle(struct PortForwarding *); +extern void pfd_override_throttle(struct PortForwarding *, int enable); +struct PortListener; +/* desthost == NULL indicates dynamic (SOCKS) port forwarding */ +extern char *pfl_listen(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, Conf *conf, + struct PortListener **pl, int address_family); +extern void pfl_terminate(struct PortListener *); + +/* Exports from x11fwd.c */ +enum { + X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 +}; +struct X11Display { + /* Broken-down components of the display name itself */ + int unixdomain; + char *hostname; + int displaynum; + int screennum; + /* OSX sometimes replaces all the above with a full Unix-socket pathname */ + char *unixsocketpath; + + /* PuTTY networking SockAddr to connect to the display, and associated + * gubbins */ + SockAddr addr; + int port; + char *realhost; + + /* Our local auth details for talking to the real X display. */ + int localauthproto; + unsigned char *localauthdata; + int localauthdatalen; +}; +struct X11FakeAuth { + /* Auth details we invented for a virtual display on the SSH server. */ + int proto; + unsigned char *data; + int datalen; + char *protoname; + char *datastring; + + /* The encrypted form of the first block, in XDM-AUTHORIZATION-1. + * Used as part of the key when these structures are organised + * into a tree. See x11_invent_fake_auth for explanation. */ + unsigned char *xa1_firstblock; + + /* + * Used inside x11fwd.c to remember recently seen + * XDM-AUTHORIZATION-1 strings, to avoid replay attacks. + */ + tree234 *xdmseen; + + /* + * What to do with an X connection matching this auth data. + */ + struct X11Display *disp; + void *share_cs, *share_chan; +}; +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_ip, int peer_port, + int *outlen); +int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ +/* + * x11_setup_display() parses the display variable and fills in an + * X11Display structure. Some remote auth details are invented; + * the supplied authtype parameter configures the preferred + * authorisation protocol to use at the remote end. The local auth + * details are looked up by calling platform_get_x11_auth. + */ +extern struct X11Display *x11_setup_display(char *display, Conf *); +void x11_free_display(struct X11Display *disp); +struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype); +void x11_free_fake_auth(struct X11FakeAuth *auth); +struct X11Connection; /* opaque outside x11fwd.c */ +struct X11Connection *x11_init(tree234 *authtree, void *, const char *, int); +extern void x11_close(struct X11Connection *); +extern int x11_send(struct X11Connection *, char *, int); +extern void x11_send_eof(struct X11Connection *s); +extern void x11_unthrottle(struct X11Connection *s); +extern void x11_override_throttle(struct X11Connection *s, int enable); +char *x11_display(const char *display); +/* Platform-dependent X11 functions */ +extern void platform_get_x11_auth(struct X11Display *display, Conf *); + /* examine a mostly-filled-in X11Display and fill in localauth* */ +extern const int platform_uses_x11_unix_by_default; + /* choose default X transport in the absence of a specified one */ +SockAddr platform_get_x11_unix_address(const char *path, int displaynum); + /* make up a SockAddr naming the address for displaynum */ +char *platform_get_x_display(void); + /* allocated local X display string, if any */ +/* Callbacks in x11.c usable _by_ platform X11 functions */ +/* + * This function does the job of platform_get_x11_auth, provided + * it is told where to find a normally formatted .Xauthority file: + * it opens that file, parses it to find an auth record which + * matches the display details in "display", and fills in the + * localauth fields. + * + * It is expected that most implementations of + * platform_get_x11_auth() will work by finding their system's + * .Xauthority file, adjusting the display details if necessary + * for local oddities like Unix-domain socket transport, and + * calling this function to do the rest of the work. + */ +void x11_get_auth_from_authfile(struct X11Display *display, + const char *authfilename); +int x11_identify_auth_proto(const char *proto); +void *x11_dehexify(const char *hex, int *outlen); + +Bignum copybn(Bignum b); +Bignum bn_power_2(int n); +void bn_restore_invariant(Bignum b); +Bignum bignum_from_long(unsigned long n); +void freebn(Bignum b); +Bignum modpow(Bignum base, Bignum exp, Bignum mod); +Bignum modmul(Bignum a, Bignum b, Bignum mod); +void decbn(Bignum n); +extern Bignum Zero, One; +Bignum bignum_from_bytes(const unsigned char *data, int nbytes); +int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result); +int bignum_bitcount(Bignum bn); +int ssh1_bignum_length(Bignum bn); +int ssh2_bignum_length(Bignum bn); +int bignum_byte(Bignum bn, int i); +int bignum_bit(Bignum bn, int i); +void bignum_set_bit(Bignum bn, int i, int value); +int ssh1_write_bignum(void *data, Bignum bn); +Bignum biggcd(Bignum a, Bignum b); +unsigned short bignum_mod_short(Bignum number, unsigned short modulus); +Bignum bignum_add_long(Bignum number, unsigned long addend); +Bignum bigadd(Bignum a, Bignum b); +Bignum bigsub(Bignum a, Bignum b); +Bignum bigmul(Bignum a, Bignum b); +Bignum bigmuladd(Bignum a, Bignum b, Bignum addend); +Bignum bigdiv(Bignum a, Bignum b); +Bignum bigmod(Bignum a, Bignum b); +Bignum modinv(Bignum number, Bignum modulus); +Bignum bignum_bitmask(Bignum number); +Bignum bignum_rshift(Bignum number, int shift); +int bignum_cmp(Bignum a, Bignum b); +char *bignum_decimal(Bignum x); + +#ifdef DEBUG +void diagbn(char *prefix, Bignum md); +#endif + +void *dh_setup_group(const struct ssh_kex *kex); +void *dh_setup_gex(Bignum pval, Bignum gval); +void dh_cleanup(void *); +Bignum dh_create_e(void *, int nbits); +const char *dh_validate_f(void *handle, Bignum f); +Bignum dh_find_K(void *, Bignum f); + +int loadrsakey(const Filename *filename, struct RSAKey *key, + char *passphrase, const char **errorstr); +int rsakey_encrypted(const Filename *filename, char **comment); +int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, + char **commentptr, const char **errorstr); + +int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase); + +extern int base64_decode_atom(char *atom, unsigned char *out); +extern int base64_lines(int datalen); +extern void base64_encode_atom(unsigned char *data, int n, char *out); +extern void base64_encode(FILE *fp, unsigned char *data, int datalen, int cpl); + +/* ssh2_load_userkey can return this as an error */ +extern struct ssh2_userkey ssh2_wrong_passphrase; +#define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase) + +int ssh2_userkey_encrypted(const Filename *filename, char **comment); +struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, + char *passphrase, const char **errorstr); +unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + int *pub_blob_len, char **commentptr, + const char **errorstr); +int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); +const struct ssh_signkey *find_pubkey_alg(const char *name); + +enum { + SSH_KEYTYPE_UNOPENABLE, + SSH_KEYTYPE_UNKNOWN, + SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2, + SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM +}; +int key_type(const Filename *filename); +char *key_type_to_str(int type); + +int import_possible(int type); +int import_target_type(int type); +int import_encrypted(const Filename *filename, int type, char **comment); +int import_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase, const char **errmsg_p); +struct ssh2_userkey *import_ssh2(const Filename *filename, int type, + char *passphrase, const char **errmsg_p); +int export_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase); +int export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase); + +void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); +void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len); +void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len); +void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len); +void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, + int len); +void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, + int len); + +void des_encrypt_xdmauth(const unsigned char *key, + unsigned char *blk, int len); +void des_decrypt_xdmauth(const unsigned char *key, + unsigned char *blk, int len); + +/* + * For progress updates in the key generation utility. + */ +#define PROGFN_INITIALISE 1 +#define PROGFN_LIN_PHASE 2 +#define PROGFN_EXP_PHASE 3 +#define PROGFN_PHASE_EXTENT 4 +#define PROGFN_READY 5 +#define PROGFN_PROGRESS 6 +typedef void (*progfn_t) (void *param, int action, int phase, int progress); + +int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, + void *pfnparam); +int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, + void *pfnparam); +Bignum primegen(int bits, int modulus, int residue, Bignum factor, + int phase, progfn_t pfn, void *pfnparam, unsigned firstbits); +void invent_firstbits(unsigned *one, unsigned *two); + + +/* + * zlib compression. + */ +void *zlib_compress_init(void); +void zlib_compress_cleanup(void *); +void *zlib_decompress_init(void); +void zlib_decompress_cleanup(void *); +int zlib_compress_block(void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); +int zlib_decompress_block(void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + +/* + * Connection-sharing API provided by platforms. This function must + * either: + * - return SHARE_NONE and do nothing + * - return SHARE_DOWNSTREAM and set *sock to a Socket connected to + * downplug + * - return SHARE_UPSTREAM and set *sock to a Socket connected to + * upplug. + */ +enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM }; +int platform_ssh_share(const char *name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream); +void platform_ssh_share_cleanup(const char *name); + +/* + * SSH-1 message type codes. + */ +#define SSH1_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ +#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */ +#define SSH1_CMSG_USER 4 /* 0x4 */ +#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */ +#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */ +#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */ +#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */ +#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */ +#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */ +#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */ +#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */ +#define SSH1_SMSG_SUCCESS 14 /* 0xe */ +#define SSH1_SMSG_FAILURE 15 /* 0xf */ +#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */ +#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */ +#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */ +#define SSH1_CMSG_EOF 19 /* 0x13 */ +#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */ +#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */ +#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */ +#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ +#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ +#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ +#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ +#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ +#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ +#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ +#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ +#define SSH1_MSG_IGNORE 32 /* 0x20 */ +#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ +#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ +#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ +#define SSH1_MSG_DEBUG 36 /* 0x24 */ +#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ +#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ +#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */ +#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */ +#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */ +#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */ +#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */ + +#define SSH1_AUTH_RHOSTS 1 /* 0x1 */ +#define SSH1_AUTH_RSA 2 /* 0x2 */ +#define SSH1_AUTH_PASSWORD 3 /* 0x3 */ +#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */ +#define SSH1_AUTH_TIS 5 /* 0x5 */ +#define SSH1_AUTH_CCARD 16 /* 0x10 */ + +#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */ +/* Mask for protoflags we will echo back to server if seen */ +#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ + +/* + * SSH-2 message type codes. + */ +#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH2_MSG_IGNORE 2 /* 0x2 */ +#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ +#define SSH2_MSG_DEBUG 4 /* 0x4 */ +#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ +#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ +#define SSH2_MSG_KEXINIT 20 /* 0x14 */ +#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ +#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ +#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 /* 0x1e */ +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 /* 0x22 */ +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ +#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ +#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ +#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ +#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ +#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ +#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ +#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ +#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ +#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ +#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ +#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ +#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ +#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ +#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ +#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +/* + * SSH-1 agent messages. + */ +#define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH1_AGENTC_RSA_CHALLENGE 3 +#define SSH1_AGENT_RSA_RESPONSE 4 +#define SSH1_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */ + +/* + * Messages common to SSH-1 and OpenSSH's SSH-2. + */ +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 + +/* + * OpenSSH's SSH-2 agent messages. + */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* + * Assorted other SSH-related enumerations. + */ +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */ +#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */ +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */ +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */ +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */ +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */ +#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */ +#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */ +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */ +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */ +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */ +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */ + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */ +#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */ +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */ +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */ + +#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ + +/* + * Need this to warn about support for the original SSH-2 keyfile + * format. + */ +void old_keyfile_warning(void); + + +struct ec_curve; + +struct ec_point { + const struct ec_curve *curve; + Bignum x, y; + Bignum z; /* Jacobian denominator */ + unsigned char infinity; +}; + +void ec_point_free(struct ec_point *point); + +/* Weierstrass form curve */ +struct ec_wcurve +{ + Bignum a, b, n; + struct ec_point G; +}; + +/* Montgomery form curve */ +struct ec_mcurve +{ + Bignum a, b; + struct ec_point G; +}; + +/* Edwards form curve */ +struct ec_ecurve +{ + Bignum l, d; + struct ec_point B; +}; + +struct ec_curve { + enum { EC_WEIERSTRASS, EC_MONTGOMERY, EC_EDWARDS } type; + /* 'name' is the identifier of the curve when it has to appear in + * wire protocol encodings, as it does in e.g. the public key and + * signature formats for NIST curves. Curves which do not format + * their keys or signatures in this way just have name==NULL. + * + * 'textname' is non-NULL for all curves, and is a human-readable + * identification suitable for putting in log messages. */ + const char *name, *textname; + unsigned int fieldBits; + Bignum p; + union { + struct ec_wcurve w; + struct ec_mcurve m; + struct ec_ecurve e; + }; +}; + +const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, + const struct ec_curve **curve); +const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, int *oidlen); +extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; +const int ec_nist_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const struct ssh_signkey **alg); +const int ec_ed_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const struct ssh_signkey **alg); + +struct ssh_signkey; + +struct ec_key { + const struct ssh_signkey *signalg; + struct ec_point publicKey; + Bignum privateKey; +}; + +struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve); diff --git a/netbox/libs/Putty/sshaes.c b/netbox/libs/Putty/sshaes.c new file mode 100644 index 000000000..5c6cf552e --- /dev/null +++ b/netbox/libs/Putty/sshaes.c @@ -0,0 +1,1265 @@ +/* + * aes.c - implementation of AES / Rijndael + * + * AES is a flexible algorithm as regards endianness: it has no + * inherent preference as to which way round you should form words + * from the input byte stream. It talks endlessly of four-byte + * _vectors_, but never of 32-bit _words_ - there's no 32-bit + * addition at all, which would force an endianness by means of + * which way the carries went. So it would be possible to write a + * working AES that read words big-endian, and another working one + * that read them little-endian, just by computing a different set + * of tables - with no speed drop. + * + * It's therefore tempting to do just that, and remove the overhead + * of GET_32BIT_MSB_FIRST() et al, allowing every system to use its + * own endianness-native code; but I decided not to, partly for + * ease of testing, and mostly because I like the flexibility that + * allows you to encrypt a non-word-aligned block of memory (which + * many systems would stop being able to do if I went the + * endianness-dependent route). + * + * This implementation reads and stores words big-endian, but + * that's a minor implementation detail. By flipping the endianness + * of everything in the E0..E3, D0..D3 tables, and substituting + * GET_32BIT_LSB_FIRST for GET_32BIT_MSB_FIRST, I could create an + * implementation that worked internally little-endian and gave the + * same answers at the same speed. + */ + +#include +#include + +#include "ssh.h" + +#define MAX_NR 14 /* max no of rounds */ +#define MAX_NK 8 /* max no of words in input key */ +#define MAX_NB 8 /* max no of words in cipher blk */ + +#define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) ) + +typedef struct AESContext AESContext; + +struct AESContext { + word32 keysched[(MAX_NR + 1) * MAX_NB]; + word32 invkeysched[(MAX_NR + 1) * MAX_NB]; + void (*encrypt) (AESContext * ctx, word32 * block); + void (*decrypt) (AESContext * ctx, word32 * block); + word32 iv[MAX_NB]; + int Nb, Nr; +}; + +static const unsigned char Sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +static const unsigned char Sboxinv[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, + 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, + 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, + 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, + 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, + 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, + 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, + 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, + 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, + 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, + 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, + 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +}; + +static const word32 E0[256] = { + 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, + 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, + 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, + 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, + 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, + 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, + 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, + 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, + 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, + 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, + 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, + 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, + 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, + 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, + 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, + 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, + 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, + 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, + 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, + 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, + 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, + 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, + 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, + 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, + 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, + 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, + 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, + 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, + 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, + 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, + 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, + 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, + 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, + 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, +}; +static const word32 E1[256] = { + 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, + 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, + 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, + 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, + 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, + 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, + 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, + 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, + 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, + 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, + 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, + 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, + 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, + 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, + 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, + 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, + 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, + 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, + 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, + 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, + 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, + 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, + 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, + 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, + 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, + 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, + 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, + 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, + 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, + 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, + 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, + 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, + 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, + 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, + 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, + 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, + 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, + 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, + 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, + 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, + 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, + 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, + 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, + 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, + 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, + 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, + 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, + 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, + 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, + 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, + 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, + 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, + 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, + 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, +}; +static const word32 E2[256] = { + 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, + 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, + 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, + 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, + 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, + 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, + 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, + 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, + 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, + 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, + 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, + 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, + 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, + 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, + 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, + 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, + 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, + 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, + 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, + 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, + 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, + 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, + 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, + 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, + 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, + 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, + 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, + 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, + 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, + 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, + 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, + 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, + 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, + 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, + 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, + 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, + 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, + 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, + 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, + 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, + 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, + 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, + 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, + 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, + 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, + 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, + 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, + 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, + 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, + 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, + 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, + 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, + 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, + 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, +}; +static const word32 E3[256] = { + 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, + 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, + 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, + 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, + 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, + 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, + 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, + 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, + 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, + 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, + 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, + 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, + 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, + 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, + 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, + 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, + 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, + 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, + 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, + 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, + 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, + 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, + 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, + 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, + 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, + 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, + 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, + 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, + 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, + 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, + 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, + 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, + 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, + 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, + 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, + 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, + 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, + 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, + 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, + 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, + 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, + 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, + 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, + 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, + 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, + 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, + 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, + 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, + 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, + 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, + 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, + 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, + 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, + 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, +}; +static const word32 D0[256] = { + 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, + 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, + 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, + 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, + 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, + 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, + 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, + 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, + 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, + 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, + 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, + 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, + 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, + 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, + 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, + 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, + 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, + 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, + 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, + 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, + 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, + 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, + 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, + 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, + 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, + 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, + 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, + 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, + 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, + 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, + 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, + 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, + 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, + 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, + 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, + 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, + 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, + 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, + 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, + 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, + 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, + 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, + 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, + 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, + 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, + 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, + 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, + 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, + 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, + 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, + 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, + 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, +}; +static const word32 D1[256] = { + 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, + 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, + 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, + 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, + 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, + 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, + 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, + 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, + 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, + 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, + 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, + 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, + 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, + 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, + 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, + 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, + 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, + 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, + 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, + 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, + 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, + 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, + 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, + 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, + 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, + 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, + 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, + 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, + 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, + 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, + 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, + 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, + 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, + 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, + 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, + 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, + 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, + 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, + 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, + 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, + 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, + 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, + 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, + 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, + 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, + 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, + 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, + 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, + 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, + 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, + 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, + 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, + 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, + 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, +}; +static const word32 D2[256] = { + 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, + 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, + 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, + 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, + 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, + 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, + 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, + 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, + 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, + 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, + 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, + 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, + 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, + 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, + 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, + 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, + 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, + 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, + 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, + 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, + 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, + 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, + 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, + 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, + 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, + 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, + 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, + 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, + 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, + 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, + 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, + 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, + 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, + 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, + 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, + 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, + 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, + 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, + 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, + 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, + 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, + 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, + 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, + 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, + 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, + 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, + 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, + 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, + 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, + 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, + 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, + 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, +}; +static const word32 D3[256] = { + 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, + 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, + 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, + 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, + 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, + 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, + 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, + 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, + 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, + 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, + 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, + 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, + 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, + 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, + 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, + 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, + 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, + 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, + 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, + 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, + 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, + 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, + 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, + 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, + 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, + 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, + 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, + 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, + 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, + 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, + 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, + 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, + 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, + 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, + 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, + 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, + 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, + 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, + 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, + 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, + 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, + 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, + 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, + 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, + 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, + 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, + 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, + 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, + 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, + 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, + 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, + 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, + 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, + 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, +}; + +/* + * Common macros in both the encryption and decryption routines. + */ +#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++) +#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++, \ + block[4]^=*keysched++, block[5]^=*keysched++) +#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++, \ + block[4]^=*keysched++, block[5]^=*keysched++, \ + block[6]^=*keysched++, block[7]^=*keysched++) +#define MOVEWORD(i) ( block[i] = newstate[i] ) + +/* + * Macros for the encryption routine. There are three encryption + * cores, for Nb=4,6,8. + */ +#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \ + E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ + E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ + E3[block[(i+C3)%Nb] & 0xFF]) ) +#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ + (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ + (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ + (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) ) + +/* + * Core encrypt routines, expecting word32 inputs read big-endian + * from the byte-oriented input stream. + */ +static void aes_encrypt_nb_4(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4; + word32 *keysched = ctx->keysched; + word32 newstate[4]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_4; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + } + ADD_ROUND_KEY_4; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + ADD_ROUND_KEY_4; +} +static void aes_encrypt_nb_6(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6; + word32 *keysched = ctx->keysched; + word32 newstate[6]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_6; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + } + ADD_ROUND_KEY_6; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + ADD_ROUND_KEY_6; +} +static void aes_encrypt_nb_8(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8; + word32 *keysched = ctx->keysched; + word32 newstate[8]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_8; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MAKEWORD(6); + MAKEWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + } + ADD_ROUND_KEY_8; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + LASTWORD(6); + LASTWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + ADD_ROUND_KEY_8; +} + +#undef MAKEWORD +#undef LASTWORD + +/* + * Macros for the decryption routine. There are three decryption + * cores, for Nb=4,6,8. + */ +#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ + D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ + D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ + D3[block[(i+C3)%Nb] & 0xFF]) ) +#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ + (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ + (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ + (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) ) + +/* + * Core decrypt routines, expecting word32 inputs read big-endian + * from the byte-oriented input stream. + */ +static void aes_decrypt_nb_4(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4; + word32 *keysched = ctx->invkeysched; + word32 newstate[4]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_4; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + } + ADD_ROUND_KEY_4; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + ADD_ROUND_KEY_4; +} +static void aes_decrypt_nb_6(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6; + word32 *keysched = ctx->invkeysched; + word32 newstate[6]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_6; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + } + ADD_ROUND_KEY_6; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + ADD_ROUND_KEY_6; +} +static void aes_decrypt_nb_8(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8; + word32 *keysched = ctx->invkeysched; + word32 newstate[8]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_8; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MAKEWORD(6); + MAKEWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + } + ADD_ROUND_KEY_8; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + LASTWORD(6); + LASTWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + ADD_ROUND_KEY_8; +} + +#undef MAKEWORD +#undef LASTWORD + + +/* + * Set up an AESContext. `keylen' and `blocklen' are measured in + * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + */ +static void aes_setup(AESContext * ctx, int blocklen, + unsigned char *key, int keylen) +{ + int i, j, Nk, rconst; + + assert(blocklen == 16 || blocklen == 24 || blocklen == 32); + assert(keylen == 16 || keylen == 24 || keylen == 32); + + /* + * Basic parameters. Words per block, words in key, rounds. + */ + Nk = keylen / 4; + ctx->Nb = blocklen / 4; + ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk); + + /* + * Assign core-function pointers. + */ + if (ctx->Nb == 8) + ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8; + else if (ctx->Nb == 6) + ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6; + else if (ctx->Nb == 4) + ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4; + + /* + * Now do the key setup itself. + */ + rconst = 1; + for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) { + if (i < Nk) + ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i); + else { + word32 temp = ctx->keysched[i - 1]; + if (i % Nk == 0) { + int a, b, c, d; + a = (temp >> 16) & 0xFF; + b = (temp >> 8) & 0xFF; + c = (temp >> 0) & 0xFF; + d = (temp >> 24) & 0xFF; + temp = Sbox[a] ^ rconst; + temp = (temp << 8) | Sbox[b]; + temp = (temp << 8) | Sbox[c]; + temp = (temp << 8) | Sbox[d]; + rconst = mulby2(rconst); + } else if (i % Nk == 4 && Nk > 6) { + int a, b, c, d; + a = (temp >> 24) & 0xFF; + b = (temp >> 16) & 0xFF; + c = (temp >> 8) & 0xFF; + d = (temp >> 0) & 0xFF; + temp = Sbox[a]; + temp = (temp << 8) | Sbox[b]; + temp = (temp << 8) | Sbox[c]; + temp = (temp << 8) | Sbox[d]; + } + ctx->keysched[i] = ctx->keysched[i - Nk] ^ temp; + } + } + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (i = 0; i <= ctx->Nr; i++) { + for (j = 0; j < ctx->Nb; j++) { + word32 temp; + temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j]; + if (i != 0 && i != ctx->Nr) { + /* + * Perform the InvMixColumn operation on i. The D + * tables give the result of InvMixColumn applied + * to Sboxinv on individual bytes, so we should + * compose Sbox with the D tables for this. + */ + int a, b, c, d; + a = (temp >> 24) & 0xFF; + b = (temp >> 16) & 0xFF; + c = (temp >> 8) & 0xFF; + d = (temp >> 0) & 0xFF; + temp = D0[Sbox[a]]; + temp ^= D1[Sbox[b]]; + temp ^= D2[Sbox[c]]; + temp ^= D3[Sbox[d]]; + } + ctx->invkeysched[i * ctx->Nb + j] = temp; + } + } +} + +static void aes_encrypt(AESContext * ctx, word32 * block) +{ + ctx->encrypt(ctx, block); +} + +static void aes_decrypt(AESContext * ctx, word32 * block) +{ + ctx->decrypt(ctx, block); +} + +static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + word32 iv[4]; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + for (i = 0; i < 4; i++) + iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); + aes_encrypt(ctx, iv); + for (i = 0; i < 4; i++) + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]); + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + word32 iv[4], x[4], ct[4]; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + for (i = 0; i < 4; i++) + x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); + aes_decrypt(ctx, x); + for (i = 0; i < 4; i++) { + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]); + iv[i] = ct[i]; + } + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx) +{ + word32 iv[4], b[4], tmp; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + memcpy(b, iv, sizeof(b)); + aes_encrypt(ctx, b); + for (i = 0; i < 4; i++) { + tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); + PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]); + } + for (i = 3; i >= 0; i--) + if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) + break; + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +void *aes_make_context(void) +{ + return snew(AESContext); +} + +void aes_free_context(void *handle) +{ + sfree(handle); +} + +void aes128_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 16); +} + +void aes192_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 24); +} + +void aes256_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 32); +} + +void aes_iv(void *handle, unsigned char *iv) +{ + AESContext *ctx = (AESContext *)handle; + int i; + for (i = 0; i < 4; i++) + ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); +} + +void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_encrypt_cbc(blk, len, ctx); +} + +void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_decrypt_cbc(blk, len, ctx); +} + +static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_sdctr(blk, len, ctx); +} + +void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + AESContext ctx; + aes_setup(&ctx, 16, key, 32); + memset(ctx.iv, 0, sizeof(ctx.iv)); + aes_encrypt_cbc(blk, len, &ctx); + smemclr(&ctx, sizeof(ctx)); +} + +void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + AESContext ctx; + aes_setup(&ctx, 16, key, 32); + memset(ctx.iv, 0, sizeof(ctx.iv)); + aes_decrypt_cbc(blk, len, &ctx); + smemclr(&ctx, sizeof(ctx)); +} + +static const struct ssh2_cipher ssh_aes128_ctr = { + aes_make_context, aes_free_context, aes_iv, aes128_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes128-ctr", + 16, 128, 0, "AES-128 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes192_ctr = { + aes_make_context, aes_free_context, aes_iv, aes192_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes192-ctr", + 16, 192, 0, "AES-192 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes256_ctr = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes256-ctr", + 16, 256, 0, "AES-256 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes128 = { + aes_make_context, aes_free_context, aes_iv, aes128_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes128-cbc", + 16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC" +}; + +static const struct ssh2_cipher ssh_aes192 = { + aes_make_context, aes_free_context, aes_iv, aes192_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes192-cbc", + 16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC" +}; + +static const struct ssh2_cipher ssh_aes256 = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes256-cbc", + 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC" +}; + +static const struct ssh2_cipher ssh_rijndael_lysator = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "rijndael-cbc@lysator.liu.se", + 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC" +}; + +static const struct ssh2_cipher *const aes_list[] = { + &ssh_aes256_ctr, + &ssh_aes256, + &ssh_rijndael_lysator, + &ssh_aes192_ctr, + &ssh_aes192, + &ssh_aes128_ctr, + &ssh_aes128, +}; + +const struct ssh2_ciphers ssh2_aes = { + sizeof(aes_list) / sizeof(*aes_list), + aes_list +}; + +#ifdef MPEXT + +#include "puttyexp.h" + +void * call_aes_make_context() +{ + return aes_make_context(); +} + +void call_aes_free_context(void * handle) +{ + aes_free_context(handle); +} + +void call_aes_setup(void * ctx, int blocklen, unsigned char * key, int keylen) +{ + aes_setup((AESContext *)ctx, blocklen, key, keylen); +} + +void call_aes_encrypt(void * ctx, unsigned int * block) +{ + aes_encrypt((AESContext *)ctx, block); +} + +void call_aes_decrypt(void * ctx, unsigned int * block) +{ + aes_decrypt((AESContext *)ctx, block); +} + +#endif diff --git a/netbox/libs/Putty/ssharcf.c b/netbox/libs/Putty/ssharcf.c new file mode 100644 index 000000000..06f235c5b --- /dev/null +++ b/netbox/libs/Putty/ssharcf.c @@ -0,0 +1,123 @@ +/* + * Arcfour (RC4) implementation for PuTTY. + * + * Coded from Schneier. + */ + +#include +#include "ssh.h" + +typedef struct { + unsigned char i, j, s[256]; +} ArcfourContext; + +static void arcfour_block(void *handle, unsigned char *blk, int len) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + unsigned k; + unsigned char tmp, i, j, *s; + + s = ctx->s; + i = ctx->i; j = ctx->j; + for (k = 0; (int)k < len; k++) { + i = (i + 1) & 0xff; + j = (j + s[i]) & 0xff; + tmp = s[i]; s[i] = s[j]; s[j] = tmp; + blk[k] ^= s[(s[i]+s[j]) & 0xff]; + } + ctx->i = i; ctx->j = j; +} + +static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key, + unsigned keybytes) +{ + unsigned char tmp, k[256], *s; + unsigned i, j; + + s = ctx->s; + assert(keybytes <= 256); + ctx->i = ctx->j = 0; + for (i = 0; i < 256; i++) { + s[i] = i; + k[i] = key[i % keybytes]; + } + j = 0; + for (i = 0; i < 256; i++) { + j = (j + s[i] + k[i]) & 0xff; + tmp = s[i]; s[i] = s[j]; s[j] = tmp; + } +} + +/* -- Interface with PuTTY -- */ + +/* + * We don't implement Arcfour in SSH-1 because it's utterly insecure in + * several ways. See CERT Vulnerability Notes VU#25309, VU#665372, + * and VU#565052. + * + * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't + * stir the cipher state before emitting keystream, and hence is likely + * to leak data about the key. + */ + +static void *arcfour_make_context(void) +{ + return snew(ArcfourContext); +} + +static void arcfour_free_context(void *handle) +{ + sfree(handle); +} + +static void arcfour_stir(ArcfourContext *ctx) +{ + unsigned char *junk = snewn(1536, unsigned char); + memset(junk, 0, 1536); + arcfour_block(ctx, junk, 1536); + smemclr(junk, 1536); + sfree(junk); +} + +static void arcfour128_key(void *handle, unsigned char *key) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + arcfour_setkey(ctx, key, 16); + arcfour_stir(ctx); +} + +static void arcfour256_key(void *handle, unsigned char *key) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + arcfour_setkey(ctx, key, 32); + arcfour_stir(ctx); +} + +static void arcfour_iv(void *handle, unsigned char *key) +{ + +} + +const struct ssh2_cipher ssh_arcfour128_ssh2 = { + arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour128_key, + arcfour_block, arcfour_block, + "arcfour128", + 1, 128, 0, "Arcfour-128" +}; + +const struct ssh2_cipher ssh_arcfour256_ssh2 = { + arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key, + arcfour_block, arcfour_block, + "arcfour256", + 1, 256, 0, "Arcfour-256" +}; + +static const struct ssh2_cipher *const arcfour_list[] = { + &ssh_arcfour256_ssh2, + &ssh_arcfour128_ssh2, +}; + +const struct ssh2_ciphers ssh2_arcfour = { + sizeof(arcfour_list) / sizeof(*arcfour_list), + arcfour_list +}; diff --git a/netbox/libs/Putty/sshblowf.c b/netbox/libs/Putty/sshblowf.c new file mode 100644 index 000000000..8a106cbe9 --- /dev/null +++ b/netbox/libs/Putty/sshblowf.c @@ -0,0 +1,609 @@ +/* + * Blowfish implementation for PuTTY. + * + * Coded from scratch from the algorithm description. + */ + +#include +#include +#include "ssh.h" + +typedef struct { + word32 S0[256], S1[256], S2[256], S3[256], P[18]; + word32 iv0, iv1; /* for CBC mode */ +} BlowfishContext; + +/* + * The Blowfish init data: hex digits of the fractional part of pi. + * (ie pi as a hex fraction is 3.243F6A8885A308D3...) + * + * If you have Simon Tatham's 'spigot' exact real calculator + * available, or any other method of generating 8336 fractional hex + * digits of pi on standard output, you can regenerate these tables + * exactly as below using the following Perl script (adjusting the + * first line or two if your pi-generator is not spigot). + +open my $spig, "spigot -n -B16 -d8336 pi |"; +read $spig, $ignore, 2; # throw away the leading "3." +for my $name ("parray", "sbox0".."sbox3") { + print "static const word32 ${name}[] = {\n"; + my $len = $name eq "parray" ? 18 : 256; + for my $i (1..$len) { + read $spig, $word, 8; + printf "%s0x%s,", ($i%6==1 ? " " : " "), uc $word; + print "\n" if ($i == $len || $i%6 == 0); + } + print "};\n\n"; +} +close $spig; + + */ +static const word32 parray[] = { + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, +}; + +static const word32 sbox0[] = { + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, + 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, + 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, + 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1, + 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, + 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706, + 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, + 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, + 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, + 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, + 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, + 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705, + 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, + 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F, + 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A, +}; + +static const word32 sbox1[] = { + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, + 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, + 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, + 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908, + 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, + 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B, + 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, + 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, + 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, + 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77, + 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, + 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646, + 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, + 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, + 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7, +}; + +static const word32 sbox2[] = { + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, + 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4, + 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, + 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58, + 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, + 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, + 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, + 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979, + 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, + 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, + 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, + 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE, + 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, + 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, + 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0, +}; + +static const word32 sbox3[] = { + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, + 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, + 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, + 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6, + 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, + 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, + 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, + 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, + 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, + 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A, + 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, + 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623, + 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, + 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, + 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6, +}; + +#define Fprime(a,b,c,d) ( ( (S0[a] + S1[b]) ^ S2[c] ) + S3[d] ) +#define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) ) +#define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t ) + +static void blowfish_encrypt(word32 xL, word32 xR, word32 * output, + BlowfishContext * ctx) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 t; + + ROUND(0); + ROUND(1); + ROUND(2); + ROUND(3); + ROUND(4); + ROUND(5); + ROUND(6); + ROUND(7); + ROUND(8); + ROUND(9); + ROUND(10); + ROUND(11); + ROUND(12); + ROUND(13); + ROUND(14); + ROUND(15); + xL ^= P[16]; + xR ^= P[17]; + + output[0] = xR; + output[1] = xL; +} + +static void blowfish_decrypt(word32 xL, word32 xR, word32 * output, + BlowfishContext * ctx) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 t; + + ROUND(17); + ROUND(16); + ROUND(15); + ROUND(14); + ROUND(13); + ROUND(12); + ROUND(11); + ROUND(10); + ROUND(9); + ROUND(8); + ROUND(7); + ROUND(6); + ROUND(5); + ROUND(4); + ROUND(3); + ROUND(2); + xL ^= P[1]; + xR ^= P[0]; + + output[0] = xR; + output[1] = xL; +} + +static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + iv0 ^= xL; + iv1 ^= xR; + blowfish_encrypt(iv0, iv1, out, ctx); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_LSB_FIRST(blk, iv0); + PUT_32BIT_LSB_FIRST(blk + 4, iv1); + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + blowfish_decrypt(xL, xR, out, ctx); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_LSB_FIRST(blk, iv0); + PUT_32BIT_LSB_FIRST(blk + 4, iv1); + iv0 = xL; + iv1 = xR; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + iv0 ^= xL; + iv1 ^= xR; + blowfish_encrypt(iv0, iv1, out, ctx); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + blowfish_decrypt(xL, xR, out, ctx); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + iv0 = xL; + iv1 = xR; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_sdctr(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 b[2], iv0, iv1, tmp; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + blowfish_encrypt(iv0, iv1, b, ctx); + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]); + tmp = GET_32BIT_MSB_FIRST(blk + 4); + PUT_32BIT_MSB_FIRST(blk + 4, tmp ^ b[1]); + if ((iv1 = (iv1 + 1) & 0xffffffff) == 0) + iv0 = (iv0 + 1) & 0xffffffff; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_setkey(BlowfishContext * ctx, + const unsigned char *key, short keybytes) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 str[2]; + int i; + + for (i = 0; i < 18; i++) { + P[i] = parray[i]; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; + P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes])); + } + + for (i = 0; i < 256; i++) { + S0[i] = sbox0[i]; + S1[i] = sbox1[i]; + S2[i] = sbox2[i]; + S3[i] = sbox3[i]; + } + + str[0] = str[1] = 0; + + for (i = 0; i < 18; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + P[i] = str[0]; + P[i + 1] = str[1]; + } + + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S0[i] = str[0]; + S0[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S1[i] = str[0]; + S1[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S2[i] = str[0]; + S2[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S3[i] = str[0]; + S3[i + 1] = str[1]; + } +} + +/* -- Interface with PuTTY -- */ + +#define SSH_SESSION_KEY_LENGTH 32 + +static void *blowfish_make_context(void) +{ + return snew(BlowfishContext); +} + +static void *blowfish_ssh1_make_context(void) +{ + /* In SSH-1, need one key for each direction */ + return snewn(2, BlowfishContext); +} + +static void blowfish_free_context(void *handle) +{ + sfree(handle); +} + +static void blowfish_key(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, 16); +} + +static void blowfish256_key(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, 32); +} + +static void blowfish_iv(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + ctx->iv0 = GET_32BIT_MSB_FIRST(key); + ctx->iv1 = GET_32BIT_MSB_FIRST(key + 4); +} + +static void blowfish_sesskey(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, SSH_SESSION_KEY_LENGTH); + ctx->iv0 = 0; + ctx->iv1 = 0; + ctx[1] = ctx[0]; /* structure copy */ +} + +static void blowfish_ssh1_encrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_lsb_encrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh1_decrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_lsb_decrypt_cbc(blk, len, ctx+1); +} + +static void blowfish_ssh2_encrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_encrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh2_decrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_decrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh2_sdctr(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_sdctr(blk, len, ctx); +} + +const struct ssh_cipher ssh_blowfish_ssh1 = { + blowfish_ssh1_make_context, blowfish_free_context, blowfish_sesskey, + blowfish_ssh1_encrypt_blk, blowfish_ssh1_decrypt_blk, + 8, "Blowfish-128 CBC" +}; + +static const struct ssh2_cipher ssh_blowfish_ssh2 = { + blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish_key, + blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk, + "blowfish-cbc", + 8, 128, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC" +}; + +static const struct ssh2_cipher ssh_blowfish_ssh2_ctr = { + blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish256_key, + blowfish_ssh2_sdctr, blowfish_ssh2_sdctr, + "blowfish-ctr", + 8, 256, 0, "Blowfish-256 SDCTR" +}; + +static const struct ssh2_cipher *const blowfish_list[] = { + &ssh_blowfish_ssh2_ctr, + &ssh_blowfish_ssh2 +}; + +const struct ssh2_ciphers ssh2_blowfish = { + sizeof(blowfish_list) / sizeof(*blowfish_list), + blowfish_list +}; diff --git a/netbox/libs/Putty/sshblowf.h b/netbox/libs/Putty/sshblowf.h new file mode 100644 index 000000000..eb6a48c15 --- /dev/null +++ b/netbox/libs/Putty/sshblowf.h @@ -0,0 +1,15 @@ +/* + * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the + * internal Blowfish routines needed by bcrypt. + */ + +typedef struct BlowfishContext BlowfishContext; + +void *blowfish_make_context(void); +void blowfish_free_context(void *handle); +void blowfish_initkey(BlowfishContext *ctx); +void blowfish_expandkey(BlowfishContext *ctx, + const unsigned char *key, short keybytes, + const unsigned char *salt, short saltbytes); +void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, + BlowfishContext *ctx); diff --git a/netbox/libs/Putty/sshbn.c b/netbox/libs/Putty/sshbn.c new file mode 100644 index 000000000..12d0e0372 --- /dev/null +++ b/netbox/libs/Putty/sshbn.c @@ -0,0 +1,2184 @@ +/* + * Bignum routines for RSA and DH and stuff. + */ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" + +#include "sshbn.h" + +#define BIGNUM_INTERNAL +typedef BignumInt *Bignum; + +#include "ssh.h" + +BignumInt bnZero[1] = { 0 }; +BignumInt bnOne[2] = { 1, 1 }; +BignumInt bnTen[2] = { 1, 10 }; + +/* + * The Bignum format is an array of `BignumInt'. The first + * element of the array counts the remaining elements. The + * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_ + * significant digit first. (So it's trivial to extract the bit + * with value 2^n for any n.) + * + * All Bignums in this module are positive. Negative numbers must + * be dealt with outside it. + * + * INVARIANT: the most significant word of any Bignum must be + * nonzero. + */ + +Bignum Zero = bnZero, One = bnOne, Ten = bnTen; + +static Bignum newbn(int length) +{ + Bignum b; + + assert(length >= 0 && length < INT_MAX / BIGNUM_INT_BITS); + + b = snewn(length + 1, BignumInt); + if (!b) + abort(); /* FIXME */ + memset(b, 0, (length + 1) * sizeof(*b)); + b[0] = length; + return b; +} + +void bn_restore_invariant(Bignum b) +{ + while (b[0] > 1 && b[b[0]] == 0) + b[0]--; +} + +Bignum copybn(Bignum orig) +{ + Bignum b = snewn(orig[0] + 1, BignumInt); + if (!b) + abort(); /* FIXME */ + memcpy(b, orig, (orig[0] + 1) * sizeof(*b)); + return b; +} + +void freebn(Bignum b) +{ + /* + * Burn the evidence, just in case. + */ + smemclr(b, sizeof(b[0]) * (b[0] + 1)); + sfree(b); +} + +Bignum bn_power_2(int n) +{ + Bignum ret; + + assert(n >= 0); + + ret = newbn(n / BIGNUM_INT_BITS + 1); + bignum_set_bit(ret, n, 1); + return ret; +} + +/* + * Internal addition. Sets c = a - b, where 'a', 'b' and 'c' are all + * big-endian arrays of 'len' BignumInts. Returns the carry off the + * top. + */ +static BignumCarry internal_add(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len) +{ + int i; + BignumCarry carry = 0; + + for (i = len-1; i >= 0; i--) + BignumADC(c[i], carry, a[i], b[i], carry); + + return (BignumInt)carry; +} + +/* + * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are + * all big-endian arrays of 'len' BignumInts. Any borrow from the top + * is ignored. + */ +static void internal_sub(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len) +{ + int i; + BignumCarry carry = 1; + + for (i = len-1; i >= 0; i--) + BignumADC(c[i], carry, a[i], ~b[i], carry); +} + +/* + * Compute c = a * b. + * Input is in the first len words of a and b. + * Result is returned in the first 2*len words of c. + * + * 'scratch' must point to an array of BignumInt of size at least + * mul_compute_scratch(len). (This covers the needs of internal_mul + * and all its recursive calls to itself.) + */ +#define KARATSUBA_THRESHOLD 50 +static int mul_compute_scratch(int len) +{ + int ret = 0; + while (len > KARATSUBA_THRESHOLD) { + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + int midlen = botlen + 1; + ret += 4*midlen; + len = midlen; + } + return ret; +} +static void internal_mul(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len, BignumInt *scratch) +{ + if (len > KARATSUBA_THRESHOLD) { + int i; + + /* + * Karatsuba divide-and-conquer algorithm. Cut each input in + * half, so that it's expressed as two big 'digits' in a giant + * base D: + * + * a = a_1 D + a_0 + * b = b_1 D + b_0 + * + * Then the product is of course + * + * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 + * + * and we compute the three coefficients by recursively + * calling ourself to do half-length multiplications. + * + * The clever bit that makes this worth doing is that we only + * need _one_ half-length multiplication for the central + * coefficient rather than the two that it obviouly looks + * like, because we can use a single multiplication to compute + * + * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0 + * + * and then we subtract the other two coefficients (a_1 b_1 + * and a_0 b_0) which we were computing anyway. + * + * Hence we get to multiply two numbers of length N in about + * three times as much work as it takes to multiply numbers of + * length N/2, which is obviously better than the four times + * as much work it would take if we just did a long + * conventional multiply. + */ + + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + int midlen = botlen + 1; + BignumCarry carry; +#ifdef KARA_DEBUG + int i; +#endif + + /* + * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping + * in the output array, so we can compute them immediately in + * place. + */ + +#ifdef KARA_DEBUG + printf("a1,a0 = 0x"); + for (i = 0; i < len; i++) { + if (i == toplen) printf(", 0x"); + printf("%0*x", BIGNUM_INT_BITS/4, a[i]); + } + printf("\n"); + printf("b1,b0 = 0x"); + for (i = 0; i < len; i++) { + if (i == toplen) printf(", 0x"); + printf("%0*x", BIGNUM_INT_BITS/4, b[i]); + } + printf("\n"); +#endif + + /* a_1 b_1 */ + internal_mul(a, b, c, toplen, scratch); +#ifdef KARA_DEBUG + printf("a1b1 = 0x"); + for (i = 0; i < 2*toplen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[i]); + } + printf("\n"); +#endif + + /* a_0 b_0 */ + internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch); +#ifdef KARA_DEBUG + printf("a0b0 = 0x"); + for (i = 0; i < 2*botlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]); + } + printf("\n"); +#endif + + /* Zero padding. midlen exceeds toplen by at most 2, so just + * zero the first two words of each input and the rest will be + * copied over. */ + scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0; + + for (i = 0; i < toplen; i++) { + scratch[midlen - toplen + i] = a[i]; /* a_1 */ + scratch[2*midlen - toplen + i] = b[i]; /* b_1 */ + } + + /* compute a_1 + a_0 */ + scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen); +#ifdef KARA_DEBUG + printf("a1plusa0 = 0x"); + for (i = 0; i < midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); + } + printf("\n"); +#endif + /* compute b_1 + b_0 */ + scratch[midlen] = internal_add(scratch+midlen+1, b+toplen, + scratch+midlen+1, botlen); +#ifdef KARA_DEBUG + printf("b1plusb0 = 0x"); + for (i = 0; i < midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]); + } + printf("\n"); +#endif + + /* + * Now we can do the third multiplication. + */ + internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen, + scratch + 4*midlen); +#ifdef KARA_DEBUG + printf("a1plusa0timesb1plusb0 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); + } + printf("\n"); +#endif + + /* + * Now we can reuse the first half of 'scratch' to compute the + * sum of the outer two coefficients, to subtract from that + * product to obtain the middle one. + */ + scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0; + for (i = 0; i < 2*toplen; i++) + scratch[2*midlen - 2*toplen + i] = c[i]; + scratch[1] = internal_add(scratch+2, c + 2*toplen, + scratch+2, 2*botlen); +#ifdef KARA_DEBUG + printf("a1b1plusa0b0 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); + } + printf("\n"); +#endif + + internal_sub(scratch + 2*midlen, scratch, + scratch + 2*midlen, 2*midlen); +#ifdef KARA_DEBUG + printf("a1b0plusa0b1 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); + } + printf("\n"); +#endif + + /* + * And now all we need to do is to add that middle coefficient + * back into the output. We may have to propagate a carry + * further up the output, but we can be sure it won't + * propagate right the way off the top. + */ + carry = internal_add(c + 2*len - botlen - 2*midlen, + scratch + 2*midlen, + c + 2*len - botlen - 2*midlen, 2*midlen); + i = 2*len - botlen - 2*midlen - 1; + while (carry) { + assert(i >= 0); + BignumADC(c[i], carry, c[i], 0, carry); + i--; + } +#ifdef KARA_DEBUG + printf("ab = 0x"); + for (i = 0; i < 2*len; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[i]); + } + printf("\n"); +#endif + + } else { + int i; + BignumInt carry; + const BignumInt *ap, *bp; + BignumInt *cp, *cps; + + /* + * Multiply in the ordinary O(N^2) way. + */ + + for (i = 0; i < 2 * len; i++) + c[i] = 0; + + for (cps = c + 2*len, ap = a + len; ap-- > a; cps--) { + carry = 0; + for (cp = cps, bp = b + len; cp--, bp-- > b ;) + BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry); + *cp = carry; + } + } +} + +/* + * Variant form of internal_mul used for the initial step of + * Montgomery reduction. Only bothers outputting 'len' words + * (everything above that is thrown away). + */ +static void internal_mul_low(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len, BignumInt *scratch) +{ + if (len > KARATSUBA_THRESHOLD) { + int i; + + /* + * Karatsuba-aware version of internal_mul_low. As before, we + * express each input value as a shifted combination of two + * halves: + * + * a = a_1 D + a_0 + * b = b_1 D + b_0 + * + * Then the full product is, as before, + * + * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 + * + * Provided we choose D on the large side (so that a_0 and b_0 + * are _at least_ as long as a_1 and b_1), we don't need the + * topmost term at all, and we only need half of the middle + * term. So there's no point in doing the proper Karatsuba + * optimisation which computes the middle term using the top + * one, because we'd take as long computing the top one as + * just computing the middle one directly. + * + * So instead, we do a much more obvious thing: we call the + * fully optimised internal_mul to compute a_0 b_0, and we + * recursively call ourself to compute the _bottom halves_ of + * a_1 b_0 and a_0 b_1, each of which we add into the result + * in the obvious way. + * + * In other words, there's no actual Karatsuba _optimisation_ + * in this function; the only benefit in doing it this way is + * that we call internal_mul proper for a large part of the + * work, and _that_ can optimise its operation. + */ + + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + + /* + * Scratch space for the various bits and pieces we're going + * to be adding together: we need botlen*2 words for a_0 b_0 + * (though we may end up throwing away its topmost word), and + * toplen words for each of a_1 b_0 and a_0 b_1. That adds up + * to exactly 2*len. + */ + + /* a_0 b_0 */ + internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen, + scratch + 2*len); + + /* a_1 b_0 */ + internal_mul_low(a, b + len - toplen, scratch + toplen, toplen, + scratch + 2*len); + + /* a_0 b_1 */ + internal_mul_low(a + len - toplen, b, scratch, toplen, + scratch + 2*len); + + /* Copy the bottom half of the big coefficient into place */ + for (i = 0; i < botlen; i++) + c[toplen + i] = scratch[2*toplen + botlen + i]; + + /* Add the two small coefficients, throwing away the returned carry */ + internal_add(scratch, scratch + toplen, scratch, toplen); + + /* And add that to the large coefficient, leaving the result in c. */ + internal_add(scratch, scratch + 2*toplen + botlen - toplen, + c, toplen); + + } else { + int i; + BignumInt carry; + const BignumInt *ap, *bp; + BignumInt *cp, *cps; + + /* + * Multiply in the ordinary O(N^2) way. + */ + + for (i = 0; i < len; i++) + c[i] = 0; + + for (cps = c + len, ap = a + len; ap-- > a; cps--) { + carry = 0; + for (cp = cps, bp = b + len; bp--, cp-- > c ;) + BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry); + } + } +} + +/* + * Montgomery reduction. Expects x to be a big-endian array of 2*len + * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len * + * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array + * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <= + * x' < n. + * + * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts + * each, containing respectively n and the multiplicative inverse of + * -n mod r. + * + * 'tmp' is an array of BignumInt used as scratch space, of length at + * least 3*len + mul_compute_scratch(len). + */ +static void monty_reduce(BignumInt *x, const BignumInt *n, + const BignumInt *mninv, BignumInt *tmp, int len) +{ + int i; + BignumInt carry; + + /* + * Multiply x by (-n)^{-1} mod r. This gives us a value m such + * that mn is congruent to -x mod r. Hence, mn+x is an exact + * multiple of r, and is also (obviously) congruent to x mod n. + */ + internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len); + + /* + * Compute t = (mn+x)/r in ordinary, non-modular, integer + * arithmetic. By construction this is exact, and is congruent mod + * n to x * r^{-1}, i.e. the answer we want. + * + * The following multiply leaves that answer in the _most_ + * significant half of the 'x' array, so then we must shift it + * down. + */ + internal_mul(tmp, n, tmp+len, len, tmp + 3*len); + carry = internal_add(x, tmp+len, x, 2*len); + for (i = 0; i < len; i++) + x[len + i] = x[i], x[i] = 0; + + /* + * Reduce t mod n. This doesn't require a full-on division by n, + * but merely a test and single optional subtraction, since we can + * show that 0 <= t < 2n. + * + * Proof: + * + we computed m mod r, so 0 <= m < r. + * + so 0 <= mn < rn, obviously + * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn + * + yielding 0 <= (mn+x)/r < 2n as required. + */ + if (!carry) { + for (i = 0; i < len; i++) + if (x[len + i] != n[i]) + break; + } + if (carry || i >= len || x[len + i] > n[i]) + internal_sub(x+len, n, x+len, len); +} + +static void internal_add_shifted(BignumInt *number, + BignumInt n, int shift) +{ + int word = 1 + (shift / BIGNUM_INT_BITS); + int bshift = shift % BIGNUM_INT_BITS; + BignumInt addendh, addendl; + BignumCarry carry; + + addendl = n << bshift; + addendh = (bshift == 0 ? 0 : n >> (BIGNUM_INT_BITS - bshift)); + + assert(word <= number[0]); + BignumADC(number[word], carry, number[word], addendl, 0); + word++; + if (!addendh && !carry) + return; + assert(word <= number[0]); + BignumADC(number[word], carry, number[word], addendh, carry); + word++; + while (carry) { + assert(word <= number[0]); + BignumADC(number[word], carry, number[word], 0, carry); + word++; + } +} + +static int bn_clz(BignumInt x) +{ + /* + * Count the leading zero bits in x. Equivalently, how far left + * would we need to shift x to make its top bit set? + * + * Precondition: x != 0. + */ + + /* FIXME: would be nice to put in some compiler intrinsics under + * ifdef here */ + int i, ret = 0; + for (i = BIGNUM_INT_BITS / 2; i != 0; i >>= 1) { + if ((x >> (BIGNUM_INT_BITS-i)) == 0) { + x <<= i; + ret += i; + } + } + return ret; +} + +static BignumInt reciprocal_word(BignumInt d) +{ + BignumInt dshort, recip, prodh, prodl; + int corrections; + + /* + * Input: a BignumInt value d, with its top bit set. + */ + assert(d >> (BIGNUM_INT_BITS-1) == 1); + + /* + * Output: a value, shifted to fill a BignumInt, which is strictly + * less than 1/(d+1), i.e. is an *under*-estimate (but by as + * little as possible within the constraints) of the reciprocal of + * any number whose first BIGNUM_INT_BITS bits match d. + * + * Ideally we'd like to _totally_ fill BignumInt, i.e. always + * return a value with the top bit set. Unfortunately we can't + * quite guarantee that for all inputs and also return a fixed + * exponent. So instead we take our reciprocal to be + * 2^(BIGNUM_INT_BITS*2-1) / d, so that it has the top bit clear + * only in the exceptional case where d takes exactly the maximum + * value BIGNUM_INT_MASK; in that case, the top bit is clear and + * the next bit down is set. + */ + + /* + * Start by computing a half-length version of the answer, by + * straightforward division within a BignumInt. + */ + dshort = (d >> (BIGNUM_INT_BITS/2)) + 1; + recip = (BIGNUM_TOP_BIT + dshort - 1) / dshort; + recip <<= BIGNUM_INT_BITS - BIGNUM_INT_BITS/2; + + /* + * Newton-Raphson iteration to improve that starting reciprocal + * estimate: take f(x) = d - 1/x, and then the N-R formula gives + * x_new = x - f(x)/f'(x) = x - (d-1/x)/(1/x^2) = x(2-d*x). Or, + * taking our fixed-point representation into account, take f(x) + * to be d - K/x (where K = 2^(BIGNUM_INT_BITS*2-1) as discussed + * above) and then we get (2K - d*x) * x/K. + * + * Newton-Raphson doubles the number of correct bits at every + * iteration, and the initial division above already gave us half + * the output word, so it's only worth doing one iteration. + */ + BignumMULADD(prodh, prodl, recip, d, recip); + prodl = ~prodl; + prodh = ~prodh; + { + BignumCarry c; + BignumADC(prodl, c, prodl, 1, 0); + prodh += c; + } + BignumMUL(prodh, prodl, prodh, recip); + recip = (prodh << 1) | (prodl >> (BIGNUM_INT_BITS-1)); + + /* + * Now make sure we have the best possible reciprocal estimate, + * before we return it. We might have been off by a handful either + * way - not enough to bother with any better-thought-out kind of + * correction loop. + */ + BignumMULADD(prodh, prodl, recip, d, recip); + corrections = 0; + if (prodh >= BIGNUM_TOP_BIT) { + do { + BignumCarry c = 1; + BignumADC(prodl, c, prodl, ~d, c); prodh += BIGNUM_INT_MASK + c; + recip--; + corrections++; + } while (prodh >= ((BignumInt)1 << (BIGNUM_INT_BITS-1))); + } else { + while (1) { + BignumInt newprodh, newprodl; + BignumCarry c = 0; + BignumADC(newprodl, c, prodl, d, c); newprodh = prodh + c; + if (newprodh >= BIGNUM_TOP_BIT) + break; + prodh = newprodh; + prodl = newprodl; + recip++; + corrections++; + } + } + + return recip; +} + +/* + * Compute a = a % m. + * Input in first alen words of a and first mlen words of m. + * Output in first alen words of a + * (of which first alen-mlen words will be zero). + * Quotient is accumulated in the `quotient' array, which is a Bignum + * rather than the internal bigendian format. + * + * 'recip' must be the result of calling reciprocal_word() on the top + * BIGNUM_INT_BITS of the modulus (denoted m0 in comments below), with + * the topmost set bit normalised to the MSB of the input to + * reciprocal_word. 'rshift' is how far left the top nonzero word of + * the modulus had to be shifted to set that top bit. + */ +static void internal_mod(BignumInt *a, int alen, + BignumInt *m, int mlen, + BignumInt *quot, BignumInt recip, int rshift) +{ + int i, k; + +#ifdef DIVISION_DEBUG + { + int d; + printf("start division, m=0x"); + for (d = 0; d < mlen; d++) + printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)m[d]); + printf(", recip=%#0*llx, rshift=%d\n", + BIGNUM_INT_BITS/4, (unsigned long long)recip, rshift); + } +#endif + + /* + * Repeatedly use that reciprocal estimate to get a decent number + * of quotient bits, and subtract off the resulting multiple of m. + * + * Normally we expect to terminate this loop by means of finding + * out q=0 part way through, but one way in which we might not get + * that far in the first place is if the input a is actually zero, + * in which case we'll discard zero words from the front of a + * until we reach the termination condition in the for statement + * here. + */ + for (i = 0; i <= alen - mlen ;) { + BignumInt product; + BignumInt aword, q; + int shift, full_bitoffset, bitoffset, wordoffset; + +#ifdef DIVISION_DEBUG + { + int d; + printf("main loop, a=0x"); + for (d = 0; d < alen; d++) + printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); + printf("\n"); + } +#endif + + if (a[i] == 0) { +#ifdef DIVISION_DEBUG + printf("zero word at i=%d\n", i); +#endif + i++; + continue; + } + + aword = a[i]; + shift = bn_clz(aword); + aword <<= shift; + if (shift > 0 && i+1 < alen) + aword |= a[i+1] >> (BIGNUM_INT_BITS - shift); + + { + BignumInt unused; + BignumMUL(q, unused, recip, aword); + (void)unused; + } + +#ifdef DIVISION_DEBUG + printf("i=%d, aword=%#0*llx, shift=%d, q=%#0*llx\n", + i, BIGNUM_INT_BITS/4, (unsigned long long)aword, + shift, BIGNUM_INT_BITS/4, (unsigned long long)q); +#endif + + /* + * Work out the right bit and word offsets to use when + * subtracting q*m from a. + * + * aword was taken from a[i], which means its LSB was at bit + * position (alen-1-i) * BIGNUM_INT_BITS. But then we shifted + * it left by 'shift', so now the low bit of aword corresponds + * to bit position (alen-1-i) * BIGNUM_INT_BITS - shift, i.e. + * aword is approximately equal to a / 2^(that). + * + * m0 comes from the top word of mod, so its LSB is at bit + * position (mlen-1) * BIGNUM_INT_BITS - rshift, i.e. it can + * be considered to be m / 2^(that power). 'recip' is the + * reciprocal of m0, times 2^(BIGNUM_INT_BITS*2-1), i.e. it's + * about 2^((mlen+1) * BIGNUM_INT_BITS - rshift - 1) / m. + * + * Hence, recip * aword is approximately equal to the product + * of those, which simplifies to + * + * a/m * 2^((mlen+2+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1) + * + * But we've also shifted recip*aword down by BIGNUM_INT_BITS + * to form q, so we have + * + * q ~= a/m * 2^((mlen+1+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1) + * + * and hence, when we now compute q*m, it will be about + * a*2^(all that lot), i.e. the negation of that expression is + * how far left we have to shift the product q*m to make it + * approximately equal to a. + */ + full_bitoffset = -((mlen+1+i-alen)*BIGNUM_INT_BITS + shift-rshift-1); +#ifdef DIVISION_DEBUG + printf("full_bitoffset=%d\n", full_bitoffset); +#endif + + if (full_bitoffset < 0) { + /* + * If we find ourselves needing to shift q*m _right_, that + * means we've reached the bottom of the quotient. Clip q + * so that its right shift becomes zero, and if that means + * q becomes _actually_ zero, this loop is done. + */ + if (full_bitoffset <= -BIGNUM_INT_BITS) + break; + q >>= -full_bitoffset; + full_bitoffset = 0; + if (!q) + break; +#ifdef DIVISION_DEBUG + printf("now full_bitoffset=%d, q=%#0*llx\n", + full_bitoffset, BIGNUM_INT_BITS/4, (unsigned long long)q); +#endif + } + + wordoffset = full_bitoffset / BIGNUM_INT_BITS; + bitoffset = full_bitoffset % BIGNUM_INT_BITS; +#ifdef DIVISION_DEBUG + printf("wordoffset=%d, bitoffset=%d\n", wordoffset, bitoffset); +#endif + + /* wordoffset as computed above is the offset between the LSWs + * of m and a. But in fact m and a are stored MSW-first, so we + * need to adjust it to be the offset between the actual array + * indices, and flip the sign too. */ + wordoffset = alen - mlen - wordoffset; + + if (bitoffset == 0) { + BignumCarry c = 1; + BignumInt prev_hi_word = 0; + for (k = mlen - 1; wordoffset+k >= i; k--) { + BignumInt mword = k<0 ? 0 : m[k]; + BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word); +#ifdef DIVISION_DEBUG + printf(" aligned sub: product word for m[%d] = %#0*llx\n", + k, BIGNUM_INT_BITS/4, + (unsigned long long)product); +#endif +#ifdef DIVISION_DEBUG + printf(" aligned sub: subtrahend for a[%d] = %#0*llx\n", + wordoffset+k, BIGNUM_INT_BITS/4, + (unsigned long long)product); +#endif + BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~product, c); + } + } else { + BignumInt add_word = 0; + BignumInt c = 1; + BignumInt prev_hi_word = 0; + for (k = mlen - 1; wordoffset+k >= i; k--) { + BignumInt mword = k<0 ? 0 : m[k]; + BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word); +#ifdef DIVISION_DEBUG + printf(" unaligned sub: product word for m[%d] = %#0*llx\n", + k, BIGNUM_INT_BITS/4, + (unsigned long long)product); +#endif + + add_word |= product << bitoffset; + +#ifdef DIVISION_DEBUG + printf(" unaligned sub: subtrahend for a[%d] = %#0*llx\n", + wordoffset+k, + BIGNUM_INT_BITS/4, (unsigned long long)add_word); +#endif + BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~add_word, c); + + add_word = product >> (BIGNUM_INT_BITS - bitoffset); + } + } + + if (quot) { +#ifdef DIVISION_DEBUG + printf("adding quotient word %#0*llx << %d\n", + BIGNUM_INT_BITS/4, (unsigned long long)q, full_bitoffset); +#endif + internal_add_shifted(quot, q, full_bitoffset); +#ifdef DIVISION_DEBUG + { + int d; + printf("now quot=0x"); + for (d = quot[0]; d > 0; d--) + printf("%0*llx", BIGNUM_INT_BITS/4, + (unsigned long long)quot[d]); + printf("\n"); + } +#endif + } + } + +#ifdef DIVISION_DEBUG + { + int d; + printf("end main loop, a=0x"); + for (d = 0; d < alen; d++) + printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); + if (quot) { + printf(", quot=0x"); + for (d = quot[0]; d > 0; d--) + printf("%0*llx", BIGNUM_INT_BITS/4, + (unsigned long long)quot[d]); + } + printf("\n"); + } +#endif + + /* + * The above loop should terminate with the remaining value in a + * being strictly less than 2*m (if a >= 2*m then we should always + * have managed to get a nonzero q word), but we can't guarantee + * that it will be strictly less than m: consider a case where the + * remainder is 1, and another where the remainder is m-1. By the + * time a contains a value that's _about m_, you clearly can't + * distinguish those cases by looking at only the top word of a - + * you have to go all the way down to the bottom before you find + * out whether it's just less or just more than m. + * + * Hence, we now do a final fixup in which we subtract one last + * copy of m, or don't, accordingly. We should never have to + * subtract more than one copy of m here. + */ + for (i = 0; i < alen; i++) { + /* Compare a with m, word by word, from the MSW down. As soon + * as we encounter a difference, we know whether we need the + * fixup. */ + int mindex = mlen-alen+i; + BignumInt mword = mindex < 0 ? 0 : m[mindex]; + if (a[i] < mword) { +#ifdef DIVISION_DEBUG + printf("final fixup not needed, a < m\n"); +#endif + return; + } else if (a[i] > mword) { +#ifdef DIVISION_DEBUG + printf("final fixup is needed, a > m\n"); +#endif + break; + } + /* If neither of those cases happened, the words are the same, + * so keep going and look at the next one. */ + } +#ifdef DIVISION_DEBUG + if (i == mlen) /* if we printed neither of the above diagnostics */ + printf("final fixup is needed, a == m\n"); +#endif + + /* + * If we got here without returning, then a >= m, so we must + * subtract m, and increment the quotient. + */ + { + BignumCarry c = 1; + for (i = alen - 1; i >= 0; i--) { + int mindex = mlen-alen+i; + BignumInt mword = mindex < 0 ? 0 : m[mindex]; + BignumADC(a[i], c, a[i], ~mword, c); + } + } + if (quot) + internal_add_shifted(quot, 1, 0); + +#ifdef DIVISION_DEBUG + { + int d; + printf("after final fixup, a=0x"); + for (d = 0; d < alen; d++) + printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); + if (quot) { + printf(", quot=0x"); + for (d = quot[0]; d > 0; d--) + printf("%0*llx", BIGNUM_INT_BITS/4, + (unsigned long long)quot[d]); + } + printf("\n"); + } +#endif +} + +/* + * Compute (base ^ exp) % mod, the pedestrian way. + */ +Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod) +{ + BignumInt *a, *b, *n, *m, *scratch; + BignumInt recip; + int rshift; + int mlen, scratchlen, i, j; + Bignum base, result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* + * Make sure the base is smaller than the modulus, by reducing + * it modulo the modulus if not. + */ + base = bigmod(base_in, mod); + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Allocate n of size mlen, copy base to n */ + n = snewn(mlen, BignumInt); + i = mlen - base[0]; + for (j = 0; j < i; j++) + n[j] = 0; + for (j = 0; j < (int)base[0]; j++) + n[i + j] = base[base[0] - j]; + + /* Allocate a and b of size 2*mlen. Set a = 1 */ + a = snewn(2 * mlen, BignumInt); + b = snewn(2 * mlen, BignumInt); + for (i = 0; i < 2 * mlen; i++) + a[i] = 0; + a[2 * mlen - 1] = 1; + + /* Scratch space for multiplies */ + scratchlen = mul_compute_scratch(mlen); + scratch = snewn(scratchlen, BignumInt); + + /* Skip leading zero bits of exp. */ + i = 0; + j = BIGNUM_INT_BITS-1; + while (i < (int)exp[0] && (exp[exp[0] - i] & ((BignumInt)1 << j)) == 0) { + j--; + if (j < 0) { + i++; + j = BIGNUM_INT_BITS-1; + } + } + + /* Compute reciprocal of the top full word of the modulus */ + { + BignumInt m0 = m[0]; + rshift = bn_clz(m0); + if (rshift) { + m0 <<= rshift; + if (mlen > 1) + m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); + } + recip = reciprocal_word(m0); + } + + /* Main computation */ + while (i < (int)exp[0]) { + while (j >= 0) { + internal_mul(a + mlen, a + mlen, b, mlen, scratch); + internal_mod(b, mlen * 2, m, mlen, NULL, recip, rshift); + if ((exp[exp[0] - i] & ((BignumInt)1 << j)) != 0) { + internal_mul(b + mlen, n, a, mlen, scratch); + internal_mod(a, mlen * 2, m, mlen, NULL, recip, rshift); + } else { + BignumInt *t; + t = a; + a = b; + b = t; + } + j--; + } + i++; + j = BIGNUM_INT_BITS-1; + } + + /* Copy result to buffer */ + result = newbn(mod[0]); + for (i = 0; i < mlen; i++) + result[result[0] - i] = a[i + mlen]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + smemclr(a, 2 * mlen * sizeof(*a)); + sfree(a); + smemclr(scratch, scratchlen * sizeof(*scratch)); + sfree(scratch); + smemclr(b, 2 * mlen * sizeof(*b)); + sfree(b); + smemclr(m, mlen * sizeof(*m)); + sfree(m); + smemclr(n, mlen * sizeof(*n)); + sfree(n); + + freebn(base); + + return result; +} + +/* + * Compute (base ^ exp) % mod. Uses the Montgomery multiplication + * technique where possible, falling back to modpow_simple otherwise. + */ +Bignum modpow(Bignum base_in, Bignum exp, Bignum mod) +{ + BignumInt *a, *b, *x, *n, *mninv, *scratch; + int len, scratchlen, i, j; + Bignum base, base2, r, rn, inv, result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* + * mod had better be odd, or we can't do Montgomery multiplication + * using a power of two at all. + */ + if (!(mod[1] & 1)) + return modpow_simple(base_in, exp, mod); + + /* + * Make sure the base is smaller than the modulus, by reducing + * it modulo the modulus if not. + */ + base = bigmod(base_in, mod); + + /* + * Compute the inverse of n mod r, for monty_reduce. (In fact we + * want the inverse of _minus_ n mod r, but we'll sort that out + * below.) + */ + len = mod[0]; + r = bn_power_2(BIGNUM_INT_BITS * len); + inv = modinv(mod, r); + assert(inv); /* cannot fail, since mod is odd and r is a power of 2 */ + + /* + * Multiply the base by r mod n, to get it into Montgomery + * representation. + */ + base2 = modmul(base, r, mod); + freebn(base); + base = base2; + + rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */ + + freebn(r); /* won't need this any more */ + + /* + * Set up internal arrays of the right lengths, in big-endian + * format, containing the base, the modulus, and the modulus's + * inverse. + */ + n = snewn(len, BignumInt); + for (j = 0; j < len; j++) + n[len - 1 - j] = mod[j + 1]; + + mninv = snewn(len, BignumInt); + for (j = 0; j < len; j++) + mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0); + freebn(inv); /* we don't need this copy of it any more */ + /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */ + x = snewn(len, BignumInt); + for (j = 0; j < len; j++) + x[j] = 0; + internal_sub(x, mninv, mninv, len); + + /* x = snewn(len, BignumInt); */ /* already done above */ + for (j = 0; j < len; j++) + x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0); + freebn(base); /* we don't need this copy of it any more */ + + a = snewn(2*len, BignumInt); + b = snewn(2*len, BignumInt); + for (j = 0; j < len; j++) + a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0); + freebn(rn); + + /* Scratch space for multiplies */ + scratchlen = 3*len + mul_compute_scratch(len); + scratch = snewn(scratchlen, BignumInt); + + /* Skip leading zero bits of exp. */ + i = 0; + j = BIGNUM_INT_BITS-1; + while (i < (int)exp[0] && (exp[exp[0] - i] & ((BignumInt)1 << j)) == 0) { + j--; + if (j < 0) { + i++; + j = BIGNUM_INT_BITS-1; + } + } + + /* Main computation */ + while (i < (int)exp[0]) { + while (j >= 0) { + internal_mul(a + len, a + len, b, len, scratch); + monty_reduce(b, n, mninv, scratch, len); + if ((exp[exp[0] - i] & ((BignumInt)1 << j)) != 0) { + internal_mul(b + len, x, a, len, scratch); + monty_reduce(a, n, mninv, scratch, len); + } else { + BignumInt *t; + t = a; + a = b; + b = t; + } + j--; + } + i++; + j = BIGNUM_INT_BITS-1; + } + + /* + * Final monty_reduce to get back from the adjusted Montgomery + * representation. + */ + monty_reduce(a, n, mninv, scratch, len); + + /* Copy result to buffer */ + result = newbn(mod[0]); + for (i = 0; i < len; i++) + result[result[0] - i] = a[i + len]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + smemclr(scratch, scratchlen * sizeof(*scratch)); + sfree(scratch); + smemclr(a, 2 * len * sizeof(*a)); + sfree(a); + smemclr(b, 2 * len * sizeof(*b)); + sfree(b); + smemclr(mninv, len * sizeof(*mninv)); + sfree(mninv); + smemclr(n, len * sizeof(*n)); + sfree(n); + smemclr(x, len * sizeof(*x)); + sfree(x); + + return result; +} + +/* + * Compute (p * q) % mod. + * The most significant word of mod MUST be non-zero. + * We assume that the result array is the same size as the mod array. + */ +Bignum modmul(Bignum p, Bignum q, Bignum mod) +{ + BignumInt *a, *n, *m, *o, *scratch; + BignumInt recip; + int rshift, scratchlen; + int pqlen, mlen, rlen, i, j; + Bignum result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + pqlen = (p[0] > q[0] ? p[0] : q[0]); + + /* + * Make sure that we're allowing enough space. The shifting below + * will underflow the vectors we allocate if pqlen is too small. + */ + if (2*pqlen <= mlen) + pqlen = mlen/2 + 1; + + /* Allocate n of size pqlen, copy p to n */ + n = snewn(pqlen, BignumInt); + i = pqlen - p[0]; + for (j = 0; j < i; j++) + n[j] = 0; + for (j = 0; j < (int)p[0]; j++) + n[i + j] = p[p[0] - j]; + + /* Allocate o of size pqlen, copy q to o */ + o = snewn(pqlen, BignumInt); + i = pqlen - q[0]; + for (j = 0; j < i; j++) + o[j] = 0; + for (j = 0; j < (int)q[0]; j++) + o[i + j] = q[q[0] - j]; + + /* Allocate a of size 2*pqlen for result */ + a = snewn(2 * pqlen, BignumInt); + + /* Scratch space for multiplies */ + scratchlen = mul_compute_scratch(pqlen); + scratch = snewn(scratchlen, BignumInt); + + /* Compute reciprocal of the top full word of the modulus */ + { + BignumInt m0 = m[0]; + rshift = bn_clz(m0); + if (rshift) { + m0 <<= rshift; + if (mlen > 1) + m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); + } + recip = reciprocal_word(m0); + } + + /* Main computation */ + internal_mul(n, o, a, pqlen, scratch); + internal_mod(a, pqlen * 2, m, mlen, NULL, recip, rshift); + + /* Copy result to buffer */ + rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2); + result = newbn(rlen); + for (i = 0; i < rlen; i++) + result[result[0] - i] = a[i + 2 * pqlen - rlen]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + smemclr(scratch, scratchlen * sizeof(*scratch)); + sfree(scratch); + smemclr(a, 2 * pqlen * sizeof(*a)); + sfree(a); + smemclr(m, mlen * sizeof(*m)); + sfree(m); + smemclr(n, pqlen * sizeof(*n)); + sfree(n); + smemclr(o, pqlen * sizeof(*o)); + sfree(o); + + return result; +} + +Bignum modsub(const Bignum a, const Bignum b, const Bignum n) +{ + Bignum a1, b1, ret; + + if (bignum_cmp(a, n) >= 0) a1 = bigmod(a, n); + else a1 = a; + if (bignum_cmp(b, n) >= 0) b1 = bigmod(b, n); + else b1 = b; + + if (bignum_cmp(a1, b1) >= 0) /* a >= b */ + { + ret = bigsub(a1, b1); + } + else + { + /* Handle going round the corner of the modulus without having + * negative support in Bignum */ + Bignum tmp = bigsub(n, b1); + assert(tmp); + ret = bigadd(tmp, a1); + freebn(tmp); + } + + if (a != a1) freebn(a1); + if (b != b1) freebn(b1); + + return ret; +} + +/* + * Compute p % mod. + * The most significant word of mod MUST be non-zero. + * We assume that the result array is the same size as the mod array. + * We optionally write out a quotient if `quotient' is non-NULL. + * We can avoid writing out the result if `result' is NULL. + */ +static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient) +{ + BignumInt *n, *m; + BignumInt recip; + int rshift; + int plen, mlen, i, j; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + plen = p[0]; + /* Ensure plen > mlen */ + if (plen <= mlen) + plen = mlen + 1; + + /* Allocate n of size plen, copy p to n */ + n = snewn(plen, BignumInt); + for (j = 0; j < plen; j++) + n[j] = 0; + for (j = 1; j <= (int)p[0]; j++) + n[plen - j] = p[j]; + + /* Compute reciprocal of the top full word of the modulus */ + { + BignumInt m0 = m[0]; + rshift = bn_clz(m0); + if (rshift) { + m0 <<= rshift; + if (mlen > 1) + m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); + } + recip = reciprocal_word(m0); + } + + /* Main computation */ + internal_mod(n, plen, m, mlen, quotient, recip, rshift); + + /* Copy result to buffer */ + if (result) { + for (i = 1; i <= (int)result[0]; i++) { + int j = plen - i; + result[i] = j >= 0 ? n[j] : 0; + } + } + + /* Free temporary arrays */ + smemclr(m, mlen * sizeof(*m)); + sfree(m); + smemclr(n, plen * sizeof(*n)); + sfree(n); +} + +/* + * Decrement a number. + */ +void decbn(Bignum bn) +{ + int i = 1; + while (i < (int)bn[0] && bn[i] == 0) + bn[i++] = BIGNUM_INT_MASK; + bn[i]--; +} + +Bignum bignum_from_bytes(const unsigned char *data, int nbytes) +{ + Bignum result; + int w, i; + + assert(nbytes >= 0 && nbytes < INT_MAX/8); + + w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ + + result = newbn(w); + for (i = 1; i <= w; i++) + result[i] = 0; + for (i = nbytes; i--;) { + unsigned char byte = *data++; + result[1 + i / BIGNUM_INT_BYTES] |= + (BignumInt)byte << (8*i % BIGNUM_INT_BITS); + } + + bn_restore_invariant(result); + return result; +} + +Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes) +{ + Bignum result; + int w, i; + + assert(nbytes >= 0 && nbytes < INT_MAX/8); + + w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ + + result = newbn(w); + for (i = 1; i <= w; i++) + result[i] = 0; + for (i = 0; i < nbytes; ++i) { + unsigned char byte = *data++; + result[1 + i / BIGNUM_INT_BYTES] |= + (BignumInt)byte << (8*i % BIGNUM_INT_BITS); + } + + bn_restore_invariant(result); + return result; +} + +Bignum bignum_from_decimal(const char *decimal) +{ + Bignum result = copybn(Zero); + + while (*decimal) { + Bignum tmp, tmp2; + + if (!isdigit((unsigned char)*decimal)) { + freebn(result); + return 0; + } + + tmp = bigmul(result, Ten); + tmp2 = bignum_from_long(*decimal - '0'); + result = bigadd(tmp, tmp2); + freebn(tmp); + freebn(tmp2); + + decimal++; + } + + return result; +} + +Bignum bignum_random_in_range(const Bignum lower, const Bignum upper) +{ + Bignum ret = NULL; + unsigned char *bytes; + int upper_len = bignum_bitcount(upper); + int upper_bytes = upper_len / 8; + int upper_bits = upper_len % 8; + if (upper_bits) ++upper_bytes; + + bytes = snewn(upper_bytes, unsigned char); + do { + int i; + + if (ret) freebn(ret); + + for (i = 0; i < upper_bytes; ++i) + { + bytes[i] = (unsigned char)random_byte(); + } + /* Mask the top to reduce failure rate to 50/50 */ + if (upper_bits) + { + bytes[i - 1] &= 0xFF >> (8 - upper_bits); + } + + ret = bignum_from_bytes(bytes, upper_bytes); + } while (bignum_cmp(ret, lower) < 0 || bignum_cmp(ret, upper) > 0); + smemclr(bytes, upper_bytes); + sfree(bytes); + + return ret; +} + +/* + * Read an SSH-1-format bignum from a data buffer. Return the number + * of bytes consumed, or -1 if there wasn't enough data. + */ +int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result) +{ + const unsigned char *p = data; + int i; + int w, b; + + if (len < 2) + return -1; + + w = 0; + for (i = 0; i < 2; i++) + w = (w << 8) + *p++; + b = (w + 7) / 8; /* bits -> bytes */ + + if (len < b+2) + return -1; + + if (!result) /* just return length */ + return b + 2; + + *result = bignum_from_bytes(p, b); + + return p + b - data; +} + +/* + * Return the bit count of a bignum, for SSH-1 encoding. + */ +int bignum_bitcount(Bignum bn) +{ + int bitcount = bn[0] * BIGNUM_INT_BITS - 1; + while (bitcount >= 0 + && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--; + return bitcount + 1; +} + +/* + * Return the byte length of a bignum when SSH-1 encoded. + */ +int ssh1_bignum_length(Bignum bn) +{ + return 2 + (bignum_bitcount(bn) + 7) / 8; +} + +/* + * Return the byte length of a bignum when SSH-2 encoded. + */ +int ssh2_bignum_length(Bignum bn) +{ + return 4 + (bignum_bitcount(bn) + 8) / 8; +} + +/* + * Return a byte from a bignum; 0 is least significant, etc. + */ +int bignum_byte(Bignum bn, int i) +{ + if (i < 0 || i >= (int)(BIGNUM_INT_BYTES * bn[0])) + return 0; /* beyond the end */ + else + return (bn[i / BIGNUM_INT_BYTES + 1] >> + ((i % BIGNUM_INT_BYTES)*8)) & 0xFF; +} + +/* + * Return a bit from a bignum; 0 is least significant, etc. + */ +int bignum_bit(Bignum bn, int i) +{ + if (i < 0 || i >= (int)(BIGNUM_INT_BITS * bn[0])) + return 0; /* beyond the end */ + else + return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1; +} + +/* + * Set a bit in a bignum; 0 is least significant, etc. + */ +void bignum_set_bit(Bignum bn, int bitnum, int value) +{ + if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) { + if (value) abort(); /* beyond the end */ + } else { + int v = bitnum / BIGNUM_INT_BITS + 1; + BignumInt mask = (BignumInt)1 << (bitnum % BIGNUM_INT_BITS); + if (value) + bn[v] |= mask; + else + bn[v] &= ~mask; + } +} + +/* + * Write a SSH-1-format bignum into a buffer. It is assumed the + * buffer is big enough. Returns the number of bytes used. + */ +int ssh1_write_bignum(void *data, Bignum bn) +{ + unsigned char *p = data; + int len = ssh1_bignum_length(bn); + int i; + int bitc = bignum_bitcount(bn); + + *p++ = (bitc >> 8) & 0xFF; + *p++ = (bitc) & 0xFF; + for (i = len - 2; i--;) + *p++ = bignum_byte(bn, i); + return len; +} + +/* + * Compare two bignums. Returns like strcmp. + */ +int bignum_cmp(Bignum a, Bignum b) +{ + int amax = a[0], bmax = b[0]; + int i; + + /* Annoyingly we have two representations of zero */ + if (amax == 1 && a[amax] == 0) + amax = 0; + if (bmax == 1 && b[bmax] == 0) + bmax = 0; + + assert(amax == 0 || a[amax] != 0); + assert(bmax == 0 || b[bmax] != 0); + + i = (amax > bmax ? amax : bmax); + while (i) { + BignumInt aval = (i > amax ? 0 : a[i]); + BignumInt bval = (i > bmax ? 0 : b[i]); + if (aval < bval) + return -1; + if (aval > bval) + return +1; + i--; + } + return 0; +} + +/* + * Right-shift one bignum to form another. + */ +Bignum bignum_rshift(Bignum a, int shift) +{ + Bignum ret; + int i, shiftw, shiftb, shiftbb, bits; + BignumInt ai, ai1; + + assert(shift >= 0); + + bits = bignum_bitcount(a) - shift; + ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); + + if (ret) { + shiftw = shift / BIGNUM_INT_BITS; + shiftb = shift % BIGNUM_INT_BITS; + shiftbb = BIGNUM_INT_BITS - shiftb; + + ai1 = a[shiftw + 1]; + for (i = 1; i <= (int)ret[0]; i++) { + ai = ai1; + ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0); + ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK; + } + } + + return ret; +} + +/* + * Left-shift one bignum to form another. + */ +Bignum bignum_lshift(Bignum a, int shift) +{ + Bignum ret; + int bits, shiftWords, shiftBits; + + assert(shift >= 0); + + bits = bignum_bitcount(a) + shift; + ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); + + shiftWords = shift / BIGNUM_INT_BITS; + shiftBits = shift % BIGNUM_INT_BITS; + + if (shiftBits == 0) + { + memcpy(&ret[1 + shiftWords], &a[1], sizeof(BignumInt) * a[0]); + } + else + { + int i; + BignumInt carry = 0; + + /* Remember that Bignum[0] is length, so add 1 */ + for (i = shiftWords + 1; i < ((int)a[0]) + shiftWords + 1; ++i) + { + BignumInt from = a[i - shiftWords]; + ret[i] = (from << shiftBits) | carry; + carry = from >> (BIGNUM_INT_BITS - shiftBits); + } + if (carry) ret[i] = carry; + } + + return ret; +} + +/* + * Non-modular multiplication and addition. + */ +Bignum bigmuladd(Bignum a, Bignum b, Bignum addend) +{ + int alen = a[0], blen = b[0]; + int mlen = (alen > blen ? alen : blen); + int rlen, i, maxspot; + int wslen; + BignumInt *workspace; + Bignum ret; + + /* mlen space for a, mlen space for b, 2*mlen for result, + * plus scratch space for multiplication */ + wslen = mlen * 4 + mul_compute_scratch(mlen); + workspace = snewn(wslen, BignumInt); + for (i = 0; i < mlen; i++) { + workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0); + workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0); + } + + internal_mul(workspace + 0 * mlen, workspace + 1 * mlen, + workspace + 2 * mlen, mlen, workspace + 4 * mlen); + + /* now just copy the result back */ + rlen = alen + blen + 1; + if (addend && rlen <= (int)addend[0]) + rlen = addend[0] + 1; + ret = newbn(rlen); + maxspot = 0; + for (i = 1; i <= (int)ret[0]; i++) { + ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0); + if (ret[i] != 0) + maxspot = i; + } + ret[0] = maxspot; + + /* now add in the addend, if any */ + if (addend) { + BignumCarry carry = 0; + for (i = 1; i <= rlen; i++) { + BignumInt retword = (i <= (int)ret[0] ? ret[i] : 0); + BignumInt addword = (i <= (int)addend[0] ? addend[i] : 0); + BignumADC(ret[i], carry, retword, addword, carry); + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + } + ret[0] = maxspot; + + smemclr(workspace, wslen * sizeof(*workspace)); + sfree(workspace); + return ret; +} + +/* + * Non-modular multiplication. + */ +Bignum bigmul(Bignum a, Bignum b) +{ + return bigmuladd(a, b, NULL); +} + +/* + * Simple addition. + */ +Bignum bigadd(Bignum a, Bignum b) +{ + int alen = a[0], blen = b[0]; + int rlen = (alen > blen ? alen : blen) + 1; + int i, maxspot; + Bignum ret; + BignumCarry carry; + + ret = newbn(rlen); + + carry = 0; + maxspot = 0; + for (i = 1; i <= rlen; i++) { + BignumInt aword = (i <= (int)a[0] ? a[i] : 0); + BignumInt bword = (i <= (int)b[0] ? b[i] : 0); + BignumADC(ret[i], carry, aword, bword, carry); + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + ret[0] = maxspot; + + return ret; +} + +/* + * Subtraction. Returns a-b, or NULL if the result would come out + * negative (recall that this entire bignum module only handles + * positive numbers). + */ +Bignum bigsub(Bignum a, Bignum b) +{ + int alen = a[0], blen = b[0]; + int rlen = (alen > blen ? alen : blen); + int i, maxspot; + Bignum ret; + BignumCarry carry; + + ret = newbn(rlen); + + carry = 1; + maxspot = 0; + for (i = 1; i <= rlen; i++) { + BignumInt aword = (i <= (int)a[0] ? a[i] : 0); + BignumInt bword = (i <= (int)b[0] ? b[i] : 0); + BignumADC(ret[i], carry, aword, ~bword, carry); + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + ret[0] = maxspot; + + if (!carry) { + freebn(ret); + return NULL; + } + + return ret; +} + +/* + * Create a bignum which is the bitmask covering another one. That + * is, the smallest integer which is >= N and is also one less than + * a power of two. + */ +Bignum bignum_bitmask(Bignum n) +{ + Bignum ret = copybn(n); + int i; + BignumInt j; + + i = ret[0]; + while (n[i] == 0 && i > 0) + i--; + if (i <= 0) + return ret; /* input was zero */ + j = 1; + while (j < n[i]) + j = 2 * j + 1; + ret[i] = j; + while (--i > 0) + ret[i] = BIGNUM_INT_MASK; + return ret; +} + +/* + * Convert an unsigned long into a bignum. + */ +Bignum bignum_from_long(unsigned long n) +{ + const int maxwords = + (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt); + Bignum ret; + int i; + + ret = newbn(maxwords); + ret[0] = 0; + for (i = 0; i < maxwords; i++) { + ret[i+1] = n >> (i * BIGNUM_INT_BITS); + if (ret[i+1] != 0) + ret[0] = i+1; + } + + return ret; +} + +/* + * Add a long to a bignum. + */ +Bignum bignum_add_long(Bignum number, unsigned long n) +{ + const int maxwords = + (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt); + Bignum ret; + int words, i; + BignumCarry carry; + + words = number[0]; + if (words < maxwords) + words = maxwords; + words++; + ret = newbn(words); + + carry = 0; + ret[0] = 0; + for (i = 0; i < words; i++) { + BignumInt nword = (i < maxwords ? n >> (i * BIGNUM_INT_BITS) : 0); + BignumInt numword = (i < number[0] ? number[i+1] : 0); + BignumADC(ret[i+1], carry, numword, nword, carry); + if (ret[i+1] != 0) + ret[0] = i+1; + } + return ret; +} + +/* + * Compute the residue of a bignum, modulo a (max 16-bit) short. + */ +unsigned short bignum_mod_short(Bignum number, unsigned short modulus) +{ + unsigned long mod = modulus, r = 0; + /* Precompute (BIGNUM_INT_MASK+1) % mod */ + unsigned long base_r = (BIGNUM_INT_MASK - modulus + 1) % mod; + int i; + + for (i = number[0]; i > 0; i--) { + /* + * Conceptually, ((r << BIGNUM_INT_BITS) + number[i]) % mod + */ + r = ((r * base_r) + (number[i] % mod)) % mod; + } + return (unsigned short) r; +} + +#ifdef DEBUG +void diagbn(char *prefix, Bignum md) +{ + int i, nibbles, morenibbles; + static const char hex[] = "0123456789ABCDEF"; + + debug(("%s0x", prefix ? prefix : "")); + + nibbles = (3 + bignum_bitcount(md)) / 4; + if (nibbles < 1) + nibbles = 1; + morenibbles = 4 * md[0] - nibbles; + for (i = 0; i < morenibbles; i++) + debug(("-")); + for (i = nibbles; i--;) + debug(("%c", + hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF])); + + if (prefix) + debug(("\n")); +} +#endif + +/* + * Simple division. + */ +Bignum bigdiv(Bignum a, Bignum b) +{ + Bignum q = newbn(a[0]); + bigdivmod(a, b, NULL, q); + while (q[0] > 1 && q[q[0]] == 0) + q[0]--; + return q; +} + +/* + * Simple remainder. + */ +Bignum bigmod(Bignum a, Bignum b) +{ + Bignum r = newbn(b[0]); + bigdivmod(a, b, r, NULL); + while (r[0] > 1 && r[r[0]] == 0) + r[0]--; + return r; +} + +/* + * Greatest common divisor. + */ +Bignum biggcd(Bignum av, Bignum bv) +{ + Bignum a = copybn(av); + Bignum b = copybn(bv); + + while (bignum_cmp(b, Zero) != 0) { + Bignum t = newbn(b[0]); + bigdivmod(a, b, t, NULL); + while (t[0] > 1 && t[t[0]] == 0) + t[0]--; + freebn(a); + a = b; + b = t; + } + + freebn(b); + return a; +} + +/* + * Modular inverse, using Euclid's extended algorithm. + */ +Bignum modinv(Bignum number, Bignum modulus) +{ + Bignum a = copybn(modulus); + Bignum b = copybn(number); + Bignum xp = copybn(Zero); + Bignum x = copybn(One); + int sign = +1; + + assert(number[number[0]] != 0); + assert(modulus[modulus[0]] != 0); + + while (bignum_cmp(b, One) != 0) { + Bignum t, q; + + if (bignum_cmp(b, Zero) == 0) { + /* + * Found a common factor between the inputs, so we cannot + * return a modular inverse at all. + */ + freebn(b); + freebn(a); + freebn(xp); + freebn(x); + return NULL; + } + + t = newbn(b[0]); + q = newbn(a[0]); + bigdivmod(a, b, t, q); + while (t[0] > 1 && t[t[0]] == 0) + t[0]--; + while (q[0] > 1 && q[q[0]] == 0) + q[0]--; + freebn(a); + a = b; + b = t; + t = xp; + xp = x; + x = bigmuladd(q, xp, t); + sign = -sign; + freebn(t); + freebn(q); + } + + freebn(b); + freebn(a); + freebn(xp); + + /* now we know that sign * x == 1, and that x < modulus */ + if (sign < 0) { + /* set a new x to be modulus - x */ + Bignum newx = newbn(modulus[0]); + BignumInt carry = 0; + int maxspot = 1; + int i; + + for (i = 1; i <= (int)newx[0]; i++) { + BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0); + BignumInt bword = (i <= (int)x[0] ? x[i] : 0); + newx[i] = aword - bword - carry; + bword = ~bword; + carry = carry ? (newx[i] >= bword) : (newx[i] > bword); + if (newx[i] != 0) + maxspot = i; + } + newx[0] = maxspot; + freebn(x); + x = newx; + } + + /* and return. */ + return x; +} + +/* + * Render a bignum into decimal. Return a malloced string holding + * the decimal representation. + */ +char *bignum_decimal(Bignum x) +{ + int ndigits, ndigit; + int i, iszero; + BignumInt carry; + char *ret; + BignumInt *workspace; + + /* + * First, estimate the number of digits. Since log(10)/log(2) + * is just greater than 93/28 (the joys of continued fraction + * approximations...) we know that for every 93 bits, we need + * at most 28 digits. This will tell us how much to malloc. + * + * Formally: if x has i bits, that means x is strictly less + * than 2^i. Since 2 is less than 10^(28/93), this is less than + * 10^(28i/93). We need an integer power of ten, so we must + * round up (rounding down might make it less than x again). + * Therefore if we multiply the bit count by 28/93, rounding + * up, we will have enough digits. + * + * i=0 (i.e., x=0) is an irritating special case. + */ + i = bignum_bitcount(x); + if (!i) + ndigits = 1; /* x = 0 */ + else + ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */ + ndigits++; /* allow for trailing \0 */ + ret = snewn(ndigits, char); + + /* + * Now allocate some workspace to hold the binary form as we + * repeatedly divide it by ten. Initialise this to the + * big-endian form of the number. + */ + workspace = snewn(x[0], BignumInt); + for (i = 0; i < (int)x[0]; i++) + workspace[i] = x[x[0] - i]; + + /* + * Next, write the decimal number starting with the last digit. + * We use ordinary short division, dividing 10 into the + * workspace. + */ + ndigit = ndigits - 1; + ret[ndigit] = '\0'; + do { + iszero = 1; + carry = 0; + for (i = 0; i < (int)x[0]; i++) { + /* + * Conceptually, we want to compute + * + * (carry << BIGNUM_INT_BITS) + workspace[i] + * ----------------------------------------- + * 10 + * + * but we don't have an integer type longer than BignumInt + * to work with. So we have to do it in pieces. + */ + + BignumInt q, r; + q = workspace[i] / 10; + r = workspace[i] % 10; + + /* I want (BIGNUM_INT_MASK+1)/10 but can't say so directly! */ + q += carry * ((BIGNUM_INT_MASK-9) / 10 + 1); + r += carry * ((BIGNUM_INT_MASK-9) % 10); + + q += r / 10; + r %= 10; + + workspace[i] = q; + carry = r; + + if (workspace[i]) + iszero = 0; + } + ret[--ndigit] = (char) (carry + '0'); + } while (!iszero); + + /* + * There's a chance we've fallen short of the start of the + * string. Correct if so. + */ + if (ndigit > 0) + memmove(ret, ret + ndigit, ndigits - ndigit); + + /* + * Done. + */ + smemclr(workspace, x[0] * sizeof(*workspace)); + sfree(workspace); + return ret; +} diff --git a/netbox/libs/Putty/sshbn.h b/netbox/libs/Putty/sshbn.h new file mode 100644 index 000000000..0f1c997c2 --- /dev/null +++ b/netbox/libs/Putty/sshbn.h @@ -0,0 +1,220 @@ +/* + * sshbn.h: the assorted conditional definitions of BignumInt and + * multiply macros used throughout the bignum code to treat numbers as + * arrays of the most conveniently sized word for the target machine. + * Exported so that other code (e.g. poly1305) can use it too. + * + * This file must export, in whatever ifdef branch it ends up in: + * + * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an + * unsigned integer type which will be used as the base word size + * for all bignum operations. BignumCarry is an unsigned integer + * type used to hold the carry flag taken as input and output by + * the BignumADC macro (see below). + * + * - four constant macros: BIGNUM_INT_BITS, BIGNUM_INT_BYTES, + * BIGNUM_TOP_BIT, BIGNUM_INT_MASK. These should be more or less + * self-explanatory, but just in case, they give the number of bits + * in BignumInt, the number of bytes that works out to, the + * BignumInt value consisting of only the top bit, and the + * BignumInt value with all bits set. + * + * - four statement macros: BignumADC, BignumMUL, BignumMULADD, + * BignumMULADD2. These do various kinds of multi-word arithmetic, + * and all produce two output values. + * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b + * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and + * a BignumCarry retc which is the carry off the top of that + * addition. + * * BignumMUL(rh,rl,a,b) returns the two halves of the + * double-width product a*b. + * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the + * double-width value a*b + addend. + * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two + * halves of the double-width value a*b + addend1 + addend2. + * + * Every branch of the main ifdef below defines the type BignumInt and + * the value BIGNUM_INT_BITS. The other three constant macros are + * filled in by common code further down. + * + * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a + * typedef statement which declares a type _twice_ the length of a + * BignumInt. This causes the common code further down to produce a + * default implementation of the four statement macros in terms of + * that double-width type, and also to defined BignumCarry to be + * BignumInt. + * + * However, if a particular compile target does not have a type twice + * the length of the BignumInt you want to use but it does provide + * some alternative means of doing add-with-carry and double-word + * multiply, then the ifdef branch in question can just define + * BignumCarry and the four statement macros itself, and that's fine + * too. + */ + +#if defined __SIZEOF_INT128__ + + /* + * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt. + * + * gcc and clang both provide a __uint128_t type on 64-bit targets + * (and, when they do, indicate its presence by the above macro), + * using the same 'two machine registers' kind of code generation + * that 32-bit targets use for 64-bit ints. + */ + + typedef unsigned long long BignumInt; + #define BIGNUM_INT_BITS 64 + #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt + +#elif defined _MSC_VER && (_MSC_VER > 1600) && defined _M_AMD64 + + /* + * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics. + * + * 64-bit Visual Studio doesn't provide very much in the way of help + * here: there's no int128 type, and also no inline assembler giving + * us direct access to the x86-64 MUL or ADC instructions. However, + * there are compiler intrinsics giving us that access, so we can + * use those - though it turns out we have to be a little careful, + * since they seem to generate wrong code if their pointer-typed + * output parameters alias their inputs. Hence all the internal temp + * variables inside the macros. + */ + + #include + typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */ + typedef unsigned __int64 BignumInt; + #define BIGNUM_INT_BITS 64 + #define BignumADC(ret, retc, a, b, c) do \ + { \ + BignumInt ADC_tmp; \ + (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \ + (ret) = ADC_tmp; \ + } while (0) + #define BignumMUL(rh, rl, a, b) do \ + { \ + BignumInt MULADD_hi; \ + (rl) = _umul128(a, b, &MULADD_hi); \ + (rh) = MULADD_hi; \ + } while (0) + #define BignumMULADD(rh, rl, a, b, addend) do \ + { \ + BignumInt MULADD_lo, MULADD_hi; \ + MULADD_lo = _umul128(a, b, &MULADD_hi); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \ + (rh) = MULADD_hi; \ + } while (0) + #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ + { \ + BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \ + MULADD_lo1 = _umul128(a, b, &MULADD_hi); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \ + (rh) = MULADD_hi; \ + } while (0) + +#elif defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L + + /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS 32 + #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt + +#elif (defined _MSC_VER && defined _M_IX86) || defined(MPEXT) + + /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS 32 + #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt + +#elif defined _LP64 + + /* + * 32-bit BignumInt, using unsigned long itself as BignumDblInt. + * + * Only for platforms where long is 64 bits, of course. + */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS 32 + #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt + +#else + + /* + * 16-bit BignumInt, using unsigned long as BignumDblInt. + * + * This is the final fallback for real emergencies: C89 guarantees + * unsigned short/long to be at least the required sizes, so this + * should work on any C implementation at all. But it'll be + * noticeably slow, so if you find yourself in this case you + * probably want to move heaven and earth to find an alternative! + */ + + typedef unsigned short BignumInt; + #define BIGNUM_INT_BITS 16 + #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt + +#endif + +/* + * Common code across all branches of that ifdef: define the three + * easy constant macros in terms of BIGNUM_INT_BITS. + */ +#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) +#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1)) +#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1)) + +/* + * Common code across _most_ branches of the ifdef: define a set of + * statement macros in terms of the BignumDblInt type provided. In + * this case, we also define BignumCarry to be the same thing as + * BignumInt, for simplicity. + */ +#ifdef DEFINE_BIGNUMDBLINT + + typedef BignumInt BignumCarry; + #define BignumADC(ret, retc, a, b, c) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt ADC_temp = (BignumInt)(a); \ + ADC_temp += (BignumInt)(b); \ + ADC_temp += (c); \ + (ret) = (BignumInt)ADC_temp; \ + (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \ + } while (0) + + #define BignumMUL(rh, rl, a, b) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + + #define BignumMULADD(rh, rl, a, b, addend) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + MUL_temp += (BignumInt)(addend); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + + #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + MUL_temp += (BignumInt)(addend1); \ + MUL_temp += (BignumInt)(addend2); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + +#endif /* DEFINE_BIGNUMDBLINT */ diff --git a/netbox/libs/Putty/sshcrc.c b/netbox/libs/Putty/sshcrc.c new file mode 100644 index 000000000..a993e98b7 --- /dev/null +++ b/netbox/libs/Putty/sshcrc.c @@ -0,0 +1,229 @@ +/* + * CRC32 implementation. + * + * The basic concept of a CRC is that you treat your bit-string + * abcdefg... as a ludicrously long polynomial M=a+bx+cx^2+dx^3+... + * over Z[2]. You then take a modulus polynomial P, and compute the + * remainder of M on division by P. Thus, an erroneous message N + * will only have the same CRC if the difference E = M-N is an + * exact multiple of P. (Note that as we are working over Z[2], M-N + * = N-M = M+N; but that's not very important.) + * + * What makes the CRC good is choosing P to have good properties: + * + * - If its first and last terms are both nonzero then it cannot + * be a factor of any single term x^i. Therefore if M and N + * differ by exactly one bit their CRCs will guaranteeably + * be distinct. + * + * - If it has a prime (irreducible) factor with three terms then + * it cannot divide a polynomial of the form x^i(1+x^j). + * Therefore if M and N differ by exactly _two_ bits they will + * have different CRCs. + * + * - If it has a factor (x+1) then it cannot divide a polynomial + * with an odd number of terms. Therefore if M and N differ by + * _any odd_ number of bits they will have different CRCs. + * + * - If the error term E is of the form x^i*B(x) where B(x) has + * order less than P (i.e. a short _burst_ of errors) then P + * cannot divide E (since no polynomial can divide a shorter + * one), so any such error burst will be spotted. + * + * The CRC32 standard polynomial is + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 + * + * In fact, we don't compute M mod P; we compute M*x^32 mod P. + * + * The concrete implementation of the CRC is this: we maintain at + * all times a 32-bit word which is the current remainder of the + * polynomial mod P. Whenever we receive an extra bit, we multiply + * the existing remainder by x, add (XOR) the x^32 term thus + * generated to the new x^32 term caused by the incoming bit, and + * remove the resulting combined x^32 term if present by replacing + * it with (P-x^32). + * + * Bit 0 of the word is the x^31 term and bit 31 is the x^0 term. + * Thus, multiplying by x means shifting right. So the actual + * algorithm goes like this: + * + * x32term = (crcword & 1) ^ newbit; + * crcword = (crcword >> 1) ^ (x32term * 0xEDB88320); + * + * In practice, we pre-compute what will happen to crcword on any + * given sequence of eight incoming bits, and store that in a table + * which we then use at run-time to do the job: + * + * outgoingplusnew = (crcword & 0xFF) ^ newbyte; + * crcword = (crcword >> 8) ^ table[outgoingplusnew]; + * + * where table[outgoingplusnew] is computed by setting crcword=0 + * and then iterating the first code fragment eight times (taking + * the incoming byte low bit first). + * + * Note that all shifts are rightward and thus no assumption is + * made about exact word length! (Although word length must be at + * _least_ 32 bits, but ANSI C guarantees this for `unsigned long' + * anyway.) + */ + +#include + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Multi-function module. Can be compiled three ways. + * + * - Compile with no special #defines. Will generate a table + * that's already initialised at compile time, and one function + * crc32_compute(buf,len) that uses it. Normal usage. + * + * - Compile with INITFUNC defined. Will generate an uninitialised + * array as the table, and as well as crc32_compute(buf,len) it + * will also generate void crc32_init(void) which sets up the + * table at run time. Useful if binary size is important. + * + * - Compile with GENPROGRAM defined. Will create a standalone + * program that does the initialisation and outputs the table as + * C code. + */ + +#define POLY (0xEDB88320L) + +#ifdef GENPROGRAM +#define INITFUNC /* the gen program needs the init func :-) */ +#endif + +#ifdef INITFUNC + +/* + * This variant of the code generates the table at run-time from an + * init function. + */ +static unsigned long crc32_table[256]; + +void crc32_init(void) +{ + unsigned long crcword; + int i; + + for (i = 0; i < 256; i++) { + unsigned long newbyte, x32term; + int j; + crcword = 0; + newbyte = i; + for (j = 0; j < 8; j++) { + x32term = (crcword ^ newbyte) & 1; + crcword = (crcword >> 1) ^ (x32term * POLY); + newbyte >>= 1; + } + crc32_table[i] = crcword; + } +} + +#else + +/* + * This variant of the code has the data already prepared. + */ +static const unsigned long crc32_table[256] = { + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, + 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, + 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, + 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, + 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, + 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, + 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, + 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, + 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, + 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, + 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, + 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, + 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, + 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, + 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, + 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, + 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, + 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, + 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, + 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, + 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, + 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, + 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, + 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, + 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, + 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, + 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, + 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, + 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, + 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, + 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, + 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, + 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, + 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, + 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, + 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, + 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, + 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, + 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, + 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, + 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, + 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, + 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, + 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, + 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, + 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, + 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, + 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, + 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, + 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, + 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, + 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, + 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, + 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, + 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, + 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, + 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, + 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL +}; + +#endif + +#ifdef GENPROGRAM +int main(void) +{ + int i; + + crc32_init(); + for (i = 0; i < 256; i++) { + printf("%s0x%08lXL%s", + (i % 4 == 0 ? " " : " "), + crc32_table[i], + (i % 4 == 3 ? (i == 255 ? "\n" : ",\n") : ",")); + } + + return 0; +} +#endif + +unsigned long crc32_update(unsigned long crcword, const void *buf, size_t len) +{ + const unsigned char *p = (const unsigned char *) buf; + while (len--) { + unsigned long newbyte = *p++; + newbyte ^= crcword & 0xFFL; + crcword = (crcword >> 8) ^ crc32_table[newbyte]; + } + return crcword; +} + +unsigned long crc32_compute(const void *buf, size_t len) +{ + return crc32_update(0L, buf, len); +} diff --git a/netbox/libs/Putty/sshcrcda.c b/netbox/libs/Putty/sshcrcda.c new file mode 100644 index 000000000..8d77cbb60 --- /dev/null +++ b/netbox/libs/Putty/sshcrcda.c @@ -0,0 +1,172 @@ +/* $OpenBSD: deattack.c,v 1.14 2001/06/23 15:12:18 itojun Exp $ */ + +/* + * Cryptographic attack detector for ssh - source code + * + * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina. + * + * All rights reserved. Redistribution and use in source and binary + * forms, with or without modification, are permitted provided that + * this copyright notice is retained. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR + * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS + * SOFTWARE. + * + * Ariel Futoransky + * + * + * Modified for use in PuTTY by Simon Tatham + */ + +#include +#include "misc.h" +#include "ssh.h" + +typedef unsigned char uchar; +typedef unsigned short uint16; + +/* SSH Constants */ +#define SSH_MAXBLOCKS (32 * 1024) +#define SSH_BLOCKSIZE (8) + +/* Hashing constants */ +#define HASH_MINSIZE (8 * 1024) +#define HASH_ENTRYSIZE (sizeof(uint16)) +#define HASH_FACTOR(x) ((x)*3/2) +#define HASH_UNUSEDCHAR (0xff) +#define HASH_UNUSED (0xffff) +#define HASH_IV (0xfffe) + +#define HASH_MINBLOCKS (7*SSH_BLOCKSIZE) + +/* Hash function (Input keys are cipher results) */ +#define HASH(x) GET_32BIT_MSB_FIRST(x) + +#define CMP(a, b) (memcmp(a, b, SSH_BLOCKSIZE)) + +uchar ONE[4] = { 1, 0, 0, 0 }; +uchar ZERO[4] = { 0, 0, 0, 0 }; + +struct crcda_ctx { + uint16 *h; + uint32 n; +}; + +void *crcda_make_context(void) +{ + struct crcda_ctx *ret = snew(struct crcda_ctx); + ret->h = NULL; + ret->n = HASH_MINSIZE / HASH_ENTRYSIZE; + return ret; +} + +void crcda_free_context(void *handle) +{ + struct crcda_ctx *ctx = (struct crcda_ctx *)handle; + if (ctx) { + sfree(ctx->h); + ctx->h = NULL; + sfree(ctx); + } +} + +static void crc_update(uint32 *a, void *b) +{ + *a = crc32_update(*a, b, 4); +} + +/* detect if a block is used in a particular pattern */ +static int check_crc(uchar *S, uchar *buf, uint32 len, uchar *IV) +{ + uint32 crc; + uchar *c; + + crc = 0; + if (IV && !CMP(S, IV)) { + crc_update(&crc, ONE); + crc_update(&crc, ZERO); + } + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) { + if (!CMP(S, c)) { + crc_update(&crc, ONE); + crc_update(&crc, ZERO); + } else { + crc_update(&crc, ZERO); + crc_update(&crc, ZERO); + } + } + return (crc == 0); +} + +/* Detect a crc32 compensation attack on a packet */ +int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) +{ + struct crcda_ctx *ctx = (struct crcda_ctx *)handle; + register uint32 i, j; + uint32 l; + register uchar *c; + uchar *d; + + assert(!(len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) || + len % SSH_BLOCKSIZE != 0)); + for (l = ctx->n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2) + ; + + if (ctx->h == NULL) { + ctx->n = l; + ctx->h = snewn(ctx->n, uint16); + } else { + if (l > ctx->n) { + ctx->n = l; + ctx->h = sresize(ctx->h, ctx->n, uint16); + } + } + + if (len <= HASH_MINBLOCKS) { + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) { + if (IV && (!CMP(c, IV))) { + if ((check_crc(c, buf, len, IV))) + return 1; /* attack detected */ + else + break; + } + for (d = buf; d < c; d += SSH_BLOCKSIZE) { + if (!CMP(c, d)) { + if ((check_crc(c, buf, len, IV))) + return 1; /* attack detected */ + else + break; + } + } + } + return 0; /* ok */ + } + memset(ctx->h, HASH_UNUSEDCHAR, ctx->n * HASH_ENTRYSIZE); + + if (IV) + ctx->h[HASH(IV) & (ctx->n - 1)] = HASH_IV; + + for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) { + for (i = HASH(c) & (ctx->n - 1); ctx->h[i] != HASH_UNUSED; + i = (i + 1) & (ctx->n - 1)) { + if (ctx->h[i] == HASH_IV) { + if (!CMP(c, IV)) { + if (check_crc(c, buf, len, IV)) + return 1; /* attack detected */ + else + break; + } + } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { + if (check_crc(c, buf, len, IV)) + return 1; /* attack detected */ + else + break; + } + } + ctx->h[i] = j; + } + return 0; /* ok */ +} diff --git a/netbox/libs/Putty/sshdes.c b/netbox/libs/Putty/sshdes.c new file mode 100644 index 000000000..f54990e7c --- /dev/null +++ b/netbox/libs/Putty/sshdes.c @@ -0,0 +1,1088 @@ +#include +#include "ssh.h" + + +/* des.c - implementation of DES + */ + +/* + * Description of DES + * ------------------ + * + * Unlike the description in FIPS 46, I'm going to use _sensible_ indices: + * bits in an n-bit word are numbered from 0 at the LSB to n-1 at the MSB. + * And S-boxes are indexed by six consecutive bits, not by the outer two + * followed by the middle four. + * + * The DES encryption routine requires a 64-bit input, and a key schedule K + * containing 16 48-bit elements. + * + * First the input is permuted by the initial permutation IP. + * Then the input is split into 32-bit words L and R. (L is the MSW.) + * Next, 16 rounds. In each round: + * (L, R) <- (R, L xor f(R, K[i])) + * Then the pre-output words L and R are swapped. + * Then L and R are glued back together into a 64-bit word. (L is the MSW, + * again, but since we just swapped them, the MSW is the R that came out + * of the last round.) + * The 64-bit output block is permuted by the inverse of IP and returned. + * + * Decryption is identical except that the elements of K are used in the + * opposite order. (This wouldn't work if that word swap didn't happen.) + * + * The function f, used in each round, accepts a 32-bit word R and a + * 48-bit key block K. It produces a 32-bit output. + * + * First R is expanded to 48 bits using the bit-selection function E. + * The resulting 48-bit block is XORed with the key block K to produce + * a 48-bit block X. + * This block X is split into eight groups of 6 bits. Each group of 6 + * bits is then looked up in one of the eight S-boxes to convert + * it to 4 bits. These eight groups of 4 bits are glued back + * together to produce a 32-bit preoutput block. + * The preoutput block is permuted using the permutation P and returned. + * + * Key setup maps a 64-bit key word into a 16x48-bit key schedule. Although + * the approved input format for the key is a 64-bit word, eight of the + * bits are discarded, so the actual quantity of key used is 56 bits. + * + * First the input key is converted to two 28-bit words C and D using + * the bit-selection function PC1. + * Then 16 rounds of key setup occur. In each round, C and D are each + * rotated left by either 1 or 2 bits (depending on which round), and + * then converted into a key schedule element using the bit-selection + * function PC2. + * + * That's the actual algorithm. Now for the tedious details: all those + * painful permutations and lookup tables. + * + * IP is a 64-to-64 bit permutation. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 6 14 22 30 38 46 54 62 4 12 20 28 36 44 52 60 + * 2 10 18 26 34 42 50 58 0 8 16 24 32 40 48 56 + * 7 15 23 31 39 47 55 63 5 13 21 29 37 45 53 61 + * 3 11 19 27 35 43 51 59 1 9 17 25 33 41 49 57 + * + * E is a 32-to-48 bit selection function. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 0 31 30 29 28 27 28 27 26 25 24 23 24 23 22 21 20 19 20 19 18 17 16 15 + * 16 15 14 13 12 11 12 11 10 9 8 7 8 7 6 5 4 3 4 3 2 1 0 31 + * + * The S-boxes are arbitrary table-lookups each mapping a 6-bit input to a + * 4-bit output. In other words, each S-box is an array[64] of 4-bit numbers. + * The S-boxes are listed below. The first S-box listed is applied to the + * most significant six bits of the block X; the last one is applied to the + * least significant. + * + * 14 0 4 15 13 7 1 4 2 14 15 2 11 13 8 1 + * 3 10 10 6 6 12 12 11 5 9 9 5 0 3 7 8 + * 4 15 1 12 14 8 8 2 13 4 6 9 2 1 11 7 + * 15 5 12 11 9 3 7 14 3 10 10 0 5 6 0 13 + * + * 15 3 1 13 8 4 14 7 6 15 11 2 3 8 4 14 + * 9 12 7 0 2 1 13 10 12 6 0 9 5 11 10 5 + * 0 13 14 8 7 10 11 1 10 3 4 15 13 4 1 2 + * 5 11 8 6 12 7 6 12 9 0 3 5 2 14 15 9 + * + * 10 13 0 7 9 0 14 9 6 3 3 4 15 6 5 10 + * 1 2 13 8 12 5 7 14 11 12 4 11 2 15 8 1 + * 13 1 6 10 4 13 9 0 8 6 15 9 3 8 0 7 + * 11 4 1 15 2 14 12 3 5 11 10 5 14 2 7 12 + * + * 7 13 13 8 14 11 3 5 0 6 6 15 9 0 10 3 + * 1 4 2 7 8 2 5 12 11 1 12 10 4 14 15 9 + * 10 3 6 15 9 0 0 6 12 10 11 1 7 13 13 8 + * 15 9 1 4 3 5 14 11 5 12 2 7 8 2 4 14 + * + * 2 14 12 11 4 2 1 12 7 4 10 7 11 13 6 1 + * 8 5 5 0 3 15 15 10 13 3 0 9 14 8 9 6 + * 4 11 2 8 1 12 11 7 10 1 13 14 7 2 8 13 + * 15 6 9 15 12 0 5 9 6 10 3 4 0 5 14 3 + * + * 12 10 1 15 10 4 15 2 9 7 2 12 6 9 8 5 + * 0 6 13 1 3 13 4 14 14 0 7 11 5 3 11 8 + * 9 4 14 3 15 2 5 12 2 9 8 5 12 15 3 10 + * 7 11 0 14 4 1 10 7 1 6 13 0 11 8 6 13 + * + * 4 13 11 0 2 11 14 7 15 4 0 9 8 1 13 10 + * 3 14 12 3 9 5 7 12 5 2 10 15 6 8 1 6 + * 1 6 4 11 11 13 13 8 12 1 3 4 7 10 14 7 + * 10 9 15 5 6 0 8 15 0 14 5 2 9 3 2 12 + * + * 13 1 2 15 8 13 4 8 6 10 15 3 11 7 1 4 + * 10 12 9 5 3 6 14 11 5 0 0 14 12 9 7 2 + * 7 2 11 1 4 14 1 7 9 4 12 10 14 8 2 13 + * 0 15 6 12 10 9 13 0 15 3 3 5 5 6 8 11 + * + * P is a 32-to-32 bit permutation. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 16 25 12 11 3 20 4 15 31 17 9 6 27 14 1 22 + * 30 24 8 18 0 5 29 23 13 19 2 26 10 21 28 7 + * + * PC1 is a 64-to-56 bit selection function. Its output is in two words, + * C and D. The word C contains the following bits of its input (listed + * in order MSB to LSB of output). + * + * 7 15 23 31 39 47 55 63 6 14 22 30 38 46 + * 54 62 5 13 21 29 37 45 53 61 4 12 20 28 + * + * And the word D contains these bits. + * + * 1 9 17 25 33 41 49 57 2 10 18 26 34 42 + * 50 58 3 11 19 27 35 43 51 59 36 44 52 60 + * + * PC2 is a 56-to-48 bit selection function. Its input is in two words, + * C and D. These are treated as one 56-bit word (with C more significant, + * so that bits 55 to 28 of the word are bits 27 to 0 of C, and bits 27 to + * 0 of the word are bits 27 to 0 of D). The output contains the following + * bits of this 56-bit input word (listed in order MSB to LSB of output). + * + * 42 39 45 32 55 51 53 28 41 50 35 46 33 37 44 52 30 48 40 49 29 36 43 54 + * 15 4 25 19 9 1 26 16 5 11 23 8 12 7 17 0 22 3 10 14 6 20 27 24 + */ + +/* + * Implementation details + * ---------------------- + * + * If you look at the code in this module, you'll find it looks + * nothing _like_ the above algorithm. Here I explain the + * differences... + * + * Key setup has not been heavily optimised here. We are not + * concerned with key agility: we aren't codebreakers. We don't + * mind a little delay (and it really is a little one; it may be a + * factor of five or so slower than it could be but it's still not + * an appreciable length of time) while setting up. The only tweaks + * in the key setup are ones which change the format of the key + * schedule to speed up the actual encryption. I'll describe those + * below. + * + * The first and most obvious optimisation is the S-boxes. Since + * each S-box always targets the same four bits in the final 32-bit + * word, so the output from (for example) S-box 0 must always be + * shifted left 28 bits, we can store the already-shifted outputs + * in the lookup tables. This reduces lookup-and-shift to lookup, + * so the S-box step is now just a question of ORing together eight + * table lookups. + * + * The permutation P is just a bit order change; it's invariant + * with respect to OR, in that P(x)|P(y) = P(x|y). Therefore, we + * can apply P to every entry of the S-box tables and then we don't + * have to do it in the code of f(). This yields a set of tables + * which might be called SP-boxes. + * + * The bit-selection function E is our next target. Note that E is + * immediately followed by the operation of splitting into 6-bit + * chunks. Examining the 6-bit chunks coming out of E we notice + * they're all contiguous within the word (speaking cyclically - + * the end two wrap round); so we can extract those bit strings + * individually rather than explicitly running E. This would yield + * code such as + * + * y |= SPboxes[0][ (rotl(R, 5) ^ top6bitsofK) & 0x3F ]; + * t |= SPboxes[1][ (rotl(R,11) ^ next6bitsofK) & 0x3F ]; + * + * and so on; and the key schedule preparation would have to + * provide each 6-bit chunk separately. + * + * Really we'd like to XOR in the key schedule element before + * looking up bit strings in R. This we can't do, naively, because + * the 6-bit strings we want overlap. But look at the strings: + * + * 3322222222221111111111 + * bit 10987654321098765432109876543210 + * + * box0 XXXXX X + * box1 XXXXXX + * box2 XXXXXX + * box3 XXXXXX + * box4 XXXXXX + * box5 XXXXXX + * box6 XXXXXX + * box7 X XXXXX + * + * The bit strings we need to XOR in for boxes 0, 2, 4 and 6 don't + * overlap with each other. Neither do the ones for boxes 1, 3, 5 + * and 7. So we could provide the key schedule in the form of two + * words that we can separately XOR into R, and then every S-box + * index is available as a (cyclically) contiguous 6-bit substring + * of one or the other of the results. + * + * The comments in Eric Young's libdes implementation point out + * that two of these bit strings require a rotation (rather than a + * simple shift) to extract. It's unavoidable that at least _one_ + * must do; but we can actually run the whole inner algorithm (all + * 16 rounds) rotated one bit to the left, so that what the `real' + * DES description sees as L=0x80000001 we see as L=0x00000003. + * This requires rotating all our SP-box entries one bit to the + * left, and rotating each word of the key schedule elements one to + * the left, and rotating L and R one bit left just after IP and + * one bit right again just before FP. And in each round we convert + * a rotate into a shift, so we've saved a few per cent. + * + * That's about it for the inner loop; the SP-box tables as listed + * below are what I've described here (the original S value, + * shifted to its final place in the input to P, run through P, and + * then rotated one bit left). All that remains is to optimise the + * initial permutation IP. + * + * IP is not an arbitrary permutation. It has the nice property + * that if you take any bit number, write it in binary (6 bits), + * permute those 6 bits and invert some of them, you get the final + * position of that bit. Specifically, the bit whose initial + * position is given (in binary) as fedcba ends up in position + * AcbFED (where a capital letter denotes the inverse of a bit). + * + * We have the 64-bit data in two 32-bit words L and R, where bits + * in L are those with f=1 and bits in R are those with f=0. We + * note that we can do a simple transformation: suppose we exchange + * the bits with f=1,c=0 and the bits with f=0,c=1. This will cause + * the bit fedcba to be in position cedfba - we've `swapped' bits c + * and f in the position of each bit! + * + * Better still, this transformation is easy. In the example above, + * bits in L with c=0 are bits 0x0F0F0F0F, and those in R with c=1 + * are 0xF0F0F0F0. So we can do + * + * difference = ((R >> 4) ^ L) & 0x0F0F0F0F + * R ^= (difference << 4) + * L ^= difference + * + * to perform the swap. Let's denote this by bitswap(4,0x0F0F0F0F). + * Also, we can invert the bit at the top just by exchanging L and + * R. So in a few swaps and a few of these bit operations we can + * do: + * + * Initially the position of bit fedcba is fedcba + * Swap L with R to make it Fedcba + * Perform bitswap( 4,0x0F0F0F0F) to make it cedFba + * Perform bitswap(16,0x0000FFFF) to make it ecdFba + * Swap L with R to make it EcdFba + * Perform bitswap( 2,0x33333333) to make it bcdFEa + * Perform bitswap( 8,0x00FF00FF) to make it dcbFEa + * Swap L with R to make it DcbFEa + * Perform bitswap( 1,0x55555555) to make it acbFED + * Swap L with R to make it AcbFED + * + * (In the actual code the four swaps are implicit: R and L are + * simply used the other way round in the first, second and last + * bitswap operations.) + * + * The final permutation is just the inverse of IP, so it can be + * performed by a similar set of operations. + */ + +typedef struct { + word32 k0246[16], k1357[16]; + word32 iv0, iv1; +} DESContext; + +#define rotl(x, c) ( (x << c) | (x >> (32-c)) ) +#define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF) + +static word32 bitsel(word32 * input, const int *bitnums, int size) +{ + word32 ret = 0; + while (size--) { + int bitpos = *bitnums++; + ret <<= 1; + if (bitpos >= 0) + ret |= 1 & (input[bitpos / 32] >> (bitpos % 32)); + } + return ret; +} + +static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched) +{ + + static const int PC1_Cbits[] = { + 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46, + 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28 + }; + static const int PC1_Dbits[] = { + 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, + 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60 + }; + /* + * The bit numbers in the two lists below don't correspond to + * the ones in the above description of PC2, because in the + * above description C and D are concatenated so `bit 28' means + * bit 0 of C. In this implementation we're using the standard + * `bitsel' function above and C is in the second word, so bit + * 0 of C is addressed by writing `32' here. + */ + static const int PC2_0246[] = { + 49, 36, 59, 55, -1, -1, 37, 41, 48, 56, 34, 52, -1, -1, 15, 4, + 25, 19, 9, 1, -1, -1, 12, 7, 17, 0, 22, 3, -1, -1, 46, 43 + }; + static const int PC2_1357[] = { + -1, -1, 57, 32, 45, 54, 39, 50, -1, -1, 44, 53, 33, 40, 47, 58, + -1, -1, 26, 16, 5, 11, 23, 8, -1, -1, 10, 14, 6, 20, 27, 24 + }; + static const int leftshifts[] = + { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + + word32 C, D; + word32 buf[2]; + int i; + + buf[0] = key_lsw; + buf[1] = key_msw; + + C = bitsel(buf, PC1_Cbits, 28); + D = bitsel(buf, PC1_Dbits, 28); + + for (i = 0; i < 16; i++) { + C = rotl28(C, leftshifts[i]); + D = rotl28(D, leftshifts[i]); + buf[0] = D; + buf[1] = C; + sched->k0246[i] = bitsel(buf, PC2_0246, 32); + sched->k1357[i] = bitsel(buf, PC2_1357, 32); + } + + sched->iv0 = sched->iv1 = 0; +} + +static const word32 SPboxes[8][64] = { + {0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004L}, + + {0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000L}, + + {0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200L}, + + {0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080L}, + + {0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100L}, + + {0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010L}, + + {0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002L}, + + {0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000L} +}; + +#define f(R, K0246, K1357) (\ + s0246 = R ^ K0246, \ + s1357 = R ^ K1357, \ + s0246 = rotl(s0246, 28), \ + SPboxes[0] [(s0246 >> 24) & 0x3F] | \ + SPboxes[1] [(s1357 >> 24) & 0x3F] | \ + SPboxes[2] [(s0246 >> 16) & 0x3F] | \ + SPboxes[3] [(s1357 >> 16) & 0x3F] | \ + SPboxes[4] [(s0246 >> 8) & 0x3F] | \ + SPboxes[5] [(s1357 >> 8) & 0x3F] | \ + SPboxes[6] [(s0246 ) & 0x3F] | \ + SPboxes[7] [(s1357 ) & 0x3F]) + +#define bitswap(L, R, n, mask) (\ + swap = mask & ( (R >> n) ^ L ), \ + R ^= swap << n, \ + L ^= swap) + +/* Initial permutation */ +#define IP(L, R) (\ + bitswap(R, L, 4, 0x0F0F0F0F), \ + bitswap(R, L, 16, 0x0000FFFF), \ + bitswap(L, R, 2, 0x33333333), \ + bitswap(L, R, 8, 0x00FF00FF), \ + bitswap(R, L, 1, 0x55555555)) + +/* Final permutation */ +#define FP(L, R) (\ + bitswap(R, L, 1, 0x55555555), \ + bitswap(L, R, 8, 0x00FF00FF), \ + bitswap(L, R, 2, 0x33333333), \ + bitswap(R, L, 16, 0x0000FFFF), \ + bitswap(R, L, 4, 0x0F0F0F0F)) + +static void des_encipher(word32 * output, word32 L, word32 R, + DESContext * sched) +{ + word32 swap, s0246, s1357; + + IP(L, R); + + L = rotl(L, 1); + R = rotl(R, 1); + + L ^= f(R, sched->k0246[0], sched->k1357[0]); + R ^= f(L, sched->k0246[1], sched->k1357[1]); + L ^= f(R, sched->k0246[2], sched->k1357[2]); + R ^= f(L, sched->k0246[3], sched->k1357[3]); + L ^= f(R, sched->k0246[4], sched->k1357[4]); + R ^= f(L, sched->k0246[5], sched->k1357[5]); + L ^= f(R, sched->k0246[6], sched->k1357[6]); + R ^= f(L, sched->k0246[7], sched->k1357[7]); + L ^= f(R, sched->k0246[8], sched->k1357[8]); + R ^= f(L, sched->k0246[9], sched->k1357[9]); + L ^= f(R, sched->k0246[10], sched->k1357[10]); + R ^= f(L, sched->k0246[11], sched->k1357[11]); + L ^= f(R, sched->k0246[12], sched->k1357[12]); + R ^= f(L, sched->k0246[13], sched->k1357[13]); + L ^= f(R, sched->k0246[14], sched->k1357[14]); + R ^= f(L, sched->k0246[15], sched->k1357[15]); + + L = rotl(L, 31); + R = rotl(R, 31); + + swap = L; + L = R; + R = swap; + + FP(L, R); + + output[0] = L; + output[1] = R; +} + +static void des_decipher(word32 * output, word32 L, word32 R, + DESContext * sched) +{ + word32 swap, s0246, s1357; + + IP(L, R); + + L = rotl(L, 1); + R = rotl(R, 1); + + L ^= f(R, sched->k0246[15], sched->k1357[15]); + R ^= f(L, sched->k0246[14], sched->k1357[14]); + L ^= f(R, sched->k0246[13], sched->k1357[13]); + R ^= f(L, sched->k0246[12], sched->k1357[12]); + L ^= f(R, sched->k0246[11], sched->k1357[11]); + R ^= f(L, sched->k0246[10], sched->k1357[10]); + L ^= f(R, sched->k0246[9], sched->k1357[9]); + R ^= f(L, sched->k0246[8], sched->k1357[8]); + L ^= f(R, sched->k0246[7], sched->k1357[7]); + R ^= f(L, sched->k0246[6], sched->k1357[6]); + L ^= f(R, sched->k0246[5], sched->k1357[5]); + R ^= f(L, sched->k0246[4], sched->k1357[4]); + L ^= f(R, sched->k0246[3], sched->k1357[3]); + R ^= f(L, sched->k0246[2], sched->k1357[2]); + L ^= f(R, sched->k0246[1], sched->k1357[1]); + R ^= f(L, sched->k0246[0], sched->k1357[0]); + + L = rotl(L, 31); + R = rotl(R, 31); + + swap = L; + L = R; + R = swap; + + FP(L, R); + + output[0] = L; + output[1] = R; +} + +static void des_cbc_encrypt(unsigned char *blk, + unsigned int len, DESContext * sched) +{ + word32 out[2], iv0, iv1; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = sched->iv0; + iv1 = sched->iv1; + for (i = 0; i < len; i += 8) { + iv0 ^= GET_32BIT_MSB_FIRST(blk); + iv1 ^= GET_32BIT_MSB_FIRST(blk + 4); + des_encipher(out, iv0, iv1, sched); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + } + sched->iv0 = iv0; + sched->iv1 = iv1; +} + +static void des_cbc_decrypt(unsigned char *blk, + unsigned int len, DESContext * sched) +{ + word32 out[2], iv0, iv1, xL, xR; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = sched->iv0; + iv1 = sched->iv1; + for (i = 0; i < len; i += 8) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + des_decipher(out, xL, xR, sched); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + iv0 = xL; + iv1 = xR; + } + sched->iv0 = iv0; + sched->iv1 = iv1; +} + +static void des_3cbc_encrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + des_cbc_encrypt(blk, len, &scheds[0]); + des_cbc_decrypt(blk, len, &scheds[1]); + des_cbc_encrypt(blk, len, &scheds[2]); +} + +static void des_cbc3_encrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 out[2], iv0, iv1; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + iv0 ^= GET_32BIT_MSB_FIRST(blk); + iv1 ^= GET_32BIT_MSB_FIRST(blk + 4); + des_encipher(out, iv0, iv1, &scheds[0]); + des_decipher(out, out[0], out[1], &scheds[1]); + des_encipher(out, out[0], out[1], &scheds[2]); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void des_3cbc_decrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + des_cbc_decrypt(blk, len, &scheds[2]); + des_cbc_encrypt(blk, len, &scheds[1]); + des_cbc_decrypt(blk, len, &scheds[0]); +} + +static void des_cbc3_decrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 out[2], iv0, iv1, xL, xR; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + des_decipher(out, xL, xR, &scheds[2]); + des_encipher(out, out[0], out[1], &scheds[1]); + des_decipher(out, out[0], out[1], &scheds[0]); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + iv0 = xL; + iv1 = xR; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void des_sdctr3(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 b[2], iv0, iv1, tmp; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + des_encipher(b, iv0, iv1, &scheds[0]); + des_decipher(b, b[0], b[1], &scheds[1]); + des_encipher(b, b[0], b[1], &scheds[2]); + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]); + blk += 4; + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[1]); + blk += 4; + if ((iv1 = (iv1 + 1) & 0xffffffff) == 0) + iv0 = (iv0 + 1) & 0xffffffff; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void *des3_make_context(void) +{ + return snewn(3, DESContext); +} + +static void *des3_ssh1_make_context(void) +{ + /* Need 3 keys for each direction, in SSH-1 */ + return snewn(6, DESContext); +} + +static void *des_make_context(void) +{ + return snew(DESContext); +} + +static void *des_ssh1_make_context(void) +{ + /* Need one key for each direction, in SSH-1 */ + return snewn(2, DESContext); +} + +static void des3_free_context(void *handle) /* used for both 3DES and DES */ +{ + sfree(handle); +} + +static void des3_key(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &keys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &keys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &keys[2]); +} + +static void des3_iv(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + keys[0].iv0 = GET_32BIT_MSB_FIRST(key); + keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4); +} + +static void des_key(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &keys[0]); +} + +static void des3_sesskey(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des3_key(keys, key); + des3_key(keys+3, key); +} + +static void des3_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_3cbc_encrypt(blk, len, keys); +} + +static void des3_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_3cbc_decrypt(blk, len, keys+3); +} + +static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc3_encrypt(blk, len, keys); +} + +static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc3_decrypt(blk, len, keys); +} + +static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_sdctr3(blk, len, keys); +} + +static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_encrypt(blk, len, keys); +} + +static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_decrypt(blk, len, keys); +} + +void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); + des_3cbc_decrypt(blk, len, ourkeys); + smemclr(ourkeys, sizeof(ourkeys)); +} + +void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); + des_3cbc_encrypt(blk, len, ourkeys); + smemclr(ourkeys, sizeof(ourkeys)); +} + +void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); + ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); + ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); + des_cbc3_decrypt(blk, len, ourkeys); + smemclr(ourkeys, sizeof(ourkeys)); +} + +void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); + ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); + ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); + des_cbc3_encrypt(blk, len, ourkeys); + smemclr(ourkeys, sizeof(ourkeys)); +} + +static void des_keysetup_xdmauth(const unsigned char *keydata, DESContext *dc) +{ + unsigned char key[8]; + int i, nbits, j; + unsigned int bits; + + bits = 0; + nbits = 0; + j = 0; + for (i = 0; i < 8; i++) { + if (nbits < 7) { + bits = (bits << 8) | keydata[j]; + nbits += 8; + j++; + } + key[i] = (bits >> (nbits - 7)) << 1; + bits &= ~(0x7F << (nbits - 7)); + nbits -= 7; + } + + des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc); +} + +void des_encrypt_xdmauth(const unsigned char *keydata, + unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); + des_cbc_encrypt(blk, len, &dc); +} + +void des_decrypt_xdmauth(const unsigned char *keydata, + unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); + des_cbc_decrypt(blk, len, &dc); +} + +static const struct ssh2_cipher ssh_3des_ssh2 = { + des3_make_context, des3_free_context, des3_iv, des3_key, + des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, + "3des-cbc", + 8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC" +}; + +static const struct ssh2_cipher ssh_3des_ssh2_ctr = { + des3_make_context, des3_free_context, des3_iv, des3_key, + des3_ssh2_sdctr, des3_ssh2_sdctr, + "3des-ctr", + 8, 168, 0, "triple-DES SDCTR" +}; + +/* + * Single DES in SSH-2. "des-cbc" is marked as HISTORIC in + * RFC 4250, referring to + * FIPS-46-3. ("Single DES (i.e., DES) will be permitted + * for legacy systems only.") , but ssh.com support it and + * apparently aren't the only people to do so, so we sigh + * and implement it anyway. + */ +static const struct ssh2_cipher ssh_des_ssh2 = { + des_make_context, des3_free_context, des3_iv, des_key, + des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, + "des-cbc", + 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC" +}; + +static const struct ssh2_cipher ssh_des_sshcom_ssh2 = { + des_make_context, des3_free_context, des3_iv, des_key, + des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, + "des-cbc@ssh.com", + 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC" +}; + +static const struct ssh2_cipher *const des3_list[] = { + &ssh_3des_ssh2_ctr, + &ssh_3des_ssh2 +}; + +const struct ssh2_ciphers ssh2_3des = { + sizeof(des3_list) / sizeof(*des3_list), + des3_list +}; + +static const struct ssh2_cipher *const des_list[] = { + &ssh_des_ssh2, + &ssh_des_sshcom_ssh2 +}; + +const struct ssh2_ciphers ssh2_des = { + sizeof(des_list) / sizeof(*des_list), + des_list +}; + +const struct ssh_cipher ssh_3des = { + des3_ssh1_make_context, des3_free_context, des3_sesskey, + des3_encrypt_blk, des3_decrypt_blk, + 8, "triple-DES inner-CBC" +}; + +static void des_sesskey(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key(keys, key); + des_key(keys+1, key); +} + +static void des_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_encrypt(blk, len, keys); +} + +static void des_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_decrypt(blk, len, keys+1); +} + +const struct ssh_cipher ssh_des = { + des_ssh1_make_context, des3_free_context, des_sesskey, + des_encrypt_blk, des_decrypt_blk, + 8, "single-DES CBC" +}; + +#ifdef TEST_XDM_AUTH + +/* + * Small standalone utility which allows encryption and decryption of + * single cipher blocks in the XDM-AUTHORIZATION-1 style. Written + * during the rework of X authorisation for connection sharing, to + * check the corner case when xa1_firstblock matches but the rest of + * the authorisation is bogus. + * + * Just compile this file on its own with the above ifdef symbol + * predefined: + +gcc -DTEST_XDM_AUTH -o sshdes sshdes.c + + */ + +#include +void *safemalloc(size_t n, size_t size) { return calloc(n, size); } +void safefree(void *p) { return free(p); } +void smemclr(void *p, size_t size) { memset(p, 0, size); } +int main(int argc, char **argv) +{ + unsigned char words[2][8]; + unsigned char out[8]; + int i, j; + + memset(words, 0, sizeof(words)); + + for (i = 0; i < 2; i++) { + for (j = 0; j < 8 && argv[i+1][2*j]; j++) { + char x[3]; + unsigned u; + x[0] = argv[i+1][2*j]; + x[1] = argv[i+1][2*j+1]; + x[2] = 0; + sscanf(x, "%02x", &u); + words[i][j] = u; + } + } + + memcpy(out, words[0], 8); + des_decrypt_xdmauth(words[1], out, 8); + printf("decrypt(%s,%s) = ", argv[1], argv[2]); + for (i = 0; i < 8; i++) printf("%02x", out[i]); + printf("\n"); + + memcpy(out, words[0], 8); + des_encrypt_xdmauth(words[1], out, 8); + printf("encrypt(%s,%s) = ", argv[1], argv[2]); + for (i = 0; i < 8; i++) printf("%02x", out[i]); + printf("\n"); +} + +#endif diff --git a/netbox/libs/Putty/sshdh.c b/netbox/libs/Putty/sshdh.c new file mode 100644 index 000000000..8f8ab2d26 --- /dev/null +++ b/netbox/libs/Putty/sshdh.c @@ -0,0 +1,253 @@ +/* + * Diffie-Hellman implementation for PuTTY. + */ + +#include "ssh.h" + +/* + * The primes used in the group1 and group14 key exchange. + */ +static const unsigned char P1[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +static const unsigned char P14[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* + * The generator g = 2 (used for both group1 and group14). + */ +static const unsigned char G[] = { 2 }; + +static const struct ssh_kex ssh_diffiehellman_group1_sha1 = { + "diffie-hellman-group1-sha1", "group1", + KEXTYPE_DH, P1, G, lenof(P1), lenof(G), &ssh_sha1 +}; + +static const struct ssh_kex *const group1_list[] = { + &ssh_diffiehellman_group1_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_group1 = { + sizeof(group1_list) / sizeof(*group1_list), + group1_list +}; + +static const struct ssh_kex ssh_diffiehellman_group14_sha1 = { + "diffie-hellman-group14-sha1", "group14", + KEXTYPE_DH, P14, G, lenof(P14), lenof(G), &ssh_sha1 +}; + +static const struct ssh_kex *const group14_list[] = { + &ssh_diffiehellman_group14_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_group14 = { + sizeof(group14_list) / sizeof(*group14_list), + group14_list +}; + +static const struct ssh_kex ssh_diffiehellman_gex_sha256 = { + "diffie-hellman-group-exchange-sha256", NULL, + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha256 +}; + +static const struct ssh_kex ssh_diffiehellman_gex_sha1 = { + "diffie-hellman-group-exchange-sha1", NULL, + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha1 +}; + +static const struct ssh_kex *const gex_list[] = { + &ssh_diffiehellman_gex_sha256, + &ssh_diffiehellman_gex_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_gex = { + sizeof(gex_list) / sizeof(*gex_list), + gex_list +}; + +/* + * Variables. + */ +struct dh_ctx { + Bignum x, e, p, q, qmask, g; +}; + +/* + * Common DH initialisation. + */ +static void dh_init(struct dh_ctx *ctx) +{ + ctx->q = bignum_rshift(ctx->p, 1); + ctx->qmask = bignum_bitmask(ctx->q); + ctx->x = ctx->e = NULL; +} + +/* + * Initialise DH for a standard group. + */ +void *dh_setup_group(const struct ssh_kex *kex) +{ + struct dh_ctx *ctx = snew(struct dh_ctx); + ctx->p = bignum_from_bytes(kex->pdata, kex->plen); + ctx->g = bignum_from_bytes(kex->gdata, kex->glen); + dh_init(ctx); + return ctx; +} + +/* + * Initialise DH for a server-supplied group. + */ +void *dh_setup_gex(Bignum pval, Bignum gval) +{ + struct dh_ctx *ctx = snew(struct dh_ctx); + ctx->p = copybn(pval); + ctx->g = copybn(gval); + dh_init(ctx); + return ctx; +} + +/* + * Clean up and free a context. + */ +void dh_cleanup(void *handle) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + freebn(ctx->x); + freebn(ctx->e); + freebn(ctx->p); + freebn(ctx->g); + freebn(ctx->q); + freebn(ctx->qmask); + sfree(ctx); +} + +/* + * DH stage 1: invent a number x between 1 and q, and compute e = + * g^x mod p. Return e. + * + * If `nbits' is greater than zero, it is used as an upper limit + * for the number of bits in x. This is safe provided that (a) you + * use twice as many bits in x as the number of bits you expect to + * use in your session key, and (b) the DH group is a safe prime + * (which SSH demands that it must be). + * + * P. C. van Oorschot, M. J. Wiener + * "On Diffie-Hellman Key Agreement with Short Exponents". + * Advances in Cryptology: Proceedings of Eurocrypt '96 + * Springer-Verlag, May 1996. + */ +Bignum dh_create_e(void *handle, int nbits) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + int i; + + int nbytes; + unsigned char *buf; + + nbytes = ssh1_bignum_length(ctx->qmask); + buf = snewn(nbytes, unsigned char); + + do { + /* + * Create a potential x, by ANDing a string of random bytes + * with qmask. + */ + if (ctx->x) + freebn(ctx->x); + if (nbits == 0 || nbits > bignum_bitcount(ctx->qmask)) { + ssh1_write_bignum(buf, ctx->qmask); + for (i = 2; i < nbytes; i++) + buf[i] &= random_byte(); + ssh1_read_bignum(buf, nbytes, &ctx->x); /* can't fail */ + } else { + int b, nb; + ctx->x = bn_power_2(nbits); + b = nb = 0; + for (i = 0; i < nbits; i++) { + if (nb == 0) { + nb = 8; + b = random_byte(); + } + bignum_set_bit(ctx->x, i, b & 1); + b >>= 1; + nb--; + } + } + } while (bignum_cmp(ctx->x, One) <= 0 || bignum_cmp(ctx->x, ctx->q) >= 0); + + sfree(buf); + + /* + * Done. Now compute e = g^x mod p. + */ + ctx->e = modpow(ctx->g, ctx->x, ctx->p); + + return ctx->e; +} + +/* + * DH stage 2-epsilon: given a number f, validate it to ensure it's in + * range. (RFC 4253 section 8: "Values of 'e' or 'f' that are not in + * the range [1, p-1] MUST NOT be sent or accepted by either side." + * Also, we rule out 1 and p-1 too, since that's easy to do and since + * they lead to obviously weak keys that even a passive eavesdropper + * can figure out.) + */ +const char *dh_validate_f(void *handle, Bignum f) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + if (bignum_cmp(f, One) <= 0) { + return "f value received is too small"; + } else { + Bignum pm1 = bigsub(ctx->p, One); + int cmp = bignum_cmp(f, pm1); + freebn(pm1); + if (cmp >= 0) + return "f value received is too large"; + } + return NULL; +} + +/* + * DH stage 2: given a number f, compute K = f^x mod p. + */ +Bignum dh_find_K(void *handle, Bignum f) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + Bignum ret; + ret = modpow(f, ctx->x, ctx->p); + return ret; +} diff --git a/netbox/libs/Putty/sshdss.c b/netbox/libs/Putty/sshdss.c new file mode 100644 index 000000000..f26527b71 --- /dev/null +++ b/netbox/libs/Putty/sshdss.c @@ -0,0 +1,680 @@ +/* + * Digital Signature Standard implementation for PuTTY. + */ + +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +static void sha_mpint(SHA_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + putty_SHA_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + putty_SHA_Bytes(s, lenbuf, 1); + } + smemclr(lenbuf, sizeof(lenbuf)); +} + +static void sha512_mpint(SHA512_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + putty_SHA512_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + putty_SHA512_Bytes(s, lenbuf, 1); + } + smemclr(lenbuf, sizeof(lenbuf)); +} + +static void getstring(const char **data, int *datalen, + const char **p, int *length) +{ + *p = NULL; + if (*datalen < 4) + return; + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; + *datalen -= 4; + *data += 4; + if (*datalen < *length) + return; + *p = *data; + *data += *length; + *datalen -= *length; +} +static Bignum getmp(const char **data, int *datalen) +{ + const char *p; + int length; + Bignum b; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + return NULL; /* negative mp */ + b = bignum_from_bytes((const unsigned char *)p, length); + return b; +} + +static Bignum get160(const char **data, int *datalen) +{ + Bignum b; + + if (*datalen < 20) + return NULL; + + b = bignum_from_bytes((const unsigned char *)*data, 20); + *data += 20; + *datalen -= 20; + + return b; +} + +static void dss_freekey(void *key); /* forward reference */ + +static void *dss_newkey(const struct ssh_signkey *self, + const char *data, int len) +{ + const char *p; + int slen; + struct dss_key *dss; + + dss = snew(struct dss_key); + getstring(&data, &len, &p, &slen); + +#ifdef DEBUG_DSS + { + int i; + printf("key:"); + for (i = 0; i < len; i++) + printf(" %02x", (unsigned char) (data[i])); + printf("\n"); + } +#endif + + if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { + sfree(dss); + return NULL; + } + dss->p = getmp(&data, &len); + dss->q = getmp(&data, &len); + dss->g = getmp(&data, &len); + dss->y = getmp(&data, &len); + dss->x = NULL; + + if (!dss->p || !dss->q || !dss->g || !dss->y || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; + } + + return dss; +} + +static void dss_freekey(void *key) +{ + struct dss_key *dss = (struct dss_key *) key; + if (dss->p) + freebn(dss->p); + if (dss->q) + freebn(dss->q); + if (dss->g) + freebn(dss->g); + if (dss->y) + freebn(dss->y); + if (dss->x) + freebn(dss->x); + sfree(dss); +} + +static char *dss_fmtkey(void *key) +{ + struct dss_key *dss = (struct dss_key *) key; + char *p; + int len, i, pos, nibbles; + static const char hex[] = "0123456789abcdef"; + if (!dss->p) + return NULL; + len = 8 + 4 + 1; /* 4 x "0x", punctuation, \0 */ + len += 4 * (bignum_bitcount(dss->p) + 15) / 16; + len += 4 * (bignum_bitcount(dss->q) + 15) / 16; + len += 4 * (bignum_bitcount(dss->g) + 15) / 16; + len += 4 * (bignum_bitcount(dss->y) + 15) / 16; + p = snewn(len, char); + if (!p) + return NULL; + + pos = 0; + pos += sprintf(p + pos, "0x"); + nibbles = (3 + bignum_bitcount(dss->p)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->p, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->q)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->q, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->g)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->g, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->y)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->y, i / 2) >> (4 * (i % 2))) & 0xF]; + p[pos] = '\0'; + return p; +} + +static int dss_verifysig(void *key, const char *sig, int siglen, + const char *data, int datalen) +{ + struct dss_key *dss = (struct dss_key *) key; + const char *p; + int slen; + char hash[20]; + Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v; + int ret; + + if (!dss->p) + return 0; + +#ifdef DEBUG_DSS + { + int i; + printf("sig:"); + for (i = 0; i < siglen; i++) + printf(" %02x", (unsigned char) (sig[i])); + printf("\n"); + } +#endif + /* + * Commercial SSH (2.0.13) and OpenSSH disagree over the format + * of a DSA signature. OpenSSH is in line with RFC 4253: + * it uses a string "ssh-dss", followed by a 40-byte string + * containing two 160-bit integers end-to-end. Commercial SSH + * can't be bothered with the header bit, and considers a DSA + * signature blob to be _just_ the 40-byte string containing + * the two 160-bit integers. We tell them apart by measuring + * the length: length 40 means the commercial-SSH bug, anything + * else is assumed to be RFC-compliant. + */ + if (siglen != 40) { /* bug not present; read admin fields */ + getstring(&sig, &siglen, &p, &slen); + if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { + return 0; + } + sig += 4, siglen -= 4; /* skip yet another length field */ + } + r = get160(&sig, &siglen); + s = get160(&sig, &siglen); + if (!r || !s) { + if (r) + freebn(r); + if (s) + freebn(s); + return 0; + } + + if (!bignum_cmp(s, Zero)) { + freebn(r); + freebn(s); + return 0; + } + + /* + * Step 1. w <- s^-1 mod q. + */ + w = modinv(s, dss->q); + if (!w) { + freebn(r); + freebn(s); + return 0; + } + + /* + * Step 2. u1 <- SHA(message) * w mod q. + */ + putty_SHA_Simple(data, datalen, (unsigned char *)hash); + p = hash; + slen = 20; + sha = get160(&p, &slen); + u1 = modmul(sha, w, dss->q); + + /* + * Step 3. u2 <- r * w mod q. + */ + u2 = modmul(r, w, dss->q); + + /* + * Step 4. v <- (g^u1 * y^u2 mod p) mod q. + */ + gu1p = modpow(dss->g, u1, dss->p); + yu2p = modpow(dss->y, u2, dss->p); + gu1yu2p = modmul(gu1p, yu2p, dss->p); + v = modmul(gu1yu2p, One, dss->q); + + /* + * Step 5. v should now be equal to r. + */ + + ret = !bignum_cmp(v, r); + + freebn(w); + freebn(sha); + freebn(u1); + freebn(u2); + freebn(gu1p); + freebn(yu2p); + freebn(gu1yu2p); + freebn(v); + freebn(r); + freebn(s); + + return ret; +} + +static unsigned char *dss_public_blob(void *key, int *len) +{ + struct dss_key *dss = (struct dss_key *) key; + int plen, qlen, glen, ylen, bloblen; + int i; + unsigned char *blob, *p; + + plen = (bignum_bitcount(dss->p) + 8) / 8; + qlen = (bignum_bitcount(dss->q) + 8) / 8; + glen = (bignum_bitcount(dss->g) + 8) / 8; + ylen = (bignum_bitcount(dss->y) + 8) / 8; + + /* + * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total + * 27 + sum of lengths. (five length fields, 20+7=27). + */ + bloblen = 27 + plen + qlen + glen + ylen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, 7); + p += 4; + memcpy(p, "ssh-dss", 7); + p += 7; + PUT_32BIT(p, plen); + p += 4; + for (i = plen; i--;) + *p++ = bignum_byte(dss->p, i); + PUT_32BIT(p, qlen); + p += 4; + for (i = qlen; i--;) + *p++ = bignum_byte(dss->q, i); + PUT_32BIT(p, glen); + p += 4; + for (i = glen; i--;) + *p++ = bignum_byte(dss->g, i); + PUT_32BIT(p, ylen); + p += 4; + for (i = ylen; i--;) + *p++ = bignum_byte(dss->y, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static unsigned char *dss_private_blob(void *key, int *len) +{ + struct dss_key *dss = (struct dss_key *) key; + int xlen, bloblen; + int i; + unsigned char *blob, *p; + + xlen = (bignum_bitcount(dss->x) + 8) / 8; + + /* + * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen. + */ + bloblen = 4 + xlen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, xlen); + p += 4; + for (i = xlen; i--;) + *p++ = bignum_byte(dss->x, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static void *dss_createkey(const struct ssh_signkey *self, + const unsigned char *pub_blob, int pub_len, + const unsigned char *priv_blob, int priv_len) +{ + struct dss_key *dss; + const char *pb = (const char *) priv_blob; + const char *hash; + int hashlen; + SHA_State s; + unsigned char digest[20]; + Bignum ytest; + + dss = dss_newkey(self, (char *) pub_blob, pub_len); + if (!dss) + return NULL; + dss->x = getmp(&pb, &priv_len); + if (!dss->x) { + dss_freekey(dss); + return NULL; + } + + /* + * Check the obsolete hash in the old DSS key format. + */ + hashlen = -1; + getstring(&pb, &priv_len, &hash, &hashlen); + if (hashlen == 20) { + putty_SHA_Init(&s); + sha_mpint(&s, dss->p); + sha_mpint(&s, dss->q); + sha_mpint(&s, dss->g); + putty_SHA_Final(&s, digest); + if (0 != memcmp(hash, digest, 20)) { + dss_freekey(dss); + return NULL; + } + } + + /* + * Now ensure g^x mod p really is y. + */ + ytest = modpow(dss->g, dss->x, dss->p); + if (0 != bignum_cmp(ytest, dss->y)) { + dss_freekey(dss); + freebn(ytest); + return NULL; + } + freebn(ytest); + + return dss; +} + +static void *dss_openssh_createkey(const struct ssh_signkey *self, + const unsigned char **blob, int *len) +{ + const char **b = (const char **) blob; + struct dss_key *dss; + + dss = snew(struct dss_key); + + dss->p = getmp(b, len); + dss->q = getmp(b, len); + dss->g = getmp(b, len); + dss->y = getmp(b, len); + dss->x = getmp(b, len); + + if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; + } + + return dss; +} + +static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct dss_key *dss = (struct dss_key *) key; + int bloblen, i; + + bloblen = + ssh2_bignum_length(dss->p) + + ssh2_bignum_length(dss->q) + + ssh2_bignum_length(dss->g) + + ssh2_bignum_length(dss->y) + + ssh2_bignum_length(dss->x); + + if (bloblen > len) + return bloblen; + + bloblen = 0; +#define ENC(x) \ + PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ + for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); + ENC(dss->p); + ENC(dss->q); + ENC(dss->g); + ENC(dss->y); + ENC(dss->x); + + return bloblen; +} + +static int dss_pubkey_bits(const struct ssh_signkey *self, + const void *blob, int len) +{ + struct dss_key *dss; + int ret; + + dss = dss_newkey(self, (const char *) blob, len); + if (!dss) + return -1; + ret = bignum_bitcount(dss->p); + dss_freekey(dss); + + return ret; +} + +Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key, + unsigned char *digest, int digest_len) +{ + /* + * The basic DSS signing algorithm is: + * + * - invent a random k between 1 and q-1 (exclusive). + * - Compute r = (g^k mod p) mod q. + * - Compute s = k^-1 * (hash + x*r) mod q. + * + * This has the dangerous properties that: + * + * - if an attacker in possession of the public key _and_ the + * signature (for example, the host you just authenticated + * to) can guess your k, he can reverse the computation of s + * and work out x = r^-1 * (s*k - hash) mod q. That is, he + * can deduce the private half of your key, and masquerade + * as you for as long as the key is still valid. + * + * - since r is a function purely of k and the public key, if + * the attacker only has a _range of possibilities_ for k + * it's easy for him to work through them all and check each + * one against r; he'll never be unsure of whether he's got + * the right one. + * + * - if you ever sign two different hashes with the same k, it + * will be immediately obvious because the two signatures + * will have the same r, and moreover an attacker in + * possession of both signatures (and the public key of + * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q, + * and from there deduce x as before. + * + * - the Bleichenbacher attack on DSA makes use of methods of + * generating k which are significantly non-uniformly + * distributed; in particular, generating a 160-bit random + * number and reducing it mod q is right out. + * + * For this reason we must be pretty careful about how we + * generate our k. Since this code runs on Windows, with no + * particularly good system entropy sources, we can't trust our + * RNG itself to produce properly unpredictable data. Hence, we + * use a totally different scheme instead. + * + * What we do is to take a SHA-512 (_big_) hash of the private + * key x, and then feed this into another SHA-512 hash that + * also includes the message hash being signed. That is: + * + * proto_k = SHA512 ( SHA512(x) || SHA160(message) ) + * + * This number is 512 bits long, so reducing it mod q won't be + * noticeably non-uniform. So + * + * k = proto_k mod q + * + * This has the interesting property that it's _deterministic_: + * signing the same hash twice with the same key yields the + * same signature. + * + * Despite this determinism, it's still not predictable to an + * attacker, because in order to repeat the SHA-512 + * construction that created it, the attacker would have to + * know the private key value x - and by assumption he doesn't, + * because if he knew that he wouldn't be attacking k! + * + * (This trick doesn't, _per se_, protect against reuse of k. + * Reuse of k is left to chance; all it does is prevent + * _excessively high_ chances of reuse of k due to entropy + * problems.) + * + * Thanks to Colin Plumb for the general idea of using x to + * ensure k is hard to guess, and to the Cambridge University + * Computer Security Group for helping to argue out all the + * fine details. + */ + SHA512_State ss; + unsigned char digest512[64]; + Bignum proto_k, k; + + /* + * Hash some identifying text plus x. + */ + putty_SHA512_Init(&ss); + putty_SHA512_Bytes(&ss, id_string, strlen(id_string) + 1); + sha512_mpint(&ss, private_key); + putty_SHA512_Final(&ss, digest512); + + /* + * Now hash that digest plus the message hash. + */ + putty_SHA512_Init(&ss); + putty_SHA512_Bytes(&ss, digest512, sizeof(digest512)); + putty_SHA512_Bytes(&ss, digest, digest_len); + + while (1) { + SHA512_State ss2 = ss; /* structure copy */ + putty_SHA512_Final(&ss2, digest512); + + smemclr(&ss2, sizeof(ss2)); + + /* + * Now convert the result into a bignum, and reduce it mod q. + */ + proto_k = bignum_from_bytes(digest512, 64); + k = bigmod(proto_k, modulus); + freebn(proto_k); + + if (bignum_cmp(k, One) != 0 && bignum_cmp(k, Zero) != 0) { + smemclr(&ss, sizeof(ss)); + smemclr(digest512, sizeof(digest512)); + return k; + } + + /* Very unlikely we get here, but if so, k was unsuitable. */ + freebn(k); + /* Perturb the hash to think of a different k. */ + putty_SHA512_Bytes(&ss, "x", 1); + /* Go round and try again. */ + } +} + +static unsigned char *dss_sign(void *key, const char *data, int datalen, + int *siglen) +{ + struct dss_key *dss = (struct dss_key *) key; + Bignum k, gkp, hash, kinv, hxr, r, s; + unsigned char digest[20]; + unsigned char *bytes; + int nbytes, i; + + putty_SHA_Simple(data, datalen, digest); + + k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, + digest, sizeof(digest)); + kinv = modinv(k, dss->q); /* k^-1 mod q */ + assert(kinv); + + /* + * Now we have k, so just go ahead and compute the signature. + */ + gkp = modpow(dss->g, k, dss->p); /* g^k mod p */ + r = bigmod(gkp, dss->q); /* r = (g^k mod p) mod q */ + freebn(gkp); + + hash = bignum_from_bytes(digest, 20); + hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */ + s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */ + freebn(hxr); + freebn(kinv); + freebn(k); + freebn(hash); + + /* + * Signature blob is + * + * string "ssh-dss" + * string two 20-byte numbers r and s, end to end + * + * i.e. 4+7 + 4+40 bytes. + */ + nbytes = 4 + 7 + 4 + 40; + bytes = snewn(nbytes, unsigned char); + PUT_32BIT(bytes, 7); + memcpy(bytes + 4, "ssh-dss", 7); + PUT_32BIT(bytes + 4 + 7, 40); + for (i = 0; i < 20; i++) { + bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i); + bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i); + } + freebn(r); + freebn(s); + + *siglen = nbytes; + return bytes; +} + +const struct ssh_signkey ssh_dss = { + dss_newkey, + dss_freekey, + dss_fmtkey, + dss_public_blob, + dss_private_blob, + dss_createkey, + dss_openssh_createkey, + dss_openssh_fmtkey, + 5 /* p,q,g,y,x */, + dss_pubkey_bits, + dss_verifysig, + dss_sign, + "ssh-dss", + "dss", + NULL, +}; diff --git a/netbox/libs/Putty/sshdssg.c b/netbox/libs/Putty/sshdssg.c new file mode 100644 index 000000000..3d7b0ef69 --- /dev/null +++ b/netbox/libs/Putty/sshdssg.c @@ -0,0 +1,145 @@ +/* + * DSS key generation. + */ + +#include "misc.h" +#include "ssh.h" + +int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, + void *pfnparam) +{ + Bignum qm1, power, g, h, tmp; + unsigned pfirst, qfirst; + int progress; + + /* + * Set up the phase limits for the progress report. We do this + * by passing minus the phase number. + * + * For prime generation: our initial filter finds things + * coprime to everything below 2^16. Computing the product of + * (p-1)/p for all prime p below 2^16 gives about 20.33; so + * among B-bit integers, one in every 20.33 will get through + * the initial filter to be a candidate prime. + * + * Meanwhile, we are searching for primes in the region of 2^B; + * since pi(x) ~ x/log(x), when x is in the region of 2^B, the + * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about + * 1/0.6931B. So the chance of any given candidate being prime + * is 20.33/0.6931B, which is roughly 29.34 divided by B. + * + * So now we have this probability P, we're looking at an + * exponential distribution with parameter P: we will manage in + * one attempt with probability P, in two with probability + * P(1-P), in three with probability P(1-P)^2, etc. The + * probability that we have still not managed to find a prime + * after N attempts is (1-P)^N. + * + * We therefore inform the progress indicator of the number B + * (29.34/B), so that it knows how much to increment by each + * time. We do this in 16-bit fixed point, so 29.34 becomes + * 0x1D.57C4. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x2800); + pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / 160); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x40 * bits); + pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / bits); + + /* + * In phase three we are finding an order-q element of the + * multiplicative group of p, by finding an element whose order + * is _divisible_ by q and raising it to the power of (p-1)/q. + * _Most_ elements will have order divisible by q, since for a + * start phi(p) of them will be primitive roots. So + * realistically we don't need to set this much below 1 (64K). + * Still, we'll set it to 1/2 (32K) to be on the safe side. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x2000); + pfn(pfnparam, PROGFN_EXP_PHASE, 3, -32768); + + /* + * In phase four we are finding an element x between 1 and q-1 + * (exclusive), by inventing 160 random bits and hoping they + * come out to a plausible number; so assuming q is uniformly + * distributed between 2^159 and 2^160, the chance of any given + * attempt succeeding is somewhere between 0.5 and 1. Lacking + * the energy to arrange to be able to specify this probability + * _after_ generating q, we'll just set it to 0.75. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 4, 0x2000); + pfn(pfnparam, PROGFN_EXP_PHASE, 4, -49152); + + pfn(pfnparam, PROGFN_READY, 0, 0); + + invent_firstbits(&pfirst, &qfirst); + /* + * Generate q: a prime of length 160. + */ + key->q = primegen(160, 2, 2, NULL, 1, pfn, pfnparam, qfirst); + /* + * Now generate p: a prime of length `bits', such that p-1 is + * divisible by q. + */ + key->p = primegen(bits-160, 2, 2, key->q, 2, pfn, pfnparam, pfirst); + + /* + * Next we need g. Raise 2 to the power (p-1)/q modulo p, and + * if that comes out to one then try 3, then 4 and so on. As + * soon as we hit a non-unit (and non-zero!) one, that'll do + * for g. + */ + power = bigdiv(key->p, key->q); /* this is floor(p/q) == (p-1)/q */ + h = bignum_from_long(1); + progress = 0; + while (1) { + pfn(pfnparam, PROGFN_PROGRESS, 3, ++progress); + g = modpow(h, power, key->p); + if (bignum_cmp(g, One) > 0) + break; /* got one */ + tmp = h; + h = bignum_add_long(h, 1); + freebn(tmp); + } + key->g = g; + freebn(h); + + /* + * Now we're nearly done. All we need now is our private key x, + * which should be a number between 1 and q-1 exclusive, and + * our public key y = g^x mod p. + */ + qm1 = copybn(key->q); + decbn(qm1); + progress = 0; + while (1) { + int i, v, byte, bitsleft; + Bignum x; + + pfn(pfnparam, PROGFN_PROGRESS, 4, ++progress); + x = bn_power_2(159); + byte = 0; + bitsleft = 0; + + for (i = 0; i < 160; i++) { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(x, i, v); + } + + if (bignum_cmp(x, One) <= 0 || bignum_cmp(x, qm1) >= 0) { + freebn(x); + continue; + } else { + key->x = x; + break; + } + } + freebn(qm1); + + key->y = modpow(key->g, key->x, key->p); + + return 1; +} diff --git a/netbox/libs/Putty/sshecc.c b/netbox/libs/Putty/sshecc.c new file mode 100644 index 000000000..b6d65cbd2 --- /dev/null +++ b/netbox/libs/Putty/sshecc.c @@ -0,0 +1,3073 @@ +/* + * Elliptic-curve crypto module for PuTTY + * Implements the three required curves, no optional curves + * + * NOTE: Only curves on prime field are handled by the maths functions + * in Weierstrass form using Jacobian co-ordinates. + * + * Montgomery form curves are supported for DH. (Curve25519) + * + * Edwards form curves are supported for DSA. (Ed25519) + */ + +/* + * References: + * + * Elliptic curves in SSH are specified in RFC 5656: + * http://tools.ietf.org/html/rfc5656 + * + * That specification delegates details of public key formatting and a + * lot of underlying mechanism to SEC 1: + * http://www.secg.org/sec1-v2.pdf + * + * Montgomery maths from: + * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13 + * http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf + * + * Curve25519 spec from libssh (with reference to other things in the + * libssh code): + * https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt + * + * Edwards DSA: + * http://ed25519.cr.yp.to/ed25519-20110926.pdf + */ + +#include +#include + +#include "ssh.h" + +#ifdef MPEXT +int ec_curve_cleanup = 0; + +static void finalize_ec_point(struct ec_point *point) +{ + if (point->x != NULL) freebn(point->x); + if (point->y != NULL) freebn(point->y); + if (point->z != NULL) freebn(point->z); +} + +static void finalize_wcurve(struct ec_curve *curve) +{ + if (curve->p != NULL) freebn(curve->p); + + if (curve->w.a != NULL) freebn(curve->w.a); + if (curve->w.b != NULL) freebn(curve->w.b); + if (curve->w.n != NULL) freebn(curve->w.n); + finalize_ec_point(&curve->w.G); +} + +static void finalize_mcurve(struct ec_curve *curve) +{ + if (curve->p != NULL) freebn(curve->p); + + if (curve->m.a != NULL) freebn(curve->m.a); + if (curve->m.b != NULL) freebn(curve->m.b); + finalize_ec_point(&curve->m.G); +} + +static void finalize_ecurve(struct ec_curve *curve) +{ + if (curve->p != NULL) freebn(curve->p); + + if (curve->e.l != NULL) freebn(curve->e.l); + if (curve->e.d != NULL) freebn(curve->e.d); + finalize_ec_point(&curve->e.B); +} +#endif + +/* ---------------------------------------------------------------------- + * Elliptic curve definitions + */ + +static void initialise_wcurve(struct ec_curve *curve, int bits, + const unsigned char *p, + const unsigned char *a, const unsigned char *b, + const unsigned char *n, const unsigned char *Gx, + const unsigned char *Gy) +{ + int length = bits / 8; + if (bits % 8) ++length; + + curve->type = EC_WEIERSTRASS; + + curve->fieldBits = bits; + curve->p = bignum_from_bytes(p, length); + + /* Curve co-efficients */ + curve->w.a = bignum_from_bytes(a, length); + curve->w.b = bignum_from_bytes(b, length); + + /* Group order and generator */ + curve->w.n = bignum_from_bytes(n, length); + curve->w.G.x = bignum_from_bytes(Gx, length); + curve->w.G.y = bignum_from_bytes(Gy, length); + curve->w.G.curve = curve; + curve->w.G.infinity = 0; +} + +static void initialise_mcurve(struct ec_curve *curve, int bits, + const unsigned char *p, + const unsigned char *a, const unsigned char *b, + const unsigned char *Gx) +{ + int length = bits / 8; + if (bits % 8) ++length; + + curve->type = EC_MONTGOMERY; + + curve->fieldBits = bits; + curve->p = bignum_from_bytes(p, length); + + /* Curve co-efficients */ + curve->m.a = bignum_from_bytes(a, length); + curve->m.b = bignum_from_bytes(b, length); + + /* Generator */ + curve->m.G.x = bignum_from_bytes(Gx, length); + curve->m.G.y = NULL; + curve->m.G.z = NULL; + curve->m.G.curve = curve; + curve->m.G.infinity = 0; +} + +static void initialise_ecurve(struct ec_curve *curve, int bits, + const unsigned char *p, + const unsigned char *l, const unsigned char *d, + const unsigned char *Bx, const unsigned char *By) +{ + int length = bits / 8; + if (bits % 8) ++length; + + curve->type = EC_EDWARDS; + + curve->fieldBits = bits; + curve->p = bignum_from_bytes(p, length); + + /* Curve co-efficients */ + curve->e.l = bignum_from_bytes(l, length); + curve->e.d = bignum_from_bytes(d, length); + + /* Group order and generator */ + curve->e.B.x = bignum_from_bytes(Bx, length); + curve->e.B.y = bignum_from_bytes(By, length); + curve->e.B.curve = curve; + curve->e.B.infinity = 0; +} + +static struct ec_curve *ec_p256(void) +{ + static struct ec_curve curve = { 0 }; + static unsigned char initialised = 0; + + #ifdef MPEXT + if (ec_curve_cleanup) + { + if (initialised) finalize_wcurve(&curve); + initialised = 0; + return NULL; + } + #endif + + if (!initialised) + { + static const unsigned char p[] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + static const unsigned char a[] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc + }; + static const unsigned char b[] = { + 0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7, + 0xb3, 0xeb, 0xbd, 0x55, 0x76, 0x98, 0x86, 0xbc, + 0x65, 0x1d, 0x06, 0xb0, 0xcc, 0x53, 0xb0, 0xf6, + 0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b + }; + static const unsigned char n[] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51 + }; + static const unsigned char Gx[] = { + 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, + 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, + 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0, + 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96 + }; + static const unsigned char Gy[] = { + 0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, + 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0x0f, 0x9e, 0x16, + 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, + 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5 + }; + + initialise_wcurve(&curve, 256, p, a, b, n, Gx, Gy); + curve.textname = curve.name = "nistp256"; + + /* Now initialised, no need to do it again */ + initialised = 1; + } + + return &curve; +} + +static struct ec_curve *ec_p384(void) +{ + static struct ec_curve curve = { 0 }; + static unsigned char initialised = 0; + + #ifdef MPEXT + if (ec_curve_cleanup) + { + if (initialised) finalize_wcurve(&curve); + initialised = 0; + return NULL; + } + #endif + + if (!initialised) + { + static const unsigned char p[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff + }; + static const unsigned char a[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfc + }; + static const unsigned char b[] = { + 0xb3, 0x31, 0x2f, 0xa7, 0xe2, 0x3e, 0xe7, 0xe4, + 0x98, 0x8e, 0x05, 0x6b, 0xe3, 0xf8, 0x2d, 0x19, + 0x18, 0x1d, 0x9c, 0x6e, 0xfe, 0x81, 0x41, 0x12, + 0x03, 0x14, 0x08, 0x8f, 0x50, 0x13, 0x87, 0x5a, + 0xc6, 0x56, 0x39, 0x8d, 0x8a, 0x2e, 0xd1, 0x9d, + 0x2a, 0x85, 0xc8, 0xed, 0xd3, 0xec, 0x2a, 0xef + }; + static const unsigned char n[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf, + 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, + 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73 + }; + static const unsigned char Gx[] = { + 0xaa, 0x87, 0xca, 0x22, 0xbe, 0x8b, 0x05, 0x37, + 0x8e, 0xb1, 0xc7, 0x1e, 0xf3, 0x20, 0xad, 0x74, + 0x6e, 0x1d, 0x3b, 0x62, 0x8b, 0xa7, 0x9b, 0x98, + 0x59, 0xf7, 0x41, 0xe0, 0x82, 0x54, 0x2a, 0x38, + 0x55, 0x02, 0xf2, 0x5d, 0xbf, 0x55, 0x29, 0x6c, + 0x3a, 0x54, 0x5e, 0x38, 0x72, 0x76, 0x0a, 0xb7 + }; + static const unsigned char Gy[] = { + 0x36, 0x17, 0xde, 0x4a, 0x96, 0x26, 0x2c, 0x6f, + 0x5d, 0x9e, 0x98, 0xbf, 0x92, 0x92, 0xdc, 0x29, + 0xf8, 0xf4, 0x1d, 0xbd, 0x28, 0x9a, 0x14, 0x7c, + 0xe9, 0xda, 0x31, 0x13, 0xb5, 0xf0, 0xb8, 0xc0, + 0x0a, 0x60, 0xb1, 0xce, 0x1d, 0x7e, 0x81, 0x9d, + 0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f + }; + + initialise_wcurve(&curve, 384, p, a, b, n, Gx, Gy); + curve.textname = curve.name = "nistp384"; + + /* Now initialised, no need to do it again */ + initialised = 1; + } + + return &curve; +} + +static struct ec_curve *ec_p521(void) +{ + static struct ec_curve curve = { 0 }; + static unsigned char initialised = 0; + + #ifdef MPEXT + if (ec_curve_cleanup) + { + if (initialised) finalize_wcurve(&curve); + initialised = 0; + return NULL; + } + #endif + + if (!initialised) + { + static const unsigned char p[] = { + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff + }; + static const unsigned char a[] = { + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc + }; + static const unsigned char b[] = { + 0x00, 0x51, 0x95, 0x3e, 0xb9, 0x61, 0x8e, 0x1c, + 0x9a, 0x1f, 0x92, 0x9a, 0x21, 0xa0, 0xb6, 0x85, + 0x40, 0xee, 0xa2, 0xda, 0x72, 0x5b, 0x99, 0xb3, + 0x15, 0xf3, 0xb8, 0xb4, 0x89, 0x91, 0x8e, 0xf1, + 0x09, 0xe1, 0x56, 0x19, 0x39, 0x51, 0xec, 0x7e, + 0x93, 0x7b, 0x16, 0x52, 0xc0, 0xbd, 0x3b, 0xb1, + 0xbf, 0x07, 0x35, 0x73, 0xdf, 0x88, 0x3d, 0x2c, + 0x34, 0xf1, 0xef, 0x45, 0x1f, 0xd4, 0x6b, 0x50, + 0x3f, 0x00 + }; + static const unsigned char n[] = { + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfa, 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, + 0x96, 0x6b, 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, + 0xa5, 0xd0, 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, + 0x47, 0xae, 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, + 0x64, 0x09 + }; + static const unsigned char Gx[] = { + 0x00, 0xc6, 0x85, 0x8e, 0x06, 0xb7, 0x04, 0x04, + 0xe9, 0xcd, 0x9e, 0x3e, 0xcb, 0x66, 0x23, 0x95, + 0xb4, 0x42, 0x9c, 0x64, 0x81, 0x39, 0x05, 0x3f, + 0xb5, 0x21, 0xf8, 0x28, 0xaf, 0x60, 0x6b, 0x4d, + 0x3d, 0xba, 0xa1, 0x4b, 0x5e, 0x77, 0xef, 0xe7, + 0x59, 0x28, 0xfe, 0x1d, 0xc1, 0x27, 0xa2, 0xff, + 0xa8, 0xde, 0x33, 0x48, 0xb3, 0xc1, 0x85, 0x6a, + 0x42, 0x9b, 0xf9, 0x7e, 0x7e, 0x31, 0xc2, 0xe5, + 0xbd, 0x66 + }; + static const unsigned char Gy[] = { + 0x01, 0x18, 0x39, 0x29, 0x6a, 0x78, 0x9a, 0x3b, + 0xc0, 0x04, 0x5c, 0x8a, 0x5f, 0xb4, 0x2c, 0x7d, + 0x1b, 0xd9, 0x98, 0xf5, 0x44, 0x49, 0x57, 0x9b, + 0x44, 0x68, 0x17, 0xaf, 0xbd, 0x17, 0x27, 0x3e, + 0x66, 0x2c, 0x97, 0xee, 0x72, 0x99, 0x5e, 0xf4, + 0x26, 0x40, 0xc5, 0x50, 0xb9, 0x01, 0x3f, 0xad, + 0x07, 0x61, 0x35, 0x3c, 0x70, 0x86, 0xa2, 0x72, + 0xc2, 0x40, 0x88, 0xbe, 0x94, 0x76, 0x9f, 0xd1, + 0x66, 0x50 + }; + + initialise_wcurve(&curve, 521, p, a, b, n, Gx, Gy); + curve.textname = curve.name = "nistp521"; + + /* Now initialised, no need to do it again */ + initialised = 1; + } + + return &curve; +} + +static struct ec_curve *ec_curve25519(void) +{ + static struct ec_curve curve = { 0 }; + static unsigned char initialised = 0; + + #ifdef MPEXT + if (ec_curve_cleanup) + { + if (initialised) finalize_mcurve(&curve); + initialised = 0; + return NULL; + } + #endif + + if (!initialised) + { + static const unsigned char p[] = { + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed + }; + static const unsigned char a[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6d, 0x06 + }; + static const unsigned char b[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + static const unsigned char gx[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 + }; + + initialise_mcurve(&curve, 256, p, a, b, gx); + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + curve.textname = "Curve25519"; + + /* Now initialised, no need to do it again */ + initialised = 1; + } + + return &curve; +} + +static struct ec_curve *ec_ed25519(void) +{ + static struct ec_curve curve = { 0 }; + static unsigned char initialised = 0; + + #ifdef MPEXT + if (ec_curve_cleanup) + { + if (initialised) finalize_ecurve(&curve); + initialised = 0; + return NULL; + } + #endif + + if (!initialised) + { + static const unsigned char q[] = { + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed + }; + static const unsigned char l[32] = { + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6, + 0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed + }; + static const unsigned char d[32] = { + 0x52, 0x03, 0x6c, 0xee, 0x2b, 0x6f, 0xfe, 0x73, + 0x8c, 0xc7, 0x40, 0x79, 0x77, 0x79, 0xe8, 0x98, + 0x00, 0x70, 0x0a, 0x4d, 0x41, 0x41, 0xd8, 0xab, + 0x75, 0xeb, 0x4d, 0xca, 0x13, 0x59, 0x78, 0xa3 + }; + static const unsigned char Bx[32] = { + 0x21, 0x69, 0x36, 0xd3, 0xcd, 0x6e, 0x53, 0xfe, + 0xc0, 0xa4, 0xe2, 0x31, 0xfd, 0xd6, 0xdc, 0x5c, + 0x69, 0x2c, 0xc7, 0x60, 0x95, 0x25, 0xa7, 0xb2, + 0xc9, 0x56, 0x2d, 0x60, 0x8f, 0x25, 0xd5, 0x1a + }; + static const unsigned char By[32] = { + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x58 + }; + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + + initialise_ecurve(&curve, 256, q, l, d, Bx, By); + curve.textname = "Ed25519"; + + /* Now initialised, no need to do it again */ + initialised = 1; + } + + return &curve; +} + +/* Return 1 if a is -3 % p, otherwise return 0 + * This is used because there are some maths optimisations */ +static int ec_aminus3(const struct ec_curve *curve) +{ + int ret; + Bignum _p; + + if (curve->type != EC_WEIERSTRASS) { + return 0; + } + + _p = bignum_add_long(curve->w.a, 3); + + ret = !bignum_cmp(curve->p, _p); + freebn(_p); + return ret; +} + +/* ---------------------------------------------------------------------- + * Elliptic curve field maths + */ + +static Bignum ecf_add(const Bignum a, const Bignum b, + const struct ec_curve *curve) +{ + Bignum a1, b1, ab, ret; + + a1 = bigmod(a, curve->p); + b1 = bigmod(b, curve->p); + + ab = bigadd(a1, b1); + freebn(a1); + freebn(b1); + + ret = bigmod(ab, curve->p); + freebn(ab); + + return ret; +} + +static Bignum ecf_square(const Bignum a, const struct ec_curve *curve) +{ + return modmul(a, a, curve->p); +} + +static Bignum ecf_treble(const Bignum a, const struct ec_curve *curve) +{ + Bignum ret, tmp; + + /* Double */ + tmp = bignum_lshift(a, 1); + + /* Add itself (i.e. treble) */ + ret = bigadd(tmp, a); + freebn(tmp); + + /* Normalise */ + while (bignum_cmp(ret, curve->p) >= 0) + { + tmp = bigsub(ret, curve->p); + assert(tmp); + freebn(ret); + ret = tmp; + } + + return ret; +} + +static Bignum ecf_double(const Bignum a, const struct ec_curve *curve) +{ + Bignum ret = bignum_lshift(a, 1); + if (bignum_cmp(ret, curve->p) >= 0) + { + Bignum tmp = bigsub(ret, curve->p); + assert(tmp); + freebn(ret); + return tmp; + } + else + { + return ret; + } +} + +/* ---------------------------------------------------------------------- + * Memory functions + */ + +void ec_point_free(struct ec_point *point) +{ + if (point == NULL) return; + point->curve = 0; + if (point->x) freebn(point->x); + if (point->y) freebn(point->y); + if (point->z) freebn(point->z); + point->infinity = 0; + sfree(point); +} + +static struct ec_point *ec_point_new(const struct ec_curve *curve, + const Bignum x, const Bignum y, const Bignum z, + unsigned char infinity) +{ + struct ec_point *point = snewn(1, struct ec_point); + point->curve = curve; + point->x = x; + point->y = y; + point->z = z; + point->infinity = infinity ? 1 : 0; + return point; +} + +static struct ec_point *ec_point_copy(const struct ec_point *a) +{ + if (a == NULL) return NULL; + return ec_point_new(a->curve, + a->x ? copybn(a->x) : NULL, + a->y ? copybn(a->y) : NULL, + a->z ? copybn(a->z) : NULL, + a->infinity); +} + +static int ec_point_verify(const struct ec_point *a) +{ + if (a->infinity) { + return 1; + } else if (a->curve->type == EC_EDWARDS) { + /* Check y^2 - x^2 - 1 - d * x^2 * y^2 == 0 */ + Bignum y2, x2, tmp, tmp2, tmp3; + int ret; + + y2 = ecf_square(a->y, a->curve); + x2 = ecf_square(a->x, a->curve); + tmp = modmul(a->curve->e.d, x2, a->curve->p); + tmp2 = modmul(tmp, y2, a->curve->p); + freebn(tmp); + tmp = modsub(y2, x2, a->curve->p); + freebn(y2); + freebn(x2); + tmp3 = modsub(tmp, tmp2, a->curve->p); + freebn(tmp); + freebn(tmp2); + ret = !bignum_cmp(tmp3, One); + freebn(tmp3); + return ret; + } else if (a->curve->type == EC_WEIERSTRASS) { + /* Verify y^2 = x^3 + ax + b */ + int ret = 0; + + Bignum lhs = NULL, x3 = NULL, ax = NULL, x3ax = NULL, x3axm = NULL, x3axb = NULL, rhs = NULL; + + Bignum Three = bignum_from_long(3); + + lhs = modmul(a->y, a->y, a->curve->p); + + /* This uses montgomery multiplication to optimise */ + x3 = modpow(a->x, Three, a->curve->p); + freebn(Three); + ax = modmul(a->curve->w.a, a->x, a->curve->p); + x3ax = bigadd(x3, ax); + freebn(x3); x3 = NULL; + freebn(ax); ax = NULL; + x3axm = bigmod(x3ax, a->curve->p); + freebn(x3ax); x3ax = NULL; + x3axb = bigadd(x3axm, a->curve->w.b); + freebn(x3axm); x3axm = NULL; + rhs = bigmod(x3axb, a->curve->p); + freebn(x3axb); + + ret = bignum_cmp(lhs, rhs) ? 0 : 1; + freebn(lhs); + freebn(rhs); + + return ret; + } else { + return 0; + } +} + +/* ---------------------------------------------------------------------- + * Elliptic curve point maths + */ + +/* Returns 1 on success and 0 on memory error */ +static int ecp_normalise(struct ec_point *a) +{ + if (!a) { + /* No point */ + return 0; + } + + if (a->infinity) { + /* Point is at infinity - i.e. normalised */ + return 1; + } + + if (a->curve->type == EC_WEIERSTRASS) { + /* In Jacobian Coordinates the triple (X, Y, Z) represents + the affine point (X / Z^2, Y / Z^3) */ + + Bignum Z2, Z2inv, Z3, Z3inv, tx, ty; + + if (!a->x || !a->y) { + /* No point defined */ + return 0; + } else if (!a->z) { + /* Already normalised */ + return 1; + } + + Z2 = ecf_square(a->z, a->curve); + Z2inv = modinv(Z2, a->curve->p); + if (!Z2inv) { + freebn(Z2); + return 0; + } + tx = modmul(a->x, Z2inv, a->curve->p); + freebn(Z2inv); + + Z3 = modmul(Z2, a->z, a->curve->p); + freebn(Z2); + Z3inv = modinv(Z3, a->curve->p); + freebn(Z3); + if (!Z3inv) { + freebn(tx); + return 0; + } + ty = modmul(a->y, Z3inv, a->curve->p); + freebn(Z3inv); + + freebn(a->x); + a->x = tx; + freebn(a->y); + a->y = ty; + freebn(a->z); + a->z = NULL; + return 1; + } else if (a->curve->type == EC_MONTGOMERY) { + /* In Montgomery (X : Z) represents the x co-ord (X / Z, ?) */ + + Bignum tmp, tmp2; + + if (!a->x) { + /* No point defined */ + return 0; + } else if (!a->z) { + /* Already normalised */ + return 1; + } + + tmp = modinv(a->z, a->curve->p); + if (!tmp) { + return 0; + } + tmp2 = modmul(a->x, tmp, a->curve->p); + freebn(tmp); + + freebn(a->z); + a->z = NULL; + freebn(a->x); + a->x = tmp2; + return 1; + } else if (a->curve->type == EC_EDWARDS) { + /* Always normalised */ + return 1; + } else { + return 0; + } +} + +static struct ec_point *ecp_doublew(const struct ec_point *a, const int aminus3) +{ + Bignum S, M, outx, outy, outz; + + if (bignum_cmp(a->y, Zero) == 0) + { + /* Identity */ + return ec_point_new(a->curve, NULL, NULL, NULL, 1); + } + + /* S = 4*X*Y^2 */ + { + Bignum Y2, XY2, _2XY2; + + Y2 = ecf_square(a->y, a->curve); + XY2 = modmul(a->x, Y2, a->curve->p); + freebn(Y2); + + _2XY2 = ecf_double(XY2, a->curve); + freebn(XY2); + S = ecf_double(_2XY2, a->curve); + freebn(_2XY2); + } + + /* Faster calculation if a = -3 */ + if (aminus3) { + /* if a = -3, then M can also be calculated as M = 3*(X + Z^2)*(X - Z^2) */ + Bignum Z2, XpZ2, XmZ2, second; + + if (a->z == NULL) { + Z2 = copybn(One); + } else { + Z2 = ecf_square(a->z, a->curve); + } + + XpZ2 = ecf_add(a->x, Z2, a->curve); + XmZ2 = modsub(a->x, Z2, a->curve->p); + freebn(Z2); + + second = modmul(XpZ2, XmZ2, a->curve->p); + freebn(XpZ2); + freebn(XmZ2); + + M = ecf_treble(second, a->curve); + freebn(second); + } else { + /* M = 3*X^2 + a*Z^4 */ + Bignum _3X2, X2, aZ4; + + if (a->z == NULL) { + aZ4 = copybn(a->curve->w.a); + } else { + Bignum Z2, Z4; + + Z2 = ecf_square(a->z, a->curve); + Z4 = ecf_square(Z2, a->curve); + freebn(Z2); + aZ4 = modmul(a->curve->w.a, Z4, a->curve->p); + freebn(Z4); + } + + X2 = modmul(a->x, a->x, a->curve->p); + _3X2 = ecf_treble(X2, a->curve); + freebn(X2); + M = ecf_add(_3X2, aZ4, a->curve); + freebn(_3X2); + freebn(aZ4); + } + + /* X' = M^2 - 2*S */ + { + Bignum M2, _2S; + + M2 = ecf_square(M, a->curve); + _2S = ecf_double(S, a->curve); + outx = modsub(M2, _2S, a->curve->p); + freebn(M2); + freebn(_2S); + } + + /* Y' = M*(S - X') - 8*Y^4 */ + { + Bignum SX, MSX, Eight, Y2, Y4, _8Y4; + + SX = modsub(S, outx, a->curve->p); + freebn(S); + MSX = modmul(M, SX, a->curve->p); + freebn(SX); + freebn(M); + Y2 = ecf_square(a->y, a->curve); + Y4 = ecf_square(Y2, a->curve); + freebn(Y2); + Eight = bignum_from_long(8); + _8Y4 = modmul(Eight, Y4, a->curve->p); + freebn(Eight); + freebn(Y4); + outy = modsub(MSX, _8Y4, a->curve->p); + freebn(MSX); + freebn(_8Y4); + } + + /* Z' = 2*Y*Z */ + { + Bignum YZ; + + if (a->z == NULL) { + YZ = copybn(a->y); + } else { + YZ = modmul(a->y, a->z, a->curve->p); + } + + outz = ecf_double(YZ, a->curve); + freebn(YZ); + } + + return ec_point_new(a->curve, outx, outy, outz, 0); +} + +static struct ec_point *ecp_doublem(const struct ec_point *a) +{ + Bignum z, outx, outz, xpz, xmz; + + z = a->z; + if (!z) { + z = One; + } + + /* 4xz = (x + z)^2 - (x - z)^2 */ + { + Bignum tmp; + + tmp = ecf_add(a->x, z, a->curve); + xpz = ecf_square(tmp, a->curve); + freebn(tmp); + + tmp = modsub(a->x, z, a->curve->p); + xmz = ecf_square(tmp, a->curve); + freebn(tmp); + } + + /* outx = (x + z)^2 * (x - z)^2 */ + outx = modmul(xpz, xmz, a->curve->p); + + /* outz = 4xz * ((x - z)^2 + ((A + 2) / 4)*4xz) */ + { + Bignum _4xz, tmp, tmp2, tmp3; + + tmp = bignum_from_long(2); + tmp2 = ecf_add(a->curve->m.a, tmp, a->curve); + freebn(tmp); + + _4xz = modsub(xpz, xmz, a->curve->p); + freebn(xpz); + tmp = modmul(tmp2, _4xz, a->curve->p); + freebn(tmp2); + + tmp2 = bignum_from_long(4); + tmp3 = modinv(tmp2, a->curve->p); + freebn(tmp2); + if (!tmp3) { + freebn(tmp); + freebn(_4xz); + freebn(outx); + freebn(xmz); + return NULL; + } + tmp2 = modmul(tmp, tmp3, a->curve->p); + freebn(tmp); + freebn(tmp3); + + tmp = ecf_add(xmz, tmp2, a->curve); + freebn(xmz); + freebn(tmp2); + outz = modmul(_4xz, tmp, a->curve->p); + freebn(_4xz); + freebn(tmp); + } + + return ec_point_new(a->curve, outx, NULL, outz, 0); +} + +/* Forward declaration for Edwards curve doubling */ +static struct ec_point *ecp_add(const struct ec_point *a, + const struct ec_point *b, + const int aminus3); + +static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3) +{ + if (a->infinity) + { + /* Identity */ + return ec_point_new(a->curve, NULL, NULL, NULL, 1); + } + + if (a->curve->type == EC_EDWARDS) + { + return ecp_add(a, a, aminus3); + } + else if (a->curve->type == EC_WEIERSTRASS) + { + return ecp_doublew(a, aminus3); + } + else + { + return ecp_doublem(a); + } +} + +static struct ec_point *ecp_addw(const struct ec_point *a, + const struct ec_point *b, + const int aminus3) +{ + Bignum U1, U2, S1, S2, outx, outy, outz; + + /* U1 = X1*Z2^2 */ + /* S1 = Y1*Z2^3 */ + if (b->z) { + Bignum Z2, Z3; + + Z2 = ecf_square(b->z, a->curve); + U1 = modmul(a->x, Z2, a->curve->p); + Z3 = modmul(Z2, b->z, a->curve->p); + freebn(Z2); + S1 = modmul(a->y, Z3, a->curve->p); + freebn(Z3); + } else { + U1 = copybn(a->x); + S1 = copybn(a->y); + } + + /* U2 = X2*Z1^2 */ + /* S2 = Y2*Z1^3 */ + if (a->z) { + Bignum Z2, Z3; + + Z2 = ecf_square(a->z, b->curve); + U2 = modmul(b->x, Z2, b->curve->p); + Z3 = modmul(Z2, a->z, b->curve->p); + freebn(Z2); + S2 = modmul(b->y, Z3, b->curve->p); + freebn(Z3); + } else { + U2 = copybn(b->x); + S2 = copybn(b->y); + } + + /* Check if multiplying by self */ + if (bignum_cmp(U1, U2) == 0) + { + freebn(U1); + freebn(U2); + if (bignum_cmp(S1, S2) == 0) + { + freebn(S1); + freebn(S2); + return ecp_double(a, aminus3); + } + else + { + freebn(S1); + freebn(S2); + /* Infinity */ + return ec_point_new(a->curve, NULL, NULL, NULL, 1); + } + } + + { + Bignum H, R, UH2, H3; + + /* H = U2 - U1 */ + H = modsub(U2, U1, a->curve->p); + freebn(U2); + + /* R = S2 - S1 */ + R = modsub(S2, S1, a->curve->p); + freebn(S2); + + /* X3 = R^2 - H^3 - 2*U1*H^2 */ + { + Bignum R2, H2, _2UH2, first; + + H2 = ecf_square(H, a->curve); + UH2 = modmul(U1, H2, a->curve->p); + freebn(U1); + H3 = modmul(H2, H, a->curve->p); + freebn(H2); + R2 = ecf_square(R, a->curve); + _2UH2 = ecf_double(UH2, a->curve); + first = modsub(R2, H3, a->curve->p); + freebn(R2); + outx = modsub(first, _2UH2, a->curve->p); + freebn(first); + freebn(_2UH2); + } + + /* Y3 = R*(U1*H^2 - X3) - S1*H^3 */ + { + Bignum RUH2mX, UH2mX, SH3; + + UH2mX = modsub(UH2, outx, a->curve->p); + freebn(UH2); + RUH2mX = modmul(R, UH2mX, a->curve->p); + freebn(UH2mX); + freebn(R); + SH3 = modmul(S1, H3, a->curve->p); + freebn(S1); + freebn(H3); + + outy = modsub(RUH2mX, SH3, a->curve->p); + freebn(RUH2mX); + freebn(SH3); + } + + /* Z3 = H*Z1*Z2 */ + if (a->z && b->z) { + Bignum ZZ; + + ZZ = modmul(a->z, b->z, a->curve->p); + outz = modmul(H, ZZ, a->curve->p); + freebn(H); + freebn(ZZ); + } else if (a->z) { + outz = modmul(H, a->z, a->curve->p); + freebn(H); + } else if (b->z) { + outz = modmul(H, b->z, b->curve->p); + freebn(H); + } else { + outz = H; + } + } + + return ec_point_new(a->curve, outx, outy, outz, 0); +} + +static struct ec_point *ecp_addm(const struct ec_point *a, + const struct ec_point *b, + const struct ec_point *base) +{ + Bignum outx, outz, az, bz; + + az = a->z; + if (!az) { + az = One; + } + bz = b->z; + if (!bz) { + bz = One; + } + + /* a-b is maintained at 1 due to Montgomery ladder implementation */ + /* Xa+b = Za-b * ((Xa - Za)*(Xb + Zb) + (Xa + Za)*(Xb - Zb))^2 */ + /* Za+b = Xa-b * ((Xa - Za)*(Xb + Zb) - (Xa + Za)*(Xb - Zb))^2 */ + { + Bignum tmp, tmp2, tmp3, tmp4; + + /* (Xa + Za) * (Xb - Zb) */ + tmp = ecf_add(a->x, az, a->curve); + tmp2 = modsub(b->x, bz, a->curve->p); + tmp3 = modmul(tmp, tmp2, a->curve->p); + freebn(tmp); + freebn(tmp2); + + /* (Xa - Za) * (Xb + Zb) */ + tmp = modsub(a->x, az, a->curve->p); + tmp2 = ecf_add(b->x, bz, a->curve); + tmp4 = modmul(tmp, tmp2, a->curve->p); + freebn(tmp); + freebn(tmp2); + + tmp = ecf_add(tmp3, tmp4, a->curve); + outx = ecf_square(tmp, a->curve); + freebn(tmp); + + tmp = modsub(tmp3, tmp4, a->curve->p); + freebn(tmp3); + freebn(tmp4); + tmp2 = ecf_square(tmp, a->curve); + freebn(tmp); + outz = modmul(base->x, tmp2, a->curve->p); + freebn(tmp2); + } + + return ec_point_new(a->curve, outx, NULL, outz, 0); +} + +static struct ec_point *ecp_adde(const struct ec_point *a, + const struct ec_point *b) +{ + Bignum outx, outy, dmul; + + /* outx = (a->x * b->y + b->x * a->y) / + * (1 + a->curve->e.d * a->x * b->x * a->y * b->y) */ + { + Bignum tmp, tmp2, tmp3, tmp4; + + tmp = modmul(a->x, b->y, a->curve->p); + tmp2 = modmul(b->x, a->y, a->curve->p); + tmp3 = ecf_add(tmp, tmp2, a->curve); + + tmp4 = modmul(tmp, tmp2, a->curve->p); + freebn(tmp); + freebn(tmp2); + dmul = modmul(a->curve->e.d, tmp4, a->curve->p); + freebn(tmp4); + + tmp = ecf_add(One, dmul, a->curve); + tmp2 = modinv(tmp, a->curve->p); + freebn(tmp); + if (!tmp2) + { + freebn(tmp3); + freebn(dmul); + return NULL; + } + + outx = modmul(tmp3, tmp2, a->curve->p); + freebn(tmp3); + freebn(tmp2); + } + + /* outy = (a->y * b->y + a->x * b->x) / + * (1 - a->curve->e.d * a->x * b->x * a->y * b->y) */ + { + Bignum tmp, tmp2, tmp3, tmp4; + + tmp = modsub(One, dmul, a->curve->p); + freebn(dmul); + + tmp2 = modinv(tmp, a->curve->p); + freebn(tmp); + if (!tmp2) + { + freebn(outx); + return NULL; + } + + tmp = modmul(a->y, b->y, a->curve->p); + tmp3 = modmul(a->x, b->x, a->curve->p); + tmp4 = ecf_add(tmp, tmp3, a->curve); + freebn(tmp); + freebn(tmp3); + + outy = modmul(tmp4, tmp2, a->curve->p); + freebn(tmp4); + freebn(tmp2); + } + + return ec_point_new(a->curve, outx, outy, NULL, 0); +} + +static struct ec_point *ecp_add(const struct ec_point *a, + const struct ec_point *b, + const int aminus3) +{ + if (a->curve != b->curve) { + return NULL; + } + + /* Check if multiplying by infinity */ + if (a->infinity) return ec_point_copy(b); + if (b->infinity) return ec_point_copy(a); + + if (a->curve->type == EC_EDWARDS) + { + return ecp_adde(a, b); + } + + if (a->curve->type == EC_WEIERSTRASS) + { + return ecp_addw(a, b, aminus3); + } + + return NULL; +} + +static struct ec_point *ecp_mul_(const struct ec_point *a, const Bignum b, int aminus3) +{ + struct ec_point *A, *ret; + int bits, i; + + A = ec_point_copy(a); + ret = ec_point_new(a->curve, NULL, NULL, NULL, 1); + + bits = bignum_bitcount(b); + for (i = 0; i < bits; ++i) + { + if (bignum_bit(b, i)) + { + struct ec_point *tmp = ecp_add(ret, A, aminus3); + ec_point_free(ret); + ret = tmp; + } + if (i+1 != bits) + { + struct ec_point *tmp = ecp_double(A, aminus3); + ec_point_free(A); + A = tmp; + } + } + + ec_point_free(A); + return ret; +} + +static struct ec_point *ecp_mulw(const struct ec_point *a, const Bignum b) +{ + struct ec_point *ret = ecp_mul_(a, b, ec_aminus3(a->curve)); + + if (!ecp_normalise(ret)) { + ec_point_free(ret); + return NULL; + } + + return ret; +} + +static struct ec_point *ecp_mule(const struct ec_point *a, const Bignum b) +{ + int i; + struct ec_point *ret; + + ret = ec_point_new(a->curve, NULL, NULL, NULL, 1); + + for (i = bignum_bitcount(b); i >= 0 && ret; --i) + { + { + struct ec_point *tmp = ecp_double(ret, 0); + ec_point_free(ret); + ret = tmp; + } + if (ret && bignum_bit(b, i)) + { + struct ec_point *tmp = ecp_add(ret, a, 0); + ec_point_free(ret); + ret = tmp; + } + } + + return ret; +} + +static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n) +{ + struct ec_point *P1, *P2; + int bits, i; + + /* P1 <- P and P2 <- [2]P */ + P2 = ecp_double(p, 0); + P1 = ec_point_copy(p); + + /* for i = bits − 2 down to 0 */ + bits = bignum_bitcount(n); + for (i = bits - 2; i >= 0; --i) + { + if (!bignum_bit(n, i)) + { + /* P2 <- P1 + P2 */ + struct ec_point *tmp = ecp_addm(P1, P2, p); + ec_point_free(P2); + P2 = tmp; + + /* P1 <- [2]P1 */ + tmp = ecp_double(P1, 0); + ec_point_free(P1); + P1 = tmp; + } + else + { + /* P1 <- P1 + P2 */ + struct ec_point *tmp = ecp_addm(P1, P2, p); + ec_point_free(P1); + P1 = tmp; + + /* P2 <- [2]P2 */ + tmp = ecp_double(P2, 0); + ec_point_free(P2); + P2 = tmp; + } + } + + ec_point_free(P2); + + if (!ecp_normalise(P1)) { + ec_point_free(P1); + return NULL; + } + + return P1; +} + +/* Not static because it is used by sshecdsag.c to generate a new key */ +struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b) +{ + if (a->curve->type == EC_WEIERSTRASS) { + return ecp_mulw(a, b); + } else if (a->curve->type == EC_EDWARDS) { + return ecp_mule(a, b); + } else { + return ecp_mulm(a, b); + } +} + +static struct ec_point *ecp_summul(const Bignum a, const Bignum b, + const struct ec_point *point) +{ + struct ec_point *aG, *bP, *ret; + int aminus3; + + if (point->curve->type != EC_WEIERSTRASS) { + return NULL; + } + + aminus3 = ec_aminus3(point->curve); + + aG = ecp_mul_(&point->curve->w.G, a, aminus3); + if (!aG) return NULL; + bP = ecp_mul_(point, b, aminus3); + if (!bP) { + ec_point_free(aG); + return NULL; + } + + ret = ecp_add(aG, bP, aminus3); + + ec_point_free(aG); + ec_point_free(bP); + + if (!ecp_normalise(ret)) { + ec_point_free(ret); + return NULL; + } + + return ret; +} +static Bignum *ecp_edx(const struct ec_curve *curve, const Bignum y) +{ + /* Get the x value on the given Edwards curve for a given y */ + Bignum x, xx; + + /* xx = (y^2 - 1) / (d * y^2 + 1) */ + { + Bignum tmp, tmp2, tmp3; + + tmp = ecf_square(y, curve); + tmp2 = modmul(curve->e.d, tmp, curve->p); + tmp3 = ecf_add(tmp2, One, curve); + freebn(tmp2); + tmp2 = modinv(tmp3, curve->p); + freebn(tmp3); + if (!tmp2) { + freebn(tmp); + return NULL; + } + + tmp3 = modsub(tmp, One, curve->p); + freebn(tmp); + xx = modmul(tmp3, tmp2, curve->p); + freebn(tmp3); + freebn(tmp2); + } + + /* x = xx^((p + 3) / 8) */ + { + Bignum tmp, tmp2; + + tmp = bignum_add_long(curve->p, 3); + tmp2 = bignum_rshift(tmp, 3); + freebn(tmp); + x = modpow(xx, tmp2, curve->p); + freebn(tmp2); + } + + /* if x^2 - xx != 0 then x = x*(2^((p - 1) / 4)) */ + { + Bignum tmp, tmp2; + + tmp = ecf_square(x, curve); + tmp2 = modsub(tmp, xx, curve->p); + freebn(tmp); + freebn(xx); + if (bignum_cmp(tmp2, Zero)) { + Bignum tmp3; + + freebn(tmp2); + + tmp = modsub(curve->p, One, curve->p); + tmp2 = bignum_rshift(tmp, 2); + freebn(tmp); + tmp = bignum_from_long(2); + tmp3 = modpow(tmp, tmp2, curve->p); + freebn(tmp); + freebn(tmp2); + + tmp = modmul(x, tmp3, curve->p); + freebn(x); + freebn(tmp3); + x = tmp; + } else { + freebn(tmp2); + } + } + + /* if x % 2 != 0 then x = p - x */ + if (bignum_bit(x, 0)) { + Bignum tmp = modsub(curve->p, x, curve->p); + freebn(x); + x = tmp; + } + + return x; +} + +/* ---------------------------------------------------------------------- + * Public point from private + */ + +struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve) +{ + if (curve->type == EC_WEIERSTRASS) { + return ecp_mul(&curve->w.G, privateKey); + } else if (curve->type == EC_EDWARDS) { + /* hash = H(sk) (where hash creates 2 * fieldBits) + * b = fieldBits + * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2 + * publicKey = aB */ + struct ec_point *ret; + unsigned char hash[512/8]; + Bignum a; + int i, keylen; + SHA512_State s; + putty_SHA512_Init(&s); + + keylen = curve->fieldBits / 8; + for (i = 0; i < keylen; ++i) { + unsigned char b = bignum_byte(privateKey, i); + putty_SHA512_Bytes(&s, &b, 1); + } + putty_SHA512_Final(&s, hash); + + /* The second part is simply turning the hash into a Bignum, + * however the 2^(b-2) bit *must* be set, and the bottom 3 + * bits *must* not be */ + hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */ + hash[31] &= 0x7f; /* Unset above (b-2) */ + hash[31] |= 0x40; /* Set 2^(b-2) */ + /* Chop off the top part and convert to int */ + a = bignum_from_bytes_le(hash, 32); + + ret = ecp_mul(&curve->e.B, a); + freebn(a); + return ret; + } else { + return NULL; + } +} + +/* ---------------------------------------------------------------------- + * Basic sign and verify routines + */ + +static int _ecdsa_verify(const struct ec_point *publicKey, + const unsigned char *data, const int dataLen, + const Bignum r, const Bignum s) +{ + int z_bits, n_bits; + Bignum z; + int valid = 0; + + if (publicKey->curve->type != EC_WEIERSTRASS) { + return 0; + } + + /* Sanity checks */ + if (bignum_cmp(r, Zero) == 0 || bignum_cmp(r, publicKey->curve->w.n) >= 0 + || bignum_cmp(s, Zero) == 0 || bignum_cmp(s, publicKey->curve->w.n) >= 0) + { + return 0; + } + + /* z = left most bitlen(curve->n) of data */ + z = bignum_from_bytes(data, dataLen); + n_bits = bignum_bitcount(publicKey->curve->w.n); + z_bits = bignum_bitcount(z); + if (z_bits > n_bits) + { + Bignum tmp = bignum_rshift(z, z_bits - n_bits); + freebn(z); + z = tmp; + } + + /* Ensure z in range of n */ + { + Bignum tmp = bigmod(z, publicKey->curve->w.n); + freebn(z); + z = tmp; + } + + /* Calculate signature */ + { + Bignum w, x, u1, u2; + struct ec_point *tmp; + + w = modinv(s, publicKey->curve->w.n); + if (!w) { + freebn(z); + return 0; + } + u1 = modmul(z, w, publicKey->curve->w.n); + u2 = modmul(r, w, publicKey->curve->w.n); + freebn(w); + + tmp = ecp_summul(u1, u2, publicKey); + freebn(u1); + freebn(u2); + if (!tmp) { + freebn(z); + return 0; + } + + x = bigmod(tmp->x, publicKey->curve->w.n); + ec_point_free(tmp); + + valid = (bignum_cmp(r, x) == 0) ? 1 : 0; + freebn(x); + } + + freebn(z); + + return valid; +} + +static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve, + const unsigned char *data, const int dataLen, + Bignum *r, Bignum *s) +{ + unsigned char digest[20]; + int z_bits, n_bits; + Bignum z, k; + struct ec_point *kG; + + *r = NULL; + *s = NULL; + + if (curve->type != EC_WEIERSTRASS) { + return; + } + + /* z = left most bitlen(curve->n) of data */ + z = bignum_from_bytes(data, dataLen); + n_bits = bignum_bitcount(curve->w.n); + z_bits = bignum_bitcount(z); + if (z_bits > n_bits) + { + Bignum tmp; + tmp = bignum_rshift(z, z_bits - n_bits); + freebn(z); + z = tmp; + } + + /* Generate k between 1 and curve->n, using the same deterministic + * k generation system we use for conventional DSA. */ + putty_SHA_Simple(data, dataLen, digest); + k = dss_gen_k("ECDSA deterministic k generator", curve->w.n, privateKey, + digest, sizeof(digest)); + + kG = ecp_mul(&curve->w.G, k); + if (!kG) { + freebn(z); + freebn(k); + return; + } + + /* r = kG.x mod n */ + *r = bigmod(kG->x, curve->w.n); + ec_point_free(kG); + + /* s = (z + r * priv)/k mod n */ + { + Bignum rPriv, zMod, first, firstMod, kInv; + rPriv = modmul(*r, privateKey, curve->w.n); + zMod = bigmod(z, curve->w.n); + freebn(z); + first = bigadd(rPriv, zMod); + freebn(rPriv); + freebn(zMod); + firstMod = bigmod(first, curve->w.n); + freebn(first); + kInv = modinv(k, curve->w.n); + freebn(k); + if (!kInv) { + freebn(firstMod); + freebn(*r); + return; + } + *s = modmul(firstMod, kInv, curve->w.n); + freebn(firstMod); + freebn(kInv); + } +} + +/* ---------------------------------------------------------------------- + * Misc functions + */ + +static void getstring(const char **data, int *datalen, + const char **p, int *length) +{ + *p = NULL; + if (*datalen < 4) + return; + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; + *datalen -= 4; + *data += 4; + if (*datalen < *length) + return; + *p = *data; + *data += *length; + *datalen -= *length; +} + +static Bignum getmp(const char **data, int *datalen) +{ + const char *p; + int length; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + return NULL; /* negative mp */ + return bignum_from_bytes((unsigned char *)p, length); +} + +static Bignum getmp_le(const char **data, int *datalen) +{ + const char *p; + int length = 0; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + return bignum_from_bytes_le((const unsigned char *)p, length); +} + +static int decodepoint_ed(const char *p, int length, struct ec_point *point) +{ + /* Got some conversion to do, first read in the y co-ord */ + int negative; + + point->y = bignum_from_bytes_le((const unsigned char*)p, length); + if ((unsigned)bignum_bitcount(point->y) > point->curve->fieldBits) { + freebn(point->y); + point->y = NULL; + return 0; + } + /* Read x bit and then reset it */ + negative = bignum_bit(point->y, point->curve->fieldBits - 1); + bignum_set_bit(point->y, point->curve->fieldBits - 1, 0); + bn_restore_invariant(point->y); + + /* Get the x from the y */ + point->x = ecp_edx(point->curve, point->y); + if (!point->x) { + freebn(point->y); + point->y = NULL; + return 0; + } + if (negative) { + Bignum tmp = modsub(point->curve->p, point->x, point->curve->p); + freebn(point->x); + point->x = tmp; + } + + /* Verify the point is on the curve */ + if (!ec_point_verify(point)) { + freebn(point->x); + point->x = NULL; + freebn(point->y); + point->y = NULL; + return 0; + } + + return 1; +} + +static int decodepoint(const char *p, int length, struct ec_point *point) +{ + if (point->curve->type == EC_EDWARDS) { + return decodepoint_ed(p, length, point); + } + + if (length < 1 || p[0] != 0x04) /* Only support uncompressed point */ + return 0; + /* Skip compression flag */ + ++p; + --length; + /* The two values must be equal length */ + if (length % 2 != 0) { + point->x = NULL; + point->y = NULL; + point->z = NULL; + return 0; + } + length = length / 2; + point->x = bignum_from_bytes((const unsigned char *)p, length); + p += length; + point->y = bignum_from_bytes((const unsigned char *)p, length); + point->z = NULL; + + /* Verify the point is on the curve */ + if (!ec_point_verify(point)) { + freebn(point->x); + point->x = NULL; + freebn(point->y); + point->y = NULL; + return 0; + } + + return 1; +} + +static int getmppoint(const char **data, int *datalen, struct ec_point *point) +{ + const char *p; + int length = 0; + + getstring(data, datalen, &p, &length); + if (!p) return 0; + return decodepoint(p, length, point); +} + +/* ---------------------------------------------------------------------- + * Exposed ECDSA interface + */ + +struct ecsign_extra { + struct ec_curve *(*curve)(void); + const struct ssh_hash *hash; + + /* These fields are used by the OpenSSH PEM format importer/exporter */ + const unsigned char *oid; + int oidlen; +}; + +static void ecdsa_freekey(void *key) +{ + struct ec_key *ec = (struct ec_key *) key; + if (!ec) return; + + if (ec->publicKey.x) + freebn(ec->publicKey.x); + if (ec->publicKey.y) + freebn(ec->publicKey.y); + if (ec->publicKey.z) + freebn(ec->publicKey.z); + if (ec->privateKey) + freebn(ec->privateKey); + sfree(ec); +} + +static void *ecdsa_newkey(const struct ssh_signkey *self, + const char *data, int len) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)self->extra; + const char *p; + int slen; + struct ec_key *ec; + struct ec_curve *curve; + + getstring(&data, &len, &p, &slen); + + if (!p) { + return NULL; + } + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); + + /* Curve name is duplicated for Weierstrass form */ + if (curve->type == EC_WEIERSTRASS) { + getstring(&data, &len, &p, &slen); + if (!p) return NULL; + if (!match_ssh_id(slen, p, curve->name)) return NULL; + } + + ec = snew(struct ec_key); + + ec->signalg = self; + ec->publicKey.curve = curve; + ec->publicKey.infinity = 0; + ec->publicKey.x = NULL; + ec->publicKey.y = NULL; + ec->publicKey.z = NULL; + ec->privateKey = NULL; + if (!getmppoint(&data, &len, &ec->publicKey)) { + ecdsa_freekey(ec); + return NULL; + } + + if (!ec->publicKey.x || !ec->publicKey.y || + bignum_cmp(ec->publicKey.x, curve->p) >= 0 || + bignum_cmp(ec->publicKey.y, curve->p) >= 0) + { + ecdsa_freekey(ec); + ec = NULL; + } + + return ec; +} + +static char *ecdsa_fmtkey(void *key) +{ + struct ec_key *ec = (struct ec_key *) key; + char *p; + int len, i, pos, nibbles; + static const char hex[] = "0123456789abcdef"; + if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve) + return NULL; + + len = 4 + 2 + 1; /* 2 x "0x", punctuation, \0 */ + if (ec->publicKey.curve->name) + len += strlen(ec->publicKey.curve->name); /* Curve name */ + len += 4 * (bignum_bitcount(ec->publicKey.x) + 15) / 16; + len += 4 * (bignum_bitcount(ec->publicKey.y) + 15) / 16; + p = snewn(len, char); + + pos = 0; + if (ec->publicKey.curve->name) + pos += sprintf(p + pos, "%s,", ec->publicKey.curve->name); + pos += sprintf(p + pos, "0x"); + nibbles = (3 + bignum_bitcount(ec->publicKey.x)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) { + p[pos++] = + hex[(bignum_byte(ec->publicKey.x, i / 2) >> (4 * (i % 2))) & 0xF]; + } + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(ec->publicKey.y)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) { + p[pos++] = + hex[(bignum_byte(ec->publicKey.y, i / 2) >> (4 * (i % 2))) & 0xF]; + } + p[pos] = '\0'; + return p; +} + +static unsigned char *ecdsa_public_blob(void *key, int *len) +{ + struct ec_key *ec = (struct ec_key *) key; + int pointlen, bloblen, fullnamelen, namelen; + int i; + unsigned char *blob, *p; + + fullnamelen = strlen(ec->signalg->name); + + if (ec->publicKey.curve->type == EC_EDWARDS) { + /* Edwards compressed form "ssh-ed25519" point y[:-1] + x[0:1] */ + + pointlen = ec->publicKey.curve->fieldBits / 8; + + /* Can't handle this in our loop */ + if (pointlen < 2) return NULL; + + bloblen = 4 + fullnamelen + 4 + pointlen; + blob = snewn(bloblen, unsigned char); + + p = blob; + PUT_32BIT(p, fullnamelen); + p += 4; + memcpy(p, ec->signalg->name, fullnamelen); + p += fullnamelen; + PUT_32BIT(p, pointlen); + p += 4; + + /* Unset last bit of y and set first bit of x in its place */ + for (i = 0; i < pointlen - 1; ++i) { + *p++ = bignum_byte(ec->publicKey.y, i); + } + /* Unset last bit of y and set first bit of x in its place */ + *p = bignum_byte(ec->publicKey.y, i) & 0x7f; + *p++ |= bignum_bit(ec->publicKey.x, 0) << 7; + } else if (ec->publicKey.curve->type == EC_WEIERSTRASS) { + assert(ec->publicKey.curve->name); + namelen = strlen(ec->publicKey.curve->name); + + pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; + + /* + * string "ecdsa-sha2-", string "", 0x04 point x, y. + */ + bloblen = 4 + fullnamelen + 4 + namelen + 4 + 1 + (pointlen * 2); + blob = snewn(bloblen, unsigned char); + + p = blob; + PUT_32BIT(p, fullnamelen); + p += 4; + memcpy(p, ec->signalg->name, fullnamelen); + p += fullnamelen; + PUT_32BIT(p, namelen); + p += 4; + memcpy(p, ec->publicKey.curve->name, namelen); + p += namelen; + PUT_32BIT(p, (2 * pointlen) + 1); + p += 4; + *p++ = 0x04; + for (i = pointlen; i--;) { + *p++ = bignum_byte(ec->publicKey.x, i); + } + for (i = pointlen; i--;) { + *p++ = bignum_byte(ec->publicKey.y, i); + } + } else { + return NULL; + } + + assert(p == blob + bloblen); + *len = bloblen; + + return blob; +} + +static unsigned char *ecdsa_private_blob(void *key, int *len) +{ + struct ec_key *ec = (struct ec_key *) key; + int keylen, bloblen; + int i; + unsigned char *blob, *p; + + if (!ec->privateKey) return NULL; + + if (ec->publicKey.curve->type == EC_EDWARDS) { + /* Unsigned */ + keylen = (bignum_bitcount(ec->privateKey) + 7) / 8; + } else { + /* Signed */ + keylen = (bignum_bitcount(ec->privateKey) + 8) / 8; + } + + /* + * mpint privateKey. Total 4 + keylen. + */ + bloblen = 4 + keylen; + blob = snewn(bloblen, unsigned char); + + p = blob; + PUT_32BIT(p, keylen); + p += 4; + if (ec->publicKey.curve->type == EC_EDWARDS) { + /* Little endian */ + for (i = 0; i < keylen; ++i) + *p++ = bignum_byte(ec->privateKey, i); + } else { + for (i = keylen; i--;) + *p++ = bignum_byte(ec->privateKey, i); + } + + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static void *ecdsa_createkey(const struct ssh_signkey *self, + const unsigned char *pub_blob, int pub_len, + const unsigned char *priv_blob, int priv_len) +{ + struct ec_key *ec; + struct ec_point *publicKey; + const char *pb = (const char *) priv_blob; + + ec = (struct ec_key*)ecdsa_newkey(self, (const char *) pub_blob, pub_len); + if (!ec) { + return NULL; + } + + if (ec->publicKey.curve->type != EC_WEIERSTRASS + && ec->publicKey.curve->type != EC_EDWARDS) { + ecdsa_freekey(ec); + return NULL; + } + + if (ec->publicKey.curve->type == EC_EDWARDS) { + ec->privateKey = getmp_le(&pb, &priv_len); + } else { + ec->privateKey = getmp(&pb, &priv_len); + } + if (!ec->privateKey) { + ecdsa_freekey(ec); + return NULL; + } + + /* Check that private key generates public key */ + publicKey = ec_public(ec->privateKey, ec->publicKey.curve); + + if (!publicKey || + bignum_cmp(publicKey->x, ec->publicKey.x) || + bignum_cmp(publicKey->y, ec->publicKey.y)) + { + ecdsa_freekey(ec); + ec = NULL; + } + ec_point_free(publicKey); + + return ec; +} + +static void *ed25519_openssh_createkey(const struct ssh_signkey *self, + const unsigned char **blob, int *len) +{ + struct ec_key *ec; + struct ec_point *publicKey; + const char *p, *q; + int plen = 0, qlen = 0; + + getstring((const char**)blob, len, &p, &plen); + if (!p) + { + return NULL; + } + + ec = snew(struct ec_key); + + ec->signalg = self; + ec->publicKey.curve = ec_ed25519(); + ec->publicKey.infinity = 0; + ec->privateKey = NULL; + ec->publicKey.x = NULL; + ec->publicKey.z = NULL; + ec->publicKey.y = NULL; + + if (!decodepoint_ed(p, plen, &ec->publicKey)) + { + ecdsa_freekey(ec); + return NULL; + } + + getstring((const char**)blob, len, &q, &qlen); + if (!q) + { + ecdsa_freekey(ec); + return NULL; + } + if (qlen != 64) + { + ecdsa_freekey(ec); + return NULL; + } + + ec->privateKey = bignum_from_bytes_le((const unsigned char *)q, 32); + + /* Check that private key generates public key */ + publicKey = ec_public(ec->privateKey, ec->publicKey.curve); + + if (!publicKey || + bignum_cmp(publicKey->x, ec->publicKey.x) || + bignum_cmp(publicKey->y, ec->publicKey.y)) + { + ecdsa_freekey(ec); + ec = NULL; + } + ec_point_free(publicKey); + + /* The OpenSSH format for ed25519 private keys also for some + * reason encodes an extra copy of the public key in the second + * half of the secret-key string. Check that that's present and + * correct as well, otherwise the key we think we've imported + * won't behave identically to the way OpenSSH would have treated + * it. */ + if (plen != 32 || 0 != memcmp(q + 32, p, 32)) { + ecdsa_freekey(ec); + return NULL; + } + + return ec; +} + +static int ed25519_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct ec_key *ec = (struct ec_key *) key; + + int pointlen; + int keylen; + int bloblen; + int i; + + if (ec->publicKey.curve->type != EC_EDWARDS) { + return 0; + } + + pointlen = (bignum_bitcount(ec->publicKey.y) + 7) / 8; + keylen = (bignum_bitcount(ec->privateKey) + 7) / 8; + bloblen = 4 + pointlen + 4 + keylen + pointlen; + + if (bloblen > len) + return bloblen; + + /* Encode the public point */ + PUT_32BIT(blob, pointlen); + blob += 4; + + for (i = 0; i < pointlen - 1; ++i) { + *blob++ = bignum_byte(ec->publicKey.y, i); + } + /* Unset last bit of y and set first bit of x in its place */ + *blob = bignum_byte(ec->publicKey.y, i) & 0x7f; + *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7; + + PUT_32BIT(blob, keylen + pointlen); + blob += 4; + for (i = 0; i < keylen; ++i) { + *blob++ = bignum_byte(ec->privateKey, i); + } + /* Now encode an extra copy of the public point as the second half + * of the private key string, as the OpenSSH format for some + * reason requires */ + for (i = 0; i < pointlen - 1; ++i) { + *blob++ = bignum_byte(ec->publicKey.y, i); + } + /* Unset last bit of y and set first bit of x in its place */ + *blob = bignum_byte(ec->publicKey.y, i) & 0x7f; + *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7; + + return bloblen; +} + +static void *ecdsa_openssh_createkey(const struct ssh_signkey *self, + const unsigned char **blob, int *len) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)self->extra; + const char **b = (const char **) blob; + const char *p; + int slen; + struct ec_key *ec; + struct ec_curve *curve; + struct ec_point *publicKey; + + getstring(b, len, &p, &slen); + + if (!p) { + return NULL; + } + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS); + + ec = snew(struct ec_key); + + ec->signalg = self; + ec->publicKey.curve = curve; + ec->publicKey.infinity = 0; + ec->publicKey.x = NULL; + ec->publicKey.y = NULL; + ec->publicKey.z = NULL; + if (!getmppoint(b, len, &ec->publicKey)) { + ecdsa_freekey(ec); + return NULL; + } + ec->privateKey = NULL; + + if (!ec->publicKey.x || !ec->publicKey.y || + bignum_cmp(ec->publicKey.x, curve->p) >= 0 || + bignum_cmp(ec->publicKey.y, curve->p) >= 0) + { + ecdsa_freekey(ec); + return NULL; + } + + ec->privateKey = getmp(b, len); + if (ec->privateKey == NULL) + { + ecdsa_freekey(ec); + return NULL; + } + + /* Now check that the private key makes the public key */ + publicKey = ec_public(ec->privateKey, ec->publicKey.curve); + if (!publicKey) + { + ecdsa_freekey(ec); + return NULL; + } + + if (bignum_cmp(ec->publicKey.x, publicKey->x) || + bignum_cmp(ec->publicKey.y, publicKey->y)) + { + /* Private key doesn't make the public key on the given curve */ + ecdsa_freekey(ec); + ec_point_free(publicKey); + return NULL; + } + + ec_point_free(publicKey); + + return ec; +} + +static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct ec_key *ec = (struct ec_key *) key; + + int pointlen; + int namelen; + int bloblen; + int i; + + if (ec->publicKey.curve->type != EC_WEIERSTRASS) { + return 0; + } + + pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; + namelen = strlen(ec->publicKey.curve->name); + bloblen = + 4 + namelen /* nistpXXX */ + + 4 + 1 + (pointlen * 2) /* 0x04 pX pY */ + + ssh2_bignum_length(ec->privateKey); + + if (bloblen > len) + return bloblen; + + bloblen = 0; + + PUT_32BIT(blob+bloblen, namelen); + bloblen += 4; + memcpy(blob+bloblen, ec->publicKey.curve->name, namelen); + bloblen += namelen; + + PUT_32BIT(blob+bloblen, 1 + (pointlen * 2)); + bloblen += 4; + blob[bloblen++] = 0x04; + for (i = pointlen; i--; ) + blob[bloblen++] = bignum_byte(ec->publicKey.x, i); + for (i = pointlen; i--; ) + blob[bloblen++] = bignum_byte(ec->publicKey.y, i); + + pointlen = (bignum_bitcount(ec->privateKey) + 8) / 8; + PUT_32BIT(blob+bloblen, pointlen); + bloblen += 4; + for (i = pointlen; i--; ) + blob[bloblen++] = bignum_byte(ec->privateKey, i); + + return bloblen; +} + +static int ecdsa_pubkey_bits(const struct ssh_signkey *self, + const void *blob, int len) +{ + struct ec_key *ec; + int ret; + + ec = (struct ec_key*)ecdsa_newkey(self, (const char *) blob, len); + if (!ec) + return -1; + ret = ec->publicKey.curve->fieldBits; + ecdsa_freekey(ec); + + return ret; +} + +static int ecdsa_verifysig(void *key, const char *sig, int siglen, + const char *data, int datalen) +{ + struct ec_key *ec = (struct ec_key *) key; + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ec->signalg->extra; + const char *p; + int slen; + int digestLen; + int ret; + + if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve) + return 0; + + /* Check the signature starts with the algorithm name */ + getstring(&sig, &siglen, &p, &slen); + if (!p) { + return 0; + } + if (!match_ssh_id(slen, p, ec->signalg->name)) { + return 0; + } + + getstring(&sig, &siglen, &p, &slen); + if (!p) return 0; + if (ec->publicKey.curve->type == EC_EDWARDS) { + struct ec_point *r; + Bignum s, h; + + /* Check that the signature is two times the length of a point */ + if (slen != (ec->publicKey.curve->fieldBits / 8) * 2) { + return 0; + } + + /* Check it's the 256 bit field so that SHA512 is the correct hash */ + if (ec->publicKey.curve->fieldBits != 256) { + return 0; + } + + /* Get the signature */ + r = ec_point_new(ec->publicKey.curve, NULL, NULL, NULL, 0); + if (!r) { + return 0; + } + if (!decodepoint(p, ec->publicKey.curve->fieldBits / 8, r)) { + ec_point_free(r); + return 0; + } + s = bignum_from_bytes_le((unsigned char*)p + (ec->publicKey.curve->fieldBits / 8), + ec->publicKey.curve->fieldBits / 8); + + /* Get the hash of the encoded value of R + encoded value of pk + message */ + { + int i, pointlen; + unsigned char b; + unsigned char digest[512 / 8]; + SHA512_State hs; + putty_SHA512_Init(&hs); + + /* Add encoded r (no need to encode it again, it was in the signature) */ + putty_SHA512_Bytes(&hs, p, ec->publicKey.curve->fieldBits / 8); + + /* Encode pk and add it */ + pointlen = ec->publicKey.curve->fieldBits / 8; + for (i = 0; i < pointlen - 1; ++i) { + b = bignum_byte(ec->publicKey.y, i); + putty_SHA512_Bytes(&hs, &b, 1); + } + /* Unset last bit of y and set first bit of x in its place */ + b = bignum_byte(ec->publicKey.y, i) & 0x7f; + b |= bignum_bit(ec->publicKey.x, 0) << 7; + putty_SHA512_Bytes(&hs, &b, 1); + + /* Add the message itself */ + putty_SHA512_Bytes(&hs, data, datalen); + + /* Get the hash */ + putty_SHA512_Final(&hs, digest); + + /* Convert to Bignum */ + h = bignum_from_bytes_le(digest, sizeof(digest)); + } + + /* Verify sB == r + h*publicKey */ + { + struct ec_point *lhs, *rhs, *tmp; + + /* lhs = sB */ + lhs = ecp_mul(&ec->publicKey.curve->e.B, s); + freebn(s); + if (!lhs) { + ec_point_free(r); + freebn(h); + return 0; + } + + /* rhs = r + h*publicKey */ + tmp = ecp_mul(&ec->publicKey, h); + freebn(h); + if (!tmp) { + ec_point_free(lhs); + ec_point_free(r); + return 0; + } + rhs = ecp_add(r, tmp, 0); + ec_point_free(r); + ec_point_free(tmp); + if (!rhs) { + ec_point_free(lhs); + return 0; + } + + /* Check the point is the same */ + ret = !bignum_cmp(lhs->x, rhs->x); + if (ret) { + ret = !bignum_cmp(lhs->y, rhs->y); + if (ret) { + ret = 1; + } + } + ec_point_free(lhs); + ec_point_free(rhs); + } + } else { + Bignum r, s; + unsigned char digest[512 / 8]; + void *hashctx; + + r = getmp(&p, &slen); + if (!r) return 0; + s = getmp(&p, &slen); + if (!s) { + freebn(r); + return 0; + } + + digestLen = extra->hash->hlen; + assert(digestLen <= sizeof(digest)); + hashctx = extra->hash->init(); + extra->hash->bytes(hashctx, data, datalen); + extra->hash->final(hashctx, digest); + + /* Verify the signature */ + ret = _ecdsa_verify(&ec->publicKey, digest, digestLen, r, s); + + freebn(r); + freebn(s); + } + + return ret; +} + +static unsigned char *ecdsa_sign(void *key, const char *data, int datalen, + int *siglen) +{ + struct ec_key *ec = (struct ec_key *) key; + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ec->signalg->extra; + unsigned char digest[512 / 8]; + int digestLen; + Bignum r = NULL, s = NULL; + unsigned char *buf, *p; + int rlen, slen, namelen; + int i; + + if (!ec->privateKey || !ec->publicKey.curve) { + return NULL; + } + + if (ec->publicKey.curve->type == EC_EDWARDS) { + struct ec_point *rp; + int pointlen = ec->publicKey.curve->fieldBits / 8; + + /* hash = H(sk) (where hash creates 2 * fieldBits) + * b = fieldBits + * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2 + * r = H(h[b/8:b/4] + m) + * R = rB + * S = (r + H(encodepoint(R) + encodepoint(pk) + m) * a) % l */ + { + unsigned char hash[512/8]; + unsigned char b; + Bignum a; + SHA512_State hs; + putty_SHA512_Init(&hs); + + for (i = 0; i < pointlen; ++i) { + unsigned char b = (unsigned char)bignum_byte(ec->privateKey, i); + putty_SHA512_Bytes(&hs, &b, 1); + } + + putty_SHA512_Final(&hs, hash); + + /* The second part is simply turning the hash into a + * Bignum, however the 2^(b-2) bit *must* be set, and the + * bottom 3 bits *must* not be */ + hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */ + hash[31] &= 0x7f; /* Unset above (b-2) */ + hash[31] |= 0x40; /* Set 2^(b-2) */ + /* Chop off the top part and convert to int */ + a = bignum_from_bytes_le(hash, 32); + + putty_SHA512_Init(&hs); + putty_SHA512_Bytes(&hs, + hash+(ec->publicKey.curve->fieldBits / 8), + (ec->publicKey.curve->fieldBits / 4) + - (ec->publicKey.curve->fieldBits / 8)); + putty_SHA512_Bytes(&hs, data, datalen); + putty_SHA512_Final(&hs, hash); + + r = bignum_from_bytes_le(hash, 512/8); + rp = ecp_mul(&ec->publicKey.curve->e.B, r); + if (!rp) { + freebn(r); + freebn(a); + return NULL; + } + + /* Now calculate s */ + putty_SHA512_Init(&hs); + /* Encode the point R */ + for (i = 0; i < pointlen - 1; ++i) { + b = bignum_byte(rp->y, i); + putty_SHA512_Bytes(&hs, &b, 1); + } + /* Unset last bit of y and set first bit of x in its place */ + b = bignum_byte(rp->y, i) & 0x7f; + b |= bignum_bit(rp->x, 0) << 7; + putty_SHA512_Bytes(&hs, &b, 1); + + /* Encode the point pk */ + for (i = 0; i < pointlen - 1; ++i) { + b = bignum_byte(ec->publicKey.y, i); + putty_SHA512_Bytes(&hs, &b, 1); + } + /* Unset last bit of y and set first bit of x in its place */ + b = bignum_byte(ec->publicKey.y, i) & 0x7f; + b |= bignum_bit(ec->publicKey.x, 0) << 7; + putty_SHA512_Bytes(&hs, &b, 1); + + /* Add the message */ + putty_SHA512_Bytes(&hs, data, datalen); + putty_SHA512_Final(&hs, hash); + + { + Bignum tmp, tmp2; + + tmp = bignum_from_bytes_le(hash, 512/8); + tmp2 = modmul(tmp, a, ec->publicKey.curve->e.l); + freebn(a); + freebn(tmp); + tmp = bigadd(r, tmp2); + freebn(r); + freebn(tmp2); + s = bigmod(tmp, ec->publicKey.curve->e.l); + freebn(tmp); + } + } + + /* Format the output */ + namelen = strlen(ec->signalg->name); + *siglen = 4+namelen+4+((ec->publicKey.curve->fieldBits / 8)*2); + buf = snewn(*siglen, unsigned char); + p = buf; + PUT_32BIT(p, namelen); + p += 4; + memcpy(p, ec->signalg->name, namelen); + p += namelen; + PUT_32BIT(p, ((ec->publicKey.curve->fieldBits / 8)*2)); + p += 4; + + /* Encode the point */ + pointlen = ec->publicKey.curve->fieldBits / 8; + for (i = 0; i < pointlen - 1; ++i) { + *p++ = bignum_byte(rp->y, i); + } + /* Unset last bit of y and set first bit of x in its place */ + *p = bignum_byte(rp->y, i) & 0x7f; + *p++ |= bignum_bit(rp->x, 0) << 7; + ec_point_free(rp); + + /* Encode the int */ + for (i = 0; i < pointlen; ++i) { + *p++ = bignum_byte(s, i); + } + freebn(s); + } else { + void *hashctx; + + digestLen = extra->hash->hlen; + assert(digestLen <= sizeof(digest)); + hashctx = extra->hash->init(); + extra->hash->bytes(hashctx, data, datalen); + extra->hash->final(hashctx, digest); + + /* Do the signature */ + _ecdsa_sign(ec->privateKey, ec->publicKey.curve, digest, digestLen, &r, &s); + if (!r || !s) { + if (r) freebn(r); + if (s) freebn(s); + return NULL; + } + + rlen = (bignum_bitcount(r) + 8) / 8; + slen = (bignum_bitcount(s) + 8) / 8; + + namelen = strlen(ec->signalg->name); + + /* Format the output */ + *siglen = 8+namelen+rlen+slen+8; + buf = snewn(*siglen, unsigned char); + p = buf; + PUT_32BIT(p, namelen); + p += 4; + memcpy(p, ec->signalg->name, namelen); + p += namelen; + PUT_32BIT(p, rlen + slen + 8); + p += 4; + PUT_32BIT(p, rlen); + p += 4; + for (i = rlen; i--;) + *p++ = bignum_byte(r, i); + PUT_32BIT(p, slen); + p += 4; + for (i = slen; i--;) + *p++ = bignum_byte(s, i); + + freebn(r); + freebn(s); + } + + return buf; +} + +const struct ecsign_extra sign_extra_ed25519 = { + ec_ed25519, NULL, + NULL, 0, +}; +const struct ssh_signkey ssh_ecdsa_ed25519 = { + ecdsa_newkey, + ecdsa_freekey, + ecdsa_fmtkey, + ecdsa_public_blob, + ecdsa_private_blob, + ecdsa_createkey, + ed25519_openssh_createkey, + ed25519_openssh_fmtkey, + 2 /* point, private exponent */, + ecdsa_pubkey_bits, + ecdsa_verifysig, + ecdsa_sign, + "ssh-ed25519", + "ssh-ed25519", + &sign_extra_ed25519, +}; + +/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */ +static const unsigned char nistp256_oid[] = { + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 +}; +const struct ecsign_extra sign_extra_nistp256 = { + ec_p256, &ssh_sha256, + nistp256_oid, lenof(nistp256_oid), +}; +const struct ssh_signkey ssh_ecdsa_nistp256 = { + ecdsa_newkey, + ecdsa_freekey, + ecdsa_fmtkey, + ecdsa_public_blob, + ecdsa_private_blob, + ecdsa_createkey, + ecdsa_openssh_createkey, + ecdsa_openssh_fmtkey, + 3 /* curve name, point, private exponent */, + ecdsa_pubkey_bits, + ecdsa_verifysig, + ecdsa_sign, + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp256", + &sign_extra_nistp256, +}; + +/* OID: 1.3.132.0.34 (secp384r1) */ +static const unsigned char nistp384_oid[] = { + 0x2b, 0x81, 0x04, 0x00, 0x22 +}; +const struct ecsign_extra sign_extra_nistp384 = { + ec_p384, &ssh_sha384, + nistp384_oid, lenof(nistp384_oid), +}; +const struct ssh_signkey ssh_ecdsa_nistp384 = { + ecdsa_newkey, + ecdsa_freekey, + ecdsa_fmtkey, + ecdsa_public_blob, + ecdsa_private_blob, + ecdsa_createkey, + ecdsa_openssh_createkey, + ecdsa_openssh_fmtkey, + 3 /* curve name, point, private exponent */, + ecdsa_pubkey_bits, + ecdsa_verifysig, + ecdsa_sign, + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp384", + &sign_extra_nistp384, +}; + +/* OID: 1.3.132.0.35 (secp521r1) */ +static const unsigned char nistp521_oid[] = { + 0x2b, 0x81, 0x04, 0x00, 0x23 +}; +const struct ecsign_extra sign_extra_nistp521 = { + ec_p521, &ssh_sha512, + nistp521_oid, lenof(nistp521_oid), +}; +const struct ssh_signkey ssh_ecdsa_nistp521 = { + ecdsa_newkey, + ecdsa_freekey, + ecdsa_fmtkey, + ecdsa_public_blob, + ecdsa_private_blob, + ecdsa_createkey, + ecdsa_openssh_createkey, + ecdsa_openssh_fmtkey, + 3 /* curve name, point, private exponent */, + ecdsa_pubkey_bits, + ecdsa_verifysig, + ecdsa_sign, + "ecdsa-sha2-nistp521", + "ecdsa-sha2-nistp521", + &sign_extra_nistp521, +}; + +/* ---------------------------------------------------------------------- + * Exposed ECDH interface + */ + +struct eckex_extra { + struct ec_curve *(*curve)(void); +}; + +static Bignum ecdh_calculate(const Bignum private, + const struct ec_point *public) +{ + struct ec_point *p; + Bignum ret; + p = ecp_mul(public, private); + if (!p) return NULL; + ret = p->x; + p->x = NULL; + + if (p->curve->type == EC_MONTGOMERY) { + /* + * Endianness-swap. The Curve25519 algorithm definition + * assumes you were doing your computation in arrays of 32 + * little-endian bytes, and now specifies that you take your + * final one of those and convert it into a bignum in + * _network_ byte order, i.e. big-endian. + * + * In particular, the spec says, you convert the _whole_ 32 + * bytes into a bignum. That is, on the rare occasions that + * p->x has come out with the most significant 8 bits zero, we + * have to imagine that being represented by a 32-byte string + * with the last byte being zero, so that has to be converted + * into an SSH-2 bignum with the _low_ byte zero, i.e. a + * multiple of 256. + */ + int i; + int bytes = (p->curve->fieldBits+7) / 8; + unsigned char *byteorder = snewn(bytes, unsigned char); + for (i = 0; i < bytes; ++i) { + byteorder[i] = bignum_byte(ret, i); + } + freebn(ret); + ret = bignum_from_bytes(byteorder, bytes); + smemclr(byteorder, bytes); + sfree(byteorder); + } + + ec_point_free(p); + return ret; +} + +const char *ssh_ecdhkex_curve_textname(const struct ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + struct ec_curve *curve = extra->curve(); + return curve->textname; +} + +void *ssh_ecdhkex_newkey(const struct ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + struct ec_curve *curve; + struct ec_key *key; + struct ec_point *publicKey; + + curve = extra->curve(); + + key = snew(struct ec_key); + + key->signalg = NULL; + key->publicKey.curve = curve; + + if (curve->type == EC_MONTGOMERY) { + unsigned char bytes[32] = {0}; + int i; + + for (i = 0; i < sizeof(bytes); ++i) + { + bytes[i] = (unsigned char)random_byte(); + } + bytes[0] &= 248; + bytes[31] &= 127; + bytes[31] |= 64; + key->privateKey = bignum_from_bytes(bytes, sizeof(bytes)); + smemclr(bytes, sizeof(bytes)); + if (!key->privateKey) { + sfree(key); + return NULL; + } + publicKey = ecp_mul(&key->publicKey.curve->m.G, key->privateKey); + if (!publicKey) { + freebn(key->privateKey); + sfree(key); + return NULL; + } + key->publicKey.x = publicKey->x; + key->publicKey.y = publicKey->y; + key->publicKey.z = NULL; + sfree(publicKey); + } else { + key->privateKey = bignum_random_in_range(One, key->publicKey.curve->w.n); + if (!key->privateKey) { + sfree(key); + return NULL; + } + publicKey = ecp_mul(&key->publicKey.curve->w.G, key->privateKey); + if (!publicKey) { + freebn(key->privateKey); + sfree(key); + return NULL; + } + key->publicKey.x = publicKey->x; + key->publicKey.y = publicKey->y; + key->publicKey.z = NULL; + sfree(publicKey); + } + return key; +} + +char *ssh_ecdhkex_getpublic(void *key, int *len) +{ + struct ec_key *ec = (struct ec_key*)key; + char *point, *p; + int i; + int pointlen; + + pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; + + if (ec->publicKey.curve->type == EC_WEIERSTRASS) { + *len = 1 + pointlen * 2; + } else { + *len = pointlen; + } + point = (char*)snewn(*len, char); + + p = point; + if (ec->publicKey.curve->type == EC_WEIERSTRASS) { + *p++ = 0x04; + for (i = pointlen; i--;) { + *p++ = bignum_byte(ec->publicKey.x, i); + } + for (i = pointlen; i--;) { + *p++ = bignum_byte(ec->publicKey.y, i); + } + } else { + for (i = 0; i < pointlen; ++i) { + *p++ = bignum_byte(ec->publicKey.x, i); + } + } + + return point; +} + +Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen) +{ + struct ec_key *ec = (struct ec_key*) key; + struct ec_point remote; + Bignum ret; + + if (ec->publicKey.curve->type == EC_WEIERSTRASS) { + remote.curve = ec->publicKey.curve; + remote.infinity = 0; + if (!decodepoint(remoteKey, remoteKeyLen, &remote)) { + return NULL; + } + } else { + /* Point length has to be the same length */ + if (remoteKeyLen != (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8) { + return NULL; + } + + remote.curve = ec->publicKey.curve; + remote.infinity = 0; + remote.x = bignum_from_bytes_le((unsigned char*)remoteKey, remoteKeyLen); + remote.y = NULL; + remote.z = NULL; + } + + ret = ecdh_calculate(ec->privateKey, &remote); + if (remote.x) freebn(remote.x); + if (remote.y) freebn(remote.y); + return ret; +} + +void ssh_ecdhkex_freekey(void *key) +{ + ecdsa_freekey(key); +} + +static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 }; +static const struct ssh_kex ssh_ec_kex_curve25519 = { + "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_curve25519, +}; + +const struct eckex_extra kex_extra_nistp256 = { ec_p256 }; +static const struct ssh_kex ssh_ec_kex_nistp256 = { + "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_nistp256, +}; + +const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; +static const struct ssh_kex ssh_ec_kex_nistp384 = { + "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, + &ssh_sha384, &kex_extra_nistp384, +}; + +const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; +static const struct ssh_kex ssh_ec_kex_nistp521 = { + "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, + &ssh_sha512, &kex_extra_nistp521, +}; + +static const struct ssh_kex *const ec_kex_list[] = { + &ssh_ec_kex_curve25519, + &ssh_ec_kex_nistp256, + &ssh_ec_kex_nistp384, + &ssh_ec_kex_nistp521, +}; + +const struct ssh_kexes ssh_ecdh_kex = { + sizeof(ec_kex_list) / sizeof(*ec_kex_list), + ec_kex_list +}; + +/* ---------------------------------------------------------------------- + * Helper functions for finding key algorithms and returning auxiliary + * data. + */ + +const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, + const struct ec_curve **curve) +{ + static const struct ssh_signkey *algs_with_oid[] = { + &ssh_ecdsa_nistp256, + &ssh_ecdsa_nistp384, + &ssh_ecdsa_nistp521, + }; + int i; + + for (i = 0; i < lenof(algs_with_oid); i++) { + const struct ssh_signkey *alg = algs_with_oid[i]; + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) { + *curve = extra->curve(); + return alg; + } + } + return NULL; +} + +const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, + int *oidlen) +{ + const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra; + *oidlen = extra->oidlen; + return extra->oid; +} + +const int ec_nist_curve_lengths[] = { 256, 384, 521 }; +const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths); + +const int ec_nist_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const struct ssh_signkey **alg) +{ + switch (bits) { + case 256: *alg = &ssh_ecdsa_nistp256; break; + case 384: *alg = &ssh_ecdsa_nistp384; break; + case 521: *alg = &ssh_ecdsa_nistp521; break; + default: return FALSE; + } + *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); + return TRUE; +} + +const int ec_ed_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const struct ssh_signkey **alg) +{ + switch (bits) { + case 256: *alg = &ssh_ecdsa_ed25519; break; + default: return FALSE; + } + *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); + return TRUE; +} + +#ifdef MPEXT + +void ec_cleanup(void) +{ + ec_curve_cleanup = 1; + ec_p256(); + ec_p384(); + ec_p521(); + ec_curve25519(); + ec_ed25519(); + // in case we want to restart (unlikely) + ec_curve_cleanup = 0; +} + +#endif diff --git a/netbox/libs/Putty/sshgss.h b/netbox/libs/Putty/sshgss.h new file mode 100644 index 000000000..32ccb4dbb --- /dev/null +++ b/netbox/libs/Putty/sshgss.h @@ -0,0 +1,188 @@ +#ifndef PUTTY_SSHGSS_H +#define PUTTY_SSHGSS_H +#include "putty.h" +#include "pgssapi.h" + +#ifndef NO_GSSAPI + +#define SSH2_GSS_OIDTYPE 0x06 +typedef void *Ssh_gss_ctx; + +typedef enum Ssh_gss_stat { + SSH_GSS_OK = 0, + SSH_GSS_S_CONTINUE_NEEDED, + SSH_GSS_NO_MEM, + SSH_GSS_BAD_HOST_NAME, + SSH_GSS_FAILURE +} Ssh_gss_stat; + +#define SSH_GSS_S_COMPLETE SSH_GSS_OK + +#define SSH_GSS_CLEAR_BUF(buf) do { \ + (*buf).length = 0; \ + (*buf).value = NULL; \ +} while (0) + +typedef gss_buffer_desc Ssh_gss_buf; +typedef gss_name_t Ssh_gss_name; + +/* Functions, provided by either wingss.c or sshgssc.c */ + +struct ssh_gss_library; + +/* + * Prepare a collection of GSSAPI libraries for use in a single SSH + * connection. Returns a structure containing a list of libraries, + * with their ids (see struct ssh_gss_library below) filled in so + * that the client can go through them in the SSH user's preferred + * order. + * + * Must always return non-NULL. (Even if no libraries are available, + * it must return an empty structure.) + * + * The free function cleans up the structure, and its associated + * libraries (if any). + */ +struct ssh_gss_liblist { + struct ssh_gss_library *libraries; + int nlibraries; +}; +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf); +void ssh_gss_cleanup(struct ssh_gss_liblist *list); + +/* + * Fills in buf with a string describing the GSSAPI mechanism in + * use. buf->data is not dynamically allocated. + */ +typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib, + Ssh_gss_buf *buf); + +/* + * Converts a name such as a hostname into a GSSAPI internal form, + * which is placed in "out". The result should be freed by + * ssh_gss_release_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib, + char *in, Ssh_gss_name *out); + +/* + * Frees the contents of an Ssh_gss_name structure filled in by + * ssh_gss_import_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib, + Ssh_gss_name *name); + +/* + * The main GSSAPI security context setup function. The "out" + * parameter will need to be freed by ssh_gss_free_tok. + */ +typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context) + (struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate, + Ssh_gss_buf *in, Ssh_gss_buf *out); + +/* + * Frees the contents of an Ssh_gss_buf filled in by + * ssh_gss_init_sec_context(). Do not accidentally call this on + * something filled in by ssh_gss_get_mic() (which requires a + * different free function) or something filled in by any other + * way. + */ +typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib, + Ssh_gss_buf *); + +/* + * Acquires the credentials to perform authentication in the first + * place. Needs to be freed by ssh_gss_release_cred(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib, + Ssh_gss_ctx *); + +/* + * Frees the contents of an Ssh_gss_ctx filled in by + * ssh_gss_acquire_cred(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib, + Ssh_gss_ctx *); + +/* + * Gets a MIC for some input data. "out" needs to be freed by + * ssh_gss_free_mic(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *in, + Ssh_gss_buf *out); + +/* + * Frees the contents of an Ssh_gss_buf filled in by + * ssh_gss_get_mic(). Do not accidentally call this on something + * filled in by ssh_gss_init_sec_context() (which requires a + * different free function) or something filled in by any other + * way. + */ +typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib, + Ssh_gss_buf *); + +/* + * Return an error message after authentication failed. The + * message string is returned in "buf", with buf->len giving the + * number of characters of printable message text and buf->data + * containing one more character which is a trailing NUL. + * buf->data should be manually freed by the caller. + */ +typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib, + Ssh_gss_ctx, Ssh_gss_buf *buf); + +struct ssh_gss_library { + /* + * Identifying number in the enumeration used by the + * configuration code to specify a preference order. + */ + int id; + + /* + * Filled in at initialisation time, if there's anything + * interesting to say about how GSSAPI was initialised (e.g. + * which of a number of alternative libraries was used). + */ + const char *gsslogmsg; + + /* + * Function pointers implementing the SSH wrapper layer on top + * of GSSAPI. (Defined in sshgssc, typically, though Windows + * provides an alternative layer to sit on top of the annoyingly + * different SSPI.) + */ + t_ssh_gss_indicate_mech indicate_mech; + t_ssh_gss_import_name import_name; + t_ssh_gss_release_name release_name; + t_ssh_gss_init_sec_context init_sec_context; + t_ssh_gss_free_tok free_tok; + t_ssh_gss_acquire_cred acquire_cred; + t_ssh_gss_release_cred release_cred; + t_ssh_gss_get_mic get_mic; + t_ssh_gss_free_mic free_mic; + t_ssh_gss_display_status display_status; + + /* + * Additional data for the wrapper layers. + */ + union { + struct gssapi_functions gssapi; + /* + * The SSPI wrappers don't need to store their Windows API + * function pointers in this structure, because there can't + * be more than one set of them available. + */ + } u; + + /* + * Wrapper layers will often also need to store a library handle + * of some sort for cleanup time. + */ + void *handle; +}; + +#endif /* NO_GSSAPI */ + +#endif /*PUTTY_SSHGSS_H*/ diff --git a/netbox/libs/Putty/sshgssc.c b/netbox/libs/Putty/sshgssc.c new file mode 100644 index 000000000..4590ed7b7 --- /dev/null +++ b/netbox/libs/Putty/sshgssc.c @@ -0,0 +1,209 @@ +#include "putty.h" + +#include +#include "sshgssc.h" +#include "misc.h" + +#ifndef NO_GSSAPI + +static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + /* Copy constant into mech */ + mech->length = GSS_MECH_KRB5->length; + mech->value = GSS_MECH_KRB5->elements; + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, + char *host, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + gss_buffer_desc host_buf; + char *pStr; + + pStr = dupcat("host@", host, NULL); + + host_buf.value = pStr; + host_buf.length = strlen(pStr); + + maj_stat = gss->import_name(&min_stat, &host_buf, + GSS_C_NT_HOSTBASED_SERVICE, srv_name); + /* Release buffer */ + sfree(pStr); + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); + + gssctx->maj_stat = gssctx->min_stat = GSS_S_COMPLETE; + gssctx->ctx = GSS_C_NO_CONTEXT; + *ctx = (Ssh_gss_ctx) gssctx; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; + OM_uint32 ret_flags; + + if (to_deleg) to_deleg = GSS_C_DELEG_FLAG; + gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat, + GSS_C_NO_CREDENTIAL, + &gssctx->ctx, + srv_name, + (gss_OID) GSS_MECH_KRB5, + GSS_C_MUTUAL_FLAG | + GSS_C_INTEG_FLAG | to_deleg, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + recv_tok, + NULL, /* ignore mech type */ + send_tok, + &ret_flags, + NULL); /* ignore time_rec */ + + if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE; + if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + OM_uint32 lmin,lmax; + OM_uint32 ccc; + gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER; + gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER; + + /* Return empty buffer in case of failure */ + SSH_GSS_CLEAR_BUF(buf); + + /* get first mesg from GSS */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj); + + if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE; + + /* get first mesg from Kerberos */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min); + + if (lmax != GSS_S_COMPLETE) { + gss->release_buffer(&lmin, &msg_maj); + return SSH_GSS_FAILURE; + } + + /* copy data into buffer */ + buf->length = msg_maj.length + msg_min.length + 1; + buf->value = snewn(buf->length + 1, char); + + /* copy mem */ + memcpy((char *)buf->value, msg_maj.value, msg_maj.length); + ((char *)buf->value)[msg_maj.length] = ' '; + memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length); + ((char *)buf->value)[buf->length] = 0; + /* free mem & exit */ + gss->release_buffer(&lmin, &msg_maj); + gss->release_buffer(&lmin, &msg_min); + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_buffer(&min_stat, send_tok); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx; + OM_uint32 min_stat; + OM_uint32 maj_stat=GSS_S_COMPLETE; + + if (gssctx == NULL) return SSH_GSS_FAILURE; + if (gssctx->ctx != GSS_C_NO_CONTEXT) + maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER); + sfree(gssctx); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + + +static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_name(&min_stat, srv_name); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + if (gssctx == NULL) return SSH_GSS_FAILURE; + return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash); +} + +static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */ + return ssh_gssapi_free_tok(lib, hash); +} + +void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_gssapi_indicate_mech; + lib->import_name = ssh_gssapi_import_name; + lib->release_name = ssh_gssapi_release_name; + lib->init_sec_context = ssh_gssapi_init_sec_context; + lib->free_tok = ssh_gssapi_free_tok; + lib->acquire_cred = ssh_gssapi_acquire_cred; + lib->release_cred = ssh_gssapi_release_cred; + lib->get_mic = ssh_gssapi_get_mic; + lib->free_mic = ssh_gssapi_free_mic; + lib->display_status = ssh_gssapi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +int ssh_gssapi_init(void) +{ + return 0; +} + +#endif diff --git a/netbox/libs/Putty/sshgssc.h b/netbox/libs/Putty/sshgssc.h new file mode 100644 index 000000000..c98ee86f6 --- /dev/null +++ b/netbox/libs/Putty/sshgssc.h @@ -0,0 +1,23 @@ +#ifndef PUTTY_SSHGSSC_H +#define PUTTY_SSHGSSC_H +#include "putty.h" +#ifndef NO_GSSAPI + +#include "pgssapi.h" +#include "sshgss.h" + +typedef struct gssapi_ssh_gss_ctx { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_ctx_id_t ctx; +} gssapi_ssh_gss_ctx; + +void ssh_gssapi_bind_fns(struct ssh_gss_library *lib); + +#else + +int ssh_gssapi_init(void); + +#endif /*NO_GSSAPI*/ + +#endif /*PUTTY_SSHGSSC_H*/ diff --git a/netbox/libs/Putty/sshmd5.c b/netbox/libs/Putty/sshmd5.c new file mode 100644 index 000000000..ca5c426cc --- /dev/null +++ b/netbox/libs/Putty/sshmd5.c @@ -0,0 +1,342 @@ +#include "ssh.h" + +/* + * MD5 implementation for PuTTY. Written directly from the spec by + * Simon Tatham. + */ + +/* ---------------------------------------------------------------------- + * Core MD5 algorithm: processes 16-word blocks into a message digest. + */ + +#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) ) +#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) ) +#define H(x,y,z) ( (x) ^ (y) ^ (z) ) +#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) ) + +#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) + +#define subround(f,w,x,y,z,k,s,ti) \ + w = x + rol(w + f(x,y,z) + block[k] + ti, s) + +static void MD5_Core_Init(MD5_Core_State * s) +{ + s->h[0] = 0x67452301; + s->h[1] = 0xefcdab89; + s->h[2] = 0x98badcfe; + s->h[3] = 0x10325476; +} + +static void MD5_Block(MD5_Core_State * s, uint32 * block) +{ + uint32 a, b, c, d; + + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + + subround(F, a, b, c, d, 0, 7, 0xd76aa478); + subround(F, d, a, b, c, 1, 12, 0xe8c7b756); + subround(F, c, d, a, b, 2, 17, 0x242070db); + subround(F, b, c, d, a, 3, 22, 0xc1bdceee); + subround(F, a, b, c, d, 4, 7, 0xf57c0faf); + subround(F, d, a, b, c, 5, 12, 0x4787c62a); + subround(F, c, d, a, b, 6, 17, 0xa8304613); + subround(F, b, c, d, a, 7, 22, 0xfd469501); + subround(F, a, b, c, d, 8, 7, 0x698098d8); + subround(F, d, a, b, c, 9, 12, 0x8b44f7af); + subround(F, c, d, a, b, 10, 17, 0xffff5bb1); + subround(F, b, c, d, a, 11, 22, 0x895cd7be); + subround(F, a, b, c, d, 12, 7, 0x6b901122); + subround(F, d, a, b, c, 13, 12, 0xfd987193); + subround(F, c, d, a, b, 14, 17, 0xa679438e); + subround(F, b, c, d, a, 15, 22, 0x49b40821); + subround(G, a, b, c, d, 1, 5, 0xf61e2562); + subround(G, d, a, b, c, 6, 9, 0xc040b340); + subround(G, c, d, a, b, 11, 14, 0x265e5a51); + subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa); + subround(G, a, b, c, d, 5, 5, 0xd62f105d); + subround(G, d, a, b, c, 10, 9, 0x02441453); + subround(G, c, d, a, b, 15, 14, 0xd8a1e681); + subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8); + subround(G, a, b, c, d, 9, 5, 0x21e1cde6); + subround(G, d, a, b, c, 14, 9, 0xc33707d6); + subround(G, c, d, a, b, 3, 14, 0xf4d50d87); + subround(G, b, c, d, a, 8, 20, 0x455a14ed); + subround(G, a, b, c, d, 13, 5, 0xa9e3e905); + subround(G, d, a, b, c, 2, 9, 0xfcefa3f8); + subround(G, c, d, a, b, 7, 14, 0x676f02d9); + subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a); + subround(H, a, b, c, d, 5, 4, 0xfffa3942); + subround(H, d, a, b, c, 8, 11, 0x8771f681); + subround(H, c, d, a, b, 11, 16, 0x6d9d6122); + subround(H, b, c, d, a, 14, 23, 0xfde5380c); + subround(H, a, b, c, d, 1, 4, 0xa4beea44); + subround(H, d, a, b, c, 4, 11, 0x4bdecfa9); + subround(H, c, d, a, b, 7, 16, 0xf6bb4b60); + subround(H, b, c, d, a, 10, 23, 0xbebfbc70); + subround(H, a, b, c, d, 13, 4, 0x289b7ec6); + subround(H, d, a, b, c, 0, 11, 0xeaa127fa); + subround(H, c, d, a, b, 3, 16, 0xd4ef3085); + subround(H, b, c, d, a, 6, 23, 0x04881d05); + subround(H, a, b, c, d, 9, 4, 0xd9d4d039); + subround(H, d, a, b, c, 12, 11, 0xe6db99e5); + subround(H, c, d, a, b, 15, 16, 0x1fa27cf8); + subround(H, b, c, d, a, 2, 23, 0xc4ac5665); + subround(I, a, b, c, d, 0, 6, 0xf4292244); + subround(I, d, a, b, c, 7, 10, 0x432aff97); + subround(I, c, d, a, b, 14, 15, 0xab9423a7); + subround(I, b, c, d, a, 5, 21, 0xfc93a039); + subround(I, a, b, c, d, 12, 6, 0x655b59c3); + subround(I, d, a, b, c, 3, 10, 0x8f0ccc92); + subround(I, c, d, a, b, 10, 15, 0xffeff47d); + subround(I, b, c, d, a, 1, 21, 0x85845dd1); + subround(I, a, b, c, d, 8, 6, 0x6fa87e4f); + subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0); + subround(I, c, d, a, b, 6, 15, 0xa3014314); + subround(I, b, c, d, a, 13, 21, 0x4e0811a1); + subround(I, a, b, c, d, 4, 6, 0xf7537e82); + subround(I, d, a, b, c, 11, 10, 0xbd3af235); + subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb); + subround(I, b, c, d, a, 9, 21, 0xeb86d391); + + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; +} + +/* ---------------------------------------------------------------------- + * Outer MD5 algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core MD5 algorithm. + */ + +#define BLKSIZE 64 + +void MD5Init(struct MD5Context *s) +{ + MD5_Core_Init(&s->core); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len) +{ + unsigned char *q = (unsigned char *) p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused + len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes little-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + (((uint32) s->block[i * 4 + 3]) << 24) | + (((uint32) s->block[i * 4 + 2]) << 16) | + (((uint32) s->block[i * 4 + 1]) << 8) | + (((uint32) s->block[i * 4 + 0]) << 0); + } + MD5_Block(&s->core, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void MD5Final(unsigned char output[16], struct MD5Context *s) +{ + int i; + unsigned pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + MD5Update(s, c, pad); + + c[7] = (lenhi >> 24) & 0xFF; + c[6] = (lenhi >> 16) & 0xFF; + c[5] = (lenhi >> 8) & 0xFF; + c[4] = (lenhi >> 0) & 0xFF; + c[3] = (lenlo >> 24) & 0xFF; + c[2] = (lenlo >> 16) & 0xFF; + c[1] = (lenlo >> 8) & 0xFF; + c[0] = (lenlo >> 0) & 0xFF; + + MD5Update(s, c, 8); + + for (i = 0; i < 4; i++) { + output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF; + output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF; + output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF; + output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF; + } +} + +void MD5Simple(void const *p, unsigned len, unsigned char output[16]) +{ + struct MD5Context s; + + MD5Init(&s); + MD5Update(&s, (unsigned char const *)p, len); + MD5Final(output, &s); + smemclr(&s, sizeof(s)); +} + +/* ---------------------------------------------------------------------- + * The above is the MD5 algorithm itself. Now we implement the + * HMAC wrapper on it. + * + * Some of these functions are exported directly, because they are + * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5). + */ + +void *hmacmd5_make_context(void) +{ + return snewn(3, struct MD5Context); +} + +void hmacmd5_free_context(void *handle) +{ + smemclr(handle, 3*sizeof(struct MD5Context)); + sfree(handle); +} + +void hmacmd5_key(void *handle, void const *keyv, int len) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + unsigned char foo[64]; + unsigned char const *key = (unsigned char const *)keyv; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + MD5Init(&keys[0]); + MD5Update(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + MD5Init(&keys[1]); + MD5Update(&keys[1], foo, 64); + + smemclr(foo, 64); /* burn the evidence */ +} + +static void hmacmd5_key_16(void *handle, unsigned char *key) +{ + hmacmd5_key(handle, key, 16); +} + +static void hmacmd5_start(void *handle) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + MD5Update(&keys[2], blk, len); +} + +static void hmacmd5_genresult(void *handle, unsigned char *hmac) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + struct MD5Context s; + unsigned char intermediate[16]; + + s = keys[2]; /* structure copy */ + MD5Final(intermediate, &s); + s = keys[1]; /* structure copy */ + MD5Update(&s, intermediate, 16); + MD5Final(hmac, &s); +} + +static int hmacmd5_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[16]; + hmacmd5_genresult(handle, correct); + return smemeq(correct, hmac, 16); +} + +static void hmacmd5_do_hmac_internal(void *handle, + unsigned char const *blk, int len, + unsigned char const *blk2, int len2, + unsigned char *hmac) +{ + hmacmd5_start(handle); + hmacmd5_bytes(handle, blk, len); + if (blk2) hmacmd5_bytes(handle, blk2, len2); + hmacmd5_genresult(handle, hmac); +} + +void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, + unsigned char *hmac) +{ + hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac); +} + +static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[16]; + + PUT_32BIT_MSB_FIRST(seqbuf, seq); + hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac); +} + +static void hmacmd5_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len); +} + +static int hmacmd5_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[16]; + hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct); + return smemeq(correct, blk + len, 16); +} + +const struct ssh_mac ssh_hmac_md5 = { + hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16, + hmacmd5_generate, hmacmd5_verify, + hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult, + "hmac-md5", + 16, + "HMAC-MD5" +}; diff --git a/netbox/libs/Putty/sshnogss.c b/netbox/libs/Putty/sshnogss.c new file mode 100644 index 000000000..fa4ac65f4 --- /dev/null +++ b/netbox/libs/Putty/sshnogss.c @@ -0,0 +1,19 @@ +#include "putty.h" +#ifndef NO_GSSAPI + +/* For platforms not supporting GSSAPI */ + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *); + list->libraries = NULL; + list->nlibraries = 0; + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list); +} + +#endif /* NO_GSSAPI */ diff --git a/netbox/libs/Putty/sshprime.c b/netbox/libs/Putty/sshprime.c new file mode 100644 index 000000000..03b9c7282 --- /dev/null +++ b/netbox/libs/Putty/sshprime.c @@ -0,0 +1,1086 @@ +/* + * Prime generation. + */ + +#include +#include "ssh.h" + +/* + * This prime generation algorithm is pretty much cribbed from + * OpenSSL. The algorithm is: + * + * - invent a B-bit random number and ensure the top and bottom + * bits are set (so it's definitely B-bit, and it's definitely + * odd) + * + * - see if it's coprime to all primes below 2^16; increment it by + * two until it is (this shouldn't take long in general) + * + * - perform the Miller-Rabin primality test enough times to + * ensure the probability of it being composite is 2^-80 or + * less + * + * - go back to square one if any M-R test fails. + */ + +/* + * The Miller-Rabin primality test is an extension to the Fermat + * test. The Fermat test just checks that a^(p-1) == 1 mod p; this + * is vulnerable to Carmichael numbers. Miller-Rabin considers how + * that 1 is derived as well. + * + * Lemma: if a^2 == 1 (mod p), and p is prime, then either a == 1 + * or a == -1 (mod p). + * + * Proof: p divides a^2-1, i.e. p divides (a+1)(a-1). Hence, + * since p is prime, either p divides (a+1) or p divides (a-1). + * But this is the same as saying that either a is congruent to + * -1 mod p or a is congruent to +1 mod p. [] + * + * Comment: This fails when p is not prime. Consider p=mn, so + * that mn divides (a+1)(a-1). Now we could have m dividing (a+1) + * and n dividing (a-1), without the whole of mn dividing either. + * For example, consider a=10 and p=99. 99 = 9 * 11; 9 divides + * 10-1 and 11 divides 10+1, so a^2 is congruent to 1 mod p + * without a having to be congruent to either 1 or -1. + * + * So the Miller-Rabin test, as well as considering a^(p-1), + * considers a^((p-1)/2), a^((p-1)/4), and so on as far as it can + * go. In other words. we write p-1 as q * 2^k, with k as large as + * possible (i.e. q must be odd), and we consider the powers + * + * a^(q*2^0) a^(q*2^1) ... a^(q*2^(k-1)) a^(q*2^k) + * i.e. a^((n-1)/2^k) a^((n-1)/2^(k-1)) ... a^((n-1)/2) a^(n-1) + * + * If p is to be prime, the last of these must be 1. Therefore, by + * the above lemma, the one before it must be either 1 or -1. And + * _if_ it's 1, then the one before that must be either 1 or -1, + * and so on ... In other words, we expect to see a trailing chain + * of 1s preceded by a -1. (If we're unlucky, our trailing chain of + * 1s will be as long as the list so we'll never get to see what + * lies before it. This doesn't count as a test failure because it + * hasn't _proved_ that p is not prime.) + * + * For example, consider a=2 and p=1729. 1729 is a Carmichael + * number: although it's not prime, it satisfies a^(p-1) == 1 mod p + * for any a coprime to it. So the Fermat test wouldn't have a + * problem with it at all, unless we happened to stumble on an a + * which had a common factor. + * + * So. 1729 - 1 equals 27 * 2^6. So we look at + * + * 2^27 mod 1729 == 645 + * 2^108 mod 1729 == 1065 + * 2^216 mod 1729 == 1 + * 2^432 mod 1729 == 1 + * 2^864 mod 1729 == 1 + * 2^1728 mod 1729 == 1 + * + * We do have a trailing string of 1s, so the Fermat test would + * have been happy. But this trailing string of 1s is preceded by + * 1065; whereas if 1729 were prime, we'd expect to see it preceded + * by -1 (i.e. 1728.). Guards! Seize this impostor. + * + * (If we were unlucky, we might have tried a=16 instead of a=2; + * now 16^27 mod 1729 == 1, so we would have seen a long string of + * 1s and wouldn't have seen the thing _before_ the 1s. So, just + * like the Fermat test, for a given p there may well exist values + * of a which fail to show up its compositeness. So we try several, + * just like the Fermat test. The difference is that Miller-Rabin + * is not _in general_ fooled by Carmichael numbers.) + * + * Put simply, then, the Miller-Rabin test requires us to: + * + * 1. write p-1 as q * 2^k, with q odd + * 2. compute z = (a^q) mod p. + * 3. report success if z == 1 or z == -1. + * 4. square z at most k-1 times, and report success if it becomes + * -1 at any point. + * 5. report failure otherwise. + * + * (We expect z to become -1 after at most k-1 squarings, because + * if it became -1 after k squarings then a^(p-1) would fail to be + * 1. And we don't need to investigate what happens after we see a + * -1, because we _know_ that -1 squared is 1 modulo anything at + * all, so after we've seen a -1 we can be sure of seeing nothing + * but 1s.) + */ + +/* + * The first few odd primes. + * + * import sys + * def sieve(n): + * z = [] + * list = [] + * for i in range(n): z.append(1) + * for i in range(2,n): + * if z[i]: + * list.append(i) + * for j in range(i,n,i): z[j] = 0 + * return list + * list = sieve(65535) + * for i in list[1:]: sys.stdout.write("%d," % i) + */ +static const unsigned short primes[] = { + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, + 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, + 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, + 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, + 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, + 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, + 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, + 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, + 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, + 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, + 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, + 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, + 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, + 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, + 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, + 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, + 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, + 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, + 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, + 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, + 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, + 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, + 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, + 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, + 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, + 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, + 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, + 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, + 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, + 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, + 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, + 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, + 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, + 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, + 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, + 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, + 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, + 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, + 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, + 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, + 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, + 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, + 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, + 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, + 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, + 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, + 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, + 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, + 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, + 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, + 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, + 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, + 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, + 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, + 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, + 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, + 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, + 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, + 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, + 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, + 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, + 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, + 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, + 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, + 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, + 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, + 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, + 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, + 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, + 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, + 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, + 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, + 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, + 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, + 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, + 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, + 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, + 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, + 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, + 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, + 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, + 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, + 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, + 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, + 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, + 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, + 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, + 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, + 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, + 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, + 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, + 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, + 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, + 9491, 9497, 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, + 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, + 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, + 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, + 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, + 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, + 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, + 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, + 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, + 10331, 10333, 10337, 10343, 10357, 10369, 10391, 10399, 10427, + 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, + 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, + 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, + 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, 10753, + 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, + 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, + 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, + 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, + 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, + 11213, 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, + 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, + 11393, 11399, 11411, 11423, 11437, 11443, 11447, 11467, 11471, + 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, + 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, + 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, + 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, + 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, + 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981, + 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, + 12097, 12101, 12107, 12109, 12113, 12119, 12143, 12149, 12157, + 12161, 12163, 12197, 12203, 12211, 12227, 12239, 12241, 12251, + 12253, 12263, 12269, 12277, 12281, 12289, 12301, 12323, 12329, + 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, + 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, 12491, + 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, + 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, + 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, + 12721, 12739, 12743, 12757, 12763, 12781, 12791, 12799, 12809, + 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, + 12911, 12917, 12919, 12923, 12941, 12953, 12959, 12967, 12973, + 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, + 13049, 13063, 13093, 13099, 13103, 13109, 13121, 13127, 13147, + 13151, 13159, 13163, 13171, 13177, 13183, 13187, 13217, 13219, + 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, 13313, + 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, + 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, + 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, + 13613, 13619, 13627, 13633, 13649, 13669, 13679, 13681, 13687, + 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, + 13757, 13759, 13763, 13781, 13789, 13799, 13807, 13829, 13831, + 13841, 13859, 13873, 13877, 13879, 13883, 13901, 13903, 13907, + 13913, 13921, 13931, 13933, 13963, 13967, 13997, 13999, 14009, + 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, + 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, 14207, + 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, + 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, + 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, + 14503, 14519, 14533, 14537, 14543, 14549, 14551, 14557, 14561, + 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, + 14657, 14669, 14683, 14699, 14713, 14717, 14723, 14731, 14737, + 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, + 14813, 14821, 14827, 14831, 14843, 14851, 14867, 14869, 14879, + 14887, 14891, 14897, 14923, 14929, 14939, 14947, 14951, 14957, + 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, 15077, + 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, + 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, + 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, + 15313, 15319, 15329, 15331, 15349, 15359, 15361, 15373, 15377, + 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, + 15467, 15473, 15493, 15497, 15511, 15527, 15541, 15551, 15559, + 15569, 15581, 15583, 15601, 15607, 15619, 15629, 15641, 15643, + 15647, 15649, 15661, 15667, 15671, 15679, 15683, 15727, 15731, + 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, + 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, 15887, + 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, + 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, + 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, + 16141, 16183, 16187, 16189, 16193, 16217, 16223, 16229, 16231, + 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, + 16361, 16363, 16369, 16381, 16411, 16417, 16421, 16427, 16433, + 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, + 16547, 16553, 16561, 16567, 16573, 16603, 16607, 16619, 16631, + 16633, 16649, 16651, 16657, 16661, 16673, 16691, 16693, 16699, + 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, 16823, + 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, + 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, + 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, + 17077, 17093, 17099, 17107, 17117, 17123, 17137, 17159, 17167, + 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, + 17291, 17293, 17299, 17317, 17321, 17327, 17333, 17341, 17351, + 17359, 17377, 17383, 17387, 17389, 17393, 17401, 17417, 17419, + 17431, 17443, 17449, 17467, 17471, 17477, 17483, 17489, 17491, + 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, + 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, 17681, + 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, + 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, + 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, + 17959, 17971, 17977, 17981, 17987, 17989, 18013, 18041, 18043, + 18047, 18049, 18059, 18061, 18077, 18089, 18097, 18119, 18121, + 18127, 18131, 18133, 18143, 18149, 18169, 18181, 18191, 18199, + 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, + 18287, 18289, 18301, 18307, 18311, 18313, 18329, 18341, 18353, + 18367, 18371, 18379, 18397, 18401, 18413, 18427, 18433, 18439, + 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, 18521, + 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, + 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, + 18749, 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, + 18869, 18899, 18911, 18913, 18917, 18919, 18947, 18959, 18973, + 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, + 19079, 19081, 19087, 19121, 19139, 19141, 19157, 19163, 19181, + 19183, 19207, 19211, 19213, 19219, 19231, 19237, 19249, 19259, + 19267, 19273, 19289, 19301, 19309, 19319, 19333, 19373, 19379, + 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, + 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, 19483, + 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, + 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, + 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, + 19777, 19793, 19801, 19813, 19819, 19841, 19843, 19853, 19861, + 19867, 19889, 19891, 19913, 19919, 19927, 19937, 19949, 19961, + 19963, 19973, 19979, 19991, 19993, 19997, 20011, 20021, 20023, + 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, + 20117, 20123, 20129, 20143, 20147, 20149, 20161, 20173, 20177, + 20183, 20201, 20219, 20231, 20233, 20249, 20261, 20269, 20287, + 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, 20359, + 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, + 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, + 20551, 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, + 20681, 20693, 20707, 20717, 20719, 20731, 20743, 20747, 20749, + 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, + 20873, 20879, 20887, 20897, 20899, 20903, 20921, 20929, 20939, + 20947, 20959, 20963, 20981, 20983, 21001, 21011, 21013, 21017, + 21019, 21023, 21031, 21059, 21061, 21067, 21089, 21101, 21107, + 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, + 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, 21283, + 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, + 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, + 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, + 21559, 21563, 21569, 21577, 21587, 21589, 21599, 21601, 21611, + 21613, 21617, 21647, 21649, 21661, 21673, 21683, 21701, 21713, + 21727, 21737, 21739, 21751, 21757, 21767, 21773, 21787, 21799, + 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, + 21881, 21893, 21911, 21929, 21937, 21943, 21961, 21977, 21991, + 21997, 22003, 22013, 22027, 22031, 22037, 22039, 22051, 22063, + 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, 22129, + 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, + 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, + 22307, 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, + 22433, 22441, 22447, 22453, 22469, 22481, 22483, 22501, 22511, + 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, + 22621, 22637, 22639, 22643, 22651, 22669, 22679, 22691, 22697, + 22699, 22709, 22717, 22721, 22727, 22739, 22741, 22751, 22769, + 22777, 22783, 22787, 22807, 22811, 22817, 22853, 22859, 22861, + 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, + 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, 23039, + 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, + 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, + 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, + 23311, 23321, 23327, 23333, 23339, 23357, 23369, 23371, 23399, + 23417, 23431, 23447, 23459, 23473, 23497, 23509, 23531, 23537, + 23539, 23549, 23557, 23561, 23563, 23567, 23581, 23593, 23599, + 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, + 23677, 23687, 23689, 23719, 23741, 23743, 23747, 23753, 23761, + 23767, 23773, 23789, 23801, 23813, 23819, 23827, 23831, 23833, + 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, 23911, + 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, + 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, + 24091, 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, + 24151, 24169, 24179, 24181, 24197, 24203, 24223, 24229, 24239, + 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, + 24379, 24391, 24407, 24413, 24419, 24421, 24439, 24443, 24469, + 24473, 24481, 24499, 24509, 24517, 24527, 24533, 24547, 24551, + 24571, 24593, 24611, 24623, 24631, 24659, 24671, 24677, 24683, + 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, + 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, 24889, + 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, + 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, + 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, + 25171, 25183, 25189, 25219, 25229, 25237, 25243, 25247, 25253, + 25261, 25301, 25303, 25307, 25309, 25321, 25339, 25343, 25349, + 25357, 25367, 25373, 25391, 25409, 25411, 25423, 25439, 25447, + 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, + 25577, 25579, 25583, 25589, 25601, 25603, 25609, 25621, 25633, + 25639, 25643, 25657, 25667, 25673, 25679, 25693, 25703, 25717, + 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, 25801, + 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, + 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, + 25999, 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, + 26107, 26111, 26113, 26119, 26141, 26153, 26161, 26171, 26177, + 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, + 26263, 26267, 26293, 26297, 26309, 26317, 26321, 26339, 26347, + 26357, 26371, 26387, 26393, 26399, 26407, 26417, 26423, 26431, + 26437, 26449, 26459, 26479, 26489, 26497, 26501, 26513, 26539, + 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, + 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, 26713, + 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, + 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, + 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, + 26981, 26987, 26993, 27011, 27017, 27031, 27043, 27059, 27061, + 27067, 27073, 27077, 27091, 27103, 27107, 27109, 27127, 27143, + 27179, 27191, 27197, 27211, 27239, 27241, 27253, 27259, 27271, + 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, + 27407, 27409, 27427, 27431, 27437, 27449, 27457, 27479, 27481, + 27487, 27509, 27527, 27529, 27539, 27541, 27551, 27581, 27583, + 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, 27697, + 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, + 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, + 27827, 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, + 27943, 27947, 27953, 27961, 27967, 27983, 27997, 28001, 28019, + 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, + 28109, 28111, 28123, 28151, 28163, 28181, 28183, 28201, 28211, + 28219, 28229, 28277, 28279, 28283, 28289, 28297, 28307, 28309, + 28319, 28349, 28351, 28387, 28393, 28403, 28409, 28411, 28429, + 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, + 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, 28591, + 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, + 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, + 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, + 28817, 28837, 28843, 28859, 28867, 28871, 28879, 28901, 28909, + 28921, 28927, 28933, 28949, 28961, 28979, 29009, 29017, 29021, + 29023, 29027, 29033, 29059, 29063, 29077, 29101, 29123, 29129, + 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, + 29207, 29209, 29221, 29231, 29243, 29251, 29269, 29287, 29297, + 29303, 29311, 29327, 29333, 29339, 29347, 29363, 29383, 29387, + 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, 29453, + 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, + 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, + 29671, 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, + 29803, 29819, 29833, 29837, 29851, 29863, 29867, 29873, 29879, + 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, + 30013, 30029, 30047, 30059, 30071, 30089, 30091, 30097, 30103, + 30109, 30113, 30119, 30133, 30137, 30139, 30161, 30169, 30181, + 30187, 30197, 30203, 30211, 30223, 30241, 30253, 30259, 30269, + 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, + 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, 30491, + 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, + 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, + 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, + 30781, 30803, 30809, 30817, 30829, 30839, 30841, 30851, 30853, + 30859, 30869, 30871, 30881, 30893, 30911, 30931, 30937, 30941, + 30949, 30971, 30977, 30983, 31013, 31019, 31033, 31039, 31051, + 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, + 31151, 31153, 31159, 31177, 31181, 31183, 31189, 31193, 31219, + 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267, 31271, + 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379, + 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, + 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, + 31601, 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, + 31699, 31721, 31723, 31727, 31729, 31741, 31751, 31769, 31771, + 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, + 31907, 31957, 31963, 31973, 31981, 31991, 32003, 32009, 32027, + 32029, 32051, 32057, 32059, 32063, 32069, 32077, 32083, 32089, + 32099, 32117, 32119, 32141, 32143, 32159, 32173, 32183, 32189, + 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, + 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359, + 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, + 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, + 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, + 32603, 32609, 32611, 32621, 32633, 32647, 32653, 32687, 32693, + 32707, 32713, 32717, 32719, 32749, 32771, 32779, 32783, 32789, + 32797, 32801, 32803, 32831, 32833, 32839, 32843, 32869, 32887, + 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, + 32983, 32987, 32993, 32999, 33013, 33023, 33029, 33037, 33049, + 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119, 33149, + 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223, + 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, + 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, + 33427, 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, + 33529, 33533, 33547, 33563, 33569, 33577, 33581, 33587, 33589, + 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, + 33647, 33679, 33703, 33713, 33721, 33739, 33749, 33751, 33757, + 33767, 33769, 33773, 33791, 33797, 33809, 33811, 33827, 33829, + 33851, 33857, 33863, 33871, 33889, 33893, 33911, 33923, 33931, + 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, + 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159, + 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, + 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, + 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, + 34439, 34457, 34469, 34471, 34483, 34487, 34499, 34501, 34511, + 34513, 34519, 34537, 34543, 34549, 34583, 34589, 34591, 34603, + 34607, 34613, 34631, 34649, 34651, 34667, 34673, 34679, 34687, + 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, + 34781, 34807, 34819, 34841, 34843, 34847, 34849, 34871, 34877, + 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963, 34981, + 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089, + 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, + 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, + 35291, 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, + 35393, 35401, 35407, 35419, 35423, 35437, 35447, 35449, 35461, + 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, + 35569, 35573, 35591, 35593, 35597, 35603, 35617, 35671, 35677, + 35729, 35731, 35747, 35753, 35759, 35771, 35797, 35801, 35803, + 35809, 35831, 35837, 35839, 35851, 35863, 35869, 35879, 35897, + 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, + 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067, + 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, + 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, + 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, + 36373, 36383, 36389, 36433, 36451, 36457, 36467, 36469, 36473, + 36479, 36493, 36497, 36523, 36527, 36529, 36541, 36551, 36559, + 36563, 36571, 36583, 36587, 36599, 36607, 36629, 36637, 36643, + 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, + 36739, 36749, 36761, 36767, 36779, 36781, 36787, 36791, 36793, + 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887, 36899, + 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973, + 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, + 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, + 37189, 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, + 37307, 37309, 37313, 37321, 37337, 37339, 37357, 37361, 37363, + 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, + 37489, 37493, 37501, 37507, 37511, 37517, 37529, 37537, 37547, + 37549, 37561, 37567, 37571, 37573, 37579, 37589, 37591, 37607, + 37619, 37633, 37643, 37649, 37657, 37663, 37691, 37693, 37699, + 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, + 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957, + 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, + 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, + 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, + 38273, 38281, 38287, 38299, 38303, 38317, 38321, 38327, 38329, + 38333, 38351, 38371, 38377, 38393, 38431, 38447, 38449, 38453, + 38459, 38461, 38501, 38543, 38557, 38561, 38567, 38569, 38593, + 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, + 38677, 38693, 38699, 38707, 38711, 38713, 38723, 38729, 38737, + 38747, 38749, 38767, 38783, 38791, 38803, 38821, 38833, 38839, + 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, 38923, + 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, + 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, + 39133, 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, + 39217, 39227, 39229, 39233, 39239, 39241, 39251, 39293, 39301, + 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, + 39383, 39397, 39409, 39419, 39439, 39443, 39451, 39461, 39499, + 39503, 39509, 39511, 39521, 39541, 39551, 39563, 39569, 39581, + 39607, 39619, 39623, 39631, 39659, 39667, 39671, 39679, 39703, + 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, + 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, 39863, + 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, + 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, + 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, + 40163, 40169, 40177, 40189, 40193, 40213, 40231, 40237, 40241, + 40253, 40277, 40283, 40289, 40343, 40351, 40357, 40361, 40387, + 40423, 40427, 40429, 40433, 40459, 40471, 40483, 40487, 40493, + 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, + 40591, 40597, 40609, 40627, 40637, 40639, 40693, 40697, 40699, + 40709, 40739, 40751, 40759, 40763, 40771, 40787, 40801, 40813, + 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, 40879, + 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, + 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, + 41081, 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, + 41179, 41183, 41189, 41201, 41203, 41213, 41221, 41227, 41231, + 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, + 41351, 41357, 41381, 41387, 41389, 41399, 41411, 41413, 41443, + 41453, 41467, 41479, 41491, 41507, 41513, 41519, 41521, 41539, + 41543, 41549, 41579, 41593, 41597, 41603, 41609, 41611, 41617, + 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, + 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, 41809, + 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, + 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, + 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, + 42071, 42073, 42083, 42089, 42101, 42131, 42139, 42157, 42169, + 42179, 42181, 42187, 42193, 42197, 42209, 42221, 42223, 42227, + 42239, 42257, 42281, 42283, 42293, 42299, 42307, 42323, 42331, + 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, + 42409, 42433, 42437, 42443, 42451, 42457, 42461, 42463, 42467, + 42473, 42487, 42491, 42499, 42509, 42533, 42557, 42569, 42571, + 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, 42683, + 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, + 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, + 42841, 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, + 42943, 42953, 42961, 42967, 42979, 42989, 43003, 43013, 43019, + 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, + 43151, 43159, 43177, 43189, 43201, 43207, 43223, 43237, 43261, + 43271, 43283, 43291, 43313, 43319, 43321, 43331, 43391, 43397, + 43399, 43403, 43411, 43427, 43441, 43451, 43457, 43481, 43487, + 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, + 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, 43669, + 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, + 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, + 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, + 43997, 44017, 44021, 44027, 44029, 44041, 44053, 44059, 44071, + 44087, 44089, 44101, 44111, 44119, 44123, 44129, 44131, 44159, + 44171, 44179, 44189, 44201, 44203, 44207, 44221, 44249, 44257, + 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, + 44371, 44381, 44383, 44389, 44417, 44449, 44453, 44483, 44491, + 44497, 44501, 44507, 44519, 44531, 44533, 44537, 44543, 44549, + 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, 44647, + 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, + 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, + 44843, 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, + 44939, 44953, 44959, 44963, 44971, 44983, 44987, 45007, 45013, + 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, + 45139, 45161, 45179, 45181, 45191, 45197, 45233, 45247, 45259, + 45263, 45281, 45289, 45293, 45307, 45317, 45319, 45329, 45337, + 45341, 45343, 45361, 45377, 45389, 45403, 45413, 45427, 45433, + 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, + 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, 45659, + 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, + 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, + 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, + 45971, 45979, 45989, 46021, 46027, 46049, 46051, 46061, 46073, + 46091, 46093, 46099, 46103, 46133, 46141, 46147, 46153, 46171, + 46181, 46183, 46187, 46199, 46219, 46229, 46237, 46261, 46271, + 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, + 46381, 46399, 46411, 46439, 46441, 46447, 46451, 46457, 46471, + 46477, 46489, 46499, 46507, 46511, 46523, 46549, 46559, 46567, + 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, 46649, + 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, + 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, + 46831, 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, + 46957, 46993, 46997, 47017, 47041, 47051, 47057, 47059, 47087, + 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, + 47161, 47189, 47207, 47221, 47237, 47251, 47269, 47279, 47287, + 47293, 47297, 47303, 47309, 47317, 47339, 47351, 47353, 47363, + 47381, 47387, 47389, 47407, 47417, 47419, 47431, 47441, 47459, + 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, + 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, 47639, + 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, + 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, + 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, + 47933, 47939, 47947, 47951, 47963, 47969, 47977, 47981, 48017, + 48023, 48029, 48049, 48073, 48079, 48091, 48109, 48119, 48121, + 48131, 48157, 48163, 48179, 48187, 48193, 48197, 48221, 48239, + 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, + 48353, 48371, 48383, 48397, 48407, 48409, 48413, 48437, 48449, + 48463, 48473, 48479, 48481, 48487, 48491, 48497, 48523, 48527, + 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, 48619, + 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, + 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, + 48817, 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, + 48889, 48907, 48947, 48953, 48973, 48989, 48991, 49003, 49009, + 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, + 49109, 49117, 49121, 49123, 49139, 49157, 49169, 49171, 49177, + 49193, 49199, 49201, 49207, 49211, 49223, 49253, 49261, 49277, + 49279, 49297, 49307, 49331, 49333, 49339, 49363, 49367, 49369, + 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, + 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, 49547, + 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, + 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, + 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, + 49843, 49853, 49871, 49877, 49891, 49919, 49921, 49927, 49937, + 49939, 49943, 49957, 49991, 49993, 49999, 50021, 50023, 50033, + 50047, 50051, 50053, 50069, 50077, 50087, 50093, 50101, 50111, + 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, + 50221, 50227, 50231, 50261, 50263, 50273, 50287, 50291, 50311, + 50321, 50329, 50333, 50341, 50359, 50363, 50377, 50383, 50387, + 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, 50513, + 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, + 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, + 50753, 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, + 50857, 50867, 50873, 50891, 50893, 50909, 50923, 50929, 50951, + 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, + 51059, 51061, 51071, 51109, 51131, 51133, 51137, 51151, 51157, + 51169, 51193, 51197, 51199, 51203, 51217, 51229, 51239, 51241, + 51257, 51263, 51283, 51287, 51307, 51329, 51341, 51343, 51347, + 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, + 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, 51503, + 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, + 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, + 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, + 51797, 51803, 51817, 51827, 51829, 51839, 51853, 51859, 51869, + 51871, 51893, 51899, 51907, 51913, 51929, 51941, 51949, 51971, + 51973, 51977, 51991, 52009, 52021, 52027, 52051, 52057, 52067, + 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, + 52181, 52183, 52189, 52201, 52223, 52237, 52249, 52253, 52259, + 52267, 52289, 52291, 52301, 52313, 52321, 52361, 52363, 52369, + 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, 52511, + 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, + 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, + 52709, 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, + 52807, 52813, 52817, 52837, 52859, 52861, 52879, 52883, 52889, + 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, + 52981, 52999, 53003, 53017, 53047, 53051, 53069, 53077, 53087, + 53089, 53093, 53101, 53113, 53117, 53129, 53147, 53149, 53161, + 53171, 53173, 53189, 53197, 53201, 53231, 53233, 53239, 53267, + 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, + 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, 53453, + 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, + 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, + 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, + 53777, 53783, 53791, 53813, 53819, 53831, 53849, 53857, 53861, + 53881, 53887, 53891, 53897, 53899, 53917, 53923, 53927, 53939, + 53951, 53959, 53987, 53993, 54001, 54011, 54013, 54037, 54049, + 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, + 54167, 54181, 54193, 54217, 54251, 54269, 54277, 54287, 54293, + 54311, 54319, 54323, 54331, 54347, 54361, 54367, 54371, 54377, + 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, 54449, + 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, + 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, + 54629, 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, + 54727, 54751, 54767, 54773, 54779, 54787, 54799, 54829, 54833, + 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, + 54959, 54973, 54979, 54983, 55001, 55009, 55021, 55049, 55051, + 55057, 55061, 55073, 55079, 55103, 55109, 55117, 55127, 55147, + 55163, 55171, 55201, 55207, 55213, 55217, 55219, 55229, 55243, + 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, + 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, 55469, + 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, + 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, + 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, + 55787, 55793, 55799, 55807, 55813, 55817, 55819, 55823, 55829, + 55837, 55843, 55849, 55871, 55889, 55897, 55901, 55903, 55921, + 55927, 55931, 55933, 55949, 55967, 55987, 55997, 56003, 56009, + 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, + 56123, 56131, 56149, 56167, 56171, 56179, 56197, 56207, 56209, + 56237, 56239, 56249, 56263, 56267, 56269, 56299, 56311, 56333, + 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, 56437, + 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, + 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, + 56599, 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, + 56701, 56711, 56713, 56731, 56737, 56747, 56767, 56773, 56779, + 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, + 56891, 56893, 56897, 56909, 56911, 56921, 56923, 56929, 56941, + 56951, 56957, 56963, 56983, 56989, 56993, 56999, 57037, 57041, + 57047, 57059, 57073, 57077, 57089, 57097, 57107, 57119, 57131, + 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, + 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, 57287, + 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, + 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, + 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, + 57649, 57653, 57667, 57679, 57689, 57697, 57709, 57713, 57719, + 57727, 57731, 57737, 57751, 57773, 57781, 57787, 57791, 57793, + 57803, 57809, 57829, 57839, 57847, 57853, 57859, 57881, 57899, + 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, + 58027, 58031, 58043, 58049, 58057, 58061, 58067, 58073, 58099, + 58109, 58111, 58129, 58147, 58151, 58153, 58169, 58171, 58189, + 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, 58243, + 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, + 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, + 58453, 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, + 58579, 58601, 58603, 58613, 58631, 58657, 58661, 58679, 58687, + 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, + 58787, 58789, 58831, 58889, 58897, 58901, 58907, 58909, 58913, + 58921, 58937, 58943, 58963, 58967, 58979, 58991, 58997, 59009, + 59011, 59021, 59023, 59029, 59051, 59053, 59063, 59069, 59077, + 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, + 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, 59239, + 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, + 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, + 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, + 59539, 59557, 59561, 59567, 59581, 59611, 59617, 59621, 59627, + 59629, 59651, 59659, 59663, 59669, 59671, 59693, 59699, 59707, + 59723, 59729, 59743, 59747, 59753, 59771, 59779, 59791, 59797, + 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, + 59971, 59981, 59999, 60013, 60017, 60029, 60037, 60041, 60077, + 60083, 60089, 60091, 60101, 60103, 60107, 60127, 60133, 60139, + 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, 60257, + 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, + 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, + 60497, 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, + 60617, 60623, 60631, 60637, 60647, 60649, 60659, 60661, 60679, + 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, + 60773, 60779, 60793, 60811, 60821, 60859, 60869, 60887, 60889, + 60899, 60901, 60913, 60917, 60919, 60923, 60937, 60943, 60953, + 60961, 61001, 61007, 61027, 61031, 61043, 61051, 61057, 61091, + 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, + 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, 61339, + 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, + 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, + 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, + 61627, 61631, 61637, 61643, 61651, 61657, 61667, 61673, 61681, + 61687, 61703, 61717, 61723, 61729, 61751, 61757, 61781, 61813, + 61819, 61837, 61843, 61861, 61871, 61879, 61909, 61927, 61933, + 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, + 62017, 62039, 62047, 62053, 62057, 62071, 62081, 62099, 62119, + 62129, 62131, 62137, 62141, 62143, 62171, 62189, 62191, 62201, + 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, 62311, + 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, + 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, + 62549, 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, + 62639, 62653, 62659, 62683, 62687, 62701, 62723, 62731, 62743, + 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, + 62869, 62873, 62897, 62903, 62921, 62927, 62929, 62939, 62969, + 62971, 62981, 62983, 62987, 62989, 63029, 63031, 63059, 63067, + 63073, 63079, 63097, 63103, 63113, 63127, 63131, 63149, 63179, + 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, + 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, 63377, + 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, + 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, + 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, + 63629, 63647, 63649, 63659, 63667, 63671, 63689, 63691, 63697, + 63703, 63709, 63719, 63727, 63737, 63743, 63761, 63773, 63781, + 63793, 63799, 63803, 63809, 63823, 63839, 63841, 63853, 63857, + 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, + 64013, 64019, 64033, 64037, 64063, 64067, 64081, 64091, 64109, + 64123, 64151, 64153, 64157, 64171, 64187, 64189, 64217, 64223, + 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, 64327, + 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, + 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, + 64601, 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, + 64679, 64693, 64709, 64717, 64747, 64763, 64781, 64783, 64793, + 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, + 64919, 64921, 64927, 64937, 64951, 64969, 64997, 65003, 65011, + 65027, 65029, 65033, 65053, 65063, 65071, 65089, 65099, 65101, + 65111, 65119, 65123, 65129, 65141, 65147, 65167, 65171, 65173, + 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, + 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, 65393, + 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, + 65519, 65521, +}; + +#define NPRIMES (sizeof(primes) / sizeof(*primes)) + +/* + * Generate a prime. We can deal with various extra properties of + * the prime: + * + * - to speed up use in RSA, we can arrange to select a prime with + * the property (prime % modulus) != residue. + * + * - for use in DSA, we can arrange to select a prime which is one + * more than a multiple of a dirty great bignum. In this case + * `bits' gives the size of the factor by which we _multiply_ + * that bignum, rather than the size of the whole number. + * + * - for the basically cosmetic purposes of generating keys of the + * length actually specified rather than off by one bit, we permit + * the caller to provide an unsigned integer 'firstbits' which will + * match the top few bits of the returned prime. (That is, there + * will exist some n such that (returnvalue >> n) == firstbits.) If + * 'firstbits' is not needed, specifying it to either 0 or 1 is + * an adequate no-op. + */ +Bignum primegen(int bits, int modulus, int residue, Bignum factor, + int phase, progfn_t pfn, void *pfnparam, unsigned firstbits) +{ + int i, k, v, byte, bitsleft, check, checks, fbsize; + unsigned long delta; + unsigned long moduli[NPRIMES + 1]; + unsigned long residues[NPRIMES + 1]; + unsigned long multipliers[NPRIMES + 1]; + Bignum p, pm1, q, wqp, wqp2; + int progress = 0; + + byte = 0; + bitsleft = 0; + + fbsize = 0; + while (firstbits >> fbsize) /* work out how to align this */ + fbsize++; + + STARTOVER: + + pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); + + /* + * Generate a k-bit random number with top and bottom bits set. + * Alternatively, if `factor' is nonzero, generate a k-bit + * random number with the top bit set and the bottom bit clear, + * multiply it by `factor', and add one. + */ + p = bn_power_2(bits - 1); + for (i = 0; i < bits; i++) { + if (i == 0 || i == bits - 1) { + v = (i != 0 || !factor) ? 1 : 0; + } else if (i >= bits - fbsize) { + v = (firstbits >> (i - (bits - fbsize))) & 1; + } else { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + } + bignum_set_bit(p, i, v); + } + if (factor) { + Bignum tmp = p; + p = bigmul(tmp, factor); + freebn(tmp); + assert(bignum_bit(p, 0) == 0); + bignum_set_bit(p, 0, 1); + } + + /* + * Ensure this random number is coprime to the first few + * primes, by repeatedly adding either 2 or 2*factor to it + * until it is. + */ + for (i = 0; i < NPRIMES; i++) { + moduli[i] = primes[i]; + residues[i] = bignum_mod_short(p, primes[i]); + if (factor) + multipliers[i] = bignum_mod_short(factor, primes[i]); + else + multipliers[i] = 1; + } + moduli[NPRIMES] = modulus; + residues[NPRIMES] = (bignum_mod_short(p, (unsigned short) modulus) + + modulus - residue); + if (factor) + multipliers[NPRIMES] = bignum_mod_short(factor, modulus); + else + multipliers[NPRIMES] = 1; + delta = 0; + while (1) { + for (i = 0; i < (sizeof(moduli) / sizeof(*moduli)); i++) + if (!((residues[i] + delta * multipliers[i]) % moduli[i])) + break; + if (i < (sizeof(moduli) / sizeof(*moduli))) { /* we broke */ + delta += 2; + if (delta > 65536) { + freebn(p); + goto STARTOVER; + } + continue; + } + break; + } + q = p; + if (factor) { + Bignum tmp; + tmp = bignum_from_long(delta); + p = bigmuladd(tmp, factor, q); + freebn(tmp); + } else { + p = bignum_add_long(q, delta); + } + freebn(q); + + /* + * Now apply the Miller-Rabin primality test a few times. First + * work out how many checks are needed. + */ + checks = 27; + if (bits >= 150) + checks = 18; + if (bits >= 200) + checks = 15; + if (bits >= 250) + checks = 12; + if (bits >= 300) + checks = 9; + if (bits >= 350) + checks = 8; + if (bits >= 400) + checks = 7; + if (bits >= 450) + checks = 6; + if (bits >= 550) + checks = 5; + if (bits >= 650) + checks = 4; + if (bits >= 850) + checks = 3; + if (bits >= 1300) + checks = 2; + + /* + * Next, write p-1 as q*2^k. + */ + for (k = 0; bignum_bit(p, k) == !k; k++) + continue; /* find first 1 bit in p-1 */ + q = bignum_rshift(p, k); + /* And store p-1 itself, which we'll need. */ + pm1 = copybn(p); + decbn(pm1); + + /* + * Now, for each check ... + */ + for (check = 0; check < checks; check++) { + Bignum w; + + /* + * Invent a random number between 1 and p-1 inclusive. + */ + while (1) { + w = bn_power_2(bits - 1); + for (i = 0; i < bits; i++) { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(w, i, v); + } + bn_restore_invariant(w); + if (bignum_cmp(w, p) >= 0 || bignum_cmp(w, Zero) == 0) { + freebn(w); + continue; + } + break; + } + + pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); + + /* + * Compute w^q mod p. + */ + wqp = modpow(w, q, p); + freebn(w); + + /* + * See if this is 1, or if it is -1, or if it becomes -1 + * when squared at most k-1 times. + */ + if (bignum_cmp(wqp, One) == 0 || bignum_cmp(wqp, pm1) == 0) { + freebn(wqp); + continue; + } + for (i = 0; i < k - 1; i++) { + wqp2 = modmul(wqp, wqp, p); + freebn(wqp); + wqp = wqp2; + if (bignum_cmp(wqp, pm1) == 0) + break; + } + if (i < k - 1) { + freebn(wqp); + continue; + } + + /* + * It didn't. Therefore, w is a witness for the + * compositeness of p. + */ + freebn(wqp); + freebn(p); + freebn(pm1); + freebn(q); + goto STARTOVER; + } + + /* + * We have a prime! + */ + freebn(q); + freebn(pm1); + return p; +} + +/* + * Invent a pair of values suitable for use as 'firstbits' in the + * above function, such that their product is at least 2. + * + * This is used for generating both RSA and DSA keys which have + * exactly the specified number of bits rather than one fewer - if you + * generate an a-bit and a b-bit number completely at random and + * multiply them together, you could end up with either an (ab-1)-bit + * number or an (ab)-bit number. The former happens log(2)*2-1 of the + * time (about 39%) and, though actually harmless, every time it + * occurs it has a non-zero probability of sparking a user email along + * the lines of 'Hey, I asked PuTTYgen for a 2048-bit key and I only + * got 2047 bits! Bug!' + */ +void invent_firstbits(unsigned *one, unsigned *two) +{ + /* + * Our criterion is that any number in the range [one,one+1) + * multiplied by any number in the range [two,two+1) should have + * the highest bit set. It should be clear that we can trivially + * test this by multiplying the smallest values in each interval, + * i.e. the ones we actually invented. + */ + do { + *one = 0x100 | random_byte(); + *two = 0x100 | random_byte(); + } while (*one * *two < 0x20000); +} diff --git a/netbox/libs/Putty/sshpubk.c b/netbox/libs/Putty/sshpubk.c new file mode 100644 index 000000000..0946cf409 --- /dev/null +++ b/netbox/libs/Putty/sshpubk.c @@ -0,0 +1,1192 @@ +/* + * Generic SSH public-key handling operations. In particular, + * reading of SSH public-key files, and also the generic `sign' + * operation for SSH-2 (which checks the type of the key and + * dispatches to the appropriate key-type specific function). + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" + +#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n" + +#define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\ + (x)-'a'<26 ? (x)-'a'+26 :\ + (x)-'0'<10 ? (x)-'0'+52 :\ + (x)=='+' ? 62 : \ + (x)=='/' ? 63 : 0 ) + +static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, + char **commentptr, char *passphrase, + const char **error) +{ + unsigned char buf[16384]; + unsigned char keybuf[16]; + int len; + int i, j, ciphertype; + int ret = 0; + struct MD5Context md5c; + char *comment; + + *error = NULL; + + /* Slurp the whole file (minus the header) into a buffer. */ + len = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + if (len < 0 || len == sizeof(buf)) { + *error = "error reading file"; + goto end; /* file too big or not read */ + } + + i = 0; + *error = "file format error"; + + /* + * A zero byte. (The signature includes a terminating NUL.) + */ + if (len - i < 1 || buf[i] != 0) + goto end; + i++; + + /* One byte giving encryption type, and one reserved uint32. */ + if (len - i < 1) + goto end; + ciphertype = buf[i]; + if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES) + goto end; + i++; + if (len - i < 4) + goto end; /* reserved field not present */ + if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0 + || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */ + i += 4; + + /* Now the serious stuff. An ordinary SSH-1 public key. */ + j = makekey(buf + i, len - i, key, NULL, 1); + if (j < 0) + goto end; /* overran */ + i += j; + + /* Next, the comment field. */ + j = toint(GET_32BIT(buf + i)); + i += 4; + if (j < 0 || len - i < j) + goto end; + comment = snewn(j + 1, char); + if (comment) { + memcpy(comment, buf + i, j); + comment[j] = '\0'; + } + i += j; + if (commentptr) + *commentptr = dupstr(comment); + if (key) + key->comment = comment; + else + sfree(comment); + + if (pub_only) { + ret = 1; + goto end; + } + + if (!key) { + ret = ciphertype != 0; + *error = NULL; + goto end; + } + + /* + * Decrypt remainder of buffer. + */ + if (ciphertype) { + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7); + smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ + } + + /* + * We are now in the secret part of the key. The first four + * bytes should be of the form a, b, a, b. + */ + if (len - i < 4) + goto end; + if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) { + *error = "wrong passphrase"; + ret = -1; + goto end; + } + i += 4; + + /* + * After that, we have one further bignum which is our + * decryption exponent, and then the three auxiliary values + * (iqmp, q, p). + */ + j = makeprivate(buf + i, len - i, key); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->iqmp); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->q); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->p); + if (j < 0) goto end; + i += j; + + if (!rsa_verify(key)) { + *error = "rsa_verify failed"; + freersakey(key); + ret = 0; + } else + ret = 1; + + end: + smemclr(buf, sizeof(buf)); /* burn the evidence */ + return ret; +} + +int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase, + const char **errorstr) +{ + FILE *fp; + char buf[64]; + int ret = 0; + const char *error = NULL; + + fp = f_open(filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto end; + } + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + /* + * This routine will take care of calling fclose() for us. + */ + ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error); + fp = NULL; + goto end; + } + + /* + * Otherwise, we have nothing. Return empty-handed. + */ + error = "not an SSH-1 RSA file"; + + end: + if (fp) + fclose(fp); + if ((ret != 1) && errorstr) + *errorstr = error; + return ret; +} + +/* + * See whether an RSA key is encrypted. Return its comment field as + * well. + */ +int rsakey_encrypted(const Filename *filename, char **comment) +{ + FILE *fp; + char buf[64]; + + fp = f_open(filename, "rb", FALSE); + if (!fp) + return 0; /* doesn't even exist */ + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + const char *dummy; + /* + * This routine will take care of calling fclose() for us. + */ + return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy); + } + fclose(fp); + return 0; /* wasn't the right kind of file */ +} + +/* + * Return a malloc'ed chunk of memory containing the public blob of + * an RSA key, as given in the agent protocol (modulus bits, + * exponent, modulus). + */ +int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, + char **commentptr, const char **errorstr) +{ + FILE *fp; + char buf[64]; + struct RSAKey key; + int ret; + const char *error = NULL; + + /* Default return if we fail. */ + *blob = NULL; + *bloblen = 0; + ret = 0; + + fp = f_open(filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto end; + } + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + memset(&key, 0, sizeof(key)); + if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) { + *blob = rsa_public_blob(&key, bloblen); + freersakey(&key); + ret = 1; + } + fp = NULL; /* loadrsakey_main unconditionally closes fp */ + } else { + error = "not an SSH-1 RSA file"; + } + + end: + if (fp) + fclose(fp); + if ((ret != 1) && errorstr) + *errorstr = error; + return ret; +} + +/* + * Save an RSA key file. Return nonzero on success. + */ +int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase) +{ + unsigned char buf[16384]; + unsigned char keybuf[16]; + struct MD5Context md5c; + unsigned char *p, *estart; + FILE *fp; + + /* + * Write the initial signature. + */ + p = buf; + memcpy(p, rsa_signature, sizeof(rsa_signature)); + p += sizeof(rsa_signature); + + /* + * One byte giving encryption type, and one reserved (zero) + * uint32. + */ + *p++ = (passphrase ? SSH_CIPHER_3DES : 0); + PUT_32BIT(p, 0); + p += 4; + + /* + * An ordinary SSH-1 public key consists of: a uint32 + * containing the bit count, then two bignums containing the + * modulus and exponent respectively. + */ + PUT_32BIT(p, bignum_bitcount(key->modulus)); + p += 4; + p += ssh1_write_bignum(p, key->modulus); + p += ssh1_write_bignum(p, key->exponent); + + /* + * A string containing the comment field. + */ + if (key->comment) { + PUT_32BIT(p, strlen(key->comment)); + p += 4; + memcpy(p, key->comment, strlen(key->comment)); + p += strlen(key->comment); + } else { + PUT_32BIT(p, 0); + p += 4; + } + + /* + * The encrypted portion starts here. + */ + estart = p; + + /* + * Two bytes, then the same two bytes repeated. + */ + *p++ = random_byte(); + *p++ = random_byte(); + p[0] = p[-2]; + p[1] = p[-1]; + p += 2; + + /* + * Four more bignums: the decryption exponent, then iqmp, then + * q, then p. + */ + p += ssh1_write_bignum(p, key->private_exponent); + p += ssh1_write_bignum(p, key->iqmp); + p += ssh1_write_bignum(p, key->q); + p += ssh1_write_bignum(p, key->p); + + /* + * Now write zeros until the encrypted portion is a multiple of + * 8 bytes. + */ + while ((p - estart) % 8) + *p++ = '\0'; + + /* + * Now encrypt the encrypted portion. + */ + if (passphrase) { + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + des3_encrypt_pubkey(keybuf, estart, p - estart); + smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ + } + + /* + * Done. Write the result to the file. + */ + fp = f_open(filename, "wb", TRUE); + if (fp) { + int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf)); + if (fclose(fp)) + ret = 0; + return ret; + } else + return 0; +} + +/* ---------------------------------------------------------------------- + * SSH-2 private key load/store functions. + */ + +/* + * PuTTY's own format for SSH-2 keys is as follows: + * + * The file is text. Lines are terminated by CRLF, although CR-only + * and LF-only are tolerated on input. + * + * The first line says "PuTTY-User-Key-File-2: " plus the name of the + * algorithm ("ssh-dss", "ssh-rsa" etc). + * + * The next line says "Encryption: " plus an encryption type. + * Currently the only supported encryption types are "aes256-cbc" + * and "none". + * + * The next line says "Comment: " plus the comment string. + * + * Next there is a line saying "Public-Lines: " plus a number N. + * The following N lines contain a base64 encoding of the public + * part of the key. This is encoded as the standard SSH-2 public key + * blob (with no initial length): so for RSA, for example, it will + * read + * + * string "ssh-rsa" + * mpint exponent + * mpint modulus + * + * Next, there is a line saying "Private-Lines: " plus a number N, + * and then N lines containing the (potentially encrypted) private + * part of the key. For the key type "ssh-rsa", this will be + * composed of + * + * mpint private_exponent + * mpint p (the larger of the two primes) + * mpint q (the smaller prime) + * mpint iqmp (the inverse of q modulo p) + * data padding (to reach a multiple of the cipher block size) + * + * And for "ssh-dss", it will be composed of + * + * mpint x (the private key parameter) + * [ string hash 20-byte hash of mpints p || q || g only in old format ] + * + * Finally, there is a line saying "Private-MAC: " plus a hex + * representation of a HMAC-SHA-1 of: + * + * string name of algorithm ("ssh-dss", "ssh-rsa") + * string encryption type + * string comment + * string public-blob + * string private-plaintext (the plaintext version of the + * private part, including the final + * padding) + * + * The key to the MAC is itself a SHA-1 hash of: + * + * data "putty-private-key-file-mac-key" + * data passphrase + * + * (An empty passphrase is used for unencrypted keys.) + * + * If the key is encrypted, the encryption key is derived from the + * passphrase by means of a succession of SHA-1 hashes. Each hash + * is the hash of: + * + * uint32 sequence-number + * data passphrase + * + * where the sequence-number increases from zero. As many of these + * hashes are used as necessary. + * + * For backwards compatibility with snapshots between 0.51 and + * 0.52, we also support the older key file format, which begins + * with "PuTTY-User-Key-File-1" (version number differs). In this + * format the Private-MAC: field only covers the private-plaintext + * field and nothing else (and without the 4-byte string length on + * the front too). Moreover, the Private-MAC: field can be replaced + * with a Private-Hash: field which is a plain SHA-1 hash instead of + * an HMAC (this was generated for unencrypted keys). + */ + +static int read_header(FILE * fp, char *header) +{ + int len = 39; + int c; + + while (1) { + c = fgetc(fp); + if (c == '\n' || c == '\r' || c == EOF) + return 0; /* failure */ + if (c == ':') { + c = fgetc(fp); + if (c != ' ') + return 0; + *header = '\0'; + return 1; /* success! */ + } + if (len == 0) + return 0; /* failure */ + *header++ = c; + len--; + } + return 0; /* failure */ +} + +static char *read_body(FILE * fp) +{ + char *text; + int len; + int size; + int c; + + size = 128; + text = snewn(size, char); + len = 0; + text[len] = '\0'; + + while (1) { + c = fgetc(fp); + if (c == '\r' || c == '\n' || c == EOF) { + if (c != EOF) { + c = fgetc(fp); + if (c != '\r' && c != '\n') + ungetc(c, fp); + } + return text; + } + if (len + 1 >= size) { + size += 128; + text = sresize(text, size, char); + } + text[len++] = c; + text[len] = '\0'; + } +} + +static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) +{ + unsigned char *blob; + char *line; + int linelen, len; + int i, j, k; + + /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */ + blob = snewn(48 * nlines, unsigned char); + len = 0; + for (i = 0; i < nlines; i++) { + line = read_body(fp); + if (!line) { + sfree(blob); + return NULL; + } + linelen = strlen(line); + if (linelen % 4 != 0 || linelen > 64) { + sfree(blob); + sfree(line); + return NULL; + } + for (j = 0; j < linelen; j += 4) { + k = base64_decode_atom(line + j, blob + len); + if (!k) { + sfree(line); + sfree(blob); + return NULL; + } + len += k; + } + sfree(line); + } + *bloblen = len; + return blob; +} + +/* + * Magic error return value for when the passphrase is wrong. + */ +struct ssh2_userkey ssh2_wrong_passphrase = { + NULL, NULL, NULL +}; + +const struct ssh_signkey *find_pubkey_alg(const char *name) +{ + if (!strcmp(name, "ssh-rsa")) + return &ssh_rsa; + else if (!strcmp(name, "ssh-dss")) + return &ssh_dss; + else + return NULL; +} + +struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, + char *passphrase, const char **errorstr) +{ + FILE *fp; + char header[40], *b, *encryption, *comment, *mac; + const struct ssh_signkey *alg; + struct ssh2_userkey *ret; + int cipher, cipherblk; + unsigned char *public_blob, *private_blob; + int public_blob_len, private_blob_len; + int i, is_mac, old_fmt; + int passlen = passphrase ? strlen(passphrase) : 0; + const char *error = NULL; + + ret = NULL; /* return NULL for most errors */ + encryption = comment = mac = NULL; + public_blob = private_blob = NULL; + + fp = f_open(filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto error; + } + + /* Read the first header line which contains the key type. */ + if (!read_header(fp, header)) + goto error; + if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { + old_fmt = 0; + } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { + /* this is an old key file; warn and then continue */ + old_keyfile_warning(); + old_fmt = 1; + } else if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) { + /* this is a key file FROM THE FUTURE; refuse it, but with a + * more specific error message than the generic one below */ + error = "PuTTY key format too new"; + goto error; + } else { + error = "not a PuTTY SSH-2 private key"; + goto error; + } + error = "file format error"; + if ((b = read_body(fp)) == NULL) + goto error; + /* Select key algorithm structure. */ + alg = find_pubkey_alg(b); + if (!alg) { + sfree(b); + goto error; + } + sfree(b); + + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) + goto error; + if ((encryption = read_body(fp)) == NULL) + goto error; + if (!strcmp(encryption, "aes256-cbc")) { + cipher = 1; + cipherblk = 16; + } else if (!strcmp(encryption, "none")) { + cipher = 0; + cipherblk = 1; + } else { + goto error; + } + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) + goto error; + if ((comment = read_body(fp)) == NULL) + goto error; + + /* Read the Public-Lines header line and the public blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + goto error; + + /* Read the Private-Lines header line and the Private blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL) + goto error; + + /* Read the Private-MAC or Private-Hash header line. */ + if (!read_header(fp, header)) + goto error; + if (0 == strcmp(header, "Private-MAC")) { + if ((mac = read_body(fp)) == NULL) + goto error; + is_mac = 1; + } else if (0 == strcmp(header, "Private-Hash") && old_fmt) { + if ((mac = read_body(fp)) == NULL) + goto error; + is_mac = 0; + } else + goto error; + + fclose(fp); + fp = NULL; + + /* + * Decrypt the private blob. + */ + if (cipher) { + unsigned char key[40]; + SHA_State s; + + if (!passphrase) + goto error; + if (private_blob_len % cipherblk) + goto error; + + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, "\0\0\0\0", 4); + putty_SHA_Bytes(&s, passphrase, passlen); + putty_SHA_Final(&s, key + 0); + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, "\0\0\0\1", 4); + putty_SHA_Bytes(&s, passphrase, passlen); + putty_SHA_Final(&s, key + 20); + aes256_decrypt_pubkey(key, private_blob, private_blob_len); + } + + /* + * Verify the MAC. + */ + { + char realmac[41]; + unsigned char binary[20]; + unsigned char *macdata; + int maclen; + int free_macdata; + + if (old_fmt) { + /* MAC (or hash) only covers the private blob. */ + macdata = private_blob; + maclen = private_blob_len; + free_macdata = 0; + } else { + unsigned char *p; + int namelen = strlen(alg->name); + int enclen = strlen(encryption); + int commlen = strlen(comment); + maclen = (4 + namelen + + 4 + enclen + + 4 + commlen + + 4 + public_blob_len + + 4 + private_blob_len); + macdata = snewn(maclen, unsigned char); + p = macdata; +#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) + DO_STR(alg->name, namelen); + DO_STR(encryption, enclen); + DO_STR(comment, commlen); + DO_STR(public_blob, public_blob_len); + DO_STR(private_blob, private_blob_len); + + free_macdata = 1; + } + + if (is_mac) { + SHA_State s; + unsigned char mackey[20]; + char header[] = "putty-private-key-file-mac-key"; + + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, header, sizeof(header)-1); + if (cipher && passphrase) + putty_SHA_Bytes(&s, passphrase, passlen); + putty_SHA_Final(&s, mackey); + + hmac_sha1_simple(mackey, 20, macdata, maclen, binary); + + smemclr(mackey, sizeof(mackey)); + smemclr(&s, sizeof(s)); + } else { + putty_SHA_Simple(macdata, maclen, binary); + } + + if (free_macdata) { + smemclr(macdata, maclen); + sfree(macdata); + } + + for (i = 0; i < 20; i++) + sprintf(realmac + 2 * i, "%02x", binary[i]); + + if (strcmp(mac, realmac)) { + /* An incorrect MAC is an unconditional Error if the key is + * unencrypted. Otherwise, it means Wrong Passphrase. */ + if (cipher) { + error = "wrong passphrase"; + ret = SSH2_WRONG_PASSPHRASE; + } else { + error = "MAC failed"; + ret = NULL; + } + goto error; + } + } + sfree(mac); + mac = NULL; + + /* + * Create and return the key. + */ + ret = snew(struct ssh2_userkey); + ret->alg = alg; + ret->comment = comment; + ret->data = alg->createkey(public_blob, public_blob_len, + private_blob, private_blob_len); + if (!ret->data) { + sfree(ret); + ret = NULL; + error = "createkey failed"; + goto error; + } + sfree(public_blob); + smemclr(private_blob, private_blob_len); + sfree(private_blob); + sfree(encryption); + if (errorstr) + *errorstr = NULL; + return ret; + + /* + * Error processing. + */ + error: + if (fp) + fclose(fp); + if (comment) + sfree(comment); + if (encryption) + sfree(encryption); + if (mac) + sfree(mac); + if (public_blob) + sfree(public_blob); + if (private_blob) { + smemclr(private_blob, private_blob_len); + sfree(private_blob); + } + if (errorstr) + *errorstr = error; + return ret; +} + +unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + int *pub_blob_len, char **commentptr, + const char **errorstr) +{ + FILE *fp; + char header[40], *b; + const struct ssh_signkey *alg; + unsigned char *public_blob; + int public_blob_len; + int i; + const char *error = NULL; + char *comment = NULL; + + public_blob = NULL; + + fp = f_open(filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto error; + } + + /* Read the first header line which contains the key type. */ + if (!read_header(fp, header) + || (0 != strcmp(header, "PuTTY-User-Key-File-2") && + 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { + if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) + error = "PuTTY key format too new"; + else + error = "not a PuTTY SSH-2 private key"; + goto error; + } + error = "file format error"; + if ((b = read_body(fp)) == NULL) + goto error; + /* Select key algorithm structure. */ + alg = find_pubkey_alg(b); + sfree(b); + if (!alg) { + goto error; + } + + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + sfree(b); /* we don't care */ + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) + goto error; + if ((comment = read_body(fp)) == NULL) + goto error; + + if (commentptr) + *commentptr = comment; + else + sfree(comment); + + /* Read the Public-Lines header line and the public blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + goto error; + + fclose(fp); + if (pub_blob_len) + *pub_blob_len = public_blob_len; + if (algorithm) + *algorithm = alg->name; + return public_blob; + + /* + * Error processing. + */ + error: + if (fp) + fclose(fp); + if (public_blob) + sfree(public_blob); + if (errorstr) + *errorstr = error; + if (comment && commentptr) { + sfree(comment); + *commentptr = NULL; + } + return NULL; +} + +int ssh2_userkey_encrypted(const Filename *filename, char **commentptr) +{ + FILE *fp; + char header[40], *b, *comment; + int ret; + + if (commentptr) + *commentptr = NULL; + + fp = f_open(filename, "rb", FALSE); + if (!fp) + return 0; + if (!read_header(fp, header) + || (0 != strcmp(header, "PuTTY-User-Key-File-2") && + 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { + fclose(fp); + return 0; + } + if ((b = read_body(fp)) == NULL) { + fclose(fp); + return 0; + } + sfree(b); /* we don't care about key type here */ + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) { + fclose(fp); + return 0; + } + if ((b = read_body(fp)) == NULL) { + fclose(fp); + return 0; + } + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) { + fclose(fp); + sfree(b); + return 1; + } + if ((comment = read_body(fp)) == NULL) { + fclose(fp); + sfree(b); + return 1; + } + + if (commentptr) + *commentptr = comment; + else + sfree(comment); + + fclose(fp); + if (!strcmp(b, "aes256-cbc")) + ret = 1; + else + ret = 0; + sfree(b); + return ret; +} + +int base64_lines(int datalen) +{ + /* When encoding, we use 64 chars/line, which equals 48 real chars. */ + return (datalen + 47) / 48; +} + +void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl) +{ + int linelen = 0; + char out[4]; + int n, i; + + while (datalen > 0) { + n = (datalen < 3 ? datalen : 3); + base64_encode_atom(data, n, out); + data += n; + datalen -= n; + for (i = 0; i < 4; i++) { + if (linelen >= cpl) { + linelen = 0; + fputc('\n', fp); + } + fputc(out[i], fp); + linelen++; + } + } + fputc('\n', fp); +} + +int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + FILE *fp; + unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted; + int pub_blob_len, priv_blob_len, priv_encrypted_len; + int passlen; + int cipherblk; + int i; + char *cipherstr; + unsigned char priv_mac[20]; + + /* + * Fetch the key component blobs. + */ + pub_blob = key->alg->public_blob(key->data, &pub_blob_len); + priv_blob = key->alg->private_blob(key->data, &priv_blob_len); + if (!pub_blob || !priv_blob) { + sfree(pub_blob); + sfree(priv_blob); + return 0; + } + + /* + * Determine encryption details, and encrypt the private blob. + */ + if (passphrase) { + cipherstr = "aes256-cbc"; + cipherblk = 16; + } else { + cipherstr = "none"; + cipherblk = 1; + } + priv_encrypted_len = priv_blob_len + cipherblk - 1; + priv_encrypted_len -= priv_encrypted_len % cipherblk; + priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char); + memset(priv_blob_encrypted, 0, priv_encrypted_len); + memcpy(priv_blob_encrypted, priv_blob, priv_blob_len); + /* Create padding based on the SHA hash of the unpadded blob. This prevents + * too easy a known-plaintext attack on the last block. */ + putty_SHA_Simple(priv_blob, priv_blob_len, priv_mac); + assert(priv_encrypted_len - priv_blob_len < 20); + memcpy(priv_blob_encrypted + priv_blob_len, priv_mac, + priv_encrypted_len - priv_blob_len); + + /* Now create the MAC. */ + { + unsigned char *macdata; + int maclen; + unsigned char *p; + int namelen = strlen(key->alg->name); + int enclen = strlen(cipherstr); + int commlen = strlen(key->comment); + SHA_State s; + unsigned char mackey[20]; + char header[] = "putty-private-key-file-mac-key"; + + maclen = (4 + namelen + + 4 + enclen + + 4 + commlen + + 4 + pub_blob_len + + 4 + priv_encrypted_len); + macdata = snewn(maclen, unsigned char); + p = macdata; +#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) + DO_STR(key->alg->name, namelen); + DO_STR(cipherstr, enclen); + DO_STR(key->comment, commlen); + DO_STR(pub_blob, pub_blob_len); + DO_STR(priv_blob_encrypted, priv_encrypted_len); + + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, header, sizeof(header)-1); + if (passphrase) + putty_SHA_Bytes(&s, passphrase, strlen(passphrase)); + putty_SHA_Final(&s, mackey); + hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac); + smemclr(macdata, maclen); + sfree(macdata); + smemclr(mackey, sizeof(mackey)); + smemclr(&s, sizeof(s)); + } + + if (passphrase) { + unsigned char key[40]; + SHA_State s; + + passlen = strlen(passphrase); + + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, "\0\0\0\0", 4); + putty_SHA_Bytes(&s, passphrase, passlen); + putty_SHA_Final(&s, key + 0); + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, "\0\0\0\1", 4); + putty_SHA_Bytes(&s, passphrase, passlen); + putty_SHA_Final(&s, key + 20); + aes256_encrypt_pubkey(key, priv_blob_encrypted, + priv_encrypted_len); + + smemclr(key, sizeof(key)); + smemclr(&s, sizeof(s)); + } + + fp = f_open(filename, "w", TRUE); + if (!fp) { + sfree(pub_blob); + smemclr(priv_blob, priv_blob_len); + sfree(priv_blob); + smemclr(priv_blob_encrypted, priv_blob_len); + sfree(priv_blob_encrypted); + return 0; + } + fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name); + fprintf(fp, "Encryption: %s\n", cipherstr); + fprintf(fp, "Comment: %s\n", key->comment); + fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len)); + base64_encode(fp, pub_blob, pub_blob_len, 64); + fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); + base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64); + fprintf(fp, "Private-MAC: "); + for (i = 0; i < 20; i++) + fprintf(fp, "%02x", priv_mac[i]); + fprintf(fp, "\n"); + fclose(fp); + + sfree(pub_blob); + smemclr(priv_blob, priv_blob_len); + sfree(priv_blob); + smemclr(priv_blob_encrypted, priv_blob_len); + sfree(priv_blob_encrypted); + return 1; +} + +/* ---------------------------------------------------------------------- + * A function to determine the type of a private key file. Returns + * 0 on failure, 1 or 2 on success. + */ +int key_type(const Filename *filename) +{ + FILE *fp; + char buf[32]; + const char putty2_sig[] = "PuTTY-User-Key-File-"; + const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT"; + const char openssh_sig[] = "-----BEGIN "; + int i; + + fp = f_open(filename, "r", FALSE); + if (!fp) + return SSH_KEYTYPE_UNOPENABLE; + i = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + if (i < 0) + return SSH_KEYTYPE_UNOPENABLE; + if (i < 32) + return SSH_KEYTYPE_UNKNOWN; + if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1)) + return SSH_KEYTYPE_SSH1; + if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1)) + return SSH_KEYTYPE_SSH2; + if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1)) + return SSH_KEYTYPE_OPENSSH; + if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1)) + return SSH_KEYTYPE_SSHCOM; + return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */ +} + +/* + * Convert the type word to a string, for `wrong type' error + * messages. + */ +char *key_type_to_str(int type) +{ + switch (type) { + case SSH_KEYTYPE_UNOPENABLE: return "unable to open file"; break; + case SSH_KEYTYPE_UNKNOWN: return "not a private key"; break; + case SSH_KEYTYPE_SSH1: return "SSH-1 private key"; break; + case SSH_KEYTYPE_SSH2: return "PuTTY SSH-2 private key"; break; + case SSH_KEYTYPE_OPENSSH: return "OpenSSH SSH-2 private key"; break; + case SSH_KEYTYPE_SSHCOM: return "ssh.com SSH-2 private key"; break; + default: return "INTERNAL ERROR"; break; + } +} diff --git a/netbox/libs/Putty/sshrand.c b/netbox/libs/Putty/sshrand.c new file mode 100644 index 000000000..ead39a9bd --- /dev/null +++ b/netbox/libs/Putty/sshrand.c @@ -0,0 +1,328 @@ +/* + * cryptographic random number generator for PuTTY's ssh client + */ + +#include "putty.h" +#include "ssh.h" +#include + +/* Collect environmental noise every 5 minutes */ +#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC) + +void noise_get_heavy(void (*func) (void *, int)); +void noise_get_light(void (*func) (void *, int)); + +/* + * `pool' itself is a pool of random data which we actually use: we + * return bytes from `pool', at position `poolpos', until `poolpos' + * reaches the end of the pool. At this point we generate more + * random data, by adding noise, stirring well, and resetting + * `poolpos' to point to just past the beginning of the pool (not + * _the_ beginning, since otherwise we'd give away the whole + * contents of our pool, and attackers would just have to guess the + * next lot of noise). + * + * `incomingb' buffers acquired noise data, until it gets full, at + * which point the acquired noise is SHA'ed into `incoming' and + * `incomingb' is cleared. The noise in `incoming' is used as part + * of the noise for each stirring of the pool, in addition to local + * time, process listings, and other such stuff. + */ + +#define HASHINPUT 64 /* 64 bytes SHA input */ +#define HASHSIZE 20 /* 160 bits SHA output */ +#define POOLSIZE 1200 /* size of random pool */ + +struct RandPool { + unsigned char pool[POOLSIZE]; + int poolpos; + + unsigned char incoming[HASHSIZE]; + + unsigned char incomingb[HASHINPUT]; + int incomingpos; + + int stir_pending; +}; + +static struct RandPool pool; +int random_active = 0; +long next_noise_collection; + +#ifdef RANDOM_DIAGNOSTICS +int random_diagnostics = 0; +#endif + +static void random_stir(void) +{ + word32 block[HASHINPUT / sizeof(word32)]; + word32 digest[HASHSIZE / sizeof(word32)]; + int i, j, k; + + /* + * noise_get_light will call random_add_noise, which may call + * back to here. Prevent recursive stirs. + */ + if (pool.stir_pending) + return; + pool.stir_pending = TRUE; + + noise_get_light(random_add_noise); + +#ifdef RANDOM_DIAGNOSTICS + { + int p, q; + printf("random stir starting\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + random_diagnostics++; + } +#endif + + SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + pool.incomingpos = 0; + + /* + * Chunks of this code are blatantly endianness-dependent, but + * as it's all random bits anyway, WHO CARES? + */ + memcpy(digest, pool.incoming, sizeof(digest)); + + /* + * Make two passes over the pool. + */ + for (i = 0; i < 2; i++) { + + /* + * We operate SHA in CFB mode, repeatedly adding the same + * block of data to the digest. But we're also fiddling + * with the digest-so-far, so this shouldn't be Bad or + * anything. + */ + memcpy(block, pool.pool, sizeof(block)); + + /* + * Each pass processes the pool backwards in blocks of + * HASHSIZE, just so that in general we get the output of + * SHA before the corresponding input, in the hope that + * things will be that much less predictable that way + * round, when we subsequently return bytes ... + */ + for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) { + /* + * XOR the bit of the pool we're processing into the + * digest. + */ + + for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) + digest[k] ^= ((word32 *) (pool.pool + j))[k]; + + /* + * Munge our unrevealed first block of the pool into + * it. + */ + SHATransform(digest, block); + + /* + * Stick the result back into the pool. + */ + + for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) + ((word32 *) (pool.pool + j))[k] = digest[k]; + } + +#ifdef RANDOM_DIAGNOSTICS + if (i == 0) { + int p, q; + printf("random stir midpoint\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + } +#endif + } + + /* + * Might as well save this value back into `incoming', just so + * there'll be some extra bizarreness there. + */ + SHATransform(digest, block); + memcpy(pool.incoming, digest, sizeof(digest)); + + pool.poolpos = sizeof(pool.incoming); + + pool.stir_pending = FALSE; + +#ifdef RANDOM_DIAGNOSTICS + { + int p, q; + printf("random stir done\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + random_diagnostics--; + } +#endif +} + +void random_add_noise(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + if (!random_active) + return; + + /* + * This function processes HASHINPUT bytes into only HASHSIZE + * bytes, so _if_ we were getting incredibly high entropy + * sources then we would be throwing away valuable stuff. + */ + while (length >= (HASHINPUT - pool.incomingpos)) { + memcpy(pool.incomingb + pool.incomingpos, p, + HASHINPUT - pool.incomingpos); + p += HASHINPUT - pool.incomingpos; + length -= HASHINPUT - pool.incomingpos; + SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + for (i = 0; i < HASHSIZE; i++) { + pool.pool[pool.poolpos++] ^= pool.incomingb[i]; + if (pool.poolpos >= POOLSIZE) + pool.poolpos = 0; + } + if (pool.poolpos < HASHSIZE) + random_stir(); + + pool.incomingpos = 0; + } + + memcpy(pool.incomingb + pool.incomingpos, p, length); + pool.incomingpos += length; +} + +void random_add_heavynoise(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE) { + for (i = 0; i < POOLSIZE; i++) + pool.pool[i] ^= *p++; + random_stir(); + length -= POOLSIZE; + } + + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + random_stir(); +} + +static void random_add_heavynoise_bitbybit(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE - pool.poolpos) { + for (i = 0; i < POOLSIZE - pool.poolpos; i++) + pool.pool[pool.poolpos + i] ^= *p++; + random_stir(); + length -= POOLSIZE - pool.poolpos; + pool.poolpos = 0; + } + + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + pool.poolpos = i; +} + +static void random_timer(void *ctx, unsigned long now) +{ + if (random_active > 0 && now == next_noise_collection) { + noise_regular(); + next_noise_collection = + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); + } +} + +void random_ref(void) +{ + if (!random_active) { + memset(&pool, 0, sizeof(pool)); /* just to start with */ + + noise_get_heavy(random_add_heavynoise_bitbybit); + random_stir(); + + next_noise_collection = + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); + } + random_active++; +} + +void random_unref(void) +{ + assert(random_active > 0); + if (random_active == 1) { + random_save_seed(); + expire_timer_context(&pool); + } + random_active--; +} + +int random_byte(void) +{ + assert(random_active); + + if (pool.poolpos >= POOLSIZE) + random_stir(); + + return pool.pool[pool.poolpos++]; +} + +void random_get_savedata(void **data, int *len) +{ + void *buf = snewn(POOLSIZE / 2, char); + random_stir(); + memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2); + *len = POOLSIZE / 2; + *data = buf; + random_stir(); +} diff --git a/netbox/libs/Putty/sshrsa.c b/netbox/libs/Putty/sshrsa.c new file mode 100644 index 000000000..380953d09 --- /dev/null +++ b/netbox/libs/Putty/sshrsa.c @@ -0,0 +1,1107 @@ +/* + * RSA implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +int makekey(unsigned char *data, int len, struct RSAKey *result, + unsigned char **keystr, int order) +{ + unsigned char *p = data; + int i, n; + + if (len < 4) + return -1; + + if (result) { + result->bits = 0; + for (i = 0; i < 4; i++) + result->bits = (result->bits << 8) + *p++; + } else + p += 4; + + len -= 4; + + /* + * order=0 means exponent then modulus (the keys sent by the + * server). order=1 means modulus then exponent (the keys + * stored in a keyfile). + */ + + if (order == 0) { + n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); + if (n < 0) return -1; + p += n; + len -= n; + } + + n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL); + if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1; + if (result) + result->bytes = n - 2; + if (keystr) + *keystr = p + 2; + p += n; + len -= n; + + if (order == 1) { + n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); + if (n < 0) return -1; + p += n; + len -= n; + } + return p - data; +} + +int makeprivate(unsigned char *data, int len, struct RSAKey *result) +{ + return ssh1_read_bignum(data, len, &result->private_exponent); +} + +int rsaencrypt(unsigned char *data, int length, struct RSAKey *key) +{ + Bignum b1, b2; + int i; + unsigned char *p; + + if (key->bytes < length + 4) + return 0; /* RSA key too short! */ + + memmove(data + key->bytes - length, data, length); + data[0] = 0; + data[1] = 2; + + for (i = 2; i < key->bytes - length - 1; i++) { + do { + data[i] = random_byte(); + } while (data[i] == 0); + } + data[key->bytes - length - 1] = 0; + + b1 = bignum_from_bytes(data, key->bytes); + + b2 = modpow(b1, key->exponent, key->modulus); + + p = data; + for (i = key->bytes; i--;) { + *p++ = bignum_byte(b2, i); + } + + freebn(b1); + freebn(b2); + + return 1; +} + +static void sha512_mpint(SHA512_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + putty_SHA512_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + putty_SHA512_Bytes(s, lenbuf, 1); + } + smemclr(lenbuf, sizeof(lenbuf)); +} + +/* + * Compute (base ^ exp) % mod, provided mod == p * q, with p,q + * distinct primes, and iqmp is the multiplicative inverse of q mod p. + * Uses Chinese Remainder Theorem to speed computation up over the + * obvious implementation of a single big modpow. + */ +Bignum crt_modpow(Bignum base, Bignum exp, Bignum mod, + Bignum p, Bignum q, Bignum iqmp) +{ + Bignum pm1, qm1, pexp, qexp, presult, qresult, diff, multiplier, ret0, ret; + + /* + * Reduce the exponent mod phi(p) and phi(q), to save time when + * exponentiating mod p and mod q respectively. Of course, since p + * and q are prime, phi(p) == p-1 and similarly for q. + */ + pm1 = copybn(p); + decbn(pm1); + qm1 = copybn(q); + decbn(qm1); + pexp = bigmod(exp, pm1); + qexp = bigmod(exp, qm1); + + /* + * Do the two modpows. + */ + presult = modpow(base, pexp, p); + qresult = modpow(base, qexp, q); + + /* + * Recombine the results. We want a value which is congruent to + * qresult mod q, and to presult mod p. + * + * We know that iqmp * q is congruent to 1 * mod p (by definition + * of iqmp) and to 0 mod q (obviously). So we start with qresult + * (which is congruent to qresult mod both primes), and add on + * (presult-qresult) * (iqmp * q) which adjusts it to be congruent + * to presult mod p without affecting its value mod q. + */ + if (bignum_cmp(presult, qresult) < 0) { + /* + * Can't subtract presult from qresult without first adding on + * p. + */ + Bignum tmp = presult; + presult = bigadd(presult, p); + freebn(tmp); + } + diff = bigsub(presult, qresult); + multiplier = bigmul(iqmp, q); + ret0 = bigmuladd(multiplier, diff, qresult); + + /* + * Finally, reduce the result mod n. + */ + ret = bigmod(ret0, mod); + + /* + * Free all the intermediate results before returning. + */ + freebn(pm1); + freebn(qm1); + freebn(pexp); + freebn(qexp); + freebn(presult); + freebn(qresult); + freebn(diff); + freebn(multiplier); + freebn(ret0); + + return ret; +} + +/* + * This function is a wrapper on modpow(). It has the same effect as + * modpow(), but employs RSA blinding to protect against timing + * attacks and also uses the Chinese Remainder Theorem (implemented + * above, in crt_modpow()) to speed up the main operation. + */ +static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key) +{ + Bignum random, random_encrypted, random_inverse; + Bignum input_blinded, ret_blinded; + Bignum ret; + + SHA512_State ss; + unsigned char digest512[64]; + int digestused = lenof(digest512); + int hashseq = 0; + + /* + * Start by inventing a random number chosen uniformly from the + * range 2..modulus-1. (We do this by preparing a random number + * of the right length and retrying if it's greater than the + * modulus, to prevent any potential Bleichenbacher-like + * attacks making use of the uneven distribution within the + * range that would arise from just reducing our number mod n. + * There are timing implications to the potential retries, of + * course, but all they tell you is the modulus, which you + * already knew.) + * + * To preserve determinism and avoid Pageant needing to share + * the random number pool, we actually generate this `random' + * number by hashing stuff with the private key. + */ + while (1) { + int bits, byte, bitsleft, v; + random = copybn(key->modulus); + /* + * Find the topmost set bit. (This function will return its + * index plus one.) Then we'll set all bits from that one + * downwards randomly. + */ + bits = bignum_bitcount(random); + byte = 0; + bitsleft = 0; + while (bits--) { + if (bitsleft <= 0) { + bitsleft = 8; + /* + * Conceptually the following few lines are equivalent to + * byte = random_byte(); + */ + if (digestused >= lenof(digest512)) { + unsigned char seqbuf[4]; + PUT_32BIT(seqbuf, hashseq); + putty_SHA512_Init(&ss); + putty_SHA512_Bytes(&ss, "RSA deterministic blinding", 26); + putty_SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf)); + sha512_mpint(&ss, key->private_exponent); + putty_SHA512_Final(&ss, digest512); + hashseq++; + + /* + * Now hash that digest plus the signature + * input. + */ + putty_SHA512_Init(&ss); + putty_SHA512_Bytes(&ss, digest512, sizeof(digest512)); + sha512_mpint(&ss, input); + putty_SHA512_Final(&ss, digest512); + + digestused = 0; + } + byte = digest512[digestused++]; + } + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(random, bits, v); + } + bn_restore_invariant(random); + + /* + * Now check that this number is strictly greater than + * zero, and strictly less than modulus. + */ + if (bignum_cmp(random, Zero) <= 0 || + bignum_cmp(random, key->modulus) >= 0) { + freebn(random); + continue; + } + + /* + * Also, make sure it has an inverse mod modulus. + */ + random_inverse = modinv(random, key->modulus); + if (!random_inverse) { + freebn(random); + continue; + } + + break; + } + + /* + * RSA blinding relies on the fact that (xy)^d mod n is equal + * to (x^d mod n) * (y^d mod n) mod n. We invent a random pair + * y and y^d; then we multiply x by y, raise to the power d mod + * n as usual, and divide by y^d to recover x^d. Thus an + * attacker can't correlate the timing of the modpow with the + * input, because they don't know anything about the number + * that was input to the actual modpow. + * + * The clever bit is that we don't have to do a huge modpow to + * get y and y^d; we will use the number we just invented as + * _y^d_, and use the _public_ exponent to compute (y^d)^e = y + * from it, which is much faster to do. + */ + random_encrypted = crt_modpow(random, key->exponent, + key->modulus, key->p, key->q, key->iqmp); + input_blinded = modmul(input, random_encrypted, key->modulus); + ret_blinded = crt_modpow(input_blinded, key->private_exponent, + key->modulus, key->p, key->q, key->iqmp); + ret = modmul(ret_blinded, random_inverse, key->modulus); + + freebn(ret_blinded); + freebn(input_blinded); + freebn(random_inverse); + freebn(random_encrypted); + freebn(random); + + return ret; +} + +Bignum rsadecrypt(Bignum input, struct RSAKey *key) +{ + return rsa_privkey_op(input, key); +} + +int rsastr_len(struct RSAKey *key) +{ + Bignum md, ex; + int mdlen, exlen; + + md = key->modulus; + ex = key->exponent; + mdlen = (bignum_bitcount(md) + 15) / 16; + exlen = (bignum_bitcount(ex) + 15) / 16; + return 4 * (mdlen + exlen) + 20; +} + +void rsastr_fmt(char *str, struct RSAKey *key) +{ + Bignum md, ex; + int len = 0, i, nibbles; + static const char hex[] = "0123456789abcdef"; + + md = key->modulus; + ex = key->exponent; + + len += sprintf(str + len, "0x"); + + nibbles = (3 + bignum_bitcount(ex)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + str[len++] = hex[(bignum_byte(ex, i / 2) >> (4 * (i % 2))) & 0xF]; + + len += sprintf(str + len, ",0x"); + + nibbles = (3 + bignum_bitcount(md)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + str[len++] = hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF]; + + str[len] = '\0'; +} + +/* + * Generate a fingerprint string for the key. Compatible with the + * OpenSSH fingerprint code. + */ +void rsa_fingerprint(char *str, int len, struct RSAKey *key) +{ + struct MD5Context md5c; + unsigned char digest[16]; + char buffer[16 * 3 + 40]; + int numlen, slen, i; + + MD5Init(&md5c); + numlen = ssh1_bignum_length(key->modulus) - 2; + for (i = numlen; i--;) { + unsigned char c = bignum_byte(key->modulus, i); + MD5Update(&md5c, &c, 1); + } + numlen = ssh1_bignum_length(key->exponent) - 2; + for (i = numlen; i--;) { + unsigned char c = bignum_byte(key->exponent, i); + MD5Update(&md5c, &c, 1); + } + MD5Final(digest, &md5c); + + sprintf(buffer, "%d ", bignum_bitcount(key->modulus)); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + strncpy(str, buffer, len); + str[len - 1] = '\0'; + slen = strlen(str); + if (key->comment && slen < len - 1) { + str[slen] = ' '; + strncpy(str + slen + 1, key->comment, len - slen - 1); + str[len - 1] = '\0'; + } +} + +/* + * Verify that the public data in an RSA key matches the private + * data. We also check the private data itself: we ensure that p > + * q and that iqmp really is the inverse of q mod p. + */ +int rsa_verify(struct RSAKey *key) +{ + Bignum n, ed, pm1, qm1; + int cmp; + + /* n must equal pq. */ + n = bigmul(key->p, key->q); + cmp = bignum_cmp(n, key->modulus); + freebn(n); + if (cmp != 0) + return 0; + + /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ + pm1 = copybn(key->p); + decbn(pm1); + ed = modmul(key->exponent, key->private_exponent, pm1); + freebn(pm1); + cmp = bignum_cmp(ed, One); + freebn(ed); + if (cmp != 0) + return 0; + + qm1 = copybn(key->q); + decbn(qm1); + ed = modmul(key->exponent, key->private_exponent, qm1); + freebn(qm1); + cmp = bignum_cmp(ed, One); + freebn(ed); + if (cmp != 0) + return 0; + + /* + * Ensure p > q. + * + * I have seen key blobs in the wild which were generated with + * p < q, so instead of rejecting the key in this case we + * should instead flip them round into the canonical order of + * p > q. This also involves regenerating iqmp. + */ + if (bignum_cmp(key->p, key->q) <= 0) { + Bignum tmp = key->p; + key->p = key->q; + key->q = tmp; + + freebn(key->iqmp); + key->iqmp = modinv(key->q, key->p); + if (!key->iqmp) + return 0; + } + + /* + * Ensure iqmp * q is congruent to 1, modulo p. + */ + n = modmul(key->iqmp, key->q, key->p); + cmp = bignum_cmp(n, One); + freebn(n); + if (cmp != 0) + return 0; + + return 1; +} + +/* Public key blob as used by Pageant: exponent before modulus. */ +unsigned char *rsa_public_blob(struct RSAKey *key, int *len) +{ + int length, pos; + unsigned char *ret; + + length = (ssh1_bignum_length(key->modulus) + + ssh1_bignum_length(key->exponent) + 4); + ret = snewn(length, unsigned char); + + PUT_32BIT(ret, bignum_bitcount(key->modulus)); + pos = 4; + pos += ssh1_write_bignum(ret + pos, key->exponent); + pos += ssh1_write_bignum(ret + pos, key->modulus); + + *len = length; + return ret; +} + +/* Given a public blob, determine its length. */ +int rsa_public_blob_len(void *data, int maxlen) +{ + unsigned char *p = (unsigned char *)data; + int n; + + if (maxlen < 4) + return -1; + p += 4; /* length word */ + maxlen -= 4; + + n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */ + if (n < 0) + return -1; + p += n; + + n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */ + if (n < 0) + return -1; + p += n; + + return p - (unsigned char *)data; +} + +void freersakey(struct RSAKey *key) +{ + if (key->modulus) + freebn(key->modulus); + if (key->exponent) + freebn(key->exponent); + if (key->private_exponent) + freebn(key->private_exponent); + if (key->p) + freebn(key->p); + if (key->q) + freebn(key->q); + if (key->iqmp) + freebn(key->iqmp); + if (key->comment) + sfree(key->comment); +} + +/* ---------------------------------------------------------------------- + * Implementation of the ssh-rsa signing key type. + */ + +static void getstring(char **data, int *datalen, char **p, int *length) +{ + *p = NULL; + if (*datalen < 4) + return; + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; + *datalen -= 4; + *data += 4; + if (*datalen < *length) + return; + *p = *data; + *data += *length; + *datalen -= *length; +} +static Bignum getmp(char **data, int *datalen) +{ + char *p; + int length; + Bignum b; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + b = bignum_from_bytes((unsigned char *)p, length); + return b; +} + +static void rsa2_freekey(void *key); /* forward reference */ + +static void *rsa2_newkey(char *data, int len) +{ + char *p; + int slen; + struct RSAKey *rsa; + + rsa = snew(struct RSAKey); + getstring(&data, &len, &p, &slen); + + if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { + sfree(rsa); + return NULL; + } + rsa->exponent = getmp(&data, &len); + rsa->modulus = getmp(&data, &len); + rsa->private_exponent = NULL; + rsa->p = rsa->q = rsa->iqmp = NULL; + rsa->comment = NULL; + + if (!rsa->exponent || !rsa->modulus) { + rsa2_freekey(rsa); + return NULL; + } + + return rsa; +} + +static void rsa2_freekey(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + freersakey(rsa); + sfree(rsa); +} + +static char *rsa2_fmtkey(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + char *p; + int len; + + len = rsastr_len(rsa); + p = snewn(len, char); + rsastr_fmt(p, rsa); + return p; +} + +static unsigned char *rsa2_public_blob(void *key, int *len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int elen, mlen, bloblen; + int i; + unsigned char *blob, *p; + + elen = (bignum_bitcount(rsa->exponent) + 8) / 8; + mlen = (bignum_bitcount(rsa->modulus) + 8) / 8; + + /* + * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen. + * (three length fields, 12+7=19). + */ + bloblen = 19 + elen + mlen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, 7); + p += 4; + memcpy(p, "ssh-rsa", 7); + p += 7; + PUT_32BIT(p, elen); + p += 4; + for (i = elen; i--;) + *p++ = bignum_byte(rsa->exponent, i); + PUT_32BIT(p, mlen); + p += 4; + for (i = mlen; i--;) + *p++ = bignum_byte(rsa->modulus, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static unsigned char *rsa2_private_blob(void *key, int *len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int dlen, plen, qlen, ulen, bloblen; + int i; + unsigned char *blob, *p; + + dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8; + plen = (bignum_bitcount(rsa->p) + 8) / 8; + qlen = (bignum_bitcount(rsa->q) + 8) / 8; + ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8; + + /* + * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 + + * sum of lengths. + */ + bloblen = 16 + dlen + plen + qlen + ulen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, dlen); + p += 4; + for (i = dlen; i--;) + *p++ = bignum_byte(rsa->private_exponent, i); + PUT_32BIT(p, plen); + p += 4; + for (i = plen; i--;) + *p++ = bignum_byte(rsa->p, i); + PUT_32BIT(p, qlen); + p += 4; + for (i = qlen; i--;) + *p++ = bignum_byte(rsa->q, i); + PUT_32BIT(p, ulen); + p += 4; + for (i = ulen; i--;) + *p++ = bignum_byte(rsa->iqmp, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static void *rsa2_createkey(unsigned char *pub_blob, int pub_len, + unsigned char *priv_blob, int priv_len) +{ + struct RSAKey *rsa; + char *pb = (char *) priv_blob; + + rsa = rsa2_newkey((char *) pub_blob, pub_len); + rsa->private_exponent = getmp(&pb, &priv_len); + rsa->p = getmp(&pb, &priv_len); + rsa->q = getmp(&pb, &priv_len); + rsa->iqmp = getmp(&pb, &priv_len); + + if (!rsa_verify(rsa)) { + rsa2_freekey(rsa); + return NULL; + } + + return rsa; +} + +static void *rsa2_openssh_createkey(unsigned char **blob, int *len) +{ + char **b = (char **) blob; + struct RSAKey *rsa; + + rsa = snew(struct RSAKey); + rsa->comment = NULL; + + rsa->modulus = getmp(b, len); + rsa->exponent = getmp(b, len); + rsa->private_exponent = getmp(b, len); + rsa->iqmp = getmp(b, len); + rsa->p = getmp(b, len); + rsa->q = getmp(b, len); + + if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent || + !rsa->iqmp || !rsa->p || !rsa->q) { + rsa2_freekey(rsa); + return NULL; + } + + if (!rsa_verify(rsa)) { + rsa2_freekey(rsa); + return NULL; + } + + return rsa; +} + +static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int bloblen, i; + + bloblen = + ssh2_bignum_length(rsa->modulus) + + ssh2_bignum_length(rsa->exponent) + + ssh2_bignum_length(rsa->private_exponent) + + ssh2_bignum_length(rsa->iqmp) + + ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q); + + if (bloblen > len) + return bloblen; + + bloblen = 0; +#define ENC(x) \ + PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ + for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); + ENC(rsa->modulus); + ENC(rsa->exponent); + ENC(rsa->private_exponent); + ENC(rsa->iqmp); + ENC(rsa->p); + ENC(rsa->q); + + return bloblen; +} + +static int rsa2_pubkey_bits(void *blob, int len) +{ + struct RSAKey *rsa; + int ret; + + rsa = rsa2_newkey((char *) blob, len); + if (!rsa) + return -1; + ret = bignum_bitcount(rsa->modulus); + rsa2_freekey(rsa); + + return ret; +} + +static char *rsa2_fingerprint(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + struct MD5Context md5c; + unsigned char digest[16], lenbuf[4]; + char buffer[16 * 3 + 40]; + char *ret; + int numlen, i; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-rsa", 11); + +#define ADD_BIGNUM(bignum) \ + numlen = (bignum_bitcount(bignum)+8)/8; \ + PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \ + for (i = numlen; i-- ;) { \ + unsigned char c = bignum_byte(bignum, i); \ + MD5Update(&md5c, &c, 1); \ + } + ADD_BIGNUM(rsa->exponent); + ADD_BIGNUM(rsa->modulus); +#undef ADD_BIGNUM + + MD5Final(digest, &md5c); + + sprintf(buffer, "ssh-rsa %d ", bignum_bitcount(rsa->modulus)); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + ret = snewn(strlen(buffer) + 1, char); + if (ret) + strcpy(ret, buffer); + return ret; +} + +/* + * This is the magic ASN.1/DER prefix that goes in the decoded + * signature, between the string of FFs and the actual SHA hash + * value. The meaning of it is: + * + * 00 -- this marks the end of the FFs; not part of the ASN.1 bit itself + * + * 30 21 -- a constructed SEQUENCE of length 0x21 + * 30 09 -- a constructed sub-SEQUENCE of length 9 + * 06 05 -- an object identifier, length 5 + * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 } + * (the 1,3 comes from 0x2B = 43 = 40*1+3) + * 05 00 -- NULL + * 04 14 -- a primitive OCTET STRING of length 0x14 + * [0x14 bytes of hash data follows] + * + * The object id in the middle there is listed as `id-sha1' in + * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn (the + * ASN module for PKCS #1) and its expanded form is as follows: + * + * id-sha1 OBJECT IDENTIFIER ::= { + * iso(1) identified-organization(3) oiw(14) secsig(3) + * algorithms(2) 26 } + */ +static const unsigned char asn1_weird_stuff[] = { + 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, + 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, +}; + +#define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) ) + +static int rsa2_verifysig(void *key, char *sig, int siglen, + char *data, int datalen) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + Bignum in, out; + char *p; + int slen; + int bytes, i, j, ret; + unsigned char hash[20]; + + getstring(&sig, &siglen, &p, &slen); + if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { + return 0; + } + in = getmp(&sig, &siglen); + if (!in) + return 0; + out = modpow(in, rsa->exponent, rsa->modulus); + freebn(in); + + ret = 1; + + bytes = (bignum_bitcount(rsa->modulus)+7) / 8; + /* Top (partial) byte should be zero. */ + if (bignum_byte(out, bytes - 1) != 0) + ret = 0; + /* First whole byte should be 1. */ + if (bignum_byte(out, bytes - 2) != 1) + ret = 0; + /* Most of the rest should be FF. */ + for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) { + if (bignum_byte(out, i) != 0xFF) + ret = 0; + } + /* Then we expect to see the asn1_weird_stuff. */ + for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) { + if (bignum_byte(out, i) != asn1_weird_stuff[j]) + ret = 0; + } + /* Finally, we expect to see the SHA-1 hash of the signed data. */ + putty_SHA_Simple(data, datalen, hash); + for (i = 19, j = 0; i >= 0; i--, j++) { + if (bignum_byte(out, i) != hash[j]) + ret = 0; + } + freebn(out); + + return ret; +} + +static unsigned char *rsa2_sign(void *key, char *data, int datalen, + int *siglen) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + unsigned char *bytes; + int nbytes; + unsigned char hash[20]; + Bignum in, out; + int i, j; + + putty_SHA_Simple(data, datalen, hash); + + nbytes = (bignum_bitcount(rsa->modulus) - 1) / 8; + assert(1 <= nbytes - 20 - ASN1_LEN); + bytes = snewn(nbytes, unsigned char); + + bytes[0] = 1; + for (i = 1; i < nbytes - 20 - ASN1_LEN; i++) + bytes[i] = 0xFF; + for (i = nbytes - 20 - ASN1_LEN, j = 0; i < nbytes - 20; i++, j++) + bytes[i] = asn1_weird_stuff[j]; + for (i = nbytes - 20, j = 0; i < nbytes; i++, j++) + bytes[i] = hash[j]; + + in = bignum_from_bytes(bytes, nbytes); + sfree(bytes); + + out = rsa_privkey_op(in, rsa); + freebn(in); + + nbytes = (bignum_bitcount(out) + 7) / 8; + bytes = snewn(4 + 7 + 4 + nbytes, unsigned char); + PUT_32BIT(bytes, 7); + memcpy(bytes + 4, "ssh-rsa", 7); + PUT_32BIT(bytes + 4 + 7, nbytes); + for (i = 0; i < nbytes; i++) + bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i); + freebn(out); + + *siglen = 4 + 7 + 4 + nbytes; + return bytes; +} + +const struct ssh_signkey ssh_rsa = { + rsa2_newkey, + rsa2_freekey, + rsa2_fmtkey, + rsa2_public_blob, + rsa2_private_blob, + rsa2_createkey, + rsa2_openssh_createkey, + rsa2_openssh_fmtkey, + rsa2_pubkey_bits, + rsa2_fingerprint, + rsa2_verifysig, + rsa2_sign, + "ssh-rsa", + "rsa2" +}; + +void *ssh_rsakex_newkey(char *data, int len) +{ + return rsa2_newkey(data, len); +} + +void ssh_rsakex_freekey(void *key) +{ + rsa2_freekey(key); +} + +int ssh_rsakex_klen(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + + return bignum_bitcount(rsa->modulus); +} + +static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, + void *vdata, int datalen) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned count = 0; + + while (datalen > 0) { + int i, max = (datalen > h->hlen ? h->hlen : datalen); + void *s; + unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN]; + + assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN); + PUT_32BIT(counter, count); + s = h->init(); + h->bytes(s, seed, seedlen); + h->bytes(s, counter, 4); + h->final(s, hash); + count++; + + for (i = 0; i < max; i++) + data[i] ^= hash[i]; + + data += max; + datalen -= max; + } +} + +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key) +{ + Bignum b1, b2; + struct RSAKey *rsa = (struct RSAKey *) key; + int k, i; + char *p; + const int HLEN = h->hlen; + + /* + * Here we encrypt using RSAES-OAEP. Essentially this means: + * + * - we have a SHA-based `mask generation function' which + * creates a pseudo-random stream of mask data + * deterministically from an input chunk of data. + * + * - we have a random chunk of data called a seed. + * + * - we use the seed to generate a mask which we XOR with our + * plaintext. + * + * - then we use _the masked plaintext_ to generate a mask + * which we XOR with the seed. + * + * - then we concatenate the masked seed and the masked + * plaintext, and RSA-encrypt that lot. + * + * The result is that the data input to the encryption function + * is random-looking and (hopefully) contains no exploitable + * structure such as PKCS1-v1_5 does. + * + * For a precise specification, see RFC 3447, section 7.1.1. + * Some of the variable names below are derived from that, so + * it'd probably help to read it anyway. + */ + + /* k denotes the length in octets of the RSA modulus. */ + k = (7 + bignum_bitcount(rsa->modulus)) / 8; + + /* The length of the input data must be at most k - 2hLen - 2. */ + assert(inlen > 0 && inlen <= k - 2*HLEN - 2); + + /* The length of the output data wants to be precisely k. */ + assert(outlen == k); + + /* + * Now perform EME-OAEP encoding. First set up all the unmasked + * output data. + */ + /* Leading byte zero. */ + out[0] = 0; + /* At position 1, the seed: HLEN bytes of random data. */ + for (i = 0; i < HLEN; i++) + out[i + 1] = random_byte(); + /* At position 1+HLEN, the data block DB, consisting of: */ + /* The hash of the label (we only support an empty label here) */ + h->final(h->init(), out + HLEN + 1); + /* A bunch of zero octets */ + memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1)); + /* A single 1 octet, followed by the input message data. */ + out[outlen - inlen - 1] = 1; + memcpy(out + outlen - inlen, in, inlen); + + /* + * Now use the seed data to mask the block DB. + */ + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* + * And now use the masked DB to mask the seed itself. + */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + + /* + * Now `out' contains precisely the data we want to + * RSA-encrypt. + */ + b1 = bignum_from_bytes(out, outlen); + b2 = modpow(b1, rsa->exponent, rsa->modulus); + p = (char *)out; + for (i = outlen; i--;) { + *p++ = bignum_byte(b2, i); + } + freebn(b1); + freebn(b2); + + /* + * And we're done. + */ +} + +static const struct ssh_kex ssh_rsa_kex_sha1 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1 +}; + +static const struct ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256 +}; + +static const struct ssh_kex *const rsa_kex_list[] = { + &ssh_rsa_kex_sha256, + &ssh_rsa_kex_sha1 +}; + +const struct ssh_kexes ssh_rsa_kex = { + sizeof(rsa_kex_list) / sizeof(*rsa_kex_list), + rsa_kex_list +}; diff --git a/netbox/libs/Putty/sshrsag.c b/netbox/libs/Putty/sshrsag.c new file mode 100644 index 000000000..d754890d8 --- /dev/null +++ b/netbox/libs/Putty/sshrsag.c @@ -0,0 +1,109 @@ +/* + * RSA key generation. + */ + +#include + +#include "ssh.h" + +#define RSA_EXPONENT 37 /* we like this prime */ + +int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, + void *pfnparam) +{ + Bignum pm1, qm1, phi_n; + unsigned pfirst, qfirst; + + /* + * Set up the phase limits for the progress report. We do this + * by passing minus the phase number. + * + * For prime generation: our initial filter finds things + * coprime to everything below 2^16. Computing the product of + * (p-1)/p for all prime p below 2^16 gives about 20.33; so + * among B-bit integers, one in every 20.33 will get through + * the initial filter to be a candidate prime. + * + * Meanwhile, we are searching for primes in the region of 2^B; + * since pi(x) ~ x/log(x), when x is in the region of 2^B, the + * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about + * 1/0.6931B. So the chance of any given candidate being prime + * is 20.33/0.6931B, which is roughly 29.34 divided by B. + * + * So now we have this probability P, we're looking at an + * exponential distribution with parameter P: we will manage in + * one attempt with probability P, in two with probability + * P(1-P), in three with probability P(1-P)^2, etc. The + * probability that we have still not managed to find a prime + * after N attempts is (1-P)^N. + * + * We therefore inform the progress indicator of the number B + * (29.34/B), so that it knows how much to increment by each + * time. We do this in 16-bit fixed point, so 29.34 becomes + * 0x1D.57C4. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x10000); + pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / (bits / 2)); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x10000); + pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / (bits - bits / 2)); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x4000); + pfn(pfnparam, PROGFN_LIN_PHASE, 3, 5); + pfn(pfnparam, PROGFN_READY, 0, 0); + + /* + * We don't generate e; we just use a standard one always. + */ + key->exponent = bignum_from_long(RSA_EXPONENT); + + /* + * Generate p and q: primes with combined length `bits', not + * congruent to 1 modulo e. (Strictly speaking, we wanted (p-1) + * and e to be coprime, and (q-1) and e to be coprime, but in + * general that's slightly more fiddly to arrange. By choosing + * a prime e, we can simplify the criterion.) + */ + invent_firstbits(&pfirst, &qfirst); + key->p = primegen(bits / 2, RSA_EXPONENT, 1, NULL, + 1, pfn, pfnparam, pfirst); + key->q = primegen(bits - bits / 2, RSA_EXPONENT, 1, NULL, + 2, pfn, pfnparam, qfirst); + + /* + * Ensure p > q, by swapping them if not. + */ + if (bignum_cmp(key->p, key->q) < 0) { + Bignum t = key->p; + key->p = key->q; + key->q = t; + } + + /* + * Now we have p, q and e. All we need to do now is work out + * the other helpful quantities: n=pq, d=e^-1 mod (p-1)(q-1), + * and (q^-1 mod p). + */ + pfn(pfnparam, PROGFN_PROGRESS, 3, 1); + key->modulus = bigmul(key->p, key->q); + pfn(pfnparam, PROGFN_PROGRESS, 3, 2); + pm1 = copybn(key->p); + decbn(pm1); + qm1 = copybn(key->q); + decbn(qm1); + phi_n = bigmul(pm1, qm1); + pfn(pfnparam, PROGFN_PROGRESS, 3, 3); + freebn(pm1); + freebn(qm1); + key->private_exponent = modinv(key->exponent, phi_n); + assert(key->private_exponent); + pfn(pfnparam, PROGFN_PROGRESS, 3, 4); + key->iqmp = modinv(key->q, key->p); + assert(key->iqmp); + pfn(pfnparam, PROGFN_PROGRESS, 3, 5); + + /* + * Clean up temporary numbers. + */ + freebn(phi_n); + + return 1; +} diff --git a/netbox/libs/Putty/sshsh256.c b/netbox/libs/Putty/sshsh256.c new file mode 100644 index 000000000..68647b032 --- /dev/null +++ b/netbox/libs/Putty/sshsh256.c @@ -0,0 +1,400 @@ +/* + * SHA-256 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Core SHA256 algorithm: processes 16-word blocks into a message digest. + */ + +#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) ) +#define shr(x,y) ( (((uint32)(x)) >> (y)) ) +#define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) ) +#define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) ) +#define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) ) +#define bigsigma1(x) ( ror((x),6) ^ ror((x),11) ^ ror((x),25) ) +#define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) ) +#define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) ) + +void SHA256_Core_Init(SHA256_State *s) { + s->h[0] = 0x6a09e667; + s->h[1] = 0xbb67ae85; + s->h[2] = 0x3c6ef372; + s->h[3] = 0xa54ff53a; + s->h[4] = 0x510e527f; + s->h[5] = 0x9b05688c; + s->h[6] = 0x1f83d9ab; + s->h[7] = 0x5be0cd19; +} + +void SHA256_Block(SHA256_State *s, uint32 *block) { + uint32 w[80]; + uint32 a,b,c,d,e,f,g,h; + static const int k[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 64; t++) + w[t] = smallsigma1(w[t-2]) + w[t-7] + smallsigma0(w[t-15]) + w[t-16]; + + a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3]; + e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; + + for (t = 0; t < 64; t+=8) { + uint32 t1, t2; + +#define ROUND(j,a,b,c,d,e,f,g,h) \ + t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \ + t2 = bigsigma0(a) + Maj(a,b,c); \ + d = d + t1; h = t1 + t2; + + ROUND(t+0, a,b,c,d,e,f,g,h); + ROUND(t+1, h,a,b,c,d,e,f,g); + ROUND(t+2, g,h,a,b,c,d,e,f); + ROUND(t+3, f,g,h,a,b,c,d,e); + ROUND(t+4, e,f,g,h,a,b,c,d); + ROUND(t+5, d,e,f,g,h,a,b,c); + ROUND(t+6, c,d,e,f,g,h,a,b); + ROUND(t+7, b,c,d,e,f,g,h,a); + } + + s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d; + s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h; +} + +/* ---------------------------------------------------------------------- + * Outer SHA256 algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core SHA256 algorithm. + */ + +#define BLKSIZE 64 + +void putty_SHA256_Init(SHA256_State *s) { + SHA256_Core_Init(s); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void putty_SHA256_Bytes(SHA256_State *s, const void *p, int len) { + unsigned char *q = (unsigned char *)p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused && s->blkused+len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + ( ((uint32)s->block[i*4+0]) << 24 ) | + ( ((uint32)s->block[i*4+1]) << 16 ) | + ( ((uint32)s->block[i*4+2]) << 8 ) | + ( ((uint32)s->block[i*4+3]) << 0 ); + } + SHA256_Block(s, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void putty_SHA256_Final(SHA256_State *s, unsigned char *digest) { + int i; + int pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + putty_SHA256_Bytes(s, &c, pad); + + c[0] = (lenhi >> 24) & 0xFF; + c[1] = (lenhi >> 16) & 0xFF; + c[2] = (lenhi >> 8) & 0xFF; + c[3] = (lenhi >> 0) & 0xFF; + c[4] = (lenlo >> 24) & 0xFF; + c[5] = (lenlo >> 16) & 0xFF; + c[6] = (lenlo >> 8) & 0xFF; + c[7] = (lenlo >> 0) & 0xFF; + + putty_SHA256_Bytes(s, &c, 8); + + for (i = 0; i < 8; i++) { + digest[i*4+0] = (s->h[i] >> 24) & 0xFF; + digest[i*4+1] = (s->h[i] >> 16) & 0xFF; + digest[i*4+2] = (s->h[i] >> 8) & 0xFF; + digest[i*4+3] = (s->h[i] >> 0) & 0xFF; + } +} + +void putty_SHA256_Simple(const void *p, int len, unsigned char *output) { + SHA256_State s; + + putty_SHA256_Init(&s); + putty_SHA256_Bytes(&s, p, len); + putty_SHA256_Final(&s, output); + smemclr(&s, sizeof(s)); +} + +/* + * Thin abstraction for things where hashes are pluggable. + */ + +static void *sha256_init(void) +{ + SHA256_State *s; + + s = snew(SHA256_State); + putty_SHA256_Init(s); + return s; +} + +static void *sha256_copy(const void *vold) +{ + const SHA256_State *old = (const SHA256_State *)vold; + SHA256_State *s; + + s = snew(SHA256_State); + *s = *old; + return s; +} + +static void sha256_free(void *handle) +{ + SHA256_State *s = handle; + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha256_bytes(void *handle, const void *p, int len) +{ + SHA256_State *s = handle; + + putty_SHA256_Bytes(s, p, len); +} + +static void sha256_final(void *handle, unsigned char *output) +{ + SHA256_State *s = handle; + + putty_SHA256_Final(s, output); + sha256_free(s); +} + +const struct ssh_hash ssh_sha256 = { + sha256_init, sha256_bytes, sha256_final, + 32, "SHA-256" +}; + +/* ---------------------------------------------------------------------- + * The above is the SHA-256 algorithm itself. Now we implement the + * HMAC wrapper on it. + */ + +static void *sha256_make_context(void *cipher_ctx) +{ + return snewn(3, SHA256_State); +} + +static void sha256_free_context(void *handle) +{ + smemclr(handle, 3 * sizeof(SHA256_State)); + sfree(handle); +} + +static void sha256_key_internal(void *handle, unsigned char *key, int len) +{ + SHA256_State *keys = (SHA256_State *)handle; + unsigned char foo[64]; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + putty_SHA256_Init(&keys[0]); + putty_SHA256_Bytes(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + putty_SHA256_Init(&keys[1]); + putty_SHA256_Bytes(&keys[1], foo, 64); + + smemclr(foo, 64); /* burn the evidence */ +} + +static void sha256_key(void *handle, unsigned char *key) +{ + sha256_key_internal(handle, key, 32); +} + +static void hmacsha256_start(void *handle) +{ + SHA256_State *keys = (SHA256_State *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacsha256_bytes(void *handle, unsigned char const *blk, int len) +{ + SHA256_State *keys = (SHA256_State *)handle; + putty_SHA256_Bytes(&keys[2], (void *)blk, len); +} + +static void hmacsha256_genresult(void *handle, unsigned char *hmac) +{ + SHA256_State *keys = (SHA256_State *)handle; + SHA256_State s; + unsigned char intermediate[32]; + + s = keys[2]; /* structure copy */ + putty_SHA256_Final(&s, intermediate); + s = keys[1]; /* structure copy */ + putty_SHA256_Bytes(&s, intermediate, 32); + putty_SHA256_Final(&s, hmac); +} + +static void sha256_do_hmac(void *handle, unsigned char *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[4]; + + PUT_32BIT_MSB_FIRST(seqbuf, seq); + hmacsha256_start(handle); + hmacsha256_bytes(handle, seqbuf, 4); + hmacsha256_bytes(handle, blk, len); + hmacsha256_genresult(handle, hmac); +} + +static void sha256_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + sha256_do_hmac(handle, blk, len, seq, blk + len); +} + +static int hmacsha256_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[32]; + hmacsha256_genresult(handle, correct); + return smemeq(correct, hmac, 32); +} + +static int sha256_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[32]; + sha256_do_hmac(handle, blk, len, seq, correct); + return smemeq(correct, blk + len, 32); +} + +const struct ssh_mac ssh_hmac_sha256 = { + sha256_make_context, sha256_free_context, sha256_key, + sha256_generate, sha256_verify, + hmacsha256_start, hmacsha256_bytes, + hmacsha256_genresult, hmacsha256_verresult, + "hmac-sha2-256", + 32, + "HMAC-SHA-256" +}; + +#ifdef TEST + +#include +#include +#include + +int main(void) { + unsigned char digest[32]; + int i, j, errors; + + struct { + const char *teststring; + unsigned char digest[32]; + } tests[] = { + { "abc", { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, + } }, + { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", { + 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, + 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, + 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, + 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1, + } }, + }; + + errors = 0; + + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + putty_SHA256_Simple(tests[i].teststring, + strlen(tests[i].teststring), digest); + for (j = 0; j < 32; j++) { + if (digest[j] != tests[i].digest[j]) { + fprintf(stderr, + "\"%s\" digest byte %d should be 0x%02x, is 0x%02x\n", + tests[i].teststring, j, tests[i].digest[j], digest[j]); + errors++; + } + } + } + + printf("%d errors\n", errors); + + return 0; +} + +#endif diff --git a/netbox/libs/Putty/sshsh512.c b/netbox/libs/Putty/sshsh512.c new file mode 100644 index 000000000..33f79bfc4 --- /dev/null +++ b/netbox/libs/Putty/sshsh512.c @@ -0,0 +1,474 @@ +/* + * SHA-512 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + * + * Modifications made for SHA-384 also + */ + +#include "ssh.h" + +#define BLKSIZE 128 + +/* + * Arithmetic implementations. Note that AND, XOR and NOT can + * overlap destination with one source, but the others can't. + */ +#define add(r,x,y) ( r.lo = y.lo + x.lo, \ + r.hi = y.hi + x.hi + ((uint32)r.lo < (uint32)y.lo) ) +#define rorB(r,x,y) ( r.lo = ((uint32)x.hi >> ((y)-32)) | ((uint32)x.lo << (64-(y))), \ + r.hi = ((uint32)x.lo >> ((y)-32)) | ((uint32)x.hi << (64-(y))) ) +#define rorL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ + r.hi = ((uint32)x.hi >> (y)) | ((uint32)x.lo << (32-(y))) ) +#define shrB(r,x,y) ( r.lo = (uint32)x.hi >> ((y)-32), r.hi = 0 ) +#define shrL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ + r.hi = (uint32)x.hi >> (y) ) +#define _and(r,x,y) ( r.lo = x.lo & y.lo, r.hi = x.hi & y.hi ) +#define _xor(r,x,y) ( r.lo = x.lo ^ y.lo, r.hi = x.hi ^ y.hi ) +#define _not(r,x) ( r.lo = ~x.lo, r.hi = ~x.hi ) +#define INIT(h,l) { h, l } +#define BUILD(r,h,l) ( r.hi = h, r.lo = l ) +#define EXTRACT(h,l,r) ( h = r.hi, l = r.lo ) + +/* ---------------------------------------------------------------------- + * Core SHA512 algorithm: processes 16-doubleword blocks into a + * message digest. + */ + +#define Ch(r,t,x,y,z) ( _not(t,x), _and(r,t,z), _and(t,x,y), _xor(r,r,t) ) +#define Maj(r,t,x,y,z) ( _and(r,x,y), _and(t,x,z), _xor(r,r,t), \ + _and(t,y,z), _xor(r,r,t) ) +#define bigsigma0(r,t,x) ( rorL(r,x,28), rorB(t,x,34), _xor(r,r,t), \ + rorB(t,x,39), _xor(r,r,t) ) +#define bigsigma1(r,t,x) ( rorL(r,x,14), rorL(t,x,18), _xor(r,r,t), \ + rorB(t,x,41), _xor(r,r,t) ) +#define smallsigma0(r,t,x) ( rorL(r,x,1), rorL(t,x,8), _xor(r,r,t), \ + shrL(t,x,7), _xor(r,r,t) ) +#define smallsigma1(r,t,x) ( rorL(r,x,19), rorB(t,x,61), _xor(r,r,t), \ + shrL(t,x,6), _xor(r,r,t) ) + +static void SHA512_Core_Init(SHA512_State *s) { + static const uint64 iv[] = { + INIT(0x6a09e667, 0xf3bcc908), + INIT(0xbb67ae85, 0x84caa73b), + INIT(0x3c6ef372, 0xfe94f82b), + INIT(0xa54ff53a, 0x5f1d36f1), + INIT(0x510e527f, 0xade682d1), + INIT(0x9b05688c, 0x2b3e6c1f), + INIT(0x1f83d9ab, 0xfb41bd6b), + INIT(0x5be0cd19, 0x137e2179), + }; + int i; + for (i = 0; i < 8; i++) + s->h[i] = iv[i]; +} + +static void SHA384_Core_Init(SHA512_State *s) { + static const uint64 iv[] = { + INIT(0xcbbb9d5d, 0xc1059ed8), + INIT(0x629a292a, 0x367cd507), + INIT(0x9159015a, 0x3070dd17), + INIT(0x152fecd8, 0xf70e5939), + INIT(0x67332667, 0xffc00b31), + INIT(0x8eb44a87, 0x68581511), + INIT(0xdb0c2e0d, 0x64f98fa7), + INIT(0x47b5481d, 0xbefa4fa4), + }; + int i; + for (i = 0; i < 8; i++) + s->h[i] = iv[i]; +} + +static void SHA512_Block(SHA512_State *s, uint64 *block) { + uint64 w[80]; + uint64 a,b,c,d,e,f,g,h; + static const uint64 k[] = { + INIT(0x428a2f98, 0xd728ae22), INIT(0x71374491, 0x23ef65cd), + INIT(0xb5c0fbcf, 0xec4d3b2f), INIT(0xe9b5dba5, 0x8189dbbc), + INIT(0x3956c25b, 0xf348b538), INIT(0x59f111f1, 0xb605d019), + INIT(0x923f82a4, 0xaf194f9b), INIT(0xab1c5ed5, 0xda6d8118), + INIT(0xd807aa98, 0xa3030242), INIT(0x12835b01, 0x45706fbe), + INIT(0x243185be, 0x4ee4b28c), INIT(0x550c7dc3, 0xd5ffb4e2), + INIT(0x72be5d74, 0xf27b896f), INIT(0x80deb1fe, 0x3b1696b1), + INIT(0x9bdc06a7, 0x25c71235), INIT(0xc19bf174, 0xcf692694), + INIT(0xe49b69c1, 0x9ef14ad2), INIT(0xefbe4786, 0x384f25e3), + INIT(0x0fc19dc6, 0x8b8cd5b5), INIT(0x240ca1cc, 0x77ac9c65), + INIT(0x2de92c6f, 0x592b0275), INIT(0x4a7484aa, 0x6ea6e483), + INIT(0x5cb0a9dc, 0xbd41fbd4), INIT(0x76f988da, 0x831153b5), + INIT(0x983e5152, 0xee66dfab), INIT(0xa831c66d, 0x2db43210), + INIT(0xb00327c8, 0x98fb213f), INIT(0xbf597fc7, 0xbeef0ee4), + INIT(0xc6e00bf3, 0x3da88fc2), INIT(0xd5a79147, 0x930aa725), + INIT(0x06ca6351, 0xe003826f), INIT(0x14292967, 0x0a0e6e70), + INIT(0x27b70a85, 0x46d22ffc), INIT(0x2e1b2138, 0x5c26c926), + INIT(0x4d2c6dfc, 0x5ac42aed), INIT(0x53380d13, 0x9d95b3df), + INIT(0x650a7354, 0x8baf63de), INIT(0x766a0abb, 0x3c77b2a8), + INIT(0x81c2c92e, 0x47edaee6), INIT(0x92722c85, 0x1482353b), + INIT(0xa2bfe8a1, 0x4cf10364), INIT(0xa81a664b, 0xbc423001), + INIT(0xc24b8b70, 0xd0f89791), INIT(0xc76c51a3, 0x0654be30), + INIT(0xd192e819, 0xd6ef5218), INIT(0xd6990624, 0x5565a910), + INIT(0xf40e3585, 0x5771202a), INIT(0x106aa070, 0x32bbd1b8), + INIT(0x19a4c116, 0xb8d2d0c8), INIT(0x1e376c08, 0x5141ab53), + INIT(0x2748774c, 0xdf8eeb99), INIT(0x34b0bcb5, 0xe19b48a8), + INIT(0x391c0cb3, 0xc5c95a63), INIT(0x4ed8aa4a, 0xe3418acb), + INIT(0x5b9cca4f, 0x7763e373), INIT(0x682e6ff3, 0xd6b2b8a3), + INIT(0x748f82ee, 0x5defb2fc), INIT(0x78a5636f, 0x43172f60), + INIT(0x84c87814, 0xa1f0ab72), INIT(0x8cc70208, 0x1a6439ec), + INIT(0x90befffa, 0x23631e28), INIT(0xa4506ceb, 0xde82bde9), + INIT(0xbef9a3f7, 0xb2c67915), INIT(0xc67178f2, 0xe372532b), + INIT(0xca273ece, 0xea26619c), INIT(0xd186b8c7, 0x21c0c207), + INIT(0xeada7dd6, 0xcde0eb1e), INIT(0xf57d4f7f, 0xee6ed178), + INIT(0x06f067aa, 0x72176fba), INIT(0x0a637dc5, 0xa2c898a6), + INIT(0x113f9804, 0xbef90dae), INIT(0x1b710b35, 0x131c471b), + INIT(0x28db77f5, 0x23047d84), INIT(0x32caab7b, 0x40c72493), + INIT(0x3c9ebe0a, 0x15c9bebc), INIT(0x431d67c4, 0x9c100d4c), + INIT(0x4cc5d4be, 0xcb3e42b6), INIT(0x597f299c, 0xfc657e2a), + INIT(0x5fcb6fab, 0x3ad6faec), INIT(0x6c44198c, 0x4a475817), + }; + + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 80; t++) { + uint64 p, q, r, tmp; + smallsigma1(p, tmp, w[t-2]); + smallsigma0(q, tmp, w[t-15]); + add(r, p, q); + add(p, r, w[t-7]); + add(w[t], p, w[t-16]); + } + + a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3]; + e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; + + for (t = 0; t < 80; t+=8) { + uint64 tmp, p, q, r; + +#define ROUND(j,a,b,c,d,e,f,g,h) \ + bigsigma1(p, tmp, e); \ + Ch(q, tmp, e, f, g); \ + add(r, p, q); \ + add(p, r, k[j]) ; \ + add(q, p, w[j]); \ + add(r, q, h); \ + bigsigma0(p, tmp, a); \ + Maj(tmp, q, a, b, c); \ + add(q, tmp, p); \ + add(p, r, d); \ + d = p; \ + add(h, q, r); + + ROUND(t+0, a,b,c,d,e,f,g,h); + ROUND(t+1, h,a,b,c,d,e,f,g); + ROUND(t+2, g,h,a,b,c,d,e,f); + ROUND(t+3, f,g,h,a,b,c,d,e); + ROUND(t+4, e,f,g,h,a,b,c,d); + ROUND(t+5, d,e,f,g,h,a,b,c); + ROUND(t+6, c,d,e,f,g,h,a,b); + ROUND(t+7, b,c,d,e,f,g,h,a); + } + + { + uint64 tmp; +#define UPDATE(state, local) ( tmp = state, add(state, tmp, local) ) + UPDATE(s->h[0], a); UPDATE(s->h[1], b); + UPDATE(s->h[2], c); UPDATE(s->h[3], d); + UPDATE(s->h[4], e); UPDATE(s->h[5], f); + UPDATE(s->h[6], g); UPDATE(s->h[7], h); + } +} + +/* ---------------------------------------------------------------------- + * Outer SHA512 algorithm: take an arbitrary length byte string, + * convert it into 16-doubleword blocks with the prescribed padding + * at the end, and pass those blocks to the core SHA512 algorithm. + */ + +void putty_SHA512_Init(SHA512_State *s) { + int i; + SHA512_Core_Init(s); + s->blkused = 0; + for (i = 0; i < 4; i++) + s->len[i] = 0; +} + +void putty_SHA384_Init(SHA512_State *s) { + int i; + SHA384_Core_Init(s); + s->blkused = 0; + for (i = 0; i < 4; i++) + s->len[i] = 0; +} + +void putty_SHA512_Bytes(SHA512_State *s, const void *p, int len) { + unsigned char *q = (unsigned char *)p; + uint64 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + for (i = 0; i < 4; i++) { + s->len[i] += lenw; + lenw = (s->len[i] < lenw); + } + + if (s->blkused && s->blkused+len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + uint32 h, l; + h = ( ((uint32)s->block[i*8+0]) << 24 ) | + ( ((uint32)s->block[i*8+1]) << 16 ) | + ( ((uint32)s->block[i*8+2]) << 8 ) | + ( ((uint32)s->block[i*8+3]) << 0 ); + l = ( ((uint32)s->block[i*8+4]) << 24 ) | + ( ((uint32)s->block[i*8+5]) << 16 ) | + ( ((uint32)s->block[i*8+6]) << 8 ) | + ( ((uint32)s->block[i*8+7]) << 0 ); + BUILD(wordblock[i], h, l); + } + SHA512_Block(s, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void putty_SHA512_Final(SHA512_State *s, unsigned char *digest) { + int i; + int pad; + unsigned char c[BLKSIZE]; + uint32 len[4]; + + if (s->blkused >= BLKSIZE-16) + pad = (BLKSIZE-16) + BLKSIZE - s->blkused; + else + pad = (BLKSIZE-16) - s->blkused; + + for (i = 4; i-- ;) { + uint32 lenhi = s->len[i]; + uint32 lenlo = i > 0 ? s->len[i-1] : 0; + len[i] = (lenhi << 3) | (lenlo >> (32-3)); + } + + memset(c, 0, pad); + c[0] = 0x80; + putty_SHA512_Bytes(s, &c, pad); + + for (i = 0; i < 4; i++) { + c[i*4+0] = (len[3-i] >> 24) & 0xFF; + c[i*4+1] = (len[3-i] >> 16) & 0xFF; + c[i*4+2] = (len[3-i] >> 8) & 0xFF; + c[i*4+3] = (len[3-i] >> 0) & 0xFF; + } + + putty_SHA512_Bytes(s, &c, 16); + + for (i = 0; i < 8; i++) { + uint32 h, l; + EXTRACT(h, l, s->h[i]); + digest[i*8+0] = (h >> 24) & 0xFF; + digest[i*8+1] = (h >> 16) & 0xFF; + digest[i*8+2] = (h >> 8) & 0xFF; + digest[i*8+3] = (h >> 0) & 0xFF; + digest[i*8+4] = (l >> 24) & 0xFF; + digest[i*8+5] = (l >> 16) & 0xFF; + digest[i*8+6] = (l >> 8) & 0xFF; + digest[i*8+7] = (l >> 0) & 0xFF; + } +} + +void putty_SHA384_Final(SHA512_State *s, unsigned char *digest) { + unsigned char biggerDigest[512 / 8]; + putty_SHA512_Final(s, biggerDigest); + memcpy(digest, biggerDigest, 384 / 8); +} + +void putty_SHA512_Simple(const void *p, int len, unsigned char *output) { + SHA512_State s; + + putty_SHA512_Init(&s); + putty_SHA512_Bytes(&s, p, len); + putty_SHA512_Final(&s, output); + smemclr(&s, sizeof(s)); +} + +void putty_SHA384_Simple(const void *p, int len, unsigned char *output) { + SHA512_State s; + + putty_SHA384_Init(&s); + putty_SHA512_Bytes(&s, p, len); + putty_SHA384_Final(&s, output); + smemclr(&s, sizeof(s)); +} + +/* + * Thin abstraction for things where hashes are pluggable. + */ + +static void *sha512_init(void) +{ + SHA512_State *s; + + s = snew(SHA512_State); + putty_SHA512_Init(s); + return s; +} + +static void *sha512_copy(const void *vold) +{ + const SHA512_State *old = (const SHA512_State *)vold; + SHA512_State *s; + + s = snew(SHA512_State); + *s = *old; + return s; +} + +static void sha512_free(void *handle) +{ + SHA512_State *s = handle; + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha512_bytes(void *handle, const void *p, int len) +{ + SHA512_State *s = handle; + + putty_SHA512_Bytes(s, p, len); +} + +static void sha512_final(void *handle, unsigned char *output) +{ + SHA512_State *s = handle; + + putty_SHA512_Final(s, output); + sha512_free(s); +} + +const struct ssh_hash ssh_sha512 = { + sha512_init, sha512_bytes, sha512_final, + 64, "SHA-512" +}; + +static void *sha384_init(void) +{ + SHA512_State *s; + + s = snew(SHA512_State); + putty_SHA384_Init(s); + return s; +} + +static void sha384_final(void *handle, unsigned char *output) +{ + SHA512_State *s = handle; + + putty_SHA384_Final(s, output); + smemclr(s, sizeof(*s)); + sfree(s); +} + +const struct ssh_hash ssh_sha384 = { + sha384_init, sha512_bytes, sha384_final, + 48, "SHA-384" +}; + +#ifdef TEST + +#include +#include +#include + +int main(void) { + unsigned char digest[64]; + int i, j, errors; + + struct { + const char *teststring; + unsigned char digest512[64]; + } tests[] = { + { "abc", { + 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, + 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, + 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, + 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, + 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, + 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, + 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, + 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f, + } }, + { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", { + 0x8e, 0x95, 0x9b, 0x75, 0xda, 0xe3, 0x13, 0xda, + 0x8c, 0xf4, 0xf7, 0x28, 0x14, 0xfc, 0x14, 0x3f, + 0x8f, 0x77, 0x79, 0xc6, 0xeb, 0x9f, 0x7f, 0xa1, + 0x72, 0x99, 0xae, 0xad, 0xb6, 0x88, 0x90, 0x18, + 0x50, 0x1d, 0x28, 0x9e, 0x49, 0x00, 0xf7, 0xe4, + 0x33, 0x1b, 0x99, 0xde, 0xc4, 0xb5, 0x43, 0x3a, + 0xc7, 0xd3, 0x29, 0xee, 0xb6, 0xdd, 0x26, 0x54, + 0x5e, 0x96, 0xe5, 0x5b, 0x87, 0x4b, 0xe9, 0x09, + } }, + { NULL, { + 0xe7, 0x18, 0x48, 0x3d, 0x0c, 0xe7, 0x69, 0x64, + 0x4e, 0x2e, 0x42, 0xc7, 0xbc, 0x15, 0xb4, 0x63, + 0x8e, 0x1f, 0x98, 0xb1, 0x3b, 0x20, 0x44, 0x28, + 0x56, 0x32, 0xa8, 0x03, 0xaf, 0xa9, 0x73, 0xeb, + 0xde, 0x0f, 0xf2, 0x44, 0x87, 0x7e, 0xa6, 0x0a, + 0x4c, 0xb0, 0x43, 0x2c, 0xe5, 0x77, 0xc3, 0x1b, + 0xeb, 0x00, 0x9c, 0x5c, 0x2c, 0x49, 0xaa, 0x2e, + 0x4e, 0xad, 0xb2, 0x17, 0xad, 0x8c, 0xc0, 0x9b, + } }, + }; + + errors = 0; + + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + if (tests[i].teststring) { + SHA512_Simple(tests[i].teststring, + strlen(tests[i].teststring), digest); + } else { + SHA512_State s; + int n; + putty_SHA512_Init(&s); + for (n = 0; n < 1000000 / 40; n++) + putty_SHA512_Bytes(&s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 40); + putty_SHA512_Final(&s, digest); + } + for (j = 0; j < 64; j++) { + if (digest[j] != tests[i].digest512[j]) { + fprintf(stderr, + "\"%s\" digest512 byte %d should be 0x%02x, is 0x%02x\n", + tests[i].teststring, j, tests[i].digest512[j], + digest[j]); + errors++; + } + } + + } + + printf("%d errors\n", errors); + + return 0; +} + +#endif diff --git a/netbox/libs/Putty/sshsha.c b/netbox/libs/Putty/sshsha.c new file mode 100644 index 000000000..0af6f0ac3 --- /dev/null +++ b/netbox/libs/Putty/sshsha.c @@ -0,0 +1,469 @@ +/* + * SHA1 hash algorithm. Used in SSH-2 as a MAC, and the transform is + * also used as a `stirring' function for the PuTTY random number + * pool. Implemented directly from the specification by Simon + * Tatham. + */ + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Core SHA algorithm: processes 16-word blocks into a message digest. + */ + +#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) + +static void SHA_Core_Init(uint32 h[5]) +{ + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; +} + +void SHATransform(word32 * digest, word32 * block) +{ + word32 w[80]; + word32 a, b, c, d, e; + int t; + +#ifdef RANDOM_DIAGNOSTICS + { + extern int random_diagnostics; + if (random_diagnostics) { + int i; + printf("SHATransform:"); + for (i = 0; i < 5; i++) + printf(" %08x", digest[i]); + printf(" +"); + for (i = 0; i < 16; i++) + printf(" %08x", block[i]); + } + } +#endif + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 80; t++) { + word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = rol(tmp, 1); + } + + a = digest[0]; + b = digest[1]; + c = digest[2]; + d = digest[3]; + e = digest[4]; + + for (t = 0; t < 20; t++) { + word32 tmp = + rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 20; t < 40; t++) { + word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 40; t < 60; t++) { + word32 tmp = rol(a, + 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] + + 0x8f1bbcdc; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 60; t < 80; t++) { + word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; + +#ifdef RANDOM_DIAGNOSTICS + { + extern int random_diagnostics; + if (random_diagnostics) { + int i; + printf(" ="); + for (i = 0; i < 5; i++) + printf(" %08x", digest[i]); + printf("\n"); + } + } +#endif +} + +/* ---------------------------------------------------------------------- + * Outer SHA algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core SHA algorithm. + */ + +void putty_SHA_Init(SHA_State * s) +{ + SHA_Core_Init(s->h); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void putty_SHA_Bytes(SHA_State * s, const void *p, int len) +{ + const unsigned char *q = (const unsigned char *) p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused && s->blkused + len < 64) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= 64) { + memcpy(s->block + s->blkused, q, 64 - s->blkused); + q += 64 - s->blkused; + len -= 64 - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + (((uint32) s->block[i * 4 + 0]) << 24) | + (((uint32) s->block[i * 4 + 1]) << 16) | + (((uint32) s->block[i * 4 + 2]) << 8) | + (((uint32) s->block[i * 4 + 3]) << 0); + } + SHATransform(s->h, wordblock); + s->blkused = 0; + } +#ifdef MPEXT + if (len > 0) +#endif + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void putty_SHA_Final(SHA_State * s, unsigned char *output) +{ + int i; + int pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + putty_SHA_Bytes(s, &c, pad); + + c[0] = (lenhi >> 24) & 0xFF; + c[1] = (lenhi >> 16) & 0xFF; + c[2] = (lenhi >> 8) & 0xFF; + c[3] = (lenhi >> 0) & 0xFF; + c[4] = (lenlo >> 24) & 0xFF; + c[5] = (lenlo >> 16) & 0xFF; + c[6] = (lenlo >> 8) & 0xFF; + c[7] = (lenlo >> 0) & 0xFF; + + putty_SHA_Bytes(s, &c, 8); + + for (i = 0; i < 5; i++) { + output[i * 4] = (s->h[i] >> 24) & 0xFF; + output[i * 4 + 1] = (s->h[i] >> 16) & 0xFF; + output[i * 4 + 2] = (s->h[i] >> 8) & 0xFF; + output[i * 4 + 3] = (s->h[i]) & 0xFF; + } +} + +void putty_SHA_Simple(const void *p, int len, unsigned char *output) +{ + SHA_State s; + + putty_SHA_Init(&s); + putty_SHA_Bytes(&s, p, len); + putty_SHA_Final(&s, output); + smemclr(&s, sizeof(s)); +} + +/* + * Thin abstraction for things where hashes are pluggable. + */ + +static void *sha1_init(void) +{ + SHA_State *s; + + s = snew(SHA_State); + putty_SHA_Init(s); + return s; +} + +static void *sha1_copy(const void *vold) +{ + const SHA_State *old = (const SHA_State *)vold; + SHA_State *s; + + s = snew(SHA_State); + *s = *old; + return s; +} + +static void sha1_free(void *handle) +{ + SHA_State *s = handle; + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha1_bytes(void *handle, const void *p, int len) +{ + SHA_State *s = handle; + + putty_SHA_Bytes(s, p, len); +} + +static void sha1_final(void *handle, unsigned char *output) +{ + SHA_State *s = handle; + + putty_SHA_Final(s, output); + sha1_free(s); +} + +const struct ssh_hash ssh_sha1 = { + sha1_init, sha1_bytes, sha1_final, 20, "SHA-1" +}; + +/* ---------------------------------------------------------------------- + * The above is the SHA-1 algorithm itself. Now we implement the + * HMAC wrapper on it. + */ + +static void *sha1_make_context(void *cipher_ctx) +{ + return snewn(3, SHA_State); +} + +static void sha1_free_context(void *handle) +{ + smemclr(handle, 3 * sizeof(SHA_State)); + sfree(handle); +} + +static void sha1_key_internal(void *handle, unsigned char *key, int len) +{ + SHA_State *keys = (SHA_State *)handle; + unsigned char foo[64]; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + putty_SHA_Init(&keys[0]); + putty_SHA_Bytes(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + putty_SHA_Init(&keys[1]); + putty_SHA_Bytes(&keys[1], foo, 64); + + smemclr(foo, 64); /* burn the evidence */ +} + +static void sha1_key(void *handle, unsigned char *key) +{ + sha1_key_internal(handle, key, 20); +} + +static void sha1_key_buggy(void *handle, unsigned char *key) +{ + sha1_key_internal(handle, key, 16); +} + +static void hmacsha1_start(void *handle) +{ + SHA_State *keys = (SHA_State *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len) +{ + SHA_State *keys = (SHA_State *)handle; + putty_SHA_Bytes(&keys[2], (void *)blk, len); +} + +static void hmacsha1_genresult(void *handle, unsigned char *hmac) +{ + SHA_State *keys = (SHA_State *)handle; + SHA_State s; + unsigned char intermediate[20]; + + s = keys[2]; /* structure copy */ + putty_SHA_Final(&s, intermediate); + s = keys[1]; /* structure copy */ + putty_SHA_Bytes(&s, intermediate, 20); + putty_SHA_Final(&s, hmac); +} + +static void sha1_do_hmac(void *handle, unsigned char *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[4]; + + PUT_32BIT_MSB_FIRST(seqbuf, seq); + hmacsha1_start(handle); + hmacsha1_bytes(handle, seqbuf, 4); + hmacsha1_bytes(handle, blk, len); + hmacsha1_genresult(handle, hmac); +} + +static void sha1_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + sha1_do_hmac(handle, blk, len, seq, blk + len); +} + +static int hmacsha1_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[20]; + hmacsha1_genresult(handle, correct); + return smemeq(correct, hmac, 20); +} + +static int sha1_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[20]; + sha1_do_hmac(handle, blk, len, seq, correct); + return smemeq(correct, blk + len, 20); +} + +static void hmacsha1_96_genresult(void *handle, unsigned char *hmac) +{ + unsigned char full[20]; + hmacsha1_genresult(handle, full); + memcpy(hmac, full, 12); +} + +static void sha1_96_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char full[20]; + sha1_do_hmac(handle, blk, len, seq, full); + memcpy(blk + len, full, 12); +} + +static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[20]; + hmacsha1_genresult(handle, correct); + return smemeq(correct, hmac, 12); +} + +static int sha1_96_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[20]; + sha1_do_hmac(handle, blk, len, seq, correct); + return smemeq(correct, blk + len, 12); +} + +void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, + unsigned char *output) { + SHA_State states[2]; + unsigned char intermediate[20]; + + sha1_key_internal(states, key, keylen); + putty_SHA_Bytes(&states[0], data, datalen); + putty_SHA_Final(&states[0], intermediate); + + putty_SHA_Bytes(&states[1], intermediate, 20); + putty_SHA_Final(&states[1], output); +} + +const struct ssh_mac ssh_hmac_sha1 = { + sha1_make_context, sha1_free_context, sha1_key, + sha1_generate, sha1_verify, + hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, + "hmac-sha1", + 20, + "HMAC-SHA1" +}; + +const struct ssh_mac ssh_hmac_sha1_96 = { + sha1_make_context, sha1_free_context, sha1_key, + sha1_96_generate, sha1_96_verify, + hmacsha1_start, hmacsha1_bytes, + hmacsha1_96_genresult, hmacsha1_96_verresult, + "hmac-sha1-96", + 12, + "HMAC-SHA1-96" +}; + +const struct ssh_mac ssh_hmac_sha1_buggy = { + sha1_make_context, sha1_free_context, sha1_key_buggy, + sha1_generate, sha1_verify, + hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, + "hmac-sha1", + 20, + "bug-compatible HMAC-SHA1" +}; + +const struct ssh_mac ssh_hmac_sha1_96_buggy = { + sha1_make_context, sha1_free_context, sha1_key_buggy, + sha1_96_generate, sha1_96_verify, + hmacsha1_start, hmacsha1_bytes, + hmacsha1_96_genresult, hmacsha1_96_verresult, + "hmac-sha1-96", + 12, + "bug-compatible HMAC-SHA1-96" +}; + +#ifdef MPEXT + +#include "puttyexp.h" + +void call_sha1_key_internal(void * handle, unsigned char * key, int len) +{ + sha1_key_internal(handle, key, len); +} + +#endif diff --git a/netbox/libs/Putty/sshshare.c b/netbox/libs/Putty/sshshare.c new file mode 100644 index 000000000..1c0e3cba8 --- /dev/null +++ b/netbox/libs/Putty/sshshare.c @@ -0,0 +1,2128 @@ +/* + * Support for SSH connection sharing, i.e. permitting one PuTTY to + * open its own channels over the SSH session being run by another. + */ + +/* + * Discussion and technical documentation + * ====================================== + * + * The basic strategy for PuTTY's implementation of SSH connection + * sharing is to have a single 'upstream' PuTTY process, which manages + * the real SSH connection and all the cryptography, and then zero or + * more 'downstream' PuTTYs, which never talk to the real host but + * only talk to the upstream through local IPC (Unix-domain sockets or + * Windows named pipes). + * + * The downstreams communicate with the upstream using a protocol + * derived from SSH itself, which I'll document in detail below. In + * brief, though: the downstream->upstream protocol uses a trivial + * binary packet protocol (just length/type/data) to encapsulate + * unencrypted SSH messages, and downstreams talk to the upstream more + * or less as if it was an SSH server itself. (So downstreams can + * themselves open multiple SSH channels, for example, by sending + * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of + * their choice within each channel, and they handle their own + * WINDOW_ADJUST messages.) + * + * The upstream would ideally handle these downstreams by just putting + * their messages into the queue for proper SSH-2 encapsulation and + * encryption and sending them straight on to the server. However, + * that's not quite feasible as written, because client-side channel + * IDs could easily conflict (between multiple downstreams, or between + * a downstream and the upstream). To protect against that, the + * upstream rewrites the client-side channel IDs in messages it passes + * on to the server, so that it's performing what you might describe + * as 'channel-number NAT'. Then the upstream remembers which of its + * own channel IDs are channels it's managing itself, and which are + * placeholders associated with a particular downstream, so that when + * replies come in from the server they can be sent on to the relevant + * downstream (after un-NATting the channel number, of course). + * + * Global requests from downstreams are only accepted if the upstream + * knows what to do about them; currently the only such requests are + * the ones having to do with remote-to-local port forwarding (in + * which, again, the upstream remembers that some of the forwardings + * it's asked the server to set up were on behalf of particular + * downstreams, and sends the incoming CHANNEL_OPENs to those + * downstreams when connections come in). + * + * Other fiddly pieces of this mechanism are X forwarding and + * (OpenSSH-style) agent forwarding. Both of these have a fundamental + * problem arising from the protocol design: that the CHANNEL_OPEN + * from the server introducing a forwarded connection does not carry + * any indication of which session channel gave rise to it; so if + * session channels from multiple downstreams enable those forwarding + * methods, it's hard for the upstream to know which downstream to + * send the resulting connections back to. + * + * For X forwarding, we can work around this in a really painful way + * by using the fake X11 authorisation data sent to the server as part + * of the forwarding setup: upstream ensures that every X forwarding + * request carries distinguishable fake auth data, and then when X + * connections come in it waits to see the auth data in the X11 setup + * message before it decides which downstream to pass the connection + * on to. + * + * For agent forwarding, that workaround is unavailable. As a result, + * this system (and, as far as I can think of, any other system too) + * has the fundamental constraint that it can only forward one SSH + * agent - it can't forward two agents to different session channels. + * So downstreams can request agent forwarding if they like, but if + * they do, they'll get whatever SSH agent is known to the upstream + * (if any) forwarded to their sessions. + * + * Downstream-to-upstream protocol + * ------------------------------- + * + * Here I document in detail the protocol spoken between PuTTY + * downstreams and upstreams over local IPC. The IPC mechanism can + * vary between host platforms, but the protocol is the same. + * + * The protocol commences with a version exchange which is exactly + * like the SSH-2 one, in that each side sends a single line of text + * of the form + * + * -- [comments] \r\n + * + * The only difference is that in real SSH-2, is the string + * "SSH", whereas in this protocol the string is + * "SSHCONNECTION@putty.projects.tartarus.org". + * + * (The SSH RFCs allow many protocol-level identifier namespaces to be + * extended by implementors without central standardisation as long as + * they suffix "@" and a domain name they control to their new ids. + * RFC 4253 does not define this particular name to be changeable at + * all, but I like to think this is obviously how it would have done + * so if the working group had foreseen the need :-) + * + * Thereafter, all data exchanged consists of a sequence of binary + * packets concatenated end-to-end, each of which is of the form + * + * uint32 length of packet, N + * byte[N] N bytes of packet data + * + * and, since these are SSH-2 messages, the first data byte is taken + * to be the packet type code. + * + * These messages are interpreted as those of an SSH connection, after + * userauth completes, and without any repeat key exchange. + * Specifically, any message from the SSH Connection Protocol is + * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, + * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport + * Protocol. + * + * This protocol imposes a few additional requirements, over and above + * those of the standard SSH Connection Protocol: + * + * Message sizes are not permitted to exceed 0x4010 (16400) bytes, + * including their length header. + * + * When the server (i.e. really the PuTTY upstream) sends + * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client + * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that + * confirmation message MUST include an initial window size of at + * least 256. (Rationale: this is a bit of a fudge which makes it + * easier, by eliminating the possibility of nasty edge cases, for an + * upstream to arrange not to pass the CHANNEL_OPEN on to downstream + * until after it's seen the X11 auth data to decide which downstream + * it needs to go to.) + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" + +struct ssh_sharing_state { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char *sockname; /* the socket name, kept for cleanup */ + Socket listensock; /* the master listening Socket */ + tree234 *connections; /* holds ssh_sharing_connstates */ + unsigned nextid; /* preferred id for next connstate */ + Ssh ssh; /* instance of the ssh backend */ + char *server_verstring; /* server version string after "SSH-" */ +}; + +struct share_globreq; + +struct ssh_sharing_connstate { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + unsigned id; /* used to identify this downstream in log messages */ + + Socket sock; /* the Socket for this connection */ + struct ssh_sharing_state *parent; + + int crLine; /* coroutine state for share_receive */ + + int sent_verstring, got_verstring, curr_packetlen; + + unsigned char recvbuf[0x4010]; + int recvlen; + + /* + * Assorted state we have to remember about this downstream, so + * that we can clean it up appropriately when the downstream goes + * away. + */ + + /* Channels which don't have a downstream id, i.e. we've passed a + * CHANNEL_OPEN down from the server but not had an + * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes + * away, we respond to all of these with OPEN_FAILURE. */ + tree234 *halfchannels; /* stores 'struct share_halfchannel' */ + + /* Channels which do have a downstream id. We need to index these + * by both server id and upstream id, so we can find a channel + * when handling either an upward or a downward message referring + * to it. */ + tree234 *channels_by_us; /* stores 'struct share_channel' */ + tree234 *channels_by_server; /* stores 'struct share_channel' */ + + /* Another class of channel which doesn't have a downstream id. + * The difference between these and halfchannels is that xchannels + * do have an *upstream* id, because upstream has already accepted + * the channel request from the server. This arises in the case of + * X forwarding, where we have to accept the request and read the + * X authorisation data before we know whether the channel needs + * to be forwarded to a downstream. */ + tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ + tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ + + /* Remote port forwarding requests in force. */ + tree234 *forwardings; /* stores 'struct share_forwarding' */ + + /* Global requests we've sent on to the server, pending replies. */ + struct share_globreq *globreq_head, *globreq_tail; +}; + +struct share_halfchannel { + unsigned server_id; +}; + +/* States of a share_channel. */ +enum { + OPEN, + SENT_CLOSE, + RCVD_CLOSE, + /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. + * If downstream goes away when a channel is in this state, we + * must wait for the server's response before starting to send + * CLOSE. Channels in this state are also not held in + * channels_by_server, because their server_id field is + * meaningless. */ + UNACKNOWLEDGED +}; + +struct share_channel { + unsigned downstream_id, upstream_id, server_id; + int downstream_maxpkt; + int state; + /* + * Some channels (specifically, channels on which downstream has + * sent "x11-req") have the additional function of storing a set + * of downstream X authorisation data and a handle to an upstream + * fake set. + */ + struct X11FakeAuth *x11_auth_upstream; + int x11_auth_proto; + char *x11_auth_data; + int x11_auth_datalen; + int x11_one_shot; +}; + +struct share_forwarding { + char *host; + int port; + int active; /* has the server sent REQUEST_SUCCESS? */ +}; + +struct share_xchannel_message { + struct share_xchannel_message *next; + int type; + unsigned char *data; + int datalen; +}; + +struct share_xchannel { + unsigned upstream_id, server_id; + + /* + * xchannels come in two flavours: live and dead. Live ones are + * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from + * downstream; dead ones have had an OPEN_FAILURE, so they only + * exist as a means of letting us conveniently respond to further + * channel messages from the server until such time as the server + * sends us CHANNEL_CLOSE. + */ + int live; + + /* + * When we receive OPEN_CONFIRMATION, we will need to send a + * WINDOW_ADJUST to the server to synchronise the windows. For + * this purpose we need to know what window we have so far offered + * the server. We record this as exactly the value in the + * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount + * by which the two X greetings differed in length. + */ + int window; + + /* + * Linked list of SSH messages from the server relating to this + * channel, which we queue up until downstream sends us an + * OPEN_CONFIRMATION and we can belatedly send them all on. + */ + struct share_xchannel_message *msghead, *msgtail; +}; + +enum { + GLOBREQ_TCPIP_FORWARD, + GLOBREQ_CANCEL_TCPIP_FORWARD +}; + +struct share_globreq { + struct share_globreq *next; + int type; + int want_reply; + struct share_forwarding *fwd; +}; + +static int share_connstate_cmp(void *av, void *bv) +{ + const struct ssh_sharing_connstate *a = + (const struct ssh_sharing_connstate *)av; + const struct ssh_sharing_connstate *b = + (const struct ssh_sharing_connstate *)bv; + + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return +1; + else + return 0; +} + +static unsigned share_find_unused_id +(struct ssh_sharing_state *sharestate, unsigned first) +{ + int low_orig, low, mid, high, high_orig; + struct ssh_sharing_connstate *cs; + unsigned ret; + + /* + * Find the lowest unused downstream ID greater or equal to + * 'first'. + * + * Begin by seeing if 'first' itself is available. If it is, we'll + * just return it; if it's already in the tree, we'll find the + * tree index where it appears and use that for the next stage. + */ + { + struct ssh_sharing_connstate dummy; + dummy.id = first; + cs = findrelpos234(sharestate->connections, &dummy, NULL, + REL234_GE, &low_orig); + if (!cs) + return first; + } + + /* + * Now binary-search using the counted B-tree, to find the largest + * ID which is in a contiguous sequence from the beginning of that + * range. + */ + low = low_orig; + high = high_orig = count234(sharestate->connections); + while (high - low > 1) { + mid = (high + low) / 2; + cs = index234(sharestate->connections, mid); + if (cs->id == first + (mid - low_orig)) + low = mid; /* this one is still in the sequence */ + else + high = mid; /* this one is past the end */ + } + + /* + * Now low is the tree index of the largest ID in the initial + * sequence. So the return value is one more than low's id, and we + * know low's id is given by the formula in the binary search loop + * above. + * + * (If an SSH connection went on for _enormously_ long, we might + * reach a point where all ids from 'first' to UINT_MAX were in + * use. In that situation the formula below would wrap round by + * one and return zero, which is conveniently the right way to + * signal 'no id available' from this function.) + */ + ret = first + (low - low_orig) + 1; + { + struct ssh_sharing_connstate dummy; + dummy.id = ret; + assert(NULL == find234(sharestate->connections, &dummy, NULL)); + } + return ret; +} + +static int share_halfchannel_cmp(void *av, void *bv) +{ + const struct share_halfchannel *a = (const struct share_halfchannel *)av; + const struct share_halfchannel *b = (const struct share_halfchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_channel_us_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_channel_server_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_xchannel_us_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_xchannel_server_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_forwarding_cmp(void *av, void *bv) +{ + const struct share_forwarding *a = (const struct share_forwarding *)av; + const struct share_forwarding *b = (const struct share_forwarding *)bv; + int i; + + if ((i = strcmp(a->host, b->host)) != 0) + return i; + else if (a->port < b->port) + return -1; + else if (a->port > b->port) + return +1; + else + return 0; +} + +static void share_xchannel_free(struct share_xchannel *xc) +{ + while (xc->msghead) { + struct share_xchannel_message *tmp = xc->msghead; + xc->msghead = tmp->next; + sfree(tmp); + } + sfree(xc); +} + +static void share_connstate_free(struct ssh_sharing_connstate *cs) +{ + struct share_halfchannel *hc; + struct share_xchannel *xc; + struct share_channel *chan; + struct share_forwarding *fwd; + + while ((hc = (struct share_halfchannel *) + delpos234(cs->halfchannels, 0)) != NULL) + sfree(hc); + freetree234(cs->halfchannels); + + /* All channels live in 'channels_by_us' but only some in + * 'channels_by_server', so we use the former to find the list of + * ones to free */ + freetree234(cs->channels_by_server); + while ((chan = (struct share_channel *) + delpos234(cs->channels_by_us, 0)) != NULL) + sfree(chan); + freetree234(cs->channels_by_us); + + /* But every xchannel is in both trees, so it doesn't matter which + * we use to free them. */ + while ((xc = (struct share_xchannel *) + delpos234(cs->xchannels_by_us, 0)) != NULL) + share_xchannel_free(xc); + freetree234(cs->xchannels_by_us); + freetree234(cs->xchannels_by_server); + + while ((fwd = (struct share_forwarding *) + delpos234(cs->forwardings, 0)) != NULL) + sfree(fwd); + freetree234(cs->forwardings); + + while (cs->globreq_head) { + struct share_globreq *globreq = cs->globreq_head; + cs->globreq_head = cs->globreq_head->next; + sfree(globreq); + } + + if (cs->sock) + sk_close(cs->sock); + + sfree(cs); +} + +void sharestate_free(void *v) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)v; + struct ssh_sharing_connstate *cs; + + platform_ssh_share_cleanup(sharestate->sockname); + + while ((cs = (struct ssh_sharing_connstate *) + delpos234(sharestate->connections, 0)) != NULL) { + share_connstate_free(cs); + } + freetree234(sharestate->connections); + if (sharestate->listensock) { + sk_close(sharestate->listensock); + sharestate->listensock = NULL; + } + sfree(sharestate->server_verstring); + sfree(sharestate->sockname); + sfree(sharestate); +} + +static struct share_halfchannel *share_add_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel *hc = snew(struct share_halfchannel); + hc->server_id = server_id; + if (add234(cs->halfchannels, hc) != hc) { + /* Duplicate?! */ + sfree(hc); + return NULL; + } else { + return hc; + } +} + +static struct share_halfchannel *share_find_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel dummyhc; + dummyhc.server_id = server_id; + return find234(cs->halfchannels, &dummyhc, NULL); +} + +static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, + struct share_halfchannel *hc) +{ + del234(cs->halfchannels, hc); + sfree(hc); +} + +static struct share_channel *share_add_channel + (struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) +{ + struct share_channel *chan = snew(struct share_channel); + chan->downstream_id = downstream_id; + chan->upstream_id = upstream_id; + chan->server_id = server_id; + chan->state = state; + chan->downstream_maxpkt = maxpkt; + chan->x11_auth_upstream = NULL; + chan->x11_auth_data = NULL; + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = 0; + if (add234(cs->channels_by_us, chan) != chan) { + sfree(chan); + return NULL; + } + if (chan->state != UNACKNOWLEDGED) { + if (add234(cs->channels_by_server, chan) != chan) { + del234(cs->channels_by_us, chan); + sfree(chan); + return NULL; + } + } + return chan; +} + +static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, + struct share_channel *chan, + unsigned server_id, int newstate) +{ + chan->server_id = server_id; + chan->state = newstate; + assert(newstate != UNACKNOWLEDGED); + add234(cs->channels_by_server, chan); +} + +static struct share_channel *share_find_channel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_channel dummychan; + dummychan.upstream_id = upstream_id; + return find234(cs->channels_by_us, &dummychan, NULL); +} + +static struct share_channel *share_find_channel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_channel dummychan; + dummychan.server_id = server_id; + return find234(cs->channels_by_server, &dummychan, NULL); +} + +static void share_remove_channel(struct ssh_sharing_connstate *cs, + struct share_channel *chan) +{ + del234(cs->channels_by_us, chan); + del234(cs->channels_by_server, chan); + if (chan->x11_auth_upstream) + ssh_sharing_remove_x11_display(cs->parent->ssh, + chan->x11_auth_upstream); + sfree(chan->x11_auth_data); + sfree(chan); +} + +static struct share_xchannel *share_add_xchannel + (struct ssh_sharing_connstate *cs, + unsigned upstream_id, unsigned server_id) +{ + struct share_xchannel *xc = snew(struct share_xchannel); + xc->upstream_id = upstream_id; + xc->server_id = server_id; + xc->live = TRUE; + xc->msghead = xc->msgtail = NULL; + if (add234(cs->xchannels_by_us, xc) != xc) { + sfree(xc); + return NULL; + } + if (add234(cs->xchannels_by_server, xc) != xc) { + del234(cs->xchannels_by_us, xc); + sfree(xc); + return NULL; + } + return xc; +} + +static struct share_xchannel *share_find_xchannel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_xchannel dummyxc; + dummyxc.upstream_id = upstream_id; + return find234(cs->xchannels_by_us, &dummyxc, NULL); +} + +static struct share_xchannel *share_find_xchannel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_xchannel dummyxc; + dummyxc.server_id = server_id; + return find234(cs->xchannels_by_server, &dummyxc, NULL); +} + +static void share_remove_xchannel(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + del234(cs->xchannels_by_us, xc); + del234(cs->xchannels_by_server, xc); + share_xchannel_free(xc); +} + +static struct share_forwarding *share_add_forwarding + (struct ssh_sharing_connstate *cs, + const char *host, int port) +{ + struct share_forwarding *fwd = snew(struct share_forwarding); + fwd->host = dupstr(host); + fwd->port = port; + fwd->active = FALSE; + if (add234(cs->forwardings, fwd) != fwd) { + /* Duplicate?! */ + sfree(fwd); + return NULL; + } + return fwd; +} + +static struct share_forwarding *share_find_forwarding + (struct ssh_sharing_connstate *cs, const char *host, int port) +{ + struct share_forwarding dummyfwd, *ret; + dummyfwd.host = dupstr(host); + dummyfwd.port = port; + ret = find234(cs->forwardings, &dummyfwd, NULL); + sfree(dummyfwd.host); + return ret; +} + +static void share_remove_forwarding(struct ssh_sharing_connstate *cs, + struct share_forwarding *fwd) +{ + del234(cs->forwardings, fwd); + sfree(fwd); +} + +static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, + int type, const void *pkt, int pktlen, + struct share_channel *chan) +{ + if (!cs->sock) /* throw away all packets destined for a dead downstream */ + return; + + if (type == SSH2_MSG_CHANNEL_DATA) { + /* + * Special case which we take care of at a low level, so as to + * be sure to apply it in all cases. On rare occasions we + * might find that we have a channel for which the + * downstream's maximum packet size exceeds the max packet + * size we presented to the server on its behalf. (This can + * occur in X11 forwarding, where we have to send _our_ + * CHANNEL_OPEN_CONFIRMATION before we discover which if any + * downstream the channel is destined for, so if that + * downstream turns out to present a smaller max packet size + * then we're in this situation.) + * + * If that happens, we just chop up the packet into pieces and + * send them as separate CHANNEL_DATA packets. + */ + const char *upkt = (const char *)pkt; + char header[13]; /* 4 length + 1 type + 4 channel id + 4 string len */ + + int len = toint(GET_32BIT(upkt + 4)); + upkt += 8; /* skip channel id + length field */ + + if (len < 0 || len > pktlen - 8) + len = pktlen - 8; + + do { + int this_len = (len > chan->downstream_maxpkt ? + chan->downstream_maxpkt : len); + PUT_32BIT(header, this_len + 9); + header[4] = type; + PUT_32BIT(header + 5, chan->downstream_id); + PUT_32BIT(header + 9, this_len); + sk_write(cs->sock, header, 13); + sk_write(cs->sock, upkt, this_len); + len -= this_len; + upkt += this_len; + } while (len > 0); + } else { + /* + * Just do the obvious thing. + */ + char header[9]; + + PUT_32BIT(header, pktlen + 1); + header[4] = type; + sk_write(cs->sock, header, 5); + sk_write(cs->sock, pkt, pktlen); + } +} + +static void share_try_cleanup(struct ssh_sharing_connstate *cs) +{ + int i; + struct share_halfchannel *hc; + struct share_channel *chan; + struct share_forwarding *fwd; + + /* + * Any half-open channels, i.e. those for which we'd received + * CHANNEL_OPEN from the server but not passed back a response + * from downstream, should be responded to with OPEN_FAILURE. + */ + while ((hc = (struct share_halfchannel *) + index234(cs->halfchannels, 0)) != NULL) { + static const char reason[] = "PuTTY downstream no longer available"; + static const char lang[] = "en"; + unsigned char packet[256]; + int pos = 0; + + PUT_32BIT(packet + pos, hc->server_id); pos += 4; + PUT_32BIT(packet + pos, SSH2_OPEN_CONNECT_FAILED); pos += 4; + PUT_32BIT(packet + pos, strlen(reason)); pos += 4; + memcpy(packet + pos, reason, strlen(reason)); pos += strlen(reason); + PUT_32BIT(packet + pos, strlen(lang)); pos += 4; + memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_OPEN_FAILURE, + packet, pos, "cleanup after" + " downstream went away"); + + share_remove_halfchannel(cs, hc); + } + + /* + * Any actually open channels should have a CHANNEL_CLOSE sent for + * them, unless we've already done so. We won't be able to + * actually clean them up until CHANNEL_CLOSE comes back from the + * server, though (unless the server happens to have sent a CLOSE + * already). + * + * Another annoying exception is UNACKNOWLEDGED channels, i.e. + * we've _sent_ a CHANNEL_OPEN to the server but not received an + * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply + * before closing the channel, because until we see that reply we + * won't have the server's channel id to put in the close message. + */ + for (i = 0; (chan = (struct share_channel *) + index234(cs->channels_by_us, i)) != NULL; i++) { + unsigned char packet[256]; + int pos = 0; + + if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { + PUT_32BIT(packet + pos, chan->server_id); pos += 4; + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_CLOSE, + packet, pos, "cleanup after" + " downstream went away"); + if (chan->state != RCVD_CLOSE) { + chan->state = SENT_CLOSE; + } else { + /* In this case, we _can_ clear up the channel now. */ + ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id); + share_remove_channel(cs, chan); + i--; /* don't accidentally skip one as a result */ + } + } + } + + /* + * Any remote port forwardings we're managing on behalf of this + * downstream should be cancelled. Again, we must defer those for + * which we haven't yet seen REQUEST_SUCCESS/FAILURE. + * + * We take a fire-and-forget approach during cleanup, not + * bothering to set want_reply. + */ + for (i = 0; (fwd = (struct share_forwarding *) + index234(cs->forwardings, i)) != NULL; i++) { + if (fwd->active) { + static const char request[] = "cancel-tcpip-forward"; + char *packet = snewn(256 + strlen(fwd->host), char); + int pos = 0; + + PUT_32BIT(packet + pos, strlen(request)); pos += 4; + memcpy(packet + pos, request, strlen(request)); + pos += strlen(request); + + packet[pos++] = 0; /* !want_reply */ + + PUT_32BIT(packet + pos, strlen(fwd->host)); pos += 4; + memcpy(packet + pos, fwd->host, strlen(fwd->host)); + pos += strlen(fwd->host); + + PUT_32BIT(packet + pos, fwd->port); pos += 4; + + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_GLOBAL_REQUEST, + packet, pos, "cleanup after" + " downstream went away"); + sfree(packet); + + share_remove_forwarding(cs, fwd); + i--; /* don't accidentally skip one as a result */ + } + } + + if (count234(cs->halfchannels) == 0 && + count234(cs->channels_by_us) == 0 && + count234(cs->forwardings) == 0) { + /* + * Now we're _really_ done, so we can get rid of cs completely. + */ + del234(cs->parent->connections, cs); + ssh_sharing_downstream_disconnected(cs->parent->ssh, cs->id); + share_connstate_free(cs); + } +} + +static void share_begin_cleanup(struct ssh_sharing_connstate *cs) +{ + + sk_close(cs->sock); + cs->sock = NULL; + + share_try_cleanup(cs); +} + +static void share_disconnect(struct ssh_sharing_connstate *cs, + const char *message) +{ + static const char lang[] = "en"; + int msglen = strlen(message); + char *packet = snewn(msglen + 256, char); + int pos = 0; + + PUT_32BIT(packet + pos, SSH2_DISCONNECT_PROTOCOL_ERROR); pos += 4; + + PUT_32BIT(packet + pos, msglen); pos += 4; + memcpy(packet + pos, message, msglen); + pos += msglen; + + PUT_32BIT(packet + pos, strlen(lang)); pos += 4; + memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); + + send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, packet, pos, NULL); + + share_begin_cleanup(cs); +} + +static int share_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + if (error_msg) + ssh_sharing_logf(cs->parent->ssh, cs->id, "%s", error_msg); + share_begin_cleanup(cs); + return 1; +} + +static int getstring_inner(const void *vdata, int datalen, + char **out, int *outlen) +{ + const unsigned char *data = (const unsigned char *)vdata; + int len; + + if (datalen < 4) + return FALSE; + + len = toint(GET_32BIT(data)); + if (len < 0 || len > datalen - 4) + return FALSE; + + if (outlen) + *outlen = len + 4; /* total size including length field */ + if (out) + *out = dupprintf("%.*s", len, (char *)data + 4); + return TRUE; +} + +static char *getstring(const void *data, int datalen) +{ + char *ret; + if (getstring_inner(data, datalen, &ret, NULL)) + return ret; + else + return NULL; +} + +static int getstring_size(const void *data, int datalen) +{ + int ret; + if (getstring_inner(data, datalen, NULL, &ret)) + return ret; + else + return -1; +} + +/* + * Append a message to the end of an xchannel's queue, with the length + * and type code filled in and the data block allocated but + * uninitialised. + */ +struct share_xchannel_message *share_xchannel_add_message +(struct share_xchannel *xc, int type, int len) +{ + unsigned char *block; + struct share_xchannel_message *msg; + + /* + * Be a little tricksy here by allocating a single memory block + * containing both the 'struct share_xchannel_message' and the + * actual data. Simplifies freeing it later. + */ + block = smalloc(sizeof(struct share_xchannel_message) + len); + msg = (struct share_xchannel_message *)block; + msg->data = block + sizeof(struct share_xchannel_message); + msg->datalen = len; + msg->type = type; + + /* + * Queue it in the xchannel. + */ + if (xc->msgtail) + xc->msgtail->next = msg; + else + xc->msghead = msg; + msg->next = NULL; + xc->msgtail = msg; + + return msg; +} + +void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * Handle queued incoming messages from the server destined for an + * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). + */ + int delete = FALSE; + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { + /* + * A CHANNEL_REQUEST is responded to by sending + * CHANNEL_FAILURE, if it has want_reply set. + */ + int wantreplypos = getstring_size(msg->data, msg->datalen); + if (wantreplypos > 0 && wantreplypos < msg->datalen && + msg->data[wantreplypos] != 0) { + unsigned char id[4]; + PUT_32BIT(id, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_FAILURE, id, 4, + "downstream refused X channel open"); + } + } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { + /* + * On CHANNEL_CLOSE we can discard the channel completely. + */ + delete = TRUE; + } + + sfree(msg); + } + xc->msgtail = NULL; + if (delete) { + ssh_delete_sharing_channel(cs->parent->ssh, xc->upstream_id); + share_remove_xchannel(cs, xc); + } +} + +void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc, + struct share_channel *chan, + unsigned downstream_window) +{ + unsigned char window_adjust[8]; + + /* + * Send all the queued messages downstream. + */ + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->datalen >= 4) + PUT_32BIT(msg->data, chan->downstream_id); + send_packet_to_downstream(cs, msg->type, + msg->data, msg->datalen, chan); + + sfree(msg); + } + + /* + * Send a WINDOW_ADJUST back upstream, to synchronise the window + * size downstream thinks it's presented with the one we've + * actually presented. + */ + PUT_32BIT(window_adjust, xc->server_id); + PUT_32BIT(window_adjust + 4, downstream_window - xc->window); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_WINDOW_ADJUST, + window_adjust, 8, "window adjustment after" + " downstream accepted X channel"); +} + +void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * If downstream refuses to open our X channel at all for some + * reason, we must respond by sending an emergency CLOSE upstream. + */ + unsigned char id[4]; + PUT_32BIT(id, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_CLOSE, id, 4, + "downstream refused X channel open"); + + /* + * Now mark the xchannel as dead, and respond to anything sent on + * it until we see CLOSE for it in turn. + */ + xc->live = FALSE; + share_dead_xchannel_respond(cs, xc); +} + +void share_setup_x11_channel(void *csv, void *chanv, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; + struct share_channel *chan = (struct share_channel *)chanv; + struct share_xchannel *xc; + struct share_xchannel_message *msg; + void *greeting; + int greeting_len; + unsigned char *pkt; + int pktlen; + + /* + * Create an xchannel containing data we've already received from + * the X client, and preload it with a CHANNEL_DATA message + * containing our own made-up authorisation greeting and any + * additional data sent from the server so far. + */ + xc = share_add_xchannel(cs, upstream_id, server_id); + greeting = x11_make_greeting(endian, protomajor, protominor, + chan->x11_auth_proto, + chan->x11_auth_data, chan->x11_auth_datalen, + peer_addr, peer_port, &greeting_len); + msg = share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, + 8 + greeting_len + initial_len); + /* leave the channel id field unfilled - we don't know the + * downstream id yet, of course */ + PUT_32BIT(msg->data + 4, greeting_len + initial_len); + memcpy(msg->data + 8, greeting, greeting_len); + memcpy(msg->data + 8 + greeting_len, initial_data, initial_len); + sfree(greeting); + + xc->window = client_adjusted_window + greeting_len; + + /* + * Send on a CHANNEL_OPEN to downstream. + */ + pktlen = 27 + strlen(peer_addr); + pkt = snewn(pktlen, unsigned char); + PUT_32BIT(pkt, 3); /* strlen("x11") */ + memcpy(pkt+4, "x11", 3); + PUT_32BIT(pkt+7, server_id); + PUT_32BIT(pkt+11, server_currwin); + PUT_32BIT(pkt+15, server_maxpkt); + PUT_32BIT(pkt+19, strlen(peer_addr)); + memcpy(pkt+23, peer_addr, strlen(peer_addr)); + PUT_32BIT(pkt+23+strlen(peer_addr), peer_port); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, pkt, pktlen, NULL); + sfree(pkt); + + /* + * If this was a once-only X forwarding, clean it up now. + */ + if (chan->x11_one_shot) { + ssh_sharing_remove_x11_display(cs->parent->ssh, + chan->x11_auth_upstream); + chan->x11_auth_upstream = NULL; + sfree(chan->x11_auth_data); + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = 0; + } +} + +void share_got_pkt_from_server(void *csv, int type, + unsigned char *pkt, int pktlen) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; + struct share_globreq *globreq; + int id_pos; + unsigned upstream_id, server_id; + struct share_channel *chan; + struct share_xchannel *xc; + + switch (type) { + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + globreq = cs->globreq_head; + if (globreq->type == GLOBREQ_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_FAILURE) { + share_remove_forwarding(cs, globreq->fwd); + } else { + globreq->fwd->active = TRUE; + } + } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_SUCCESS) { + share_remove_forwarding(cs, globreq->fwd); + } + } + if (globreq->want_reply) { + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + } + cs->globreq_head = globreq->next; + sfree(globreq); + if (cs->globreq_head == NULL) + cs->globreq_tail = NULL; + + if (!cs->sock) { + /* Retry cleaning up this connection, in case that reply + * was the last thing we were waiting for. */ + share_try_cleanup(cs); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN: + id_pos = getstring_size(pkt, pktlen); + assert(id_pos >= 0); + server_id = GET_32BIT(pkt + id_pos); + share_add_halfchannel(cs, server_id); + + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * All these messages have the recipient channel id as the + * first uint32 field in the packet. Substitute the downstream + * channel id for our one and pass the packet downstream. + */ + assert(pktlen >= 4); + upstream_id = GET_32BIT(pkt); + if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { + /* + * The normal case: this id refers to an open channel. + */ + PUT_32BIT(pkt, chan->downstream_id); + send_packet_to_downstream(cs, type, pkt, pktlen, chan); + + /* + * Update the channel state, for messages that need it. + */ + if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { + share_channel_set_server_id(cs, chan, GET_32BIT(pkt+4), + OPEN); + if (!cs->sock) { + /* Retry cleaning up this connection, so that we + * can send an immediate CLOSE on this channel for + * which we now know the server id. */ + share_try_cleanup(cs); + } + } + } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { + ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id); + share_remove_channel(cs, chan); + } else if (type == SSH2_MSG_CHANNEL_CLOSE) { + if (chan->state == SENT_CLOSE) { + ssh_delete_sharing_channel(cs->parent->ssh, + chan->upstream_id); + share_remove_channel(cs, chan); + if (!cs->sock) { + /* Retry cleaning up this connection, in case this + * channel closure was the last thing we were + * waiting for. */ + share_try_cleanup(cs); + } + } else { + chan->state = RCVD_CLOSE; + } + } + } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) + != NULL) { + /* + * The unusual case: this id refers to an xchannel. Add it + * to the xchannel's queue. + */ + struct share_xchannel_message *msg; + + msg = share_xchannel_add_message(xc, type, pktlen); + memcpy(msg->data, pkt, pktlen); + + /* If the xchannel is dead, then also respond to it (which + * may involve deleting the channel). */ + if (!xc->live) + share_dead_xchannel_respond(cs, xc); + } + break; + + default: + assert(!"This packet type should never have come from ssh.c"); + break; + } +} + +static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, + int type, + unsigned char *pkt, int pktlen) +{ + char *request_name; + struct share_forwarding *fwd; + int id_pos; + unsigned old_id, new_id, server_id; + struct share_globreq *globreq; + struct share_channel *chan; + struct share_halfchannel *hc; + struct share_xchannel *xc; + char *err = NULL; + + switch (type) { + case SSH2_MSG_DISCONNECT: + /* + * This message stops here: if downstream is disconnecting + * from us, that doesn't mean we want to disconnect from the + * SSH server. Close the downstream connection and start + * cleanup. + */ + share_begin_cleanup(cs); + break; + + case SSH2_MSG_GLOBAL_REQUEST: + /* + * The only global requests we understand are "tcpip-forward" + * and "cancel-tcpip-forward". Since those require us to + * maintain state, we must assume that other global requests + * will probably require that too, and so we don't forward on + * any request we don't understand. + */ + request_name = getstring(pkt, pktlen); + if (request_name == NULL) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + + if (!strcmp(request_name, "tcpip-forward")) { + int wantreplypos, orig_wantreply, port, ret; + char *host; + + sfree(request_name); + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + orig_wantreply = pkt[wantreplypos]; + port = getstring_size(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + port += (wantreplypos + 1); + if (port < 0 || port > pktlen - 4) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = getstring(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + assert(host != NULL); + port = GET_32BIT(pkt + port); + + /* + * See if we can allocate space in ssh.c's tree of remote + * port forwardings. If we can't, it's because another + * client sharing this connection has already allocated + * the identical port forwarding, so we take it on + * ourselves to manufacture a failure packet and send it + * back to downstream. + */ + ret = ssh_alloc_sharing_rportfwd(cs->parent->ssh, host, port, cs); + if (!ret) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * We've managed to make space for this forwarding + * locally. Pass the request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that we know whether this forwarding needs to be + * cleaned up if downstream goes away. + */ + int old_wantreply = pkt[wantreplypos]; + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, type, pkt, pktlen, + old_wantreply ? NULL : "upstream added want_reply flag"); + fwd = share_add_forwarding(cs, host, port); + ssh_sharing_queue_global_request(cs->parent->ssh, cs); + + if (fwd) { + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_TCPIP_FORWARD; + } + } + + sfree(host); + } else if (!strcmp(request_name, "cancel-tcpip-forward")) { + int wantreplypos, orig_wantreply, port; + char *host; + struct share_forwarding *fwd; + + sfree(request_name); + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + orig_wantreply = pkt[wantreplypos]; + port = getstring_size(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + port += (wantreplypos + 1); + if (port < 0 || port > pktlen - 4) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = getstring(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + assert(host != NULL); + port = GET_32BIT(pkt + port); + + /* + * Look up the existing forwarding with these details. + */ + fwd = share_find_forwarding(cs, host, port); + if (!fwd) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * Pass the cancel request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that _we_ know whether the forwarding has been + * deleted even if downstream doesn't want to know. + */ + int old_wantreply = pkt[wantreplypos]; + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, type, pkt, pktlen, + old_wantreply ? NULL : "upstream added want_reply flag"); + ssh_sharing_queue_global_request(cs->parent->ssh, cs); + } + + sfree(host); + } else { + /* + * Request we don't understand. Manufacture a failure + * message if an answer was required. + */ + int wantreplypos; + + sfree(request_name); + + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + if (pkt[wantreplypos]) + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + break; + + case SSH2_MSG_CHANNEL_OPEN: + /* Sender channel id comes after the channel type string */ + id_pos = getstring_size(pkt, pktlen); + if (id_pos < 0 || id_pos > pktlen - 12) { + err = dupprintf("Truncated CHANNEL_OPEN packet"); + goto confused; + } + + old_id = GET_32BIT(pkt + id_pos); + new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); + share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, + GET_32BIT(pkt + id_pos + 8)); + PUT_32BIT(pkt + id_pos, new_id); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + if (pktlen < 16) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + id_pos = 4; /* sender channel id is 2nd uint32 field in packet */ + old_id = GET_32BIT(pkt + id_pos); + + server_id = GET_32BIT(pkt); + /* This server id may refer to either a halfchannel or an xchannel. */ + hc = NULL, xc = NULL; /* placate optimiser */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + new_id = xc->upstream_id; + } else { + err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + PUT_32BIT(pkt + id_pos, new_id); + + chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, + GET_32BIT(pkt + 12)); + + if (hc) { + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if (xc) { + unsigned downstream_window = GET_32BIT(pkt + 8); + if (downstream_window < 256) { + err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); + goto confused; + } + share_xchannel_confirmation(cs, xc, chan, downstream_window); + share_remove_xchannel(cs, xc); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + if (pktlen < 4) { + err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); + goto confused; + } + + server_id = GET_32BIT(pkt); + /* This server id may refer to either a halfchannel or an xchannel. */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + share_xchannel_failure(cs, xc); + } else { + err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + case SSH2_MSG_IGNORE: + case SSH2_MSG_DEBUG: + if (type == SSH2_MSG_CHANNEL_REQUEST && + (request_name = getstring(pkt + 4, pktlen - 4)) != NULL) { + /* + * Agent forwarding requests from downstream are treated + * specially. Because OpenSSHD doesn't let us enable agent + * forwarding independently per session channel, and in + * particular because the OpenSSH-defined agent forwarding + * protocol does not mark agent-channel requests with the + * id of the session channel they originate from, the only + * way we can implement agent forwarding in a + * connection-shared PuTTY is to forward the _upstream_ + * agent. Hence, we unilaterally deny agent forwarding + * requests from downstreams if we aren't prepared to + * forward an agent ourselves. + * + * (If we are, then we dutifully pass agent forwarding + * requests upstream. OpenSSHD has the curious behaviour + * that all but the first such request will be rejected, + * but all session channels opened after the first request + * get agent forwarding enabled whether they ask for it or + * not; but that's not our concern, since other SSH + * servers supporting the same piece of protocol might in + * principle at least manage to enable agent forwarding on + * precisely the channels that requested it, even if the + * subsequent CHANNEL_OPENs still can't be associated with + * a parent session channel.) + */ + if (!strcmp(request_name, "auth-agent-req@openssh.com") && + !ssh_agent_forwarding_permitted(cs->parent->ssh)) { + unsigned server_id = GET_32BIT(pkt); + unsigned char recipient_id[4]; + + sfree(request_name); + + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + PUT_32BIT(recipient_id, chan->downstream_id); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, + recipient_id, 4, NULL); + } else { + char *buf = dupprintf("Agent forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + break; + } + + /* + * Another thing we treat specially is X11 forwarding + * requests. For these, we have to make up another set of + * X11 auth data, and enter it into our SSH connection's + * list of possible X11 authorisation credentials so that + * when we see an X11 channel open request we can know + * whether it's one to handle locally or one to pass on to + * a downstream, and if the latter, which one. + */ + if (!strcmp(request_name, "x11-req")) { + unsigned server_id = GET_32BIT(pkt); + int want_reply, single_connection, screen; + char *auth_proto_str, *auth_data; + int auth_proto, protolen, datalen; + int pos; + + sfree(request_name); + + chan = share_find_channel_by_server(cs, server_id); + if (!chan) { + char *buf = dupprintf("X11 forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + + /* + * Pick apart the whole message to find the downstream + * auth details. + */ + /* we have already seen: 4 bytes channel id, 4+7 request name */ + if (pktlen < 17) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); + goto confused; + } + want_reply = pkt[15] != 0; + single_connection = pkt[16] != 0; + auth_proto_str = getstring(pkt+17, pktlen-17); + auth_proto = x11_identify_auth_proto(auth_proto_str); + sfree(auth_proto_str); + pos = 17 + getstring_size(pkt+17, pktlen-17); + auth_data = getstring(pkt+pos, pktlen-pos); + pos += getstring_size(pkt+pos, pktlen-pos); + + if (pktlen < pos+4) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); + sfree(auth_data); + goto confused; + } + screen = GET_32BIT(pkt+pos); + + if (auth_proto < 0) { + /* Reject due to not understanding downstream's + * requested authorisation method. */ + unsigned char recipient_id[4]; + PUT_32BIT(recipient_id, chan->downstream_id); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, + recipient_id, 4, NULL); + sfree(auth_data); + break; + } + + chan->x11_auth_proto = auth_proto; + chan->x11_auth_data = x11_dehexify(auth_data, + &chan->x11_auth_datalen); + sfree(auth_data); + chan->x11_auth_upstream = + ssh_sharing_add_x11_display(cs->parent->ssh, auth_proto, + cs, chan); + chan->x11_one_shot = single_connection; + + /* + * Now construct a replacement X forwarding request, + * containing our own auth data, and send that to the + * server. + */ + protolen = strlen(chan->x11_auth_upstream->protoname); + datalen = strlen(chan->x11_auth_upstream->datastring); + pktlen = 29+protolen+datalen; + pkt = snewn(pktlen, unsigned char); + PUT_32BIT(pkt, server_id); + PUT_32BIT(pkt+4, 7); /* strlen("x11-req") */ + memcpy(pkt+8, "x11-req", 7); + pkt[15] = want_reply; + pkt[16] = single_connection; + PUT_32BIT(pkt+17, protolen); + memcpy(pkt+21, chan->x11_auth_upstream->protoname, protolen); + PUT_32BIT(pkt+21+protolen, datalen); + memcpy(pkt+25+protolen, chan->x11_auth_upstream->datastring, + datalen); + PUT_32BIT(pkt+25+protolen+datalen, screen); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_REQUEST, + pkt, pktlen, NULL); + sfree(pkt); + + break; + } + + sfree(request_name); + } + + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { + server_id = GET_32BIT(pkt); + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + if (chan->state == RCVD_CLOSE) { + ssh_delete_sharing_channel(cs->parent->ssh, + chan->upstream_id); + share_remove_channel(cs, chan); + } else { + chan->state = SENT_CLOSE; + } + } + } + break; + + default: + err = dupprintf("Unexpected packet type %d\n", type); + goto confused; + + /* + * Any other packet type is unexpected. In particular, we + * never pass GLOBAL_REQUESTs downstream, so we never expect + * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. + */ + confused: + assert(err != NULL); + share_disconnect(cs, err); + sfree(err); + break; + } +} + +/* + * Coroutine macros similar to, but simplified from, those in ssh.c. + */ +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crFinish(z) } *crLine = 0; return (z); } +#define crGetChar(c) do \ + { \ + while (len == 0) { \ + *crLine =__LINE__; return 1; case __LINE__:; \ + } \ + len--; \ + (c) = (unsigned char)*data++; \ + } while (0) + +static int share_receive(Plug plug, int urgent, char *data, int len) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + static const char expected_verstring_prefix[] = + "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; + unsigned char c; + + crBegin(cs->crLine); + + /* + * First read the version string from downstream. + */ + cs->recvlen = 0; + while (1) { + crGetChar(c); + if (c == '\012') + break; + if (cs->recvlen >= sizeof(cs->recvbuf)) { + char *buf = dupprintf("Version string far too long\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + cs->recvbuf[cs->recvlen++] = c; + } + + /* + * Now parse the version string to make sure it's at least vaguely + * sensible, and log it. + */ + if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || + memcmp(cs->recvbuf, expected_verstring_prefix, + sizeof(expected_verstring_prefix) - 1)) { + char *buf = dupprintf("Version string did not have expected prefix\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') + cs->recvlen--; /* trim off \r before \n */ + ssh_sharing_logf(cs->parent->ssh, cs->id, + "Downstream version string: %.*s", + cs->recvlen, cs->recvbuf); + + /* + * Loop round reading packets. + */ + while (1) { + cs->recvlen = 0; + while (cs->recvlen < 4) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + cs->curr_packetlen = toint(GET_32BIT(cs->recvbuf) + 4); + if (cs->curr_packetlen < 5 || + cs->curr_packetlen > sizeof(cs->recvbuf)) { + char *buf = dupprintf("Bad packet length %u\n", + (unsigned)cs->curr_packetlen); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + while (cs->recvlen < cs->curr_packetlen) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + + share_got_pkt_from_downstream(cs, cs->recvbuf[4], + cs->recvbuf + 5, cs->recvlen - 5); + } + + dead:; + crFinish(1); +} + +static void share_sent(Plug plug, int bufsize) +{ + /* struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; */ + + /* + * We do nothing here, because we expect that there won't be a + * need to throttle and unthrottle the connection to a downstream. + * It should automatically throttle itself: if the SSH server + * sends huge amounts of data on all channels then it'll run out + * of window until our downstream sends it back some + * WINDOW_ADJUSTs. + */ +} + +static int share_listen_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + if (error_msg) + ssh_sharing_logf(sharestate->ssh, 0, + "listening socket: %s", error_msg); + sk_close(sharestate->listensock); + sharestate->listensock = NULL; + return 1; +} + +static void share_send_verstring(struct ssh_sharing_connstate *cs) +{ + char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", + cs->parent->server_verstring, "\015\012", NULL); + sk_write(cs->sock, fullstring, strlen(fullstring)); + sfree(fullstring); + + cs->sent_verstring = TRUE; +} + +int share_ndownstreams(void *state) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state; + return count234(sharestate->connections); +} + +void share_activate(void *state, const char *server_verstring) +{ + /* + * Indication from ssh.c that we are now ready to begin serving + * any downstreams that have already connected to us. + */ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state; + struct ssh_sharing_connstate *cs; + int i; + + /* + * Trim the server's version string down to just the software + * version component, removing "SSH-2.0-" or whatever at the + * front. + */ + for (i = 0; i < 2; i++) { + server_verstring += strcspn(server_verstring, "-"); + if (*server_verstring) + server_verstring++; + } + + sharestate->server_verstring = dupstr(server_verstring); + + for (i = 0; (cs = (struct ssh_sharing_connstate *) + index234(sharestate->connections, i)) != NULL; i++) { + assert(!cs->sent_verstring); + share_send_verstring(cs); + } +} + +static int share_listen_accepting(Plug plug, + accept_fn_t constructor, accept_ctx_t ctx) +{ + static const struct plug_function_table connection_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + share_closing, + share_receive, + share_sent, + NULL /* no accepting function, because we've already done it */ + }; + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + struct ssh_sharing_connstate *cs; + const char *err; + char *peerinfo; + + /* + * A new downstream has connected to us. + */ + cs = snew(struct ssh_sharing_connstate); + cs->fn = &connection_fn_table; + cs->parent = sharestate; + + if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && + (cs->id = share_find_unused_id(sharestate, 1)) == 0) { + sfree(cs); + return 1; + } + sharestate->nextid = cs->id + 1; + if (sharestate->nextid == 0) + sharestate->nextid++; /* only happens in VERY long-running upstreams */ + + cs->sock = constructor(ctx, (Plug) cs); + if ((err = sk_socket_error(cs->sock)) != NULL) { + sfree(cs); + return err != NULL; + } + + sk_set_frozen(cs->sock, 0); + + add234(cs->parent->connections, cs); + + cs->sent_verstring = FALSE; + if (sharestate->server_verstring) + share_send_verstring(cs); + + cs->got_verstring = FALSE; + cs->recvlen = 0; + cs->crLine = 0; + cs->halfchannels = newtree234(share_halfchannel_cmp); + cs->channels_by_us = newtree234(share_channel_us_cmp); + cs->channels_by_server = newtree234(share_channel_server_cmp); + cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); + cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); + cs->forwardings = newtree234(share_forwarding_cmp); + cs->globreq_head = cs->globreq_tail = NULL; + + peerinfo = sk_peer_info(cs->sock); + ssh_sharing_downstream_connected(sharestate->ssh, cs->id, peerinfo); + sfree(peerinfo); + + return 0; +} + +/* Per-application overrides for what roles we can take (e.g. pscp + * will never be an upstream) */ +extern const int share_can_be_downstream; +extern const int share_can_be_upstream; + +/* + * Init function for connection sharing. We either open a listening + * socket and become an upstream, or connect to an existing one and + * become a downstream, or do neither. We are responsible for deciding + * which of these to do (including checking the Conf to see if + * connection sharing is even enabled in the first place). If we + * become a downstream, we return the Socket with which we connected + * to the upstream; otherwise (whether or not we have established an + * upstream) we return NULL. + */ +Socket ssh_connection_sharing_init(const char *host, int port, + Conf *conf, Ssh ssh, void **state) +{ + static const struct plug_function_table listen_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + share_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + share_listen_accepting + }; + + int result, can_upstream, can_downstream; + char *logtext, *ds_err, *us_err; + char *sockname; + Socket sock; + struct ssh_sharing_state *sharestate; + + if (!conf_get_int(conf, CONF_ssh_connection_sharing)) + return NULL; /* do not share anything */ + can_upstream = share_can_be_upstream && + conf_get_int(conf, CONF_ssh_connection_sharing_upstream); + can_downstream = share_can_be_downstream && + conf_get_int(conf, CONF_ssh_connection_sharing_downstream); + if (!can_upstream && !can_downstream) + return NULL; + + /* + * Decide on the string used to identify the connection point + * between upstream and downstream (be it a Windows named pipe or + * a Unix-domain socket or whatever else). + * + * I wondered about making this a SHA hash of all sorts of pieces + * of the PuTTY configuration - essentially everything PuTTY uses + * to know where and how to make a connection, including all the + * proxy details (or rather, all the _relevant_ ones - only + * including settings that other settings didn't prevent from + * having any effect), plus the username. However, I think it's + * better to keep it really simple: the connection point + * identifier is derived from the hostname and port used to index + * the host-key cache (not necessarily where we _physically_ + * connected to, in cases involving proxies or CONF_loghost), plus + * the username if one is specified. + */ + { + char *username = get_remote_username(conf); + + if (port == 22) { + if (username) + sockname = dupprintf("%s@%s", username, host); + else + sockname = dupprintf("%s", host); + } else { + if (username) + sockname = dupprintf("%s@%s:%d", username, host, port); + else + sockname = dupprintf("%s:%d", host, port); + } + + sfree(username); + + /* + * The platform-specific code may transform this further in + * order to conform to local namespace conventions (e.g. not + * using slashes in filenames), but that's its job and not + * ours. + */ + } + + /* + * Create a data structure for the listening plug if we turn out + * to be an upstream. + */ + sharestate = snew(struct ssh_sharing_state); + sharestate->fn = &listen_fn_table; + sharestate->listensock = NULL; + + /* + * Now hand off to a per-platform routine that either connects to + * an existing upstream (using 'ssh' as the plug), establishes our + * own upstream (using 'sharestate' as the plug), or forks off a + * separate upstream and then connects to that. It will return a + * code telling us which kind of socket it put in 'sock'. + */ + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share(sockname, conf, (Plug)ssh, + (Plug)sharestate, &sock, &logtext, &ds_err, + &us_err, can_upstream, can_downstream); + ssh_connshare_log(ssh, result, logtext, ds_err, us_err); + sfree(logtext); + sfree(ds_err); + sfree(us_err); + switch (result) { + case SHARE_NONE: + /* + * We aren't sharing our connection at all (e.g. something + * went wrong setting the socket up). Free the upstream + * structure and return NULL. + */ + assert(sock == NULL); + *state = NULL; + sfree(sharestate); + sfree(sockname); + return NULL; + + case SHARE_DOWNSTREAM: + /* + * We are downstream, so free sharestate which it turns out we + * don't need after all, and return the downstream socket as a + * replacement for an ordinary SSH connection. + */ + *state = NULL; + sfree(sharestate); + sfree(sockname); + return sock; + + case SHARE_UPSTREAM: + /* + * We are upstream. Set up sharestate properly and pass a copy + * to the caller; return NULL, to tell ssh.c that it has to + * make an ordinary connection after all. + */ + *state = sharestate; + sharestate->listensock = sock; + sharestate->connections = newtree234(share_connstate_cmp); + sharestate->ssh = ssh; + sharestate->server_verstring = NULL; + sharestate->sockname = sockname; + sharestate->nextid = 1; + return NULL; + } + + return NULL; +} diff --git a/netbox/libs/Putty/sshzlib.c b/netbox/libs/Putty/sshzlib.c new file mode 100644 index 000000000..c69edfb81 --- /dev/null +++ b/netbox/libs/Putty/sshzlib.c @@ -0,0 +1,1396 @@ +/* + * Zlib (RFC1950 / RFC1951) compression for PuTTY. + * + * There will no doubt be criticism of my decision to reimplement + * Zlib compression from scratch instead of using the existing zlib + * code. People will cry `reinventing the wheel'; they'll claim + * that the `fundamental basis of OSS' is code reuse; they'll want + * to see a really good reason for me having chosen not to use the + * existing code. + * + * Well, here are my reasons. Firstly, I don't want to link the + * whole of zlib into the PuTTY binary; PuTTY is justifiably proud + * of its small size and I think zlib contains a lot of unnecessary + * baggage for the kind of compression that SSH requires. + * + * Secondly, I also don't like the alternative of using zlib.dll. + * Another thing PuTTY is justifiably proud of is its ease of + * installation, and the last thing I want to do is to start + * mandating DLLs. Not only that, but there are two _kinds_ of + * zlib.dll kicking around, one with C calling conventions on the + * exported functions and another with WINAPI conventions, and + * there would be a significant danger of getting the wrong one. + * + * Thirdly, there seems to be a difference of opinion on the IETF + * secsh mailing list about the correct way to round off a + * compressed packet and start the next. In particular, there's + * some talk of switching to a mechanism zlib isn't currently + * capable of supporting (see below for an explanation). Given that + * sort of uncertainty, I thought it might be better to have code + * that will support even the zlib-incompatible worst case. + * + * Fourthly, it's a _second implementation_. Second implementations + * are fundamentally a Good Thing in standardisation efforts. The + * difference of opinion mentioned above has arisen _precisely_ + * because there has been only one zlib implementation and + * everybody has used it. I don't intend that this should happen + * again. + */ + +#include +#include +#include + +#ifdef ZLIB_STANDALONE + +/* + * This module also makes a handy zlib decoding tool for when + * you're picking apart Zip files or PDFs or PNGs. If you compile + * it with ZLIB_STANDALONE defined, it builds on its own and + * becomes a command-line utility. + * + * Therefore, here I provide a self-contained implementation of the + * macros required from the rest of the PuTTY sources. + */ +#define snew(type) ( (type *) malloc(sizeof(type)) ) +#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) ) +#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) ) +#define sfree(x) ( free((x)) ) + +#else +#include "ssh.h" +#endif + +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +/* ---------------------------------------------------------------------- + * Basic LZ77 code. This bit is designed modularly, so it could be + * ripped out and used in a different LZ77 compressor. Go to it, + * and good luck :-) + */ + +struct LZ77InternalContext; +struct LZ77Context { + struct LZ77InternalContext *ictx; + void *userdata; + void (*literal) (struct LZ77Context * ctx, unsigned char c); + void (*match) (struct LZ77Context * ctx, int distance, int len); +}; + +/* + * Initialise the private fields of an LZ77Context. It's up to the + * user to initialise the public fields. + */ +static int lz77_init(struct LZ77Context *ctx); + +/* + * Supply data to be compressed. Will update the private fields of + * the LZ77Context, and will call literal() and match() to output. + * If `compress' is FALSE, it will never emit a match, but will + * instead call literal() for everything. + */ +static void lz77_compress(struct LZ77Context *ctx, + unsigned char *data, int len, int compress); + +/* + * Modifiable parameters. + */ +#define WINSIZE 32768 /* window size. Must be power of 2! */ +#define HASHMAX 2039 /* one more than max hash value */ +#define MAXMATCH 32 /* how many matches we track */ +#define HASHCHARS 3 /* how many chars make a hash */ + +/* + * This compressor takes a less slapdash approach than the + * gzip/zlib one. Rather than allowing our hash chains to fall into + * disuse near the far end, we keep them doubly linked so we can + * _find_ the far end, and then every time we add a new byte to the + * window (thus rolling round by one and removing the previous + * byte), we can carefully remove the hash chain entry. + */ + +#define INVALID -1 /* invalid hash _and_ invalid offset */ +struct WindowEntry { + short next, prev; /* array indices within the window */ + short hashval; +}; + +struct HashEntry { + short first; /* window index of first in chain */ +}; + +struct Match { + int distance, len; +}; + +struct LZ77InternalContext { + struct WindowEntry win[WINSIZE]; + unsigned char data[WINSIZE]; + int winpos; + struct HashEntry hashtab[HASHMAX]; + unsigned char pending[HASHCHARS]; + int npending; +}; + +static int lz77_hash(unsigned char *data) +{ + return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX; +} + +static int lz77_init(struct LZ77Context *ctx) +{ + struct LZ77InternalContext *st; + int i; + + st = snew(struct LZ77InternalContext); + if (!st) + return 0; + + ctx->ictx = st; + + for (i = 0; i < WINSIZE; i++) + st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID; + for (i = 0; i < HASHMAX; i++) + st->hashtab[i].first = INVALID; + st->winpos = 0; + + st->npending = 0; + + return 1; +} + +static void lz77_advance(struct LZ77InternalContext *st, + unsigned char c, int hash) +{ + int off; + + /* + * Remove the hash entry at winpos from the tail of its chain, + * or empty the chain if it's the only thing on the chain. + */ + if (st->win[st->winpos].prev != INVALID) { + st->win[st->win[st->winpos].prev].next = INVALID; + } else if (st->win[st->winpos].hashval != INVALID) { + st->hashtab[st->win[st->winpos].hashval].first = INVALID; + } + + /* + * Create a new entry at winpos and add it to the head of its + * hash chain. + */ + st->win[st->winpos].hashval = hash; + st->win[st->winpos].prev = INVALID; + off = st->win[st->winpos].next = st->hashtab[hash].first; + st->hashtab[hash].first = st->winpos; + if (off != INVALID) + st->win[off].prev = st->winpos; + st->data[st->winpos] = c; + + /* + * Advance the window pointer. + */ + st->winpos = (st->winpos + 1) & (WINSIZE - 1); +} + +#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] ) + +static void lz77_compress(struct LZ77Context *ctx, + unsigned char *data, int len, int compress) +{ + struct LZ77InternalContext *st = ctx->ictx; + int i, hash, distance, off, nmatch, matchlen, advance; + struct Match defermatch, matches[MAXMATCH]; + int deferchr; + + assert(st->npending <= HASHCHARS); + + /* + * Add any pending characters from last time to the window. (We + * might not be able to.) + * + * This leaves st->pending empty in the usual case (when len >= + * HASHCHARS); otherwise it leaves st->pending empty enough that + * adding all the remaining 'len' characters will not push it past + * HASHCHARS in size. + */ + for (i = 0; i < st->npending; i++) { + unsigned char foo[HASHCHARS]; + int j; + if (len + st->npending - i < HASHCHARS) { + /* Update the pending array. */ + for (j = i; j < st->npending; j++) + st->pending[j - i] = st->pending[j]; + break; + } + for (j = 0; j < HASHCHARS; j++) + foo[j] = (i + j < st->npending ? st->pending[i + j] : + data[i + j - st->npending]); + lz77_advance(st, foo[0], lz77_hash(foo)); + } + st->npending -= i; + + defermatch.distance = 0; /* appease compiler */ + defermatch.len = 0; + deferchr = '\0'; + while (len > 0) { + + /* Don't even look for a match, if we're not compressing. */ + if (compress && len >= HASHCHARS) { + /* + * Hash the next few characters. + */ + hash = lz77_hash(data); + + /* + * Look the hash up in the corresponding hash chain and see + * what we can find. + */ + nmatch = 0; + for (off = st->hashtab[hash].first; + off != INVALID; off = st->win[off].next) { + /* distance = 1 if off == st->winpos-1 */ + /* distance = WINSIZE if off == st->winpos */ + distance = + WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE; + for (i = 0; i < HASHCHARS; i++) + if (CHARAT(i) != CHARAT(i - distance)) + break; + if (i == HASHCHARS) { + matches[nmatch].distance = distance; + matches[nmatch].len = 3; + if (++nmatch >= MAXMATCH) + break; + } + } + } else { + nmatch = 0; + hash = INVALID; + } + + if (nmatch > 0) { + /* + * We've now filled up matches[] with nmatch potential + * matches. Follow them down to find the longest. (We + * assume here that it's always worth favouring a + * longer match over a shorter one.) + */ + matchlen = HASHCHARS; + while (matchlen < len) { + int j; + for (i = j = 0; i < nmatch; i++) { + if (CHARAT(matchlen) == + CHARAT(matchlen - matches[i].distance)) { + matches[j++] = matches[i]; + } + } + if (j == 0) + break; + matchlen++; + nmatch = j; + } + + /* + * We've now got all the longest matches. We favour the + * shorter distances, which means we go with matches[0]. + * So see if we want to defer it or throw it away. + */ + matches[0].len = matchlen; + if (defermatch.len > 0) { + if (matches[0].len > defermatch.len + 1) { + /* We have a better match. Emit the deferred char, + * and defer this match. */ + ctx->literal(ctx, (unsigned char) deferchr); + defermatch = matches[0]; + deferchr = data[0]; + advance = 1; + } else { + /* We don't have a better match. Do the deferred one. */ + ctx->match(ctx, defermatch.distance, defermatch.len); + advance = defermatch.len - 1; + defermatch.len = 0; + } + } else { + /* There was no deferred match. Defer this one. */ + defermatch = matches[0]; + deferchr = data[0]; + advance = 1; + } + } else { + /* + * We found no matches. Emit the deferred match, if + * any; otherwise emit a literal. + */ + if (defermatch.len > 0) { + ctx->match(ctx, defermatch.distance, defermatch.len); + advance = defermatch.len - 1; + defermatch.len = 0; + } else { + ctx->literal(ctx, data[0]); + advance = 1; + } + } + + /* + * Now advance the position by `advance' characters, + * keeping the window and hash chains consistent. + */ + while (advance > 0) { + if (len >= HASHCHARS) { + lz77_advance(st, *data, lz77_hash(data)); + } else { + assert(st->npending < HASHCHARS); + st->pending[st->npending++] = *data; + } + data++; + len--; + advance--; + } + } +} + +/* ---------------------------------------------------------------------- + * Zlib compression. We always use the static Huffman tree option. + * Mostly this is because it's hard to scan a block in advance to + * work out better trees; dynamic trees are great when you're + * compressing a large file under no significant time constraint, + * but when you're compressing little bits in real time, things get + * hairier. + * + * I suppose it's possible that I could compute Huffman trees based + * on the frequencies in the _previous_ block, as a sort of + * heuristic, but I'm not confident that the gain would balance out + * having to transmit the trees. + */ + +struct Outbuf { + unsigned char *outbuf; + int outlen, outsize; + unsigned long outbits; + int noutbits; + int firstblock; + int comp_disabled; +}; + +static void outbits(struct Outbuf *out, unsigned long bits, int nbits) +{ + assert(out->noutbits + nbits <= 32); + out->outbits |= bits << out->noutbits; + out->noutbits += nbits; + while (out->noutbits >= 8) { + if (out->outlen >= out->outsize) { + out->outsize = out->outlen + 64; + out->outbuf = sresize(out->outbuf, out->outsize, unsigned char); + } + out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF); + out->outbits >>= 8; + out->noutbits -= 8; + } +} + +static const unsigned char mirrorbytes[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +typedef struct { + short code, extrabits; + int min, max; +} coderecord; + +static const coderecord lencodes[] = { + {257, 0, 3, 3}, + {258, 0, 4, 4}, + {259, 0, 5, 5}, + {260, 0, 6, 6}, + {261, 0, 7, 7}, + {262, 0, 8, 8}, + {263, 0, 9, 9}, + {264, 0, 10, 10}, + {265, 1, 11, 12}, + {266, 1, 13, 14}, + {267, 1, 15, 16}, + {268, 1, 17, 18}, + {269, 2, 19, 22}, + {270, 2, 23, 26}, + {271, 2, 27, 30}, + {272, 2, 31, 34}, + {273, 3, 35, 42}, + {274, 3, 43, 50}, + {275, 3, 51, 58}, + {276, 3, 59, 66}, + {277, 4, 67, 82}, + {278, 4, 83, 98}, + {279, 4, 99, 114}, + {280, 4, 115, 130}, + {281, 5, 131, 162}, + {282, 5, 163, 194}, + {283, 5, 195, 226}, + {284, 5, 227, 257}, + {285, 0, 258, 258}, +}; + +static const coderecord distcodes[] = { + {0, 0, 1, 1}, + {1, 0, 2, 2}, + {2, 0, 3, 3}, + {3, 0, 4, 4}, + {4, 1, 5, 6}, + {5, 1, 7, 8}, + {6, 2, 9, 12}, + {7, 2, 13, 16}, + {8, 3, 17, 24}, + {9, 3, 25, 32}, + {10, 4, 33, 48}, + {11, 4, 49, 64}, + {12, 5, 65, 96}, + {13, 5, 97, 128}, + {14, 6, 129, 192}, + {15, 6, 193, 256}, + {16, 7, 257, 384}, + {17, 7, 385, 512}, + {18, 8, 513, 768}, + {19, 8, 769, 1024}, + {20, 9, 1025, 1536}, + {21, 9, 1537, 2048}, + {22, 10, 2049, 3072}, + {23, 10, 3073, 4096}, + {24, 11, 4097, 6144}, + {25, 11, 6145, 8192}, + {26, 12, 8193, 12288}, + {27, 12, 12289, 16384}, + {28, 13, 16385, 24576}, + {29, 13, 24577, 32768}, +}; + +static void zlib_literal(struct LZ77Context *ectx, unsigned char c) +{ + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + + if (out->comp_disabled) { + /* + * We're in an uncompressed block, so just output the byte. + */ + outbits(out, c, 8); + return; + } + + if (c <= 143) { + /* 0 through 143 are 8 bits long starting at 00110000. */ + outbits(out, mirrorbytes[0x30 + c], 8); + } else { + /* 144 through 255 are 9 bits long starting at 110010000. */ + outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9); + } +} + +static void zlib_match(struct LZ77Context *ectx, int distance, int len) +{ + const coderecord *d, *l; + int i, j, k; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + + assert(!out->comp_disabled); + + while (len > 0) { + int thislen; + + /* + * We can transmit matches of lengths 3 through 258 + * inclusive. So if len exceeds 258, we must transmit in + * several steps, with 258 or less in each step. + * + * Specifically: if len >= 261, we can transmit 258 and be + * sure of having at least 3 left for the next step. And if + * len <= 258, we can just transmit len. But if len == 259 + * or 260, we must transmit len-3. + */ + thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3); + len -= thislen; + + /* + * Binary-search to find which length code we're + * transmitting. + */ + i = -1; + j = sizeof(lencodes) / sizeof(*lencodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (thislen < lencodes[k].min) + j = k; + else if (thislen > lencodes[k].max) + i = k; + else { + l = &lencodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the length code. 256-279 are seven bits + * starting at 0000000; 280-287 are eight bits starting at + * 11000000. + */ + if (l->code <= 279) { + outbits(out, mirrorbytes[(l->code - 256) * 2], 7); + } else { + outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8); + } + + /* + * Transmit the extra bits. + */ + if (l->extrabits) + outbits(out, thislen - l->min, l->extrabits); + + /* + * Binary-search to find which distance code we're + * transmitting. + */ + i = -1; + j = sizeof(distcodes) / sizeof(*distcodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (distance < distcodes[k].min) + j = k; + else if (distance > distcodes[k].max) + i = k; + else { + d = &distcodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the distance code. Five bits starting at 00000. + */ + outbits(out, mirrorbytes[d->code * 8], 5); + + /* + * Transmit the extra bits. + */ + if (d->extrabits) + outbits(out, distance - d->min, d->extrabits); + } +} + +void *zlib_compress_init(void) +{ + struct Outbuf *out; + struct LZ77Context *ectx = snew(struct LZ77Context); + + lz77_init(ectx); + ectx->literal = zlib_literal; + ectx->match = zlib_match; + + out = snew(struct Outbuf); + out->outbits = out->noutbits = 0; + out->firstblock = 1; + out->comp_disabled = FALSE; + ectx->userdata = out; + + return ectx; +} + +void zlib_compress_cleanup(void *handle) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + sfree(ectx->userdata); + sfree(ectx->ictx); + sfree(ectx); +} + +/* + * Turn off actual LZ77 analysis for one block, to facilitate + * construction of a precise-length IGNORE packet. Returns the + * length adjustment (which is only valid for packets < 65536 + * bytes, but that seems reasonable enough). + */ +static int zlib_disable_compression(void *handle) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + int n; + + out->comp_disabled = TRUE; + + n = 0; + /* + * If this is the first block, we will start by outputting two + * header bytes, and then three bits to begin an uncompressed + * block. This will cost three bytes (because we will start on + * a byte boundary, this is certain). + */ + if (out->firstblock) { + n = 3; + } else { + /* + * Otherwise, we will output seven bits to close the + * previous static block, and _then_ three bits to begin an + * uncompressed block, and then flush the current byte. + * This may cost two bytes or three, depending on noutbits. + */ + n += (out->noutbits + 10) / 8; + } + + /* + * Now we output four bytes for the length / ~length pair in + * the uncompressed block. + */ + n += 4; + + return n; +} + +int zlib_compress_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + int in_block; + + out->outbuf = NULL; + out->outlen = out->outsize = 0; + + /* + * If this is the first block, output the Zlib (RFC1950) header + * bytes 78 9C. (Deflate compression, 32K window size, default + * algorithm.) + */ + if (out->firstblock) { + outbits(out, 0x9C78, 16); + out->firstblock = 0; + + in_block = FALSE; + } else + in_block = TRUE; + + if (out->comp_disabled) { + if (in_block) + outbits(out, 0, 7); /* close static block */ + + while (len > 0) { + int blen = (len < 65535 ? len : 65535); + + /* + * Start a Deflate (RFC1951) uncompressed block. We + * transmit a zero bit (BFINAL=0), followed by two more + * zero bits (BTYPE=00). Of course these are in the + * wrong order (00 0), not that it matters. + */ + outbits(out, 0, 3); + + /* + * Output zero bits to align to a byte boundary. + */ + if (out->noutbits) + outbits(out, 0, 8 - out->noutbits); + + /* + * Output the block length, and then its one's + * complement. They're little-endian, so all we need to + * do is pass them straight to outbits() with bit count + * 16. + */ + outbits(out, blen, 16); + outbits(out, blen ^ 0xFFFF, 16); + + /* + * Do the `compression': we need to pass the data to + * lz77_compress so that it will be taken into account + * for subsequent (distance,length) pairs. But + * lz77_compress is passed FALSE, which means it won't + * actually find (or even look for) any matches; so + * every character will be passed straight to + * zlib_literal which will spot out->comp_disabled and + * emit in the uncompressed format. + */ + lz77_compress(ectx, block, blen, FALSE); + + len -= blen; + block += blen; + } + outbits(out, 2, 3); /* open new block */ + } else { + if (!in_block) { + /* + * Start a Deflate (RFC1951) fixed-trees block. We + * transmit a zero bit (BFINAL=0), followed by a zero + * bit and a one bit (BTYPE=01). Of course these are in + * the wrong order (01 0). + */ + outbits(out, 2, 3); + } + + /* + * Do the compression. + */ + lz77_compress(ectx, block, len, TRUE); + + /* + * End the block (by transmitting code 256, which is + * 0000000 in fixed-tree mode), and transmit some empty + * blocks to ensure we have emitted the byte containing the + * last piece of genuine data. There are three ways we can + * do this: + * + * - Minimal flush. Output end-of-block and then open a + * new static block. This takes 9 bits, which is + * guaranteed to flush out the last genuine code in the + * closed block; but allegedly zlib can't handle it. + * + * - Zlib partial flush. Output EOB, open and close an + * empty static block, and _then_ open the new block. + * This is the best zlib can handle. + * + * - Zlib sync flush. Output EOB, then an empty + * _uncompressed_ block (000, then sync to byte + * boundary, then send bytes 00 00 FF FF). Then open the + * new block. + * + * For the moment, we will use Zlib partial flush. + */ + outbits(out, 0, 7); /* close block */ + outbits(out, 2, 3 + 7); /* empty static block */ + outbits(out, 2, 3); /* open new block */ + } + + out->comp_disabled = FALSE; + + *outblock = out->outbuf; + *outlen = out->outlen; + + return 1; +} + +/* ---------------------------------------------------------------------- + * Zlib decompression. Of course, even though our compressor always + * uses static trees, our _decompressor_ has to be capable of + * handling dynamic trees if it sees them. + */ + +/* + * The way we work the Huffman decode is to have a table lookup on + * the first N bits of the input stream (in the order they arrive, + * of course, i.e. the first bit of the Huffman code is in bit 0). + * Each table entry lists the number of bits to consume, plus + * either an output code or a pointer to a secondary table. + */ +struct zlib_table; +struct zlib_tableentry; + +struct zlib_tableentry { + unsigned char nbits; + short code; + struct zlib_table *nexttable; +}; + +struct zlib_table { + int mask; /* mask applied to input bit stream */ + struct zlib_tableentry *table; +}; + +#define MAXCODELEN 16 +#define MAXSYMS 288 + +/* + * Build a single-level decode table for elements + * [minlength,maxlength) of the provided code/length tables, and + * recurse to build subtables. + */ +static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths, + int nsyms, + int pfx, int pfxbits, int bits) +{ + struct zlib_table *tab = snew(struct zlib_table); + int pfxmask = (1 << pfxbits) - 1; + int nbits, i, j, code; + + tab->table = snewn(1 << bits, struct zlib_tableentry); + tab->mask = (1 << bits) - 1; + + for (code = 0; code <= tab->mask; code++) { + tab->table[code].code = -1; + tab->table[code].nbits = 0; + tab->table[code].nexttable = NULL; + } + + for (i = 0; i < nsyms; i++) { + if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx) + continue; + code = (codes[i] >> pfxbits) & tab->mask; + for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) { + tab->table[j].code = i; + nbits = lengths[i] - pfxbits; + if (tab->table[j].nbits < nbits) + tab->table[j].nbits = nbits; + } + } + for (code = 0; code <= tab->mask; code++) { + if (tab->table[code].nbits <= bits) + continue; + /* Generate a subtable. */ + tab->table[code].code = -1; + nbits = tab->table[code].nbits - bits; + if (nbits > 7) + nbits = 7; + tab->table[code].nbits = bits; + tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms, + pfx | (code << pfxbits), + pfxbits + bits, nbits); + } + + return tab; +} + +/* + * Build a decode table, given a set of Huffman tree lengths. + */ +static struct zlib_table *zlib_mktable(unsigned char *lengths, + int nlengths) +{ + int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS]; + int code, maxlen; + int i, j; + + /* Count the codes of each length. */ + maxlen = 0; + for (i = 1; i < MAXCODELEN; i++) + count[i] = 0; + for (i = 0; i < nlengths; i++) { + count[lengths[i]]++; + if (maxlen < lengths[i]) + maxlen = lengths[i]; + } + /* Determine the starting code for each length block. */ + code = 0; + for (i = 1; i < MAXCODELEN; i++) { + startcode[i] = code; + code += count[i]; + code <<= 1; + } + /* Determine the code for each symbol. Mirrored, of course. */ + for (i = 0; i < nlengths; i++) { + code = startcode[lengths[i]]++; + codes[i] = 0; + for (j = 0; j < lengths[i]; j++) { + codes[i] = (codes[i] << 1) | (code & 1); + code >>= 1; + } + } + + /* + * Now we have the complete list of Huffman codes. Build a + * table. + */ + return zlib_mkonetab(codes, lengths, nlengths, 0, 0, + maxlen < 9 ? maxlen : 9); +} + +static int zlib_freetable(struct zlib_table **ztab) +{ + struct zlib_table *tab; + int code; + + if (ztab == NULL) + return -1; + + if (*ztab == NULL) + return 0; + + tab = *ztab; + + for (code = 0; code <= tab->mask; code++) + if (tab->table[code].nexttable != NULL) + zlib_freetable(&tab->table[code].nexttable); + + sfree(tab->table); + tab->table = NULL; + + sfree(tab); + *ztab = NULL; + + return (0); +} + +struct zlib_decompress_ctx { + struct zlib_table *staticlentable, *staticdisttable; + struct zlib_table *currlentable, *currdisttable, *lenlentable; + enum { + START, OUTSIDEBLK, + TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP, + INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM, + UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA + } state; + int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len, + lenrep; + int uncomplen; + unsigned char lenlen[19]; + unsigned char lengths[286 + 32]; + unsigned long bits; + int nbits; + unsigned char window[WINSIZE]; + int winpos; + unsigned char *outblk; + int outlen, outsize; +}; + +void *zlib_decompress_init(void) +{ + struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx); + unsigned char lengths[288]; + + memset(lengths, 8, 144); + memset(lengths + 144, 9, 256 - 144); + memset(lengths + 256, 7, 280 - 256); + memset(lengths + 280, 8, 288 - 280); + dctx->staticlentable = zlib_mktable(lengths, 288); + memset(lengths, 5, 32); + dctx->staticdisttable = zlib_mktable(lengths, 32); + dctx->state = START; /* even before header */ + dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL; + dctx->bits = 0; + dctx->nbits = 0; + dctx->winpos = 0; + + return dctx; +} + +void zlib_decompress_cleanup(void *handle) +{ + struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + + if (dctx->currlentable && dctx->currlentable != dctx->staticlentable) + zlib_freetable(&dctx->currlentable); + if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable) + zlib_freetable(&dctx->currdisttable); + if (dctx->lenlentable) + zlib_freetable(&dctx->lenlentable); + zlib_freetable(&dctx->staticlentable); + zlib_freetable(&dctx->staticdisttable); + sfree(dctx); +} + +static int zlib_huflookup(unsigned long *bitsp, int *nbitsp, + struct zlib_table *tab) +{ + unsigned long bits = *bitsp; + int nbits = *nbitsp; + while (1) { + struct zlib_tableentry *ent; + ent = &tab->table[bits & tab->mask]; + if (ent->nbits > nbits) + return -1; /* not enough data */ + bits >>= ent->nbits; + nbits -= ent->nbits; + if (ent->code == -1) + tab = ent->nexttable; + else { + *bitsp = bits; + *nbitsp = nbits; + return ent->code; + } + + if (!tab) { + /* + * There was a missing entry in the table, presumably + * due to an invalid Huffman table description, and the + * subsequent data has attempted to use the missing + * entry. Return a decoding failure. + */ + return -2; + } + } +} + +static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c) +{ + dctx->window[dctx->winpos] = c; + dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1); + if (dctx->outlen >= dctx->outsize) { + dctx->outsize = dctx->outlen + 512; + dctx->outblk = sresize(dctx->outblk, dctx->outsize, unsigned char); + } + dctx->outblk[dctx->outlen++] = c; +} + +#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) + +int zlib_decompress_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + const coderecord *rec; + int code, blktype, rep, dist, nlen, header; + static const unsigned char lenlenmap[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + dctx->outblk = snewn(256, unsigned char); + dctx->outsize = 256; + dctx->outlen = 0; + + while (len > 0 || dctx->nbits > 0) { + while (dctx->nbits < 24 && len > 0) { + dctx->bits |= (*block++) << dctx->nbits; + dctx->nbits += 8; + len--; + } + switch (dctx->state) { + case START: + /* Expect 16-bit zlib header. */ + if (dctx->nbits < 16) + goto finished; /* done all we can */ + + /* + * The header is stored as a big-endian 16-bit integer, + * in contrast to the general little-endian policy in + * the rest of the format :-( + */ + header = (((dctx->bits & 0xFF00) >> 8) | + ((dctx->bits & 0x00FF) << 8)); + EATBITS(16); + + /* + * Check the header: + * + * - bits 8-11 should be 1000 (Deflate/RFC1951) + * - bits 12-15 should be at most 0111 (window size) + * - bit 5 should be zero (no dictionary present) + * - we don't care about bits 6-7 (compression rate) + * - bits 0-4 should be set up to make the whole thing + * a multiple of 31 (checksum). + */ + if ((header & 0x0F00) != 0x0800 || + (header & 0xF000) > 0x7000 || + (header & 0x0020) != 0x0000 || + (header % 31) != 0) + goto decode_error; + + dctx->state = OUTSIDEBLK; + break; + case OUTSIDEBLK: + /* Expect 3-bit block header. */ + if (dctx->nbits < 3) + goto finished; /* done all we can */ + EATBITS(1); + blktype = dctx->bits & 3; + EATBITS(2); + if (blktype == 0) { + int to_eat = dctx->nbits & 7; + dctx->state = UNCOMP_LEN; + EATBITS(to_eat); /* align to byte boundary */ + } else if (blktype == 1) { + dctx->currlentable = dctx->staticlentable; + dctx->currdisttable = dctx->staticdisttable; + dctx->state = INBLK; + } else if (blktype == 2) { + dctx->state = TREES_HDR; + } + break; + case TREES_HDR: + /* + * Dynamic block header. Five bits of HLIT, five of + * HDIST, four of HCLEN. + */ + if (dctx->nbits < 5 + 5 + 4) + goto finished; /* done all we can */ + dctx->hlit = 257 + (dctx->bits & 31); + EATBITS(5); + dctx->hdist = 1 + (dctx->bits & 31); + EATBITS(5); + dctx->hclen = 4 + (dctx->bits & 15); + EATBITS(4); + dctx->lenptr = 0; + dctx->state = TREES_LENLEN; + memset(dctx->lenlen, 0, sizeof(dctx->lenlen)); + break; + case TREES_LENLEN: + if (dctx->nbits < 3) + goto finished; + while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) { + dctx->lenlen[lenlenmap[dctx->lenptr++]] = + (unsigned char) (dctx->bits & 7); + EATBITS(3); + } + if (dctx->lenptr == dctx->hclen) { + dctx->lenlentable = zlib_mktable(dctx->lenlen, 19); + dctx->state = TREES_LEN; + dctx->lenptr = 0; + } + break; + case TREES_LEN: + if (dctx->lenptr >= dctx->hlit + dctx->hdist) { + dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit); + dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit, + dctx->hdist); + zlib_freetable(&dctx->lenlentable); + dctx->lenlentable = NULL; + dctx->state = INBLK; + break; + } + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + if (code < 16) + dctx->lengths[dctx->lenptr++] = code; + else { + dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7); + dctx->lenaddon = (code == 18 ? 11 : 3); + dctx->lenrep = (code == 16 && dctx->lenptr > 0 ? + dctx->lengths[dctx->lenptr - 1] : 0); + dctx->state = TREES_LENREP; + } + break; + case TREES_LENREP: + if (dctx->nbits < dctx->lenextrabits) + goto finished; + rep = + dctx->lenaddon + + (dctx->bits & ((1 << dctx->lenextrabits) - 1)); + EATBITS(dctx->lenextrabits); + while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) { + dctx->lengths[dctx->lenptr] = dctx->lenrep; + dctx->lenptr++; + rep--; + } + dctx->state = TREES_LEN; + break; + case INBLK: + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + if (code < 256) + zlib_emit_char(dctx, code); + else if (code == 256) { + dctx->state = OUTSIDEBLK; + if (dctx->currlentable != dctx->staticlentable) { + zlib_freetable(&dctx->currlentable); + dctx->currlentable = NULL; + } + if (dctx->currdisttable != dctx->staticdisttable) { + zlib_freetable(&dctx->currdisttable); + dctx->currdisttable = NULL; + } + } else if (code < 286) { /* static tree can give >285; ignore */ + dctx->state = GOTLENSYM; + dctx->sym = code; + } + break; + case GOTLENSYM: + rec = &lencodes[dctx->sym - 257]; + if (dctx->nbits < rec->extrabits) + goto finished; + dctx->len = + rec->min + (dctx->bits & ((1 << rec->extrabits) - 1)); + EATBITS(rec->extrabits); + dctx->state = GOTLEN; + break; + case GOTLEN: + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, + dctx->currdisttable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + if (code >= 30) /* dist symbols 30 and 31 are invalid */ + goto decode_error; + dctx->state = GOTDISTSYM; + dctx->sym = code; + break; + case GOTDISTSYM: + rec = &distcodes[dctx->sym]; + if (dctx->nbits < rec->extrabits) + goto finished; + dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1)); + EATBITS(rec->extrabits); + dctx->state = INBLK; + while (dctx->len--) + zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) & + (WINSIZE - 1)]); + break; + case UNCOMP_LEN: + /* + * Uncompressed block. We expect to see a 16-bit LEN. + */ + if (dctx->nbits < 16) + goto finished; + dctx->uncomplen = dctx->bits & 0xFFFF; + EATBITS(16); + dctx->state = UNCOMP_NLEN; + break; + case UNCOMP_NLEN: + /* + * Uncompressed block. We expect to see a 16-bit NLEN, + * which should be the one's complement of the previous + * LEN. + */ + if (dctx->nbits < 16) + goto finished; + nlen = dctx->bits & 0xFFFF; + EATBITS(16); + if (dctx->uncomplen != (nlen ^ 0xFFFF)) + goto decode_error; + if (dctx->uncomplen == 0) + dctx->state = OUTSIDEBLK; /* block is empty */ + else + dctx->state = UNCOMP_DATA; + break; + case UNCOMP_DATA: + if (dctx->nbits < 8) + goto finished; + zlib_emit_char(dctx, dctx->bits & 0xFF); + EATBITS(8); + if (--dctx->uncomplen == 0) + dctx->state = OUTSIDEBLK; /* end of uncompressed block */ + break; + } + } + + finished: + *outblock = dctx->outblk; + *outlen = dctx->outlen; + return 1; + + decode_error: + sfree(dctx->outblk); + *outblock = dctx->outblk = NULL; + *outlen = 0; + return 0; +} + +#ifdef ZLIB_STANDALONE + +#include +#include + +int main(int argc, char **argv) +{ + unsigned char buf[16], *outbuf; + int ret, outlen; + void *handle; + int noheader = FALSE, opts = TRUE; + char *filename = NULL; + FILE *fp; + + while (--argc) { + char *p = *++argv; + + if (p[0] == '-' && opts) { + if (!strcmp(p, "-d")) + noheader = TRUE; + else if (!strcmp(p, "--")) + opts = FALSE; /* next thing is filename */ + else { + fprintf(stderr, "unknown command line option '%s'\n", p); + return 1; + } + } else if (!filename) { + filename = p; + } else { + fprintf(stderr, "can only handle one filename\n"); + return 1; + } + } + + handle = zlib_decompress_init(); + + if (noheader) { + /* + * Provide missing zlib header if -d was specified. + */ + zlib_decompress_block(handle, "\x78\x9C", 2, &outbuf, &outlen); + assert(outlen == 0); + } + + if (filename) + fp = fopen(filename, "rb"); + else + fp = stdin; + + if (!fp) { + assert(filename); + fprintf(stderr, "unable to open '%s'\n", filename); + return 1; + } + + while (1) { + ret = fread(buf, 1, sizeof(buf), fp); + if (ret <= 0) + break; + zlib_decompress_block(handle, buf, ret, &outbuf, &outlen); + if (outbuf) { + if (outlen) + fwrite(outbuf, 1, outlen, stdout); + sfree(outbuf); + } else { + fprintf(stderr, "decoding error\n"); + return 1; + } + } + + zlib_decompress_cleanup(handle); + + if (filename) + fclose(fp); + + return 0; +} + +#else + +const struct ssh_compress ssh_zlib = { + "zlib", + "zlib@openssh.com", /* delayed version */ + zlib_compress_init, + zlib_compress_cleanup, + zlib_compress_block, + zlib_decompress_init, + zlib_decompress_cleanup, + zlib_decompress_block, + zlib_disable_compression, + "zlib (RFC1950)" +}; + +#endif diff --git a/netbox/libs/Putty/storage.h b/netbox/libs/Putty/storage.h new file mode 100644 index 000000000..8e07ef0ca --- /dev/null +++ b/netbox/libs/Putty/storage.h @@ -0,0 +1,114 @@ +/* + * storage.h: interface defining functions for storage and recovery + * of PuTTY's persistent data. + */ + +#ifndef PUTTY_STORAGE_H +#define PUTTY_STORAGE_H + +/* ---------------------------------------------------------------------- + * Functions to save and restore PuTTY sessions. Note that this is + * only the low-level code to do the reading and writing. The + * higher-level code that translates an internal Conf structure into + * a set of (key,value) pairs in their external storage format is + * elsewhere, since it doesn't (mostly) change between platforms. + */ + +/* + * Write a saved session. The caller is expected to call + * open_setting_w() to get a `void *' handle, then pass that to a + * number of calls to write_setting_s() and write_setting_i(), and + * then close it using close_settings_w(). At the end of this call + * sequence the settings should have been written to the PuTTY + * persistent storage area. + * + * A given key will be written at most once while saving a session. + * Keys may be up to 255 characters long. String values have no length + * limit. + * + * Any returned error message must be freed after use. + */ +void *open_settings_w(const char *sessionname, char **errmsg); +void write_setting_s(void *handle, const char *key, const char *value); +void write_setting_i(void *handle, const char *key, int value); +void write_setting_filename(void *handle, const char *key, Filename *value); +void write_setting_fontspec(void *handle, const char *key, FontSpec *font); +void close_settings_w(void *handle); + +/* + * Read a saved session. The caller is expected to call + * open_setting_r() to get a `void *' handle, then pass that to a + * number of calls to read_setting_s() and read_setting_i(), and + * then close it using close_settings_r(). + * + * read_setting_s() returns a dynamically allocated string which the + * caller must free. read_setting_filename() and + * read_setting_fontspec() likewise return dynamically allocated + * structures. + * + * If a particular string setting is not present in the session, + * read_setting_s() can return NULL, in which case the caller + * should invent a sensible default. If an integer setting is not + * present, read_setting_i() returns its provided default. + */ +void *open_settings_r(const char *sessionname); +char *read_setting_s(void *handle, const char *key); +int read_setting_i(void *handle, const char *key, int defvalue); +Filename *read_setting_filename(void *handle, const char *key); +FontSpec *read_setting_fontspec(void *handle, const char *key); +void close_settings_r(void *handle); + +/* + * Delete a whole saved session. + */ +void del_settings(const char *sessionname); + +/* + * Enumerate all saved sessions. + */ +void *enum_settings_start(void); +char *enum_settings_next(void *handle, char *buffer, int buflen); +void enum_settings_finish(void *handle); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's host key database. + */ + +/* + * See if a host key matches the database entry. Return values can + * be 0 (entry matches database), 1 (entry is absent in database), + * or 2 (entry exists in database and is different). + */ +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* + * Write a host key into the database, overwriting any previous + * entry that might have been there. + */ +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's random number seed file. + */ + +typedef void (*noise_consumer_t) (void *data, int len); + +/* + * Read PuTTY's random seed file and pass its contents to a noise + * consumer function. + */ +void read_random_seed(noise_consumer_t consumer); + +/* + * Write PuTTY's random seed file from a given chunk of noise. + */ +void write_random_seed(void *data, int len); + +/* ---------------------------------------------------------------------- + * Cleanup function: remove all of PuTTY's persistent state. + */ +void cleanup_all(void); + +#endif diff --git a/netbox/libs/Putty/telnet.c b/netbox/libs/Putty/telnet.c new file mode 100644 index 000000000..098db292c --- /dev/null +++ b/netbox/libs/Putty/telnet.c @@ -0,0 +1,1135 @@ +/* + * Telnet backend. + */ + +#include +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define IAC 255 /* interpret as command: */ +#define DONT 254 /* you are not to use option */ +#define DO 253 /* please, you use option */ +#define WONT 252 /* I won't use option */ +#define WILL 251 /* I will use option */ +#define SB 250 /* interpret as subnegotiation */ +#define SE 240 /* end sub negotiation */ + +#define GA 249 /* you may reverse the line */ +#define EL 248 /* erase the current line */ +#define EC 247 /* erase the current character */ +#define AYT 246 /* are you there */ +#define AO 245 /* abort output--but let prog finish */ +#define IP 244 /* interrupt process--permanently */ +#define BREAK 243 /* break */ +#define DM 242 /* data mark--for connect. cleaning */ +#define NOP 241 /* nop */ +#define EOR 239 /* end of record (transparent mode) */ +#define ABORT 238 /* Abort process */ +#define SUSP 237 /* Suspend process */ +#define xEOF 236 /* End of file: EOF is already used... */ + +#define TELOPTS(X) \ + X(BINARY, 0) /* 8-bit data path */ \ + X(ECHO, 1) /* echo */ \ + X(RCP, 2) /* prepare to reconnect */ \ + X(SGA, 3) /* suppress go ahead */ \ + X(NAMS, 4) /* approximate message size */ \ + X(STATUS, 5) /* give status */ \ + X(TM, 6) /* timing mark */ \ + X(RCTE, 7) /* remote controlled transmission and echo */ \ + X(NAOL, 8) /* negotiate about output line width */ \ + X(NAOP, 9) /* negotiate about output page size */ \ + X(NAOCRD, 10) /* negotiate about CR disposition */ \ + X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ + X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ + X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ + X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ + X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ + X(NAOLFD, 16) /* negotiate about output LF disposition */ \ + X(XASCII, 17) /* extended ascic character set */ \ + X(LOGOUT, 18) /* force logout */ \ + X(BM, 19) /* byte macro */ \ + X(DET, 20) /* data entry terminal */ \ + X(SUPDUP, 21) /* supdup protocol */ \ + X(SUPDUPOUTPUT, 22) /* supdup output */ \ + X(SNDLOC, 23) /* send location */ \ + X(TTYPE, 24) /* terminal type */ \ + X(EOR, 25) /* end or record */ \ + X(TUID, 26) /* TACACS user identification */ \ + X(OUTMRK, 27) /* output marking */ \ + X(TTYLOC, 28) /* terminal location number */ \ + X(3270REGIME, 29) /* 3270 regime */ \ + X(X3PAD, 30) /* X.3 PAD */ \ + X(NAWS, 31) /* window size */ \ + X(TSPEED, 32) /* terminal speed */ \ + X(LFLOW, 33) /* remote flow control */ \ + X(LINEMODE, 34) /* Linemode option */ \ + X(XDISPLOC, 35) /* X Display Location */ \ + X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ + X(AUTHENTICATION, 37) /* Authenticate */ \ + X(ENCRYPT, 38) /* Encryption option */ \ + X(NEW_ENVIRON, 39) /* New - Environment variables */ \ + X(TN3270E, 40) /* TN3270 enhancements */ \ + X(XAUTH, 41) \ + X(CHARSET, 42) /* Character set */ \ + X(RSP, 43) /* Remote serial port */ \ + X(COM_PORT_OPTION, 44) /* Com port control */ \ + X(SLE, 45) /* Suppress local echo */ \ + X(STARTTLS, 46) /* Start TLS */ \ + X(KERMIT, 47) /* Automatic Kermit file transfer */ \ + X(SEND_URL, 48) \ + X(FORWARD_X, 49) \ + X(PRAGMA_LOGON, 138) \ + X(SSPI_LOGON, 139) \ + X(PRAGMA_HEARTBEAT, 140) \ + X(EXOPL, 255) /* extended-options-list */ + +#define telnet_enum(x,y) TELOPT_##x = y, +enum { TELOPTS(telnet_enum) dummy=0 }; +#undef telnet_enum + +#define TELQUAL_IS 0 /* option is... */ +#define TELQUAL_SEND 1 /* send option */ +#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ +#define BSD_VAR 1 +#define BSD_VALUE 0 +#define RFC_VAR 0 +#define RFC_VALUE 1 + +#define CR 13 +#define LF 10 +#define NUL 0 + +#define iswritable(x) \ + ( (x) != IAC && \ + (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR)) + +static char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return ""; + } +#undef telnet_str +} + +static void telnet_size(void *handle, int width, int height); + +struct Opt { + int send; /* what we initially send */ + int nsend; /* -ve send if requested to stop it */ + int ack, nak; /* +ve and -ve acknowledgements */ + int option; /* the option code */ + int index; /* index into telnet->opt_states[] */ + enum { + REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE + } initial_state; +}; + +enum { + OPTINDEX_NAWS, + OPTINDEX_TSPEED, + OPTINDEX_TTYPE, + OPTINDEX_OENV, + OPTINDEX_NENV, + OPTINDEX_ECHO, + OPTINDEX_WE_SGA, + OPTINDEX_THEY_SGA, + OPTINDEX_WE_BIN, + OPTINDEX_THEY_BIN, + NUM_OPTS +}; + +static const struct Opt o_naws = + { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; +static const struct Opt o_tspeed = + { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED }; +static const struct Opt o_ttype = + { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; +static const struct Opt o_oenv = + { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; +static const struct Opt o_nenv = + { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; +static const struct Opt o_echo = + { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; +static const struct Opt o_we_sga = + { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; +static const struct Opt o_they_sga = + { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; +static const struct Opt o_we_bin = + { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE }; +static const struct Opt o_they_bin = + { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE }; + +static const struct Opt *const opts[] = { + &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo, + &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL +}; + +typedef struct telnet_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + int closed_on_socket_error; + + void *frontend; + void *ldisc; + int term_width, term_height; + + int opt_states[NUM_OPTS]; + + int echoing, editing; + int activated; + int bufsize; + int in_synch; + int sb_opt, sb_len; + unsigned char *sb_buf; + int sb_size; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Conf *conf; + + Pinger pinger; +} *Telnet; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void c_write(Telnet telnet, char *buf, int len) +{ + int backlog; + backlog = from_backend(telnet->frontend, 0, buf, len); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static void log_option(Telnet telnet, char *sender, int cmd, int option) +{ + char *buf; + /* + * The strange-looking "" below is there to avoid a + * trigraph - a double question mark followed by > maps to a + * closing brace character! + */ + buf = dupprintf("%s:\t%s %s", sender, + (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : + cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), + telopt(option)); + logevent(telnet->frontend, buf); + sfree(buf); +} + +static void send_opt(Telnet telnet, int cmd, int option) +{ + unsigned char b[3]; + + b[0] = IAC; + b[1] = cmd; + b[2] = option; + telnet->bufsize = sk_write(telnet->s, (char *)b, 3); + log_option(telnet, "client", cmd, option); +} + +static void deactivate_option(Telnet telnet, const struct Opt *o) +{ + if (telnet->opt_states[o->index] == REQUESTED || + telnet->opt_states[o->index] == ACTIVE) + send_opt(telnet, o->nsend, o->option); + telnet->opt_states[o->index] = REALLY_INACTIVE; +} + +/* + * Generate side effects of enabling or disabling an option. + */ +static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) +{ + if (o->option == TELOPT_ECHO && o->send == DO) + telnet->echoing = !enabled; + else if (o->option == TELOPT_SGA && o->send == DO) + telnet->editing = !enabled; + if (telnet->ldisc) /* cause ldisc to notice the change */ + ldisc_send(telnet->ldisc, NULL, 0, 0); + + /* Ensure we get the minimum options */ + if (!telnet->activated) { + if (telnet->opt_states[o_echo.index] == INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + if (telnet->opt_states[o_we_sga.index] == INACTIVE) { + telnet->opt_states[o_we_sga.index] = REQUESTED; + send_opt(telnet, o_we_sga.send, o_we_sga.option); + } + if (telnet->opt_states[o_they_sga.index] == INACTIVE) { + telnet->opt_states[o_they_sga.index] = REQUESTED; + send_opt(telnet, o_they_sga.send, o_they_sga.option); + } + telnet->activated = TRUE; + } +} + +static void activate_option(Telnet telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NAWS) + telnet_size(telnet, telnet->term_width, telnet->term_height); + if (o->send == WILL && + (o->option == TELOPT_NEW_ENVIRON || + o->option == TELOPT_OLD_ENVIRON)) { + /* + * We may only have one kind of ENVIRON going at a time. + * This is a hack, but who cares. + */ + deactivate_option(telnet, o->option == + TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); + } + option_side_effects(telnet, o, 1); +} + +static void refused_option(Telnet telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && + telnet->opt_states[o_oenv.index] == INACTIVE) { + send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); + telnet->opt_states[o_oenv.index] = REQUESTED; + } + option_side_effects(telnet, o, 0); +} + +static void proc_rec_opt(Telnet telnet, int cmd, int option) +{ + const struct Opt *const *o; + + log_option(telnet, "server", cmd, option); + for (o = opts; *o; o++) { + if ((*o)->option == option && (*o)->ack == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = ACTIVE; + activate_option(telnet, *o); + break; + case ACTIVE: + break; + case INACTIVE: + telnet->opt_states[(*o)->index] = ACTIVE; + send_opt(telnet, (*o)->send, option); + activate_option(telnet, *o); + break; + case REALLY_INACTIVE: + send_opt(telnet, (*o)->nsend, option); + break; + } + return; + } else if ((*o)->option == option && (*o)->nak == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = INACTIVE; + refused_option(telnet, *o); + break; + case ACTIVE: + telnet->opt_states[(*o)->index] = INACTIVE; + send_opt(telnet, (*o)->nsend, option); + option_side_effects(telnet, *o, 0); + break; + case INACTIVE: + case REALLY_INACTIVE: + break; + } + return; + } + } + /* + * If we reach here, the option was one we weren't prepared to + * cope with. If the request was positive (WILL or DO), we send + * a negative ack to indicate refusal. If the request was + * negative (WONT / DONT), we must do nothing. + */ + if (cmd == WILL || cmd == DO) + send_opt(telnet, (cmd == WILL ? DONT : WONT), option); +} + +static void process_subneg(Telnet telnet) +{ + unsigned char *b, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; + + switch (telnet->sb_opt) { + case TELOPT_TSPEED: + if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { + char *logbuf; + char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); + b = snewn(20 + strlen(termspeed), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2); + logevent(telnet->frontend, "server:\tSB TSPEED SEND"); + logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + sfree(b); + } else + logevent(telnet->frontend, "server:\tSB TSPEED "); + break; + case TELOPT_TTYPE: + if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { + char *logbuf; + char *termtype = conf_get_str(telnet->conf, CONF_termtype); + b = snewn(20 + strlen(termtype), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6); + b[n + 4] = 0; + logevent(telnet->frontend, "server:\tSB TTYPE SEND"); + logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + sfree(b); + } else + logevent(telnet->frontend, "server:\tSB TTYPE \r\n"); + break; + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + p = telnet->sb_buf; + q = p + telnet->sb_len; + if (p < q && *p == TELQUAL_SEND) { + char *logbuf; + p++; + logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt)); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { + if (conf_get_int(telnet->conf, CONF_rfc_environ)) { + value = RFC_VALUE; + var = RFC_VAR; + } else { + value = BSD_VALUE; + var = BSD_VAR; + } + /* + * Try to guess the sense of VAR and VALUE. + */ + while (p < q) { + if (*p == RFC_VAR) { + value = RFC_VALUE; + var = RFC_VAR; + } else if (*p == BSD_VAR) { + value = BSD_VALUE; + var = BSD_VAR; + } + p++; + } + } else { + /* + * With NEW_ENVIRON, the sense of VAR and VALUE + * isn't in doubt. + */ + value = RFC_VALUE; + var = RFC_VAR; + } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n); + if (n == 6) { + logbuf = dupprintf("client:\tSB %s IS ", + telopt(telnet->sb_opt)); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } else { + logbuf = dupprintf("client:\tSB %s IS:", + telopt(telnet->sb_opt)); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + logbuf = dupprintf("\t%s=%s", ekey, eval); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } + if (user) { + logbuf = dupprintf("\tUSER=%s", user); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } + } + sfree(b); + sfree(user); + } + break; + } +} + +static void do_telnet_read(Telnet telnet, char *buf, int len) +{ + char *outbuf = NULL; + int outbuflen = 0, outbufsize = 0; + +#define ADDTOBUF(c) do { \ + if (outbuflen >= outbufsize) { \ + outbufsize = outbuflen + 256; \ + outbuf = sresize(outbuf, outbufsize, char); \ + } \ + outbuf[outbuflen++] = (c); \ +} while (0) + + while (len--) { + int c = (unsigned char) *buf++; + + switch (telnet->state) { + case TOP_LEVEL: + case SEENCR: + if (c == NUL && telnet->state == SEENCR) + telnet->state = TOP_LEVEL; + else if (c == IAC) + telnet->state = SEENIAC; + else { + if (!telnet->in_synch) + ADDTOBUF(c); + +#if 1 + /* I can't get the F***ing winsock to insert the urgent IAC + * into the right position! Even with SO_OOBINLINE it gives + * it to recv too soon. And of course the DM byte (that + * arrives in the same packet!) appears several K later!! + * + * Oh well, we do get the DM in the right place so I'll + * just stop hiding on the next 0xf2 and hope for the best. + */ + else if (c == DM) + telnet->in_synch = 0; +#endif + if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) + telnet->state = SEENCR; + else + telnet->state = TOP_LEVEL; + } + break; + case SEENIAC: + if (c == DO) + telnet->state = SEENDO; + else if (c == DONT) + telnet->state = SEENDONT; + else if (c == WILL) + telnet->state = SEENWILL; + else if (c == WONT) + telnet->state = SEENWONT; + else if (c == SB) + telnet->state = SEENSB; + else if (c == DM) { + telnet->in_synch = 0; + telnet->state = TOP_LEVEL; + } else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + ADDTOBUF(c); + } + telnet->state = TOP_LEVEL; + } + break; + case SEENWILL: + proc_rec_opt(telnet, WILL, c); + telnet->state = TOP_LEVEL; + break; + case SEENWONT: + proc_rec_opt(telnet, WONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENDO: + proc_rec_opt(telnet, DO, c); + telnet->state = TOP_LEVEL; + break; + case SEENDONT: + proc_rec_opt(telnet, DONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENSB: + telnet->sb_opt = c; + telnet->sb_len = 0; + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + if (telnet->sb_len >= telnet->sb_size) { + telnet->sb_size += SB_DELTA; + telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size, + unsigned char); + } + telnet->sb_buf[telnet->sb_len++] = c; + telnet->state = SUBNEGOT; /* in case we came here by goto */ + } + break; + case SUBNEG_IAC: + if (c != SE) + goto subneg_addchar; /* yes, it's a hack, I know, but... */ + else { + process_subneg(telnet); + telnet->state = TOP_LEVEL; + } + break; + } + } + + if (outbuflen) + c_write(telnet, outbuf, outbuflen); + sfree(outbuf); +} + +static void telnet_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Telnet telnet = (Telnet) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(telnet->frontend, msg); + sfree(msg); +} + +static int telnet_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Telnet telnet = (Telnet) plug; + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (telnet->s) { + sk_close(telnet->s); + telnet->s = NULL; + if (error_msg) + telnet->closed_on_socket_error = TRUE; + notify_remote_exit(telnet->frontend); + } + if (error_msg) { + logevent(telnet->frontend, error_msg); + connection_fatal(telnet->frontend, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ + return 0; +} + +static int telnet_receive(Plug plug, int urgent, char *data, int len) +{ + Telnet telnet = (Telnet) plug; + if (urgent) + telnet->in_synch = TRUE; + do_telnet_read(telnet, data, len); + return 1; +} + +static void telnet_sent(Plug plug, int bufsize) +{ + Telnet telnet = (Telnet) plug; + telnet->bufsize = bufsize; +} + +/* + * Called to set up the Telnet connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *telnet_init(void *frontend_handle, void **backend_handle, + Conf *conf, char *host, int port, + char **realhost, int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + telnet_log, + telnet_closing, + telnet_receive, + telnet_sent + }; + SockAddr addr; + const char *err; + Telnet telnet; + char *loghost; + int addressfamily; + + telnet = snew(struct telnet_tag); + telnet->fn = &fn_table; + telnet->conf = conf_copy(conf); + telnet->s = NULL; + telnet->closed_on_socket_error = FALSE; + telnet->echoing = TRUE; + telnet->editing = TRUE; + telnet->activated = FALSE; + telnet->sb_buf = NULL; + telnet->sb_size = 0; + telnet->frontend = frontend_handle; + telnet->term_width = conf_get_int(telnet->conf, CONF_width); + telnet->term_height = conf_get_int(telnet->conf, CONF_height); + telnet->state = TOP_LEVEL; + telnet->ldisc = NULL; + telnet->pinger = NULL; + *backend_handle = telnet; + + /* + * Try to find host. + */ + { + char *buf; + addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); + buf = dupprintf("Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(telnet->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, telnet->conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + telnet->s = new_connection(addr, *realhost, port, 0, 1, + nodelay, keepalive, (Plug) telnet, telnet->conf); + if ((err = sk_socket_error(telnet->s)) != NULL) + return err; + + telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet); + + /* + * Initialise option states. + */ + if (conf_get_int(telnet->conf, CONF_passive_telnet)) { + const struct Opt *const *o; + + for (o = opts; *o; o++) + telnet->opt_states[(*o)->index] = INACTIVE; + } else { + const struct Opt *const *o; + + for (o = opts; *o; o++) { + telnet->opt_states[(*o)->index] = (*o)->initial_state; + if (telnet->opt_states[(*o)->index] == REQUESTED) + send_opt(telnet, (*o)->send, (*o)->option); + } + telnet->activated = TRUE; + } + + /* + * Set up SYNCH state. + */ + telnet->in_synch = FALSE; + + /* + * We can send special commands from the start. + */ + update_specials_menu(telnet->frontend); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(telnet->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void telnet_free(void *handle) +{ + Telnet telnet = (Telnet) handle; + + sfree(telnet->sb_buf); + if (telnet->s) + sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); + conf_free(telnet->conf); + sfree(telnet); +} +/* + * Reconfigure the Telnet backend. There's no immediate action + * necessary, in this backend: we just save the fresh config for + * any subsequent negotiations. + */ +static void telnet_reconfig(void *handle, Conf *conf) +{ + Telnet telnet = (Telnet) handle; + pinger_reconfig(telnet->pinger, telnet->conf, conf); + conf_free(telnet->conf); + telnet->conf = conf_copy(conf); +} + +/* + * Called to send data down the Telnet connection. + */ +static int telnet_send(void *handle, char *buf, int len) +{ + Telnet telnet = (Telnet) handle; + unsigned char *p, *end; + static const unsigned char iac[2] = { IAC, IAC }; + static const unsigned char cr[2] = { CR, NUL }; +#if 0 + static const unsigned char nl[2] = { CR, LF }; +#endif + + if (telnet->s == NULL) + return 0; + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + telnet->bufsize = sk_write(telnet->s, (char *)q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2); + p++; + } + } + + return telnet->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int telnet_sendbuffer(void *handle) +{ + Telnet telnet = (Telnet) handle; + return telnet->bufsize; +} + +/* + * Called to set the size of the window from Telnet's POV. + */ +static void telnet_size(void *handle, int width, int height) +{ + Telnet telnet = (Telnet) handle; + unsigned char b[24]; + int n; + char *logbuf; + + telnet->term_width = width; + telnet->term_height = height; + + if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE) + return; + n = 0; + b[n++] = IAC; + b[n++] = SB; + b[n++] = TELOPT_NAWS; + b[n++] = telnet->term_width >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_width & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n); + logbuf = dupprintf("client:\tSB NAWS %d,%d", + telnet->term_width, telnet->term_height); + logevent(telnet->frontend, logbuf); + sfree(logbuf); +} + +/* + * Send Telnet special codes. + */ +static void telnet_special(void *handle, Telnet_Special code) +{ + Telnet telnet = (Telnet) handle; + unsigned char b[2]; + + if (telnet->s == NULL) + return; + + b[0] = IAC; + switch (code) { + case TS_AYT: + b[1] = AYT; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_BRK: + b[1] = BREAK; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EC: + b[1] = EC; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EL: + b[1] = EL; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_GA: + b[1] = GA; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_NOP: + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_ABORT: + b[1] = ABORT; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_AO: + b[1] = AO; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_IP: + b[1] = IP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_SUSP: + b[1] = SUSP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOR: + b[1] = EOR; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOF: + b[1] = xEOF; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOL: + /* In BINARY mode, CR-LF becomes just CR - + * and without the NUL suffix too. */ + if (telnet->opt_states[o_we_bin.index] == ACTIVE) + telnet->bufsize = sk_write(telnet->s, "\r", 1); + else + telnet->bufsize = sk_write(telnet->s, "\r\n", 2); + break; + case TS_SYNCH: + b[1] = DM; + telnet->bufsize = sk_write(telnet->s, (char *)b, 1); + telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1); + break; + case TS_RECHO: + if (telnet->opt_states[o_echo.index] == INACTIVE || + telnet->opt_states[o_echo.index] == REALLY_INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + break; + case TS_LECHO: + if (telnet->opt_states[o_echo.index] == ACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.nsend, o_echo.option); + } + break; + case TS_PING: + if (telnet->opt_states[o_they_sga.index] == ACTIVE) { + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + } + break; + default: + break; /* never heard of it */ + } +} + +static const struct telnet_special *telnet_get_specials(void *handle) +{ + static const struct telnet_special specials[] = { + {"Are You There", TS_AYT}, + {"Break", TS_BRK}, + {"Synch", TS_SYNCH}, + {"Erase Character", TS_EC}, + {"Erase Line", TS_EL}, + {"Go Ahead", TS_GA}, + {"No Operation", TS_NOP}, + {NULL, TS_SEP}, + {"Abort Process", TS_ABORT}, + {"Abort Output", TS_AO}, + {"Interrupt Process", TS_IP}, + {"Suspend Process", TS_SUSP}, + {NULL, TS_SEP}, + {"End Of Record", TS_EOR}, + {"End Of File", TS_EOF}, + {NULL, TS_EXITMENU} + }; + return specials; +} + +static int telnet_connected(void *handle) +{ + Telnet telnet = (Telnet) handle; + return telnet->s != NULL; +} + +static int telnet_sendok(void *handle) +{ + /* Telnet telnet = (Telnet) handle; */ + return 1; +} + +static void telnet_unthrottle(void *handle, int backlog) +{ + Telnet telnet = (Telnet) handle; + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static int telnet_ldisc(void *handle, int option) +{ + Telnet telnet = (Telnet) handle; + if (option == LD_ECHO) + return telnet->echoing; + if (option == LD_EDIT) + return telnet->editing; + return FALSE; +} + +static void telnet_provide_ldisc(void *handle, void *ldisc) +{ + Telnet telnet = (Telnet) handle; + telnet->ldisc = ldisc; +} + +static void telnet_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int telnet_exitcode(void *handle) +{ + Telnet telnet = (Telnet) handle; + if (telnet->s != NULL) + return -1; /* still connected */ + else if (telnet->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Telnet doesn't transmit exit codes back to the client */ + return 0; +} + +/* + * cfg_info for Telnet does nothing at all. + */ +static int telnet_cfg_info(void *handle) +{ + return 0; +} + +Backend telnet_backend = { + telnet_init, + telnet_free, + telnet_reconfig, + telnet_send, + telnet_sendbuffer, + telnet_size, + telnet_special, + telnet_get_specials, + telnet_connected, + telnet_exitcode, + telnet_sendok, + telnet_ldisc, + telnet_provide_ldisc, + telnet_provide_logctx, + telnet_unthrottle, + telnet_cfg_info, + "telnet", + PROT_TELNET, + 23 +}; diff --git a/netbox/libs/Putty/terminal.c b/netbox/libs/Putty/terminal.c new file mode 100644 index 000000000..ae85eb58a --- /dev/null +++ b/netbox/libs/Putty/terminal.c @@ -0,0 +1,6492 @@ +/* + * Terminal emulator. + */ + +#include +#include +#include +#include + +#include +#include +#include "putty.h" +#include "terminal.h" + +#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) ) +#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) ) +#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x ) +#define posdiff(p1,p2) ( ((p1).y - (p2).y) * (term->cols+1) + (p1).x - (p2).x ) + +/* Product-order comparisons for rectangular block selection. */ +#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x ) +#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x ) + +#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) ) +#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) ) + +#define VT52_PLUS + +#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */ +#define CL_VT100 0x0002 /* VT100 */ +#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */ +#define CL_VT102 0x0008 /* VT102 */ +#define CL_VT220 0x0010 /* VT220 */ +#define CL_VT320 0x0020 /* VT320 */ +#define CL_VT420 0x0040 /* VT420 */ +#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */ +#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */ +#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */ +#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */ +#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */ + +#define TM_VT100 (CL_ANSIMIN|CL_VT100) +#define TM_VT100AVO (TM_VT100|CL_VT100AVO) +#define TM_VT102 (TM_VT100AVO|CL_VT102) +#define TM_VT220 (TM_VT102|CL_VT220) +#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320) +#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI) + +#define TM_PUTTY (0xFFFF) + +#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */ +#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/ +#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */ +#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */ + +#define compatibility(x) \ + if ( ((CL_##x)&term->compatibility_level) == 0 ) { \ + term->termstate=TOPLEVEL; \ + break; \ + } +#define compatibility2(x,y) \ + if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \ + term->termstate=TOPLEVEL; \ + break; \ + } + +#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 ) + +char *EMPTY_WINDOW_TITLE = ""; + +const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) +const wchar_t sel_nl[] = SEL_NL; + +/* + * Fetch the character at a particular position in a line array, + * for purposes of `wordtype'. The reason this isn't just a simple + * array reference is that if the character we find is UCSWIDE, + * then we must look one space further to the left. + */ +#define UCSGET(a, x) \ + ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr ) + +/* + * Detect the various aliases of U+0020 SPACE. + */ +#define IS_SPACE_CHR(chr) \ + ((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20)) + +/* + * Spot magic CSETs. + */ +#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0) + +/* + * Internal prototypes. + */ +static void resizeline(Terminal *, termline *, int); +static termline *lineptr(Terminal *, int, int, int); +static void unlineptr(termline *); +static void check_line_size(Terminal *, termline *); +static void do_paint(Terminal *, Context, int); +static void erase_lots(Terminal *, int, int, int); +static int find_last_nonempty_line(Terminal *, tree234 *); +static void swap_screen(Terminal *, int, int, int); +static void update_sbar(Terminal *); +static void deselect(Terminal *); +static void term_print_finish(Terminal *); +static void scroll(Terminal *, int, int, int, int); +#ifdef OPTIMISE_SCROLL +static void scroll_display(Terminal *, int, int, int); +#endif /* OPTIMISE_SCROLL */ + +static termline *newline(Terminal *term, int cols, int bce) +{ + termline *line; + int j; + + line = snew(termline); + line->chars = snewn(cols, termchar); + for (j = 0; j < cols; j++) + line->chars[j] = (bce ? term->erase_char : term->basic_erase_char); + line->cols = line->size = cols; + line->lattr = LATTR_NORM; + line->temporary = FALSE; + line->cc_free = 0; + + return line; +} + +static void freeline(termline *line) +{ + if (line) { + sfree(line->chars); + sfree(line); + } +} + +static void unlineptr(termline *line) +{ + if (line->temporary) + freeline(line); +} + +#ifdef TERM_CC_DIAGS +/* + * Diagnostic function: verify that a termline has a correct + * combining character structure. + * + * This is a performance-intensive check, so it's no longer enabled + * by default. + */ +static void cc_check(termline *line) +{ + unsigned char *flags; + int i, j; + + assert(line->size >= line->cols); + + flags = snewn(line->size, unsigned char); + + for (i = 0; i < line->size; i++) + flags[i] = (i < line->cols); + + for (i = 0; i < line->cols; i++) { + j = i; + while (line->chars[j].cc_next) { + j += line->chars[j].cc_next; + assert(j >= line->cols && j < line->size); + assert(!flags[j]); + flags[j] = TRUE; + } + } + + j = line->cc_free; + if (j) { + while (1) { + assert(j >= line->cols && j < line->size); + assert(!flags[j]); + flags[j] = TRUE; + if (line->chars[j].cc_next) + j += line->chars[j].cc_next; + else + break; + } + } + + j = 0; + for (i = 0; i < line->size; i++) + j += (flags[i] != 0); + + assert(j == line->size); + + sfree(flags); +} +#endif + +/* + * Add a combining character to a character cell. + */ +static void add_cc(termline *line, int col, unsigned long chr) +{ + int newcc; + + assert(col >= 0 && col < line->cols); + + /* + * Start by extending the cols array if the free list is empty. + */ + if (!line->cc_free) { + int n = line->size; + line->size += 16 + (line->size - line->cols) / 2; + line->chars = sresize(line->chars, line->size, termchar); + line->cc_free = n; + while (n < line->size) { + if (n+1 < line->size) + line->chars[n].cc_next = 1; + else + line->chars[n].cc_next = 0; + n++; + } + } + + /* + * Now walk the cc list of the cell in question. + */ + while (line->chars[col].cc_next) + col += line->chars[col].cc_next; + + /* + * `col' now points at the last cc currently in this cell; so + * we simply add another one. + */ + newcc = line->cc_free; + if (line->chars[newcc].cc_next) + line->cc_free = newcc + line->chars[newcc].cc_next; + else + line->cc_free = 0; + line->chars[newcc].cc_next = 0; + line->chars[newcc].chr = chr; + line->chars[col].cc_next = newcc - col; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Clear the combining character list in a character cell. + */ +static void clear_cc(termline *line, int col) +{ + int oldfree, origcol = col; + + assert(col >= 0 && col < line->cols); + + if (!line->chars[col].cc_next) + return; /* nothing needs doing */ + + oldfree = line->cc_free; + line->cc_free = col + line->chars[col].cc_next; + while (line->chars[col].cc_next) + col += line->chars[col].cc_next; + if (oldfree) + line->chars[col].cc_next = oldfree - col; + else + line->chars[col].cc_next = 0; + + line->chars[origcol].cc_next = 0; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Compare two character cells for equality. Special case required + * in do_paint() where we override what we expect the chr and attr + * fields to be. + */ +static int termchars_equal_override(termchar *a, termchar *b, + unsigned long bchr, unsigned long battr) +{ + /* FULL-TERMCHAR */ + if (a->chr != bchr) + return FALSE; + if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK)) + return FALSE; + while (a->cc_next || b->cc_next) { + if (!a->cc_next || !b->cc_next) + return FALSE; /* one cc-list ends, other does not */ + a += a->cc_next; + b += b->cc_next; + if (a->chr != b->chr) + return FALSE; + } + return TRUE; +} + +static int termchars_equal(termchar *a, termchar *b) +{ + return termchars_equal_override(a, b, b->chr, b->attr); +} + +/* + * Copy a character cell. (Requires a pointer to the destination + * termline, so as to access its free list.) + */ +static void copy_termchar(termline *destline, int x, termchar *src) +{ + clear_cc(destline, x); + + destline->chars[x] = *src; /* copy everything except cc-list */ + destline->chars[x].cc_next = 0; /* and make sure this is zero */ + + while (src->cc_next) { + src += src->cc_next; + add_cc(destline, x, src->chr); + } + +#ifdef TERM_CC_DIAGS + cc_check(destline); +#endif +} + +/* + * Move a character cell within its termline. + */ +static void move_termchar(termline *line, termchar *dest, termchar *src) +{ + /* First clear the cc list from the original char, just in case. */ + clear_cc(line, dest - line->chars); + + /* Move the character cell and adjust its cc_next. */ + *dest = *src; /* copy everything except cc-list */ + if (src->cc_next) + dest->cc_next = src->cc_next - (dest-src); + + /* Ensure the original cell doesn't have a cc list. */ + src->cc_next = 0; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Compress and decompress a termline into an RLE-based format for + * storing in scrollback. (Since scrollback almost never needs to + * be modified and exists in huge quantities, this is a sensible + * tradeoff, particularly since it allows us to continue adding + * features to the main termchar structure without proportionally + * bloating the terminal emulator's memory footprint unless those + * features are in constant use.) + */ +struct buf { + unsigned char *data; + int len, size; +}; +static void add(struct buf *b, unsigned char c) +{ + if (b->len >= b->size) { + b->size = (b->len * 3 / 2) + 512; + b->data = sresize(b->data, b->size, unsigned char); + } + b->data[b->len++] = c; +} +static int get(struct buf *b) +{ + return b->data[b->len++]; +} +static void makerle(struct buf *b, termline *ldata, + void (*makeliteral)(struct buf *b, termchar *c, + unsigned long *state)) +{ + int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2; + termchar *c = ldata->chars; + unsigned long state = 0, oldstate; + + n = ldata->cols; + + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + prevlen = prevpos = 0; + prev2 = FALSE; + + while (n-- > 0) { + thispos = b->len; + makeliteral(b, c++, &state); + thislen = b->len - thispos; + if (thislen == prevlen && + !memcmp(b->data + prevpos, b->data + thispos, thislen)) { + /* + * This literal precisely matches the previous one. + * Turn it into a run if it's worthwhile. + * + * With one-byte literals, it costs us two bytes to + * encode a run, plus another byte to write the header + * to resume normal output; so a three-element run is + * neutral, and anything beyond that is unconditionally + * worthwhile. With two-byte literals or more, even a + * 2-run is a win. + */ + if (thislen > 1 || prev2) { + int runpos, runlen; + + /* + * It's worth encoding a run. Start at prevpos, + * unless hdrsize==0 in which case we can back up + * another one and start by overwriting hdrpos. + */ + + hdrsize--; /* remove the literal at prevpos */ + if (prev2) { + assert(hdrsize > 0); + hdrsize--; + prevpos -= prevlen;/* and possibly another one */ + } + + if (hdrsize == 0) { + assert(prevpos == hdrpos + 1); + runpos = hdrpos; + b->len = prevpos+prevlen; + } else { + memmove(b->data + prevpos+1, b->data + prevpos, prevlen); + runpos = prevpos; + b->len = prevpos+prevlen+1; + /* + * Terminate the previous run of ordinary + * literals. + */ + assert(hdrsize >= 1 && hdrsize <= 128); + b->data[hdrpos] = hdrsize - 1; + } + + runlen = prev2 ? 3 : 2; + + while (n > 0 && runlen < 129) { + int tmppos, tmplen; + tmppos = b->len; + oldstate = state; + makeliteral(b, c, &state); + tmplen = b->len - tmppos; + b->len = tmppos; + if (tmplen != thislen || + memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) { + state = oldstate; + break; /* run over */ + } + n--, c++, runlen++; + } + + assert(runlen >= 2 && runlen <= 129); + b->data[runpos] = runlen + 0x80 - 2; + + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + /* And ensure this run doesn't interfere with the next. */ + prevlen = prevpos = 0; + prev2 = FALSE; + + continue; + } else { + /* + * Just flag that the previous two literals were + * identical, in case we find a third identical one + * we want to turn into a run. + */ + prev2 = TRUE; + prevlen = thislen; + prevpos = thispos; + } + } else { + prev2 = FALSE; + prevlen = thislen; + prevpos = thispos; + } + + /* + * This character isn't (yet) part of a run. Add it to + * hdrsize. + */ + hdrsize++; + if (hdrsize == 128) { + b->data[hdrpos] = hdrsize - 1; + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + prevlen = prevpos = 0; + prev2 = FALSE; + } + } + + /* + * Clean up. + */ + if (hdrsize > 0) { + assert(hdrsize <= 128); + b->data[hdrpos] = hdrsize - 1; + } else { + b->len = hdrpos; + } +} +static void makeliteral_chr(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * My encoding for characters is UTF-8-like, in that it stores + * 7-bit ASCII in one byte and uses high-bit-set bytes as + * introducers to indicate a longer sequence. However, it's + * unlike UTF-8 in that it doesn't need to be able to + * resynchronise, and therefore I don't want to waste two bits + * per byte on having recognisable continuation characters. + * Also I don't want to rule out the possibility that I may one + * day use values 0x80000000-0xFFFFFFFF for interesting + * purposes, so unlike UTF-8 I need a full 32-bit range. + * Accordingly, here is my encoding: + * + * 00000000-0000007F: 0xxxxxxx (but see below) + * 00000080-00003FFF: 10xxxxxx xxxxxxxx + * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx + * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx + * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + * + * (`Z' is like `x' but is always going to be zero since the + * values I'm encoding don't go above 2^32. In principle the + * five-byte form of the encoding could extend to 2^35, and + * there could be six-, seven-, eight- and nine-byte forms as + * well to allow up to 64-bit values to be encoded. But that's + * completely unnecessary for these purposes!) + * + * The encoding as written above would be very simple, except + * that 7-bit ASCII can occur in several different ways in the + * terminal data; sometimes it crops up in the D800 page + * (CSET_ASCII) but at other times it's in the 0000 page (real + * Unicode). Therefore, this encoding is actually _stateful_: + * the one-byte encoding of 00-7F actually indicates `reuse the + * upper three bytes of the last character', and to encode an + * absolute value of 00-7F you need to use the two-byte form + * instead. + */ + if ((c->chr & ~0x7F) == *state) { + add(b, (unsigned char)(c->chr & 0x7F)); + } else if (c->chr < 0x4000) { + add(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else if (c->chr < 0x200000) { + add(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else if (c->chr < 0x10000000) { + add(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0)); + add(b, (unsigned char)((c->chr >> 16) & 0xFF)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else { + add(b, 0xF0); + add(b, (unsigned char)((c->chr >> 24) & 0xFF)); + add(b, (unsigned char)((c->chr >> 16) & 0xFF)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } + *state = c->chr & ~0xFF; +} +static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * My encoding for attributes is 16-bit-granular and assumes + * that the top bit of the word is never required. I either + * store a two-byte value with the top bit clear (indicating + * just that value), or a four-byte value with the top bit set + * (indicating the same value with its top bit clear). + * + * However, first I permute the bits of the attribute value, so + * that the eight bits of colour (four in each of fg and bg) + * which are never non-zero unless xterm 256-colour mode is in + * use are placed higher up the word than everything else. This + * ensures that attribute values remain 16-bit _unless_ the + * user uses extended colour. + */ + unsigned attr, colourbits; + + attr = c->attr; + + assert(ATTR_BGSHIFT > ATTR_FGSHIFT); + + colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF; + colourbits <<= 4; + colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF; + + attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) | + (attr & ((1 << (ATTR_BGSHIFT + 4))-1))); + attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) | + (attr & ((1 << (ATTR_FGSHIFT + 4))-1))); + + attr |= (colourbits << (32-9)); + + if (attr < 0x8000) { + add(b, (unsigned char)((attr >> 8) & 0xFF)); + add(b, (unsigned char)(attr & 0xFF)); + } else { + add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80)); + add(b, (unsigned char)((attr >> 16) & 0xFF)); + add(b, (unsigned char)((attr >> 8) & 0xFF)); + add(b, (unsigned char)(attr & 0xFF)); + } +} +static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * For combining characters, I just encode a bunch of ordinary + * chars using makeliteral_chr, and terminate with a \0 + * character (which I know won't come up as a combining char + * itself). + * + * I don't use the stateful encoding in makeliteral_chr. + */ + unsigned long zstate; + termchar z; + + while (c->cc_next) { + c += c->cc_next; + + assert(c->chr != 0); + + zstate = 0; + makeliteral_chr(b, c, &zstate); + } + + z.chr = 0; + zstate = 0; + makeliteral_chr(b, &z, &zstate); +} + +static termline *decompressline(unsigned char *data, int *bytes_used); + +static unsigned char *compressline(termline *ldata) +{ + struct buf buffer = { NULL, 0, 0 }, *b = &buffer; + + /* + * First, store the column count, 7 bits at a time, least + * significant `digit' first, with the high bit set on all but + * the last. + */ + { + int n = ldata->cols; + while (n >= 128) { + add(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + add(b, (unsigned char)(n)); + } + + /* + * Next store the lattrs; same principle. + */ + { + int n = ldata->lattr; + while (n >= 128) { + add(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + add(b, (unsigned char)(n)); + } + + /* + * Now we store a sequence of separate run-length encoded + * fragments, each containing exactly as many symbols as there + * are columns in the ldata. + * + * All of these have a common basic format: + * + * - a byte 00-7F indicates that X+1 literals follow it + * - a byte 80-FF indicates that a single literal follows it + * and expects to be repeated (X-0x80)+2 times. + * + * The format of the `literals' varies between the fragments. + */ + makerle(b, ldata, makeliteral_chr); + makerle(b, ldata, makeliteral_attr); + makerle(b, ldata, makeliteral_cc); + + /* + * Diagnostics: ensure that the compressed data really does + * decompress to the right thing. + * + * This is a bit performance-heavy for production code. + */ +#ifdef TERM_CC_DIAGS +#ifndef CHECK_SB_COMPRESSION + { + int dused; + termline *dcl; + int i; + +#ifdef DIAGNOSTIC_SB_COMPRESSION + for (i = 0; i < b->len; i++) { + printf(" %02x ", b->data[i]); + } + printf("\n"); +#endif + + dcl = decompressline(b->data, &dused); + assert(b->len == dused); + assert(ldata->cols == dcl->cols); + assert(ldata->lattr == dcl->lattr); + for (i = 0; i < ldata->cols; i++) + assert(termchars_equal(&ldata->chars[i], &dcl->chars[i])); + +#ifdef DIAGNOSTIC_SB_COMPRESSION + printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n", + ldata->cols, 4 * ldata->cols, dused, + (double)dused / (4 * ldata->cols)); +#endif + + freeline(dcl); + } +#endif +#endif /* TERM_CC_DIAGS */ + + /* + * Trim the allocated memory so we don't waste any, and return. + */ + return sresize(b->data, b->len, unsigned char); +} + +static void readrle(struct buf *b, termline *ldata, + void (*readliteral)(struct buf *b, termchar *c, + termline *ldata, unsigned long *state)) +{ + int n = 0; + unsigned long state = 0; + + while (n < ldata->cols) { + int hdr = get(b); + + if (hdr >= 0x80) { + /* A run. */ + + int pos = b->len, count = hdr + 2 - 0x80; + while (count--) { + assert(n < ldata->cols); + b->len = pos; + readliteral(b, ldata->chars + n, ldata, &state); + n++; + } + } else { + /* Just a sequence of consecutive literals. */ + + int count = hdr + 1; + while (count--) { + assert(n < ldata->cols); + readliteral(b, ldata->chars + n, ldata, &state); + n++; + } + } + } + + assert(n == ldata->cols); +} +static void readliteral_chr(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + int byte; + + /* + * 00000000-0000007F: 0xxxxxxx + * 00000080-00003FFF: 10xxxxxx xxxxxxxx + * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx + * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx + * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + */ + + byte = get(b); + if (byte < 0x80) { + c->chr = byte | *state; + } else if (byte < 0xC0) { + c->chr = (byte &~ 0xC0) << 8; + c->chr |= get(b); + } else if (byte < 0xE0) { + c->chr = (byte &~ 0xE0) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } else if (byte < 0xF0) { + c->chr = (byte &~ 0xF0) << 24; + c->chr |= get(b) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } else { + assert(byte == 0xF0); + c->chr = get(b) << 24; + c->chr |= get(b) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } + *state = c->chr & ~0xFF; +} +static void readliteral_attr(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + unsigned val, attr, colourbits; + + val = get(b) << 8; + val |= get(b); + + if (val >= 0x8000) { + val &= ~0x8000; + val <<= 16; + val |= get(b) << 8; + val |= get(b); + } + + colourbits = (val >> (32-9)) & 0xFF; + attr = (val & ((1<<(32-9))-1)); + + attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) | + (attr & ((1 << (ATTR_FGSHIFT + 4))-1))); + attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) | + (attr & ((1 << (ATTR_BGSHIFT + 4))-1))); + + attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4); + attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4); + + c->attr = attr; +} +static void readliteral_cc(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + termchar n; + unsigned long zstate; + int x = c - ldata->chars; + + c->cc_next = 0; + + while (1) { + zstate = 0; + readliteral_chr(b, &n, ldata, &zstate); + if (!n.chr) + break; + add_cc(ldata, x, n.chr); + } +} + +static termline *decompressline(unsigned char *data, int *bytes_used) +{ + int ncols, byte, shift; + struct buf buffer, *b = &buffer; + termline *ldata; + + b->data = data; + b->len = 0; + + /* + * First read in the column count. + */ + ncols = shift = 0; + do { + byte = get(b); + ncols |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + + /* + * Now create the output termline. + */ + ldata = snew(termline); + ldata->chars = snewn(ncols, termchar); + ldata->cols = ldata->size = ncols; + ldata->temporary = TRUE; + ldata->cc_free = 0; + + /* + * We must set all the cc pointers in ldata->chars to 0 right + * now, so that cc diagnostics that verify the integrity of the + * whole line will make sense while we're in the middle of + * building it up. + */ + { + int i; + for (i = 0; i < ldata->cols; i++) + ldata->chars[i].cc_next = 0; + } + + /* + * Now read in the lattr. + */ + ldata->lattr = shift = 0; + do { + byte = get(b); + ldata->lattr |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + + /* + * Now we read in each of the RLE streams in turn. + */ + readrle(b, ldata, readliteral_chr); + readrle(b, ldata, readliteral_attr); + readrle(b, ldata, readliteral_cc); + + /* Return the number of bytes read, for diagnostic purposes. */ + if (bytes_used) + *bytes_used = b->len; + + return ldata; +} + +/* + * Resize a line to make it `cols' columns wide. + */ +static void resizeline(Terminal *term, termline *line, int cols) +{ + int i, oldcols; + + if (line->cols != cols) { + + oldcols = line->cols; + + /* + * This line is the wrong length, which probably means it + * hasn't been accessed since a resize. Resize it now. + * + * First, go through all the characters that will be thrown + * out in the resize (if we're shrinking the line) and + * return their cc lists to the cc free list. + */ + for (i = cols; i < oldcols; i++) + clear_cc(line, i); + + /* + * If we're shrinking the line, we now bodily move the + * entire cc section from where it started to where it now + * needs to be. (We have to do this before the resize, so + * that the data we're copying is still there. However, if + * we're expanding, we have to wait until _after_ the + * resize so that the space we're copying into is there.) + */ + if (cols < oldcols) + memmove(line->chars + cols, line->chars + oldcols, + (line->size - line->cols) * TSIZE); + + /* + * Now do the actual resize, leaving the _same_ amount of + * cc space as there was to begin with. + */ + line->size += cols - oldcols; + line->chars = sresize(line->chars, line->size, TTYPE); + line->cols = cols; + + /* + * If we're expanding the line, _now_ we move the cc + * section. + */ + if (cols > oldcols) + memmove(line->chars + cols, line->chars + oldcols, + (line->size - line->cols) * TSIZE); + + /* + * Go through what's left of the original line, and adjust + * the first cc_next pointer in each list. (All the + * subsequent ones are still valid because they are + * relative offsets within the cc block.) Also do the same + * to the head of the cc_free list. + */ + for (i = 0; i < oldcols && i < cols; i++) + if (line->chars[i].cc_next) + line->chars[i].cc_next += cols - oldcols; + if (line->cc_free) + line->cc_free += cols - oldcols; + + /* + * And finally fill in the new space with erase chars. (We + * don't have to worry about cc lists here, because we + * _know_ the erase char doesn't have one.) + */ + for (i = oldcols; i < cols; i++) + line->chars[i] = term->basic_erase_char; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif + } +} + +/* + * Get the number of lines in the scrollback. + */ +static int sblines(Terminal *term) +{ + int sblines = count234(term->scrollback); + if (term->erase_to_scrollback && + term->alt_which && term->alt_screen) { + sblines += term->alt_sblines; + } + return sblines; +} + +/* + * Retrieve a line of the screen or of the scrollback, according to + * whether the y coordinate is non-negative or negative + * (respectively). + */ +static termline *lineptr(Terminal *term, int y, int lineno, int screen) +{ + termline *line; + tree234 *whichtree; + int treeindex; + + if (y >= 0) { + whichtree = term->screen; + treeindex = y; + } else { + int altlines = 0; + + assert(!screen); + + if (term->erase_to_scrollback && + term->alt_which && term->alt_screen) { + altlines = term->alt_sblines; + } + if (y < -altlines) { + whichtree = term->scrollback; + treeindex = y + altlines + count234(term->scrollback); + } else { + whichtree = term->alt_screen; + treeindex = y + term->alt_sblines; + /* treeindex = y + count234(term->alt_screen); */ + } + } + if (whichtree == term->scrollback) { + unsigned char *cline = index234(whichtree, treeindex); + line = decompressline(cline, NULL); + } else { + line = index234(whichtree, treeindex); + } + + /* We assume that we don't screw up and retrieve something out of range. */ + if (line == NULL) { + fatalbox("line==NULL in terminal.c\n" + "lineno=%d y=%d w=%d h=%d\n" + "count(scrollback=%p)=%d\n" + "count(screen=%p)=%d\n" + "count(alt=%p)=%d alt_sblines=%d\n" + "whichtree=%p treeindex=%d\n\n" + "Please contact " + "and pass on the above information.", + lineno, y, term->cols, term->rows, + term->scrollback, count234(term->scrollback), + term->screen, count234(term->screen), + term->alt_screen, count234(term->alt_screen), term->alt_sblines, + whichtree, treeindex); + } + assert(line != NULL); + + /* + * Here we resize lines to _at least_ the right length, but we + * don't truncate them. Truncation is done as a side effect of + * modifying the line. + * + * The point of this policy is to try to arrange that resizing the + * terminal window repeatedly - e.g. successive steps in an X11 + * opaque window-resize drag, or resizing as a side effect of + * retiling by tiling WMs such as xmonad - does not throw away + * data gratuitously. Specifically, we want a sequence of resize + * operations with no terminal output between them to have the + * same effect as a single resize to the ultimate terminal size, + * and also (for the case in which xmonad narrows a window that's + * scrolling things) we want scrolling up new text at the bottom + * of a narrowed window to avoid truncating lines further up when + * the window is re-widened. + */ + if (term->cols > line->cols) + resizeline(term, line, term->cols); + + return line; +} + +#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE) +#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) + +/* + * Coerce a termline to the terminal's current width. Unlike the + * optional resize in lineptr() above, this is potentially destructive + * of text, since it can shrink as well as grow the line. + * + * We call this whenever a termline is actually going to be modified. + * Helpfully, putting a single call to this function in check_boundary + * deals with _nearly_ all such cases, leaving only a few things like + * bulk erase and ESC#8 to handle separately. + */ +static void check_line_size(Terminal *term, termline *line) +{ + if (term->cols != line->cols) /* trivial optimisation */ + resizeline(term, line, term->cols); +} + +static void term_schedule_tblink(Terminal *term); +static void term_schedule_cblink(Terminal *term); + +static void term_timer(void *ctx, unsigned long now) +{ + Terminal *term = (Terminal *)ctx; + int update = FALSE; + + if (term->tblink_pending && now == term->next_tblink) { + term->tblinker = !term->tblinker; + term->tblink_pending = FALSE; + term_schedule_tblink(term); + update = TRUE; + } + + if (term->cblink_pending && now == term->next_cblink) { + term->cblinker = !term->cblinker; + term->cblink_pending = FALSE; + term_schedule_cblink(term); + update = TRUE; + } + + if (term->in_vbell && now == term->vbell_end) { + term->in_vbell = FALSE; + update = TRUE; + } + + if (update || + (term->window_update_pending && now == term->next_update)) + term_update(term); +} + +static void term_schedule_update(Terminal *term) +{ + if (!term->window_update_pending) { + term->window_update_pending = TRUE; + term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); + } +} + +/* + * Call this whenever the terminal window state changes, to queue + * an update. + */ +static void seen_disp_event(Terminal *term) +{ + term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */ + term_schedule_update(term); +} + +/* + * Call when the terminal's blinking-text settings change, or when + * a text blink has just occurred. + */ +static void term_schedule_tblink(Terminal *term) +{ + if (term->blink_is_real) { + if (!term->tblink_pending) + term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term); + term->tblink_pending = TRUE; + } else { + term->tblinker = 1; /* reset when not in use */ + term->tblink_pending = FALSE; + } +} + +/* + * Likewise with cursor blinks. + */ +static void term_schedule_cblink(Terminal *term) +{ + if (term->blink_cur && term->has_focus) { + if (!term->cblink_pending) + term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); + term->cblink_pending = TRUE; + } else { + term->cblinker = 1; /* reset when not in use */ + term->cblink_pending = FALSE; + } +} + +/* + * Call to reset cursor blinking on new output. + */ +static void term_reset_cblink(Terminal *term) +{ + seen_disp_event(term); + term->cblinker = 1; + term->cblink_pending = FALSE; + term_schedule_cblink(term); +} + +/* + * Call to begin a visual bell. + */ +static void term_schedule_vbell(Terminal *term, int already_started, + long startpoint) +{ + long ticks_already_gone; + + if (already_started) + ticks_already_gone = GETTICKCOUNT() - startpoint; + else + ticks_already_gone = 0; + + if (ticks_already_gone < VBELL_DELAY) { + term->in_vbell = TRUE; + term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone, + term_timer, term); + } else { + term->in_vbell = FALSE; + } +} + +/* + * Set up power-on settings for the terminal. + * If 'clear' is false, don't actually clear the primary screen, and + * position the cursor below the last non-blank line (scrolling if + * necessary). + */ +static void power_on(Terminal *term, int clear) +{ + term->alt_x = term->alt_y = 0; + term->savecurs.x = term->savecurs.y = 0; + term->alt_savecurs.x = term->alt_savecurs.y = 0; + term->alt_t = term->marg_t = 0; + if (term->rows != -1) + term->alt_b = term->marg_b = term->rows - 1; + else + term->alt_b = term->marg_b = 0; + if (term->cols != -1) { + int i; + for (i = 0; i < term->cols; i++) + term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + } + term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); + term->alt_ins = term->insert = FALSE; + term->alt_wnext = term->wrapnext = + term->save_wnext = term->alt_save_wnext = FALSE; + term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); + term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; + term->alt_utf = term->utf = term->save_utf = term->alt_save_utf = 0; + term->utf_state = 0; + term->alt_sco_acs = term->sco_acs = + term->save_sco_acs = term->alt_save_sco_acs = 0; + term->cset_attr[0] = term->cset_attr[1] = + term->save_csattr = term->alt_save_csattr = CSET_ASCII; + term->rvideo = 0; + term->in_vbell = FALSE; + term->cursor_on = 1; + term->big_cursor = 0; + term->default_attr = term->save_attr = + term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; + term->term_editing = term->term_echoing = FALSE; + term->app_cursor_keys = conf_get_int(term->conf, CONF_app_cursor); + term->app_keypad_keys = conf_get_int(term->conf, CONF_app_keypad); + term->use_bce = conf_get_int(term->conf, CONF_bce); + term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); + term->erase_char = term->basic_erase_char; + term->alt_which = 0; + term_print_finish(term); + term->xterm_mouse = 0; + term->xterm_extended_mouse = 0; + term->urxvt_extended_mouse = 0; + set_raw_mouse_mode(term->frontend, FALSE); + term->bracketed_paste = FALSE; + { + int i; + for (i = 0; i < 256; i++) + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); + } + if (term->screen) { + swap_screen(term, 1, FALSE, FALSE); + erase_lots(term, FALSE, TRUE, TRUE); + swap_screen(term, 0, FALSE, FALSE); + if (clear) + erase_lots(term, FALSE, TRUE, TRUE); + term->curs.y = find_last_nonempty_line(term, term->screen) + 1; + if (term->curs.y == term->rows) { + term->curs.y--; + scroll(term, 0, term->rows - 1, 1, TRUE); + } + } else { + term->curs.y = 0; + } + term->curs.x = 0; + term_schedule_tblink(term); + term_schedule_cblink(term); +} + +/* + * Force a screen update. + */ +void term_update(Terminal *term) +{ + Context ctx; + + term->window_update_pending = FALSE; + + ctx = get_ctx(term->frontend); + if (ctx) { + int need_sbar_update = term->seen_disp_event; + if (term->seen_disp_event && term->scroll_on_disp) { + term->disptop = 0; /* return to main screen */ + term->seen_disp_event = 0; + need_sbar_update = TRUE; + } + + if (need_sbar_update) + update_sbar(term); + do_paint(term, ctx, TRUE); + sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop); + free_ctx(ctx); + } +} + +/* + * Called from front end when a keypress occurs, to trigger + * anything magical that needs to happen in that situation. + */ +void term_seen_key_event(Terminal *term) +{ + /* + * On any keypress, clear the bell overload mechanism + * completely, on the grounds that large numbers of + * beeps coming from deliberate key action are likely + * to be intended (e.g. beeps from filename completion + * blocking repeatedly). + */ + term->beep_overloaded = FALSE; + while (term->beephead) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + } + term->beeptail = NULL; + term->nbeeps = 0; + + /* + * Reset the scrollback on keypress, if we're doing that. + */ + if (term->scroll_on_key) { + term->disptop = 0; /* return to main screen */ + seen_disp_event(term); + } +} + +/* + * Same as power_on(), but an external function. + */ +void term_pwron(Terminal *term, int clear) +{ + power_on(term, clear); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + term->disptop = 0; + deselect(term); + term_update(term); +} + +static void set_erase_char(Terminal *term) +{ + term->erase_char = term->basic_erase_char; + if (term->use_bce) + term->erase_char.attr = (term->curr_attr & + (ATTR_FGMASK | ATTR_BGMASK)); +} + +/* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated tree234 + * lookups which would be involved in fetching them from the former + * every time. + */ +void term_copy_stuff_from_conf(Terminal *term) +{ + term->ansi_colour = conf_get_int(term->conf, CONF_ansi_colour); + term->arabicshaping = conf_get_int(term->conf, CONF_arabicshaping); + term->beep = conf_get_int(term->conf, CONF_beep); + term->bellovl = conf_get_int(term->conf, CONF_bellovl); + term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n); + term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s); + term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t); + term->bidi = conf_get_int(term->conf, CONF_bidi); + term->bksp_is_delete = conf_get_int(term->conf, CONF_bksp_is_delete); + term->blink_cur = conf_get_int(term->conf, CONF_blink_cur); + term->blinktext = conf_get_int(term->conf, CONF_blinktext); + term->cjk_ambig_wide = conf_get_int(term->conf, CONF_cjk_ambig_wide); + term->conf_height = conf_get_int(term->conf, CONF_height); + term->conf_width = conf_get_int(term->conf, CONF_width); + term->crhaslf = conf_get_int(term->conf, CONF_crhaslf); + term->erase_to_scrollback = conf_get_int(term->conf, CONF_erase_to_scrollback); + term->funky_type = conf_get_int(term->conf, CONF_funky_type); + term->lfhascr = conf_get_int(term->conf, CONF_lfhascr); + term->logflush = conf_get_int(term->conf, CONF_logflush); + term->logtype = conf_get_int(term->conf, CONF_logtype); + term->mouse_override = conf_get_int(term->conf, CONF_mouse_override); + term->nethack_keypad = conf_get_int(term->conf, CONF_nethack_keypad); + term->no_alt_screen = conf_get_int(term->conf, CONF_no_alt_screen); + term->no_applic_c = conf_get_int(term->conf, CONF_no_applic_c); + term->no_applic_k = conf_get_int(term->conf, CONF_no_applic_k); + term->no_dbackspace = conf_get_int(term->conf, CONF_no_dbackspace); + term->no_mouse_rep = conf_get_int(term->conf, CONF_no_mouse_rep); + term->no_remote_charset = conf_get_int(term->conf, CONF_no_remote_charset); + term->no_remote_resize = conf_get_int(term->conf, CONF_no_remote_resize); + term->no_remote_wintitle = conf_get_int(term->conf, CONF_no_remote_wintitle); + term->rawcnp = conf_get_int(term->conf, CONF_rawcnp); + term->rect_select = conf_get_int(term->conf, CONF_rect_select); + term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); + term->rxvt_homeend = conf_get_int(term->conf, CONF_rxvt_homeend); + term->scroll_on_disp = conf_get_int(term->conf, CONF_scroll_on_disp); + term->scroll_on_key = conf_get_int(term->conf, CONF_scroll_on_key); + term->xterm_256_colour = conf_get_int(term->conf, CONF_xterm_256_colour); + + /* + * Parse the control-character escapes in the configured + * answerback string. + */ + { + char *answerback = conf_get_str(term->conf, CONF_answerback); + int maxlen = strlen(answerback); + + term->answerback = snewn(maxlen, char); + term->answerbacklen = 0; + + while (*answerback) { + char *n; + char c = ctrlparse(answerback, &n); + if (n) { + term->answerback[term->answerbacklen++] = c; + answerback = n; + } else { + term->answerback[term->answerbacklen++] = *answerback++; + } + } + } +} + +/* + * When the user reconfigures us, we need to check the forbidden- + * alternate-screen config option, disable raw mouse mode if the + * user has disabled mouse reporting, and abandon a print job if + * the user has disabled printing. + */ +void term_reconfig(Terminal *term, Conf *conf) +{ + /* + * Before adopting the new config, check all those terminal + * settings which control power-on defaults; and if they've + * changed, we will modify the current state as well as the + * default one. The full list is: Auto wrap mode, DEC Origin + * Mode, BCE, blinking text, character classes. + */ + int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; + int i; + + reset_wrap = (conf_get_int(term->conf, CONF_wrap_mode) != + conf_get_int(conf, CONF_wrap_mode)); + reset_decom = (conf_get_int(term->conf, CONF_dec_om) != + conf_get_int(conf, CONF_dec_om)); + reset_bce = (conf_get_int(term->conf, CONF_bce) != + conf_get_int(conf, CONF_bce)); + reset_tblink = (conf_get_int(term->conf, CONF_blinktext) != + conf_get_int(conf, CONF_blinktext)); + reset_charclass = 0; + for (i = 0; i < 256; i++) + if (conf_get_int_int(term->conf, CONF_wordness, i) != + conf_get_int_int(conf, CONF_wordness, i)) + reset_charclass = 1; + + /* + * If the bidi or shaping settings have changed, flush the bidi + * cache completely. + */ + if (conf_get_int(term->conf, CONF_arabicshaping) != + conf_get_int(conf, CONF_arabicshaping) || + conf_get_int(term->conf, CONF_bidi) != + conf_get_int(conf, CONF_bidi)) { + for (i = 0; i < term->bidi_cache_size; i++) { + sfree(term->pre_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].chars); + term->pre_bidi_cache[i].width = -1; + term->pre_bidi_cache[i].chars = NULL; + term->post_bidi_cache[i].width = -1; + term->post_bidi_cache[i].chars = NULL; + } + } + + conf_free(term->conf); + term->conf = conf_copy(conf); + + if (reset_wrap) + term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); + if (reset_decom) + term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); + if (reset_bce) { + term->use_bce = conf_get_int(term->conf, CONF_bce); + set_erase_char(term); + } + if (reset_tblink) { + term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); + } + if (reset_charclass) + for (i = 0; i < 256; i++) + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); + + if (conf_get_int(term->conf, CONF_no_alt_screen)) + swap_screen(term, 0, FALSE, FALSE); + if (conf_get_int(term->conf, CONF_no_mouse_rep)) { + term->xterm_mouse = 0; + set_raw_mouse_mode(term->frontend, 0); + } + if (conf_get_int(term->conf, CONF_no_remote_charset)) { + term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII; + term->sco_acs = term->alt_sco_acs = 0; + term->utf = 0; + } + if (!conf_get_str(term->conf, CONF_printer)) { + term_print_finish(term); + } + term_schedule_tblink(term); + term_schedule_cblink(term); + term_copy_stuff_from_conf(term); +} + +/* + * Clear the scrollback. + */ +void term_clrsb(Terminal *term) +{ + unsigned char *line; + int i; + + /* + * Scroll forward to the current screen, if we were back in the + * scrollback somewhere until now. + */ + term->disptop = 0; + + /* + * Clear the actual scrollback. + */ + while ((line = delpos234(term->scrollback, 0)) != NULL) { + sfree(line); /* this is compressed data, not a termline */ + } + + /* + * When clearing the scrollback, we also truncate any termlines on + * the current screen which have remembered data from a previous + * larger window size. Rationale: clearing the scrollback is + * sometimes done to protect privacy, so the user intention is + * specifically that we should not retain evidence of what + * previously happened in the terminal, and that ought to include + * evidence to the right as well as evidence above. + */ + for (i = 0; i < term->rows; i++) + check_line_size(term, scrlineptr(i)); + + /* + * There are now no lines of real scrollback which can be pulled + * back into the screen by a resize, and no lines of the alternate + * screen which should be displayed as if part of the scrollback. + */ + term->tempsblines = 0; + term->alt_sblines = 0; + + /* + * Update the scrollbar to reflect the new state of the world. + */ + update_sbar(term); +} + +/* + * Initialise the terminal. + */ +Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, + void *frontend) +{ + Terminal *term; + + /* + * Allocate a new Terminal structure and initialise the fields + * that need it. + */ + term = snew(Terminal); + term->frontend = frontend; + term->ucsdata = ucsdata; + term->conf = conf_copy(myconf); + term->logctx = NULL; + term->compatibility_level = TM_PUTTY; + strcpy(term->id_string, "\033[?6c"); + term->cblink_pending = term->tblink_pending = FALSE; + term->paste_buffer = NULL; + term->paste_len = 0; + bufchain_init(&term->inbuf); + bufchain_init(&term->printer_buf); + term->printing = term->only_printing = FALSE; + term->print_job = NULL; + term->vt52_mode = FALSE; + term->cr_lf_return = FALSE; + term->seen_disp_event = FALSE; + term->mouse_is_down = FALSE; + term->reset_132 = FALSE; + term->cblinker = term->tblinker = 0; + term->has_focus = 1; + term->repeat_off = FALSE; + term->termstate = TOPLEVEL; + term->selstate = NO_SELECTION; + term->curstype = 0; + + term_copy_stuff_from_conf(term); + + term->screen = term->alt_screen = term->scrollback = NULL; + term->tempsblines = 0; + term->alt_sblines = 0; + term->disptop = 0; + term->disptext = NULL; + term->dispcursx = term->dispcursy = -1; + term->tabs = NULL; + deselect(term); + term->rows = term->cols = -1; + power_on(term, TRUE); + term->beephead = term->beeptail = NULL; +#ifdef OPTIMISE_SCROLL + term->scrollhead = term->scrolltail = NULL; +#endif /* OPTIMISE_SCROLL */ + term->nbeeps = 0; + term->lastbeep = FALSE; + term->beep_overloaded = FALSE; + term->attr_mask = 0xffffffff; + term->resize_fn = NULL; + term->resize_ctx = NULL; + term->in_term_out = FALSE; + term->ltemp = NULL; + term->ltemp_size = 0; + term->wcFrom = NULL; + term->wcTo = NULL; + term->wcFromTo_size = 0; + + term->window_update_pending = FALSE; + + term->bidi_cache_size = 0; + term->pre_bidi_cache = term->post_bidi_cache = NULL; + + /* FULL-TERMCHAR */ + term->basic_erase_char.chr = CSET_ASCII | ' '; + term->basic_erase_char.attr = ATTR_DEFAULT; + term->basic_erase_char.cc_next = 0; + term->erase_char = term->basic_erase_char; + + return term; +} + +void term_free(Terminal *term) +{ + termline *line; + struct beeptime *beep; + int i; + + while ((line = delpos234(term->scrollback, 0)) != NULL) + sfree(line); /* compressed data, not a termline */ + freetree234(term->scrollback); + while ((line = delpos234(term->screen, 0)) != NULL) + freeline(line); + freetree234(term->screen); + while ((line = delpos234(term->alt_screen, 0)) != NULL) + freeline(line); + freetree234(term->alt_screen); + if (term->disptext) { + for (i = 0; i < term->rows; i++) + freeline(term->disptext[i]); + } + sfree(term->disptext); + while (term->beephead) { + beep = term->beephead; + term->beephead = beep->next; + sfree(beep); + } + bufchain_clear(&term->inbuf); + if(term->print_job) + printer_finish_job(term->print_job); + bufchain_clear(&term->printer_buf); + sfree(term->paste_buffer); + sfree(term->ltemp); + sfree(term->wcFrom); + sfree(term->wcTo); + + for (i = 0; i < term->bidi_cache_size; i++) { + sfree(term->pre_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].forward); + sfree(term->post_bidi_cache[i].backward); + } + sfree(term->pre_bidi_cache); + sfree(term->post_bidi_cache); + + sfree(term->tabs); + + expire_timer_context(term); + + conf_free(term->conf); + + sfree(term); +} + +/* + * Set up the terminal for a given size. + */ +void term_size(Terminal *term, int newrows, int newcols, int newsavelines) +{ + tree234 *newalt; + termline **newdisp, *line; + int i, j, oldrows = term->rows; + int sblen; + int save_alt_which = term->alt_which; + + if (newrows == term->rows && newcols == term->cols && + newsavelines == term->savelines) + return; /* nothing to do */ + + /* Behave sensibly if we're given zero (or negative) rows/cols */ + + if (newrows < 1) newrows = 1; + if (newcols < 1) newcols = 1; + + deselect(term); + swap_screen(term, 0, FALSE, FALSE); + + term->alt_t = term->marg_t = 0; + term->alt_b = term->marg_b = newrows - 1; + + if (term->rows == -1) { + term->scrollback = newtree234(NULL); + term->screen = newtree234(NULL); + term->tempsblines = 0; + term->rows = 0; + } + + /* + * Resize the screen and scrollback. We only need to shift + * lines around within our data structures, because lineptr() + * will take care of resizing each individual line if + * necessary. So: + * + * - If the new screen is longer, we shunt lines in from temporary + * scrollback if possible, otherwise we add new blank lines at + * the bottom. + * + * - If the new screen is shorter, we remove any blank lines at + * the bottom if possible, otherwise shunt lines above the cursor + * to scrollback if possible, otherwise delete lines below the + * cursor. + * + * - Then, if the new scrollback length is less than the + * amount of scrollback we actually have, we must throw some + * away. + */ + sblen = count234(term->scrollback); + /* Do this loop to expand the screen if newrows > rows */ + assert(term->rows == count234(term->screen)); + while (term->rows < newrows) { + if (term->tempsblines > 0) { + unsigned char *cline; + /* Insert a line from the scrollback at the top of the screen. */ + assert(sblen >= term->tempsblines); + cline = delpos234(term->scrollback, --sblen); + line = decompressline(cline, NULL); + sfree(cline); + line->temporary = FALSE; /* reconstituted line is now real */ + term->tempsblines -= 1; + addpos234(term->screen, line, 0); + term->curs.y += 1; + term->savecurs.y += 1; + term->alt_y += 1; + term->alt_savecurs.y += 1; + } else { + /* Add a new blank line at the bottom of the screen. */ + line = newline(term, newcols, FALSE); + addpos234(term->screen, line, count234(term->screen)); + } + term->rows += 1; + } + /* Do this loop to shrink the screen if newrows < rows */ + while (term->rows > newrows) { + if (term->curs.y < term->rows - 1) { + /* delete bottom row, unless it contains the cursor */ + line = delpos234(term->screen, term->rows - 1); + freeline(line); + } else { + /* push top row to scrollback */ + line = delpos234(term->screen, 0); + addpos234(term->scrollback, compressline(line), sblen++); + freeline(line); + term->tempsblines += 1; + term->curs.y -= 1; + term->savecurs.y -= 1; + term->alt_y -= 1; + term->alt_savecurs.y -= 1; + } + term->rows -= 1; + } + assert(term->rows == newrows); + assert(count234(term->screen) == newrows); + + /* Delete any excess lines from the scrollback. */ + while (sblen > newsavelines) { + line = delpos234(term->scrollback, 0); + sfree(line); + sblen--; + } + if (sblen < term->tempsblines) + term->tempsblines = sblen; + assert(count234(term->scrollback) <= newsavelines); + assert(count234(term->scrollback) >= term->tempsblines); + term->disptop = 0; + + /* Make a new displayed text buffer. */ + newdisp = snewn(newrows, termline *); + for (i = 0; i < newrows; i++) { + newdisp[i] = newline(term, newcols, FALSE); + for (j = 0; j < newcols; j++) + newdisp[i]->chars[j].attr = ATTR_INVALID; + } + if (term->disptext) { + for (i = 0; i < oldrows; i++) + freeline(term->disptext[i]); + } + sfree(term->disptext); + term->disptext = newdisp; + term->dispcursx = term->dispcursy = -1; + + /* Make a new alternate screen. */ + newalt = newtree234(NULL); + for (i = 0; i < newrows; i++) { + line = newline(term, newcols, TRUE); + addpos234(newalt, line, i); + } + if (term->alt_screen) { + while (NULL != (line = delpos234(term->alt_screen, 0))) + freeline(line); + freetree234(term->alt_screen); + } + term->alt_screen = newalt; + term->alt_sblines = 0; + + term->tabs = sresize(term->tabs, newcols, unsigned char); + { + int i; + for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++) + term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + } + + /* Check that the cursor positions are still valid. */ + if (term->savecurs.y < 0) + term->savecurs.y = 0; + if (term->savecurs.y >= newrows) + term->savecurs.y = newrows - 1; + if (term->savecurs.x >= newcols) + term->savecurs.x = newcols - 1; + if (term->alt_savecurs.y < 0) + term->alt_savecurs.y = 0; + if (term->alt_savecurs.y >= newrows) + term->alt_savecurs.y = newrows - 1; + if (term->alt_savecurs.x >= newcols) + term->alt_savecurs.x = newcols - 1; + if (term->curs.y < 0) + term->curs.y = 0; + if (term->curs.y >= newrows) + term->curs.y = newrows - 1; + if (term->curs.x >= newcols) + term->curs.x = newcols - 1; + if (term->alt_y < 0) + term->alt_y = 0; + if (term->alt_y >= newrows) + term->alt_y = newrows - 1; + if (term->alt_x >= newcols) + term->alt_x = newcols - 1; + term->alt_x = term->alt_y = 0; + term->wrapnext = term->alt_wnext = FALSE; + + term->rows = newrows; + term->cols = newcols; + term->savelines = newsavelines; + + swap_screen(term, save_alt_which, FALSE, FALSE); + + update_sbar(term); + term_update(term); + if (term->resize_fn) + term->resize_fn(term->resize_ctx, term->cols, term->rows); +} + +/* + * Hand a function and context pointer to the terminal which it can + * use to notify a back end of resizes. + */ +void term_provide_resize_fn(Terminal *term, + void (*resize_fn)(void *, int, int), + void *resize_ctx) +{ + term->resize_fn = resize_fn; + term->resize_ctx = resize_ctx; + if (resize_fn && term->cols > 0 && term->rows > 0) + resize_fn(resize_ctx, term->cols, term->rows); +} + +/* Find the bottom line on the screen that has any content. + * If only the top line has content, returns 0. + * If no lines have content, return -1. + */ +static int find_last_nonempty_line(Terminal * term, tree234 * screen) +{ + int i; + for (i = count234(screen) - 1; i >= 0; i--) { + termline *line = index234(screen, i); + int j; + for (j = 0; j < line->cols; j++) + if (!termchars_equal(&line->chars[j], &term->erase_char)) + break; + if (j != line->cols) break; + } + return i; +} + +/* + * Swap screens. If `reset' is TRUE and we have been asked to + * switch to the alternate screen, we must bring most of its + * configuration from the main screen and erase the contents of the + * alternate screen completely. (This is even true if we're already + * on it! Blame xterm.) + */ +static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) +{ + int t; + pos tp; + tree234 *ttr; + + if (!which) + reset = FALSE; /* do no weird resetting if which==0 */ + + if (which != term->alt_which) { + term->alt_which = which; + + ttr = term->alt_screen; + term->alt_screen = term->screen; + term->screen = ttr; + term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1; + t = term->curs.x; + if (!reset && !keep_cur_pos) + term->curs.x = term->alt_x; + term->alt_x = t; + t = term->curs.y; + if (!reset && !keep_cur_pos) + term->curs.y = term->alt_y; + term->alt_y = t; + t = term->marg_t; + if (!reset) term->marg_t = term->alt_t; + term->alt_t = t; + t = term->marg_b; + if (!reset) term->marg_b = term->alt_b; + term->alt_b = t; + t = term->dec_om; + if (!reset) term->dec_om = term->alt_om; + term->alt_om = t; + t = term->wrap; + if (!reset) term->wrap = term->alt_wrap; + term->alt_wrap = t; + t = term->wrapnext; + if (!reset) term->wrapnext = term->alt_wnext; + term->alt_wnext = t; + t = term->insert; + if (!reset) term->insert = term->alt_ins; + term->alt_ins = t; + t = term->cset; + if (!reset) term->cset = term->alt_cset; + term->alt_cset = t; + t = term->utf; + if (!reset) term->utf = term->alt_utf; + term->alt_utf = t; + t = term->sco_acs; + if (!reset) term->sco_acs = term->alt_sco_acs; + term->alt_sco_acs = t; + + tp = term->savecurs; + if (!reset && !keep_cur_pos) + term->savecurs = term->alt_savecurs; + term->alt_savecurs = tp; + t = term->save_cset; + if (!reset && !keep_cur_pos) + term->save_cset = term->alt_save_cset; + term->alt_save_cset = t; + t = term->save_csattr; + if (!reset && !keep_cur_pos) + term->save_csattr = term->alt_save_csattr; + term->alt_save_csattr = t; + t = term->save_attr; + if (!reset && !keep_cur_pos) + term->save_attr = term->alt_save_attr; + term->alt_save_attr = t; + t = term->save_utf; + if (!reset && !keep_cur_pos) + term->save_utf = term->alt_save_utf; + term->alt_save_utf = t; + t = term->save_wnext; + if (!reset && !keep_cur_pos) + term->save_wnext = term->alt_save_wnext; + term->alt_save_wnext = t; + t = term->save_sco_acs; + if (!reset && !keep_cur_pos) + term->save_sco_acs = term->alt_save_sco_acs; + term->alt_save_sco_acs = t; + } + + if (reset && term->screen) { + /* + * Yes, this _is_ supposed to honour background-colour-erase. + */ + erase_lots(term, FALSE, TRUE, TRUE); + } +} + +/* + * Update the scroll bar. + */ +static void update_sbar(Terminal *term) +{ + int nscroll = sblines(term); + set_sbar(term->frontend, nscroll + term->rows, + nscroll + term->disptop, term->rows); +} + +/* + * Check whether the region bounded by the two pointers intersects + * the scroll region, and de-select the on-screen selection if so. + */ +static void check_selection(Terminal *term, pos from, pos to) +{ + if (poslt(from, term->selend) && poslt(term->selstart, to)) + deselect(term); +} + +/* + * Scroll the screen. (`lines' is +ve for scrolling forward, -ve + * for backward.) `sb' is TRUE if the scrolling is permitted to + * affect the scrollback buffer. + */ +static void scroll(Terminal *term, int topline, int botline, int lines, int sb) +{ + termline *line; + int i, seltop, scrollwinsize; +#ifdef OPTIMISE_SCROLL + int olddisptop, shift; +#endif /* OPTIMISE_SCROLL */ + + if (topline != 0 || term->alt_which != 0) + sb = FALSE; + +#ifdef OPTIMISE_SCROLL + olddisptop = term->disptop; + shift = lines; +#endif /* OPTIMISE_SCROLL */ + + scrollwinsize = botline - topline + 1; + + if (lines < 0) { + lines = -lines; + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { + line = delpos234(term->screen, botline); + resizeline(term, line, term->cols); + for (i = 0; i < term->cols; i++) + copy_termchar(line, i, &term->erase_char); + line->lattr = LATTR_NORM; + addpos234(term->screen, line, topline); + + if (term->selstart.y >= topline && term->selstart.y <= botline) { + term->selstart.y++; + if (term->selstart.y > botline) { + term->selstart.y = botline + 1; + term->selstart.x = 0; + } + } + if (term->selend.y >= topline && term->selend.y <= botline) { + term->selend.y++; + if (term->selend.y > botline) { + term->selend.y = botline + 1; + term->selend.x = 0; + } + } + } + } else { + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { + line = delpos234(term->screen, topline); +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif + if (sb && term->savelines > 0) { + int sblen = count234(term->scrollback); + /* + * We must add this line to the scrollback. We'll + * remove a line from the top of the scrollback if + * the scrollback is full. + */ + if (sblen == term->savelines) { + unsigned char *cline; + + sblen--; + cline = delpos234(term->scrollback, 0); + sfree(cline); + } else + term->tempsblines += 1; + + addpos234(term->scrollback, compressline(line), sblen); + + /* now `line' itself can be reused as the bottom line */ + + /* + * If the user is currently looking at part of the + * scrollback, and they haven't enabled any options + * that are going to reset the scrollback as a + * result of this movement, then the chances are + * they'd like to keep looking at the same line. So + * we move their viewpoint at the same rate as the + * scroll, at least until their viewpoint hits the + * top end of the scrollback buffer, at which point + * we don't have the choice any more. + * + * Thanks to Jan Holmen Holsten for the idea and + * initial implementation. + */ + if (term->disptop > -term->savelines && term->disptop < 0) + term->disptop--; + } + resizeline(term, line, term->cols); + for (i = 0; i < term->cols; i++) + copy_termchar(line, i, &term->erase_char); + line->lattr = LATTR_NORM; + addpos234(term->screen, line, botline); + + /* + * If the selection endpoints move into the scrollback, + * we keep them moving until they hit the top. However, + * of course, if the line _hasn't_ moved into the + * scrollback then we don't do this, and cut them off + * at the top of the scroll region. + * + * This applies to selstart and selend (for an existing + * selection), and also selanchor (for one being + * selected as we speak). + */ + seltop = sb ? -term->savelines : topline; + + if (term->selstate != NO_SELECTION) { + if (term->selstart.y >= seltop && + term->selstart.y <= botline) { + term->selstart.y--; + if (term->selstart.y < seltop) { + term->selstart.y = seltop; + term->selstart.x = 0; + } + } + if (term->selend.y >= seltop && term->selend.y <= botline) { + term->selend.y--; + if (term->selend.y < seltop) { + term->selend.y = seltop; + term->selend.x = 0; + } + } + if (term->selanchor.y >= seltop && + term->selanchor.y <= botline) { + term->selanchor.y--; + if (term->selanchor.y < seltop) { + term->selanchor.y = seltop; + term->selanchor.x = 0; + } + } + } + } + } +#ifdef OPTIMISE_SCROLL + shift += term->disptop - olddisptop; + if (shift < term->rows && shift > -term->rows && shift != 0) + scroll_display(term, topline, botline, shift); +#endif /* OPTIMISE_SCROLL */ +} + +#ifdef OPTIMISE_SCROLL +/* + * Add a scroll of a region on the screen into the pending scroll list. + * `lines' is +ve for scrolling forward, -ve for backward. + * + * If the scroll is on the same area as the last scroll in the list, + * merge them. + */ +static void save_scroll(Terminal *term, int topline, int botline, int lines) +{ + struct scrollregion *newscroll; + if (term->scrolltail && + term->scrolltail->topline == topline && + term->scrolltail->botline == botline) { + term->scrolltail->lines += lines; + } else { + newscroll = snew(struct scrollregion); + newscroll->topline = topline; + newscroll->botline = botline; + newscroll->lines = lines; + newscroll->next = NULL; + + if (!term->scrollhead) + term->scrollhead = newscroll; + else + term->scrolltail->next = newscroll; + term->scrolltail = newscroll; + } +} + +/* + * Scroll the physical display, and our conception of it in disptext. + */ +static void scroll_display(Terminal *term, int topline, int botline, int lines) +{ + int distance, nlines, i, j; + + distance = lines > 0 ? lines : -lines; + nlines = botline - topline + 1 - distance; + if (lines > 0) { + for (i = 0; i < nlines; i++) + for (j = 0; j < term->cols; j++) + copy_termchar(term->disptext[i], j, + term->disptext[i+distance]->chars+j); + if (term->dispcursy >= 0 && + term->dispcursy >= topline + distance && + term->dispcursy < topline + distance + nlines) + term->dispcursy -= distance; + for (i = 0; i < distance; i++) + for (j = 0; j < term->cols; j++) + term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID; + } else { + for (i = nlines; i-- ;) + for (j = 0; j < term->cols; j++) + copy_termchar(term->disptext[i+distance], j, + term->disptext[i]->chars+j); + if (term->dispcursy >= 0 && + term->dispcursy >= topline && + term->dispcursy < topline + nlines) + term->dispcursy += distance; + for (i = 0; i < distance; i++) + for (j = 0; j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + save_scroll(term, topline, botline, lines); +} +#endif /* OPTIMISE_SCROLL */ + +/* + * Move the cursor to a given position, clipping at boundaries. We + * may or may not want to clip at the scroll margin: marg_clip is 0 + * not to, 1 to disallow _passing_ the margins, and 2 to disallow + * even _being_ outside the margins. + */ +static void move(Terminal *term, int x, int y, int marg_clip) +{ + if (x < 0) + x = 0; + if (x >= term->cols) + x = term->cols - 1; + if (marg_clip) { + if ((term->curs.y >= term->marg_t || marg_clip == 2) && + y < term->marg_t) + y = term->marg_t; + if ((term->curs.y <= term->marg_b || marg_clip == 2) && + y > term->marg_b) + y = term->marg_b; + } + if (y < 0) + y = 0; + if (y >= term->rows) + y = term->rows - 1; + term->curs.x = x; + term->curs.y = y; + term->wrapnext = FALSE; +} + +/* + * Save or restore the cursor and SGR mode. + */ +static void save_cursor(Terminal *term, int save) +{ + if (save) { + term->savecurs = term->curs; + term->save_attr = term->curr_attr; + term->save_cset = term->cset; + term->save_utf = term->utf; + term->save_wnext = term->wrapnext; + term->save_csattr = term->cset_attr[term->cset]; + term->save_sco_acs = term->sco_acs; + } else { + term->curs = term->savecurs; + /* Make sure the window hasn't shrunk since the save */ + if (term->curs.x >= term->cols) + term->curs.x = term->cols - 1; + if (term->curs.y >= term->rows) + term->curs.y = term->rows - 1; + + term->curr_attr = term->save_attr; + term->cset = term->save_cset; + term->utf = term->save_utf; + term->wrapnext = term->save_wnext; + /* + * wrapnext might reset to False if the x position is no + * longer at the rightmost edge. + */ + if (term->wrapnext && term->curs.x < term->cols-1) + term->wrapnext = FALSE; + term->cset_attr[term->cset] = term->save_csattr; + term->sco_acs = term->save_sco_acs; + set_erase_char(term); + } +} + +/* + * This function is called before doing _anything_ which affects + * only part of a line of text. It is used to mark the boundary + * between two character positions, and it indicates that some sort + * of effect is going to happen on only one side of that boundary. + * + * The effect of this function is to check whether a CJK + * double-width character is straddling the boundary, and to remove + * it and replace it with two spaces if so. (Of course, one or + * other of those spaces is then likely to be replaced with + * something else again, as a result of whatever happens next.) + * + * Also, if the boundary is at the right-hand _edge_ of the screen, + * it implies something deliberate is being done to the rightmost + * column position; hence we must clear LATTR_WRAPPED2. + * + * The input to the function is the coordinates of the _second_ + * character of the pair. + */ +static void check_boundary(Terminal *term, int x, int y) +{ + termline *ldata; + + /* Validate input coordinates, just in case. */ + if (x <= 0 || x > term->cols) + return; + + ldata = scrlineptr(y); + check_line_size(term, ldata); + if (x == term->cols) { + ldata->lattr &= ~LATTR_WRAPPED2; + } else { + if (ldata->chars[x].chr == UCSWIDE) { + clear_cc(ldata, x-1); + clear_cc(ldata, x); + ldata->chars[x-1].chr = ' ' | CSET_ASCII; + ldata->chars[x] = ldata->chars[x-1]; + } + } +} + +/* + * Erase a large portion of the screen: the whole screen, or the + * whole line, or parts thereof. + */ +static void erase_lots(Terminal *term, + int line_only, int from_begin, int to_end) +{ + pos start, end; + int erase_lattr; + int erasing_lines_from_top = 0; + + if (line_only) { + start.y = term->curs.y; + start.x = 0; + end.y = term->curs.y + 1; + end.x = 0; + erase_lattr = FALSE; + } else { + start.y = 0; + start.x = 0; + end.y = term->rows; + end.x = 0; + erase_lattr = TRUE; + } + if (!from_begin) { + start = term->curs; + } + if (!to_end) { + end = term->curs; + incpos(end); + } + if (!from_begin || !to_end) + check_boundary(term, term->curs.x, term->curs.y); + check_selection(term, start, end); + + /* Clear screen also forces a full window redraw, just in case. */ + if (start.y == 0 && start.x == 0 && end.y == term->rows) + term_invalidate(term); + + /* Lines scrolled away shouldn't be brought back on if the terminal + * resizes. */ + if (start.y == 0 && start.x == 0 && end.x == 0 && erase_lattr) + erasing_lines_from_top = 1; + + if (term->erase_to_scrollback && erasing_lines_from_top) { + /* If it's a whole number of lines, starting at the top, and + * we're fully erasing them, erase by scrolling and keep the + * lines in the scrollback. */ + int scrolllines = end.y; + if (end.y == term->rows) { + /* Shrink until we find a non-empty row.*/ + scrolllines = find_last_nonempty_line(term, term->screen) + 1; + } + if (scrolllines > 0) + scroll(term, 0, scrolllines - 1, scrolllines, TRUE); + } else { + termline *ldata = scrlineptr(start.y); + while (poslt(start, end)) { + check_line_size(term, ldata); + if (start.x == term->cols) { + if (!erase_lattr) + ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2); + else + ldata->lattr = LATTR_NORM; + } else { + copy_termchar(ldata, start.x, &term->erase_char); + } + if (incpos(start) && start.y < term->rows) { + ldata = scrlineptr(start.y); + } + } + } + + /* After an erase of lines from the top of the screen, we shouldn't + * bring the lines back again if the terminal enlarges (since the user or + * application has explictly thrown them away). */ + if (erasing_lines_from_top && !(term->alt_which)) + term->tempsblines = 0; +} + +/* + * Insert or delete characters within the current line. n is +ve if + * insertion is desired, and -ve for deletion. + */ +static void insch(Terminal *term, int n) +{ + int dir = (n < 0 ? -1 : +1); + int m, j; + pos eol; + termline *ldata; + + n = (n < 0 ? -n : n); + if (n > term->cols - term->curs.x) + n = term->cols - term->curs.x; + m = term->cols - term->curs.x - n; + + /* + * We must de-highlight the selection if it overlaps any part of + * the region affected by this operation, i.e. the region from the + * current cursor position to end-of-line, _unless_ the entirety + * of the selection is going to be moved to the left or right by + * this operation but otherwise unchanged, in which case we can + * simply move the highlight with the text. + */ + eol.y = term->curs.y; + eol.x = term->cols; + if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) { + pos okstart = term->curs; + pos okend = eol; + if (dir > 0) { + /* Insertion: n characters at EOL will be splatted. */ + okend.x -= n; + } else { + /* Deletion: n characters at cursor position will be splatted. */ + okstart.x += n; + } + if (posle(okstart, term->selstart) && posle(term->selend, okend)) { + /* Selection is contained entirely in the interval + * [okstart,okend), so we need only adjust the selection + * bounds. */ + term->selstart.x += dir * n; + term->selend.x += dir * n; + assert(term->selstart.x >= term->curs.x); + assert(term->selstart.x < term->cols); + assert(term->selend.x > term->curs.x); + assert(term->selend.x <= term->cols); + } else { + /* Selection is not wholly contained in that interval, so + * we must unhighlight it. */ + deselect(term); + } + } + + check_boundary(term, term->curs.x, term->curs.y); + if (dir < 0) + check_boundary(term, term->curs.x + n, term->curs.y); + ldata = scrlineptr(term->curs.y); + if (dir < 0) { + for (j = 0; j < m; j++) + move_termchar(ldata, + ldata->chars + term->curs.x + j, + ldata->chars + term->curs.x + j + n); + while (n--) + copy_termchar(ldata, term->curs.x + m++, &term->erase_char); + } else { + for (j = m; j-- ;) + move_termchar(ldata, + ldata->chars + term->curs.x + j + n, + ldata->chars + term->curs.x + j); + while (n--) + copy_termchar(ldata, term->curs.x + n, &term->erase_char); + } +} + +/* + * Toggle terminal mode `mode' to state `state'. (`query' indicates + * whether the mode is a DEC private one or a normal one.) + */ +static void toggle_mode(Terminal *term, int mode, int query, int state) +{ + if (query) + switch (mode) { + case 1: /* DECCKM: application cursor keys */ + term->app_cursor_keys = state; + break; + case 2: /* DECANM: VT52 mode */ + term->vt52_mode = !state; + if (term->vt52_mode) { + term->blink_is_real = FALSE; + term->vt52_bold = FALSE; + } else { + term->blink_is_real = term->blinktext; + } + term_schedule_tblink(term); + break; + case 3: /* DECCOLM: 80/132 columns */ + deselect(term); + if (!term->no_remote_resize) + request_resize(term->frontend, state ? 132 : 80, term->rows); + term->reset_132 = state; + term->alt_t = term->marg_t = 0; + term->alt_b = term->marg_b = term->rows - 1; + move(term, 0, 0, 0); + erase_lots(term, FALSE, TRUE, TRUE); + break; + case 5: /* DECSCNM: reverse video */ + /* + * Toggle reverse video. If we receive an OFF within the + * visual bell timeout period after an ON, we trigger an + * effective visual bell, so that ESC[?5hESC[?5l will + * always be an actually _visible_ visual bell. + */ + if (term->rvideo && !state) { + /* This is an OFF, so set up a vbell */ + term_schedule_vbell(term, TRUE, term->rvbell_startpoint); + } else if (!term->rvideo && state) { + /* This is an ON, so we notice the time and save it. */ + term->rvbell_startpoint = GETTICKCOUNT(); + } + term->rvideo = state; + seen_disp_event(term); + break; + case 6: /* DECOM: DEC origin mode */ + term->dec_om = state; + break; + case 7: /* DECAWM: auto wrap */ + term->wrap = state; + break; + case 8: /* DECARM: auto key repeat */ + term->repeat_off = !state; + break; + case 10: /* DECEDM: set local edit mode */ + term->term_editing = state; + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + break; + case 25: /* DECTCEM: enable/disable cursor */ + compatibility2(OTHER, VT220); + term->cursor_on = state; + seen_disp_event(term); + break; + case 47: /* alternate screen */ + compatibility(OTHER); + deselect(term); + swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 1000: /* xterm mouse 1 (normal) */ + term->xterm_mouse = state ? 1 : 0; + set_raw_mouse_mode(term->frontend, state); + break; + case 1002: /* xterm mouse 2 (inc. button drags) */ + term->xterm_mouse = state ? 2 : 0; + set_raw_mouse_mode(term->frontend, state); + break; + case 1006: /* xterm extended mouse */ + term->xterm_extended_mouse = state ? 1 : 0; + break; + case 1015: /* urxvt extended mouse */ + term->urxvt_extended_mouse = state ? 1 : 0; + break; + case 1047: /* alternate screen */ + compatibility(OTHER); + deselect(term); + swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, TRUE); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 1048: /* save/restore cursor */ + if (!term->no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + break; + case 1049: /* cursor & alternate screen */ + if (state && !term->no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + compatibility(OTHER); + deselect(term); + swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE); + if (!state && !term->no_alt_screen) + save_cursor(term, state); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 2004: /* xterm bracketed paste */ + term->bracketed_paste = state ? TRUE : FALSE; + break; + } else + switch (mode) { + case 4: /* IRM: set insert mode */ + compatibility(VT102); + term->insert = state; + break; + case 12: /* SRM: set echo mode */ + term->term_echoing = !state; + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + break; + case 20: /* LNM: Return sends ... */ + term->cr_lf_return = state; + break; + case 34: /* WYULCURM: Make cursor BIG */ + compatibility2(OTHER, VT220); + term->big_cursor = !state; + } +} + +/* + * Process an OSC sequence: set window title or icon name. + */ +static void do_osc(Terminal *term) +{ + if (term->osc_w) { + while (term->osc_strlen--) + term->wordness[(unsigned char) + term->osc_string[term->osc_strlen]] = term->esc_args[0]; + } else { + term->osc_string[term->osc_strlen] = '\0'; + switch (term->esc_args[0]) { + case 0: + case 1: + if (!term->no_remote_wintitle) + set_icon(term->frontend, term->osc_string); + if (term->esc_args[0] == 1) + break; + /* fall through: parameter 0 means set both */ + case 2: + case 21: + if (!term->no_remote_wintitle) + set_title(term->frontend, term->osc_string); + break; + } + } +} + +/* + * ANSI printing routines. + */ +static void term_print_setup(Terminal *term, char *printer) +{ + bufchain_clear(&term->printer_buf); + term->print_job = printer_start_job(printer); +} +static void term_print_flush(Terminal *term) +{ + void *data; + int len; + int size; + while ((size = bufchain_size(&term->printer_buf)) > 5) { + bufchain_prefix(&term->printer_buf, &data, &len); + if (len > size-5) + len = size-5; + printer_job_data(term->print_job, data, len); + bufchain_consume(&term->printer_buf, len); + } +} +static void term_print_finish(Terminal *term) +{ + void *data; + int len, size; + char c; + + if (!term->printing && !term->only_printing) + return; /* we need do nothing */ + + term_print_flush(term); + while ((size = bufchain_size(&term->printer_buf)) > 0) { + bufchain_prefix(&term->printer_buf, &data, &len); + c = *(char *)data; + if (c == '\033' || c == '\233') { + bufchain_consume(&term->printer_buf, size); + break; + } else { + printer_job_data(term->print_job, &c, 1); + bufchain_consume(&term->printer_buf, 1); + } + } + printer_finish_job(term->print_job); + term->print_job = NULL; + term->printing = term->only_printing = FALSE; +} + +/* + * Remove everything currently in `inbuf' and stick it up on the + * in-memory display. There's a big state machine in here to + * process escape sequences... + */ +static void term_out(Terminal *term) +{ + unsigned long c; + int unget; + unsigned char localbuf[256], *chars; + int nchars = 0; + + unget = -1; + + chars = NULL; /* placate compiler warnings */ + while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) { + if (unget == -1) { + if (nchars == 0) { + void *ret; + bufchain_prefix(&term->inbuf, &ret, &nchars); + if (nchars > sizeof(localbuf)) + nchars = sizeof(localbuf); + memcpy(localbuf, ret, nchars); + bufchain_consume(&term->inbuf, nchars); + chars = localbuf; + assert(chars != NULL); + } + c = *chars++; + nchars--; + + /* + * Optionally log the session traffic to a file. Useful for + * debugging and possibly also useful for actual logging. + */ + if (term->logtype == LGTYP_DEBUG && term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG); + } else { + c = unget; + unget = -1; + } + + /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even + * be able to display 8-bit characters, but I'll let that go 'cause + * of i18n. + */ + + /* + * If we're printing, add the character to the printer + * buffer. + */ + if (term->printing) { + bufchain_add(&term->printer_buf, &c, 1); + + /* + * If we're in print-only mode, we use a much simpler + * state machine designed only to recognise the ESC[4i + * termination sequence. + */ + if (term->only_printing) { + if (c == '\033') + term->print_state = 1; + else if (c == (unsigned char)'\233') + term->print_state = 2; + else if (c == '[' && term->print_state == 1) + term->print_state = 2; + else if (c == '4' && term->print_state == 2) + term->print_state = 3; + else if (c == 'i' && term->print_state == 3) + term->print_state = 4; + else + term->print_state = 0; + if (term->print_state == 4) { + term_print_finish(term); + } + continue; + } + } + + /* First see about all those translations. */ + if (term->termstate == TOPLEVEL) { + if (in_utf(term)) + switch (term->utf_state) { + case 0: + if (c < 0x80) { + /* UTF-8 must be stateless so we ignore iso2022. */ + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else c = ((unsigned char)c) | CSET_ASCII; + break; + } else if ((c & 0xe0) == 0xc0) { + term->utf_size = term->utf_state = 1; + term->utf_char = (c & 0x1f); + } else if ((c & 0xf0) == 0xe0) { + term->utf_size = term->utf_state = 2; + term->utf_char = (c & 0x0f); + } else if ((c & 0xf8) == 0xf0) { + term->utf_size = term->utf_state = 3; + term->utf_char = (c & 0x07); + } else if ((c & 0xfc) == 0xf8) { + term->utf_size = term->utf_state = 4; + term->utf_char = (c & 0x03); + } else if ((c & 0xfe) == 0xfc) { + term->utf_size = term->utf_state = 5; + term->utf_char = (c & 0x01); + } else { + c = UCSERR; + break; + } + continue; + case 1: + case 2: + case 3: + case 4: + case 5: + if ((c & 0xC0) != 0x80) { + unget = c; + c = UCSERR; + term->utf_state = 0; + break; + } + term->utf_char = (term->utf_char << 6) | (c & 0x3f); + if (--term->utf_state) + continue; + + c = term->utf_char; + + /* Is somebody trying to be evil! */ + if (c < 0x80 || + (c < 0x800 && term->utf_size >= 2) || + (c < 0x10000 && term->utf_size >= 3) || + (c < 0x200000 && term->utf_size >= 4) || + (c < 0x4000000 && term->utf_size >= 5)) + c = UCSERR; + + /* Unicode line separator and paragraph separator are CR-LF */ + if (c == 0x2028 || c == 0x2029) + c = 0x85; + + /* High controls are probably a Baaad idea too. */ + if (c < 0xA0) + c = 0xFFFD; + + /* The UTF-16 surrogates are not nice either. */ + /* The standard give the option of decoding these: + * I don't want to! */ + if (c >= 0xD800 && c < 0xE000) + c = UCSERR; + + /* ISO 10646 characters now limited to UTF-16 range. */ + if (c > 0x10FFFF) + c = UCSERR; + + /* This is currently a TagPhobic application.. */ + if (c >= 0xE0000 && c <= 0xE007F) + continue; + + /* U+FEFF is best seen as a null. */ + if (c == 0xFEFF) + continue; + /* But U+FFFE is an error. */ + if (c == 0xFFFE || c == 0xFFFF) + c = UCSERR; + + break; + } + /* Are we in the nasty ACS mode? Note: no sco in utf mode. */ + else if(term->sco_acs && + (c!='\033' && c!='\012' && c!='\015' && c!='\b')) + { + if (term->sco_acs == 2) c |= 0x80; + c |= CSET_SCOACS; + } else { + switch (term->cset_attr[term->cset]) { + /* + * Linedraw characters are different from 'ESC ( B' + * only for a small range. For ones outside that + * range, make sure we use the same font as well as + * the same encoding. + */ + case CSET_LINEDRW: + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else + c = ((unsigned char) c) | CSET_LINEDRW; + break; + + case CSET_GBCHR: + /* If UK-ASCII, make the '#' a LineDraw Pound */ + if (c == '#') { + c = '}' | CSET_LINEDRW; + break; + } + /*FALLTHROUGH*/ case CSET_ASCII: + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else + c = ((unsigned char) c) | CSET_ASCII; + break; + case CSET_SCOACS: + if (c>=' ') c = ((unsigned char)c) | CSET_SCOACS; + break; + } + } + } + + /* + * How about C1 controls? + * Explicitly ignore SCI (0x9a), which we don't translate to DECID. + */ + if ((c & -32) == 0x80 && term->termstate < DO_CTRLS && + !term->vt52_mode && has_compat(VT220)) { + if (c == 0x9a) + c = 0; + else { + term->termstate = SEEN_ESC; + term->esc_query = FALSE; + c = '@' + (c & 0x1F); + } + } + + /* Or the GL control. */ + if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) { + if (term->curs.x && !term->wrapnext) + term->curs.x--; + term->wrapnext = FALSE; + /* destructive backspace might be disabled */ + if (!term->no_dbackspace) { + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+1, term->curs.y); + copy_termchar(scrlineptr(term->curs.y), + term->curs.x, &term->erase_char); + } + } else + /* Or normal C0 controls. */ + if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) { + switch (c) { + case '\005': /* ENQ: terminal type query */ + /* + * Strictly speaking this is VT100 but a VT100 defaults to + * no response. Other terminals respond at their option. + * + * Don't put a CR in the default string as this tends to + * upset some weird software. + */ + compatibility(ANSIMIN); + if (term->ldisc) { + lpage_send(term->ldisc, DEFAULT_CODEPAGE, + term->answerback, term->answerbacklen, 0); + } + break; + case '\007': /* BEL: Bell */ + { + struct beeptime *newbeep; + unsigned long ticks; + + ticks = GETTICKCOUNT(); + + if (!term->beep_overloaded) { + newbeep = snew(struct beeptime); + newbeep->ticks = ticks; + newbeep->next = NULL; + if (!term->beephead) + term->beephead = newbeep; + else + term->beeptail->next = newbeep; + term->beeptail = newbeep; + term->nbeeps++; + } + + /* + * Throw out any beeps that happened more than + * t seconds ago. + */ + while (term->beephead && + term->beephead->ticks < ticks - term->bellovl_t) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + if (!term->beephead) + term->beeptail = NULL; + term->nbeeps--; + } + + if (term->bellovl && term->beep_overloaded && + ticks - term->lastbeep >= (unsigned)term->bellovl_s) { + /* + * If we're currently overloaded and the + * last beep was more than s seconds ago, + * leave overload mode. + */ + term->beep_overloaded = FALSE; + } else if (term->bellovl && !term->beep_overloaded && + term->nbeeps >= term->bellovl_n) { + /* + * Now, if we have n or more beeps + * remaining in the queue, go into overload + * mode. + */ + term->beep_overloaded = TRUE; + } + term->lastbeep = ticks; + + /* + * Perform an actual beep if we're not overloaded. + */ + if (!term->bellovl || !term->beep_overloaded) { + do_beep(term->frontend, term->beep); + + if (term->beep == BELL_VISUAL) { + term_schedule_vbell(term, FALSE, 0); + } + } + seen_disp_event(term); + } + break; + case '\b': /* BS: Back space */ + if (term->curs.x == 0 && + (term->curs.y == 0 || term->wrap == 0)) + /* do nothing */ ; + else if (term->curs.x == 0 && term->curs.y > 0) + term->curs.x = term->cols - 1, term->curs.y--; + else if (term->wrapnext) + term->wrapnext = FALSE; + else + term->curs.x--; + seen_disp_event(term); + break; + case '\016': /* LS1: Locking-shift one */ + compatibility(VT100); + term->cset = 1; + break; + case '\017': /* LS0: Locking-shift zero */ + compatibility(VT100); + term->cset = 0; + break; + case '\033': /* ESC: Escape */ + if (term->vt52_mode) + term->termstate = VT52_ESC; + else { + compatibility(ANSIMIN); + term->termstate = SEEN_ESC; + term->esc_query = FALSE; + } + break; + case '\015': /* CR: Carriage return */ + term->curs.x = 0; + term->wrapnext = FALSE; + seen_disp_event(term); + + if (term->crhaslf) { + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + } + if (term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); + break; + case '\014': /* FF: Form feed */ + if (has_compat(SCOANSI)) { + move(term, 0, 0, 0); + erase_lots(term, FALSE, FALSE, TRUE); + if (term->scroll_on_disp) + term->disptop = 0; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + } + case '\013': /* VT: Line tabulation */ + compatibility(VT100); + case '\012': /* LF: Line feed */ + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + if (term->lfhascr) + term->curs.x = 0; + term->wrapnext = FALSE; + seen_disp_event(term); + if (term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); + break; + case '\t': /* HT: Character tabulation */ + { + pos old_curs = term->curs; + termline *ldata = scrlineptr(term->curs.y); + + do { + term->curs.x++; + } while (term->curs.x < term->cols - 1 && + !term->tabs[term->curs.x]); + + if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) { + if (term->curs.x >= term->cols / 2) + term->curs.x = term->cols / 2 - 1; + } else { + if (term->curs.x >= term->cols) + term->curs.x = term->cols - 1; + } + + check_selection(term, old_curs, term->curs); + } + seen_disp_event(term); + break; + } + } else + switch (term->termstate) { + case TOPLEVEL: + /* Only graphic characters get this far; + * ctrls are stripped above */ + { + termline *cline = scrlineptr(term->curs.y); + int width = 0; + if (DIRECT_CHAR(c)) + width = 1; + if (!width) + width = (term->cjk_ambig_wide ? + mk_wcwidth_cjk((unsigned int) c) : + mk_wcwidth((unsigned int) c)); + + if (term->wrapnext && term->wrap && width > 0) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = FALSE; + cline = scrlineptr(term->curs.y); + } + if (term->insert && width > 0) + insch(term, width); + if (term->selstate != NO_SELECTION) { + pos cursplus = term->curs; + incpos(cursplus); + check_selection(term, term->curs, cursplus); + } + if (((c & CSET_MASK) == CSET_ASCII || + (c & CSET_MASK) == 0) && + term->logctx) + logtraffic(term->logctx, (unsigned char) c, + LGTYP_ASCII); + + switch (width) { + case 2: + /* + * If we're about to display a double-width + * character starting in the rightmost + * column, then we do something special + * instead. We must print a space in the + * last column of the screen, then wrap; + * and we also set LATTR_WRAPPED2 which + * instructs subsequent cut-and-pasting not + * only to splice this line to the one + * after it, but to ignore the space in the + * last character position as well. + * (Because what was actually output to the + * terminal was presumably just a sequence + * of CJK characters, and we don't want a + * space to be pasted in the middle of + * those just because they had the + * misfortune to start in the wrong parity + * column. xterm concurs.) + */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + if (term->curs.x == term->cols-1) { + copy_termchar(cline, term->curs.x, + &term->erase_char); + cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, + 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + cline = scrlineptr(term->curs.y); + /* Now we must check_boundary again, of course. */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + } + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + + term->curs.x++; + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = UCSWIDE; + cline->chars[term->curs.x].attr = term->curr_attr; + + break; + case 1: + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+1, term->curs.y); + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + + break; + case 0: + if (term->curs.x > 0) { + int x = term->curs.x - 1; + + /* If we're in wrapnext state, the character + * to combine with is _here_, not to our left. */ + if (term->wrapnext) + x++; + + /* + * If the previous character is + * UCSWIDE, back up another one. + */ + if (cline->chars[x].chr == UCSWIDE) { + assert(x > 0); + x--; + } + + add_cc(cline, x, c); + seen_disp_event(term); + } + continue; + default: + continue; + } + term->curs.x++; + if (term->curs.x == term->cols) { + term->curs.x--; + term->wrapnext = TRUE; + if (term->wrap && term->vt52_mode) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = FALSE; + } + } + seen_disp_event(term); + } + break; + + case OSC_MAYBE_ST: + /* + * This state is virtually identical to SEEN_ESC, with the + * exception that we have an OSC sequence in the pipeline, + * and _if_ we see a backslash, we process it. + */ + if (c == '\\') { + do_osc(term); + term->termstate = TOPLEVEL; + break; + } + /* else fall through */ + case SEEN_ESC: + if (c >= ' ' && c <= '/') { + if (term->esc_query) + term->esc_query = -1; + else + term->esc_query = c; + break; + } + term->termstate = TOPLEVEL; + switch (ANSI(c, term->esc_query)) { + case '[': /* enter CSI mode */ + term->termstate = SEEN_CSI; + term->esc_nargs = 1; + term->esc_args[0] = ARG_DEFAULT; + term->esc_query = FALSE; + break; + case ']': /* OSC: xterm escape sequences */ + /* Compatibility is nasty here, xterm, linux, decterm yuk! */ + compatibility(OTHER); + term->termstate = SEEN_OSC; + term->esc_args[0] = 0; + break; + case '7': /* DECSC: save cursor */ + compatibility(VT100); + save_cursor(term, TRUE); + break; + case '8': /* DECRC: restore cursor */ + compatibility(VT100); + save_cursor(term, FALSE); + seen_disp_event(term); + break; + case '=': /* DECKPAM: Keypad application mode */ + compatibility(VT100); + term->app_keypad_keys = TRUE; + break; + case '>': /* DECKPNM: Keypad numeric mode */ + compatibility(VT100); + term->app_keypad_keys = FALSE; + break; + case 'D': /* IND: exactly equivalent to LF */ + compatibility(VT100); + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'E': /* NEL: exactly equivalent to CR-LF */ + compatibility(VT100); + term->curs.x = 0; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'M': /* RI: reverse index - backwards LF */ + compatibility(VT100); + if (term->curs.y == term->marg_t) + scroll(term, term->marg_t, term->marg_b, -1, TRUE); + else if (term->curs.y > 0) + term->curs.y--; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'Z': /* DECID: terminal type query */ + compatibility(VT100); + if (term->ldisc) + ldisc_send(term->ldisc, term->id_string, + strlen(term->id_string), 0); + break; + case 'c': /* RIS: restore power-on settings */ + compatibility(VT100); + power_on(term, TRUE); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + if (term->reset_132) { + if (!term->no_remote_resize) + request_resize(term->frontend, 80, term->rows); + term->reset_132 = 0; + } + if (term->scroll_on_disp) + term->disptop = 0; + seen_disp_event(term); + break; + case 'H': /* HTS: set a tab */ + compatibility(VT100); + term->tabs[term->curs.x] = TRUE; + break; + + case ANSI('8', '#'): /* DECALN: fills screen with Es :-) */ + compatibility(VT100); + { + termline *ldata; + int i, j; + pos scrtop, scrbot; + + for (i = 0; i < term->rows; i++) { + ldata = scrlineptr(i); + check_line_size(term, ldata); + for (j = 0; j < term->cols; j++) { + copy_termchar(ldata, j, + &term->basic_erase_char); + ldata->chars[j].chr = 'E'; + } + ldata->lattr = LATTR_NORM; + } + if (term->scroll_on_disp) + term->disptop = 0; + seen_disp_event(term); + scrtop.x = scrtop.y = 0; + scrbot.x = 0; + scrbot.y = term->rows; + check_selection(term, scrtop, scrbot); + } + break; + + case ANSI('3', '#'): + case ANSI('4', '#'): + case ANSI('5', '#'): + case ANSI('6', '#'): + compatibility(VT100); + { + int nlattr; + termline *ldata; + + switch (ANSI(c, term->esc_query)) { + case ANSI('3', '#'): /* DECDHL: 2*height, top */ + nlattr = LATTR_TOP; + break; + case ANSI('4', '#'): /* DECDHL: 2*height, bottom */ + nlattr = LATTR_BOT; + break; + case ANSI('5', '#'): /* DECSWL: normal */ + nlattr = LATTR_NORM; + break; + default: /* case ANSI('6', '#'): DECDWL: 2*width */ + nlattr = LATTR_WIDE; + break; + } + ldata = scrlineptr(term->curs.y); + check_line_size(term, ldata); + ldata->lattr = nlattr; + } + break; + /* GZD4: G0 designate 94-set */ + case ANSI('A', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_GBCHR; + break; + case ANSI('B', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_ASCII; + break; + case ANSI('0', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_LINEDRW; + break; + case ANSI('U', '('): + compatibility(OTHER); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_SCOACS; + break; + /* G1D4: G1-designate 94-set */ + case ANSI('A', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_GBCHR; + break; + case ANSI('B', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_ASCII; + break; + case ANSI('0', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_LINEDRW; + break; + case ANSI('U', ')'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_SCOACS; + break; + /* DOCS: Designate other coding system */ + case ANSI('8', '%'): /* Old Linux code */ + case ANSI('G', '%'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->utf = 1; + break; + case ANSI('@', '%'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->utf = 0; + break; + } + break; + case SEEN_CSI: + term->termstate = TOPLEVEL; /* default */ + if (isdigit(c)) { + if (term->esc_nargs <= ARGS_MAX) { + if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT) + term->esc_args[term->esc_nargs - 1] = 0; + if (term->esc_args[term->esc_nargs - 1] <= + UINT_MAX / 10 && + term->esc_args[term->esc_nargs - 1] * 10 <= + UINT_MAX - c - '0') + term->esc_args[term->esc_nargs - 1] = + 10 * term->esc_args[term->esc_nargs - 1] + + c - '0'; + else + term->esc_args[term->esc_nargs - 1] = UINT_MAX; + } + term->termstate = SEEN_CSI; + } else if (c == ';') { + if (term->esc_nargs < ARGS_MAX) + term->esc_args[term->esc_nargs++] = ARG_DEFAULT; + term->termstate = SEEN_CSI; + } else if (c < '@') { + if (term->esc_query) + term->esc_query = -1; + else if (c == '?') + term->esc_query = TRUE; + else + term->esc_query = c; + term->termstate = SEEN_CSI; + } else +#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg)) + switch (ANSI(c, term->esc_query)) { + case 'A': /* CUU: move up N lines */ + CLAMP(term->esc_args[0], term->rows); + move(term, term->curs.x, + term->curs.y - def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case 'e': /* VPR: move down N lines */ + compatibility(ANSI); + /* FALLTHROUGH */ + case 'B': /* CUD: Cursor down */ + CLAMP(term->esc_args[0], term->rows); + move(term, term->curs.x, + term->curs.y + def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case ANSI('c', '>'): /* DA: report xterm version */ + compatibility(OTHER); + /* this reports xterm version 136 so that VIM can + use the drag messages from the mouse reporting */ + if (term->ldisc) + ldisc_send(term->ldisc, "\033[>0;136;0c", 11, 0); + break; + case 'a': /* HPR: move right N cols */ + compatibility(ANSI); + /* FALLTHROUGH */ + case 'C': /* CUF: Cursor right */ + CLAMP(term->esc_args[0], term->cols); + move(term, term->curs.x + def(term->esc_args[0], 1), + term->curs.y, 1); + seen_disp_event(term); + break; + case 'D': /* CUB: move left N cols */ + CLAMP(term->esc_args[0], term->cols); + move(term, term->curs.x - def(term->esc_args[0], 1), + term->curs.y, 1); + seen_disp_event(term); + break; + case 'E': /* CNL: move down N lines and CR */ + compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); + move(term, 0, + term->curs.y + def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case 'F': /* CPL: move up N lines and CR */ + compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); + move(term, 0, + term->curs.y - def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case 'G': /* CHA */ + case '`': /* HPA: set horizontal posn */ + compatibility(ANSI); + CLAMP(term->esc_args[0], term->cols); + move(term, def(term->esc_args[0], 1) - 1, + term->curs.y, 0); + seen_disp_event(term); + break; + case 'd': /* VPA: set vertical posn */ + compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); + move(term, term->curs.x, + ((term->dec_om ? term->marg_t : 0) + + def(term->esc_args[0], 1) - 1), + (term->dec_om ? 2 : 0)); + seen_disp_event(term); + break; + case 'H': /* CUP */ + case 'f': /* HVP: set horz and vert posns at once */ + if (term->esc_nargs < 2) + term->esc_args[1] = ARG_DEFAULT; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->cols); + move(term, def(term->esc_args[1], 1) - 1, + ((term->dec_om ? term->marg_t : 0) + + def(term->esc_args[0], 1) - 1), + (term->dec_om ? 2 : 0)); + seen_disp_event(term); + break; + case 'J': /* ED: erase screen or parts of it */ + { + unsigned int i = def(term->esc_args[0], 0); + if (i == 3) { + /* Erase Saved Lines (xterm) + * This follows Thomas Dickey's xterm. */ + term_clrsb(term); + } else { + i++; + if (i > 3) + i = 0; + erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); + } + } + if (term->scroll_on_disp) + term->disptop = 0; + seen_disp_event(term); + break; + case 'K': /* EL: erase line or parts of it */ + { + unsigned int i = def(term->esc_args[0], 0) + 1; + if (i > 3) + i = 0; + erase_lots(term, TRUE, !!(i & 2), !!(i & 1)); + } + seen_disp_event(term); + break; + case 'L': /* IL: insert lines */ + compatibility(VT102); + CLAMP(term->esc_args[0], term->rows); + if (term->curs.y <= term->marg_b) + scroll(term, term->curs.y, term->marg_b, + -def(term->esc_args[0], 1), FALSE); + seen_disp_event(term); + break; + case 'M': /* DL: delete lines */ + compatibility(VT102); + CLAMP(term->esc_args[0], term->rows); + if (term->curs.y <= term->marg_b) + scroll(term, term->curs.y, term->marg_b, + def(term->esc_args[0], 1), + TRUE); + seen_disp_event(term); + break; + case '@': /* ICH: insert chars */ + /* XXX VTTEST says this is vt220, vt510 manual says vt102 */ + compatibility(VT102); + CLAMP(term->esc_args[0], term->cols); + insch(term, def(term->esc_args[0], 1)); + seen_disp_event(term); + break; + case 'P': /* DCH: delete chars */ + compatibility(VT102); + CLAMP(term->esc_args[0], term->cols); + insch(term, -def(term->esc_args[0], 1)); + seen_disp_event(term); + break; + case 'c': /* DA: terminal type query */ + compatibility(VT100); + /* This is the response for a VT102 */ + if (term->ldisc) + ldisc_send(term->ldisc, term->id_string, + strlen(term->id_string), 0); + break; + case 'n': /* DSR: cursor position query */ + if (term->ldisc) { + if (term->esc_args[0] == 6) { + char buf[32]; + sprintf(buf, "\033[%d;%dR", term->curs.y + 1, + term->curs.x + 1); + ldisc_send(term->ldisc, buf, strlen(buf), 0); + } else if (term->esc_args[0] == 5) { + ldisc_send(term->ldisc, "\033[0n", 4, 0); + } + } + break; + case 'h': /* SM: toggle modes to high */ + case ANSI_QUE('h'): + compatibility(VT100); + { + int i; + for (i = 0; i < term->esc_nargs; i++) + toggle_mode(term, term->esc_args[i], + term->esc_query, TRUE); + } + break; + case 'i': /* MC: Media copy */ + case ANSI_QUE('i'): + compatibility(VT100); + { + char *printer; + if (term->esc_nargs != 1) break; + if (term->esc_args[0] == 5 && + (printer = conf_get_str(term->conf, + CONF_printer))[0]) { + term->printing = TRUE; + term->only_printing = !term->esc_query; + term->print_state = 0; + term_print_setup(term, printer); + } else if (term->esc_args[0] == 4 && + term->printing) { + term_print_finish(term); + } + } + break; + case 'l': /* RM: toggle modes to low */ + case ANSI_QUE('l'): + compatibility(VT100); + { + int i; + for (i = 0; i < term->esc_nargs; i++) + toggle_mode(term, term->esc_args[i], + term->esc_query, FALSE); + } + break; + case 'g': /* TBC: clear tabs */ + compatibility(VT100); + if (term->esc_nargs == 1) { + if (term->esc_args[0] == 0) { + term->tabs[term->curs.x] = FALSE; + } else if (term->esc_args[0] == 3) { + int i; + for (i = 0; i < term->cols; i++) + term->tabs[i] = FALSE; + } + } + break; + case 'r': /* DECSTBM: set scroll margins */ + compatibility(VT100); + if (term->esc_nargs <= 2) { + int top, bot; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->rows); + top = def(term->esc_args[0], 1) - 1; + bot = (term->esc_nargs <= 1 + || term->esc_args[1] == 0 ? + term->rows : + def(term->esc_args[1], term->rows)) - 1; + if (bot >= term->rows) + bot = term->rows - 1; + /* VTTEST Bug 9 - if region is less than 2 lines + * don't change region. + */ + if (bot - top > 0) { + term->marg_t = top; + term->marg_b = bot; + term->curs.x = 0; + /* + * I used to think the cursor should be + * placed at the top of the newly marginned + * area. Apparently not: VMS TPU falls over + * if so. + * + * Well actually it should for + * Origin mode - RDB + */ + term->curs.y = (term->dec_om ? + term->marg_t : 0); + seen_disp_event(term); + } + } + break; + case 'm': /* SGR: set graphics rendition */ + { + /* + * A VT100 without the AVO only had one + * attribute, either underline or + * reverse video depending on the + * cursor type, this was selected by + * CSI 7m. + * + * case 2: + * This is sometimes DIM, eg on the + * GIGI and Linux + * case 8: + * This is sometimes INVIS various ANSI. + * case 21: + * This like 22 disables BOLD, DIM and INVIS + * + * The ANSI colours appear on any + * terminal that has colour (obviously) + * but the interaction between sgr0 and + * the colours varies but is usually + * related to the background colour + * erase item. The interaction between + * colour attributes and the mono ones + * is also very implementation + * dependent. + * + * The 39 and 49 attributes are likely + * to be unimplemented. + */ + int i; + for (i = 0; i < term->esc_nargs; i++) { + switch (def(term->esc_args[i], 0)) { + case 0: /* restore defaults */ + term->curr_attr = term->default_attr; + break; + case 1: /* enable bold */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_BOLD; + break; + case 21: /* (enable double underline) */ + compatibility(OTHER); + case 4: /* enable underline */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_UNDER; + break; + case 5: /* enable blink */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_BLINK; + break; + case 6: /* SCO light bkgrd */ + compatibility(SCOANSI); + term->blink_is_real = FALSE; + term->curr_attr |= ATTR_BLINK; + term_schedule_tblink(term); + break; + case 7: /* enable reverse video */ + term->curr_attr |= ATTR_REVERSE; + break; + case 10: /* SCO acs off */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 0; break; + case 11: /* SCO acs on */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 1; break; + case 12: /* SCO acs on, |0x80 */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 2; break; + case 22: /* disable bold */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_BOLD; + break; + case 24: /* disable underline */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_UNDER; + break; + case 25: /* disable blink */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_BLINK; + break; + case 27: /* disable reverse video */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_REVERSE; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + /* foreground */ + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + (term->esc_args[i] - 30)<curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + ((term->esc_args[i] - 90 + 8) + << ATTR_FGSHIFT); + break; + case 39: /* default-foreground */ + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= ATTR_DEFFG; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + /* background */ + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + (term->esc_args[i] - 40)<curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + ((term->esc_args[i] - 100 + 8) + << ATTR_BGSHIFT); + break; + case 49: /* default-background */ + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= ATTR_DEFBG; + break; + case 38: /* xterm 256-colour mode */ + if (i+2 < term->esc_nargs && + term->esc_args[i+1] == 5) { + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + ((term->esc_args[i+2] & 0xFF) + << ATTR_FGSHIFT); + i += 2; + } + break; + case 48: /* xterm 256-colour mode */ + if (i+2 < term->esc_nargs && + term->esc_args[i+1] == 5) { + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + ((term->esc_args[i+2] & 0xFF) + << ATTR_BGSHIFT); + i += 2; + } + break; + } + } + set_erase_char(term); + } + break; + case 's': /* save cursor */ + save_cursor(term, TRUE); + break; + case 'u': /* restore cursor */ + save_cursor(term, FALSE); + seen_disp_event(term); + break; + case 't': /* DECSLPP: set page size - ie window height */ + /* + * VT340/VT420 sequence DECSLPP, DEC only allows values + * 24/25/36/48/72/144 other emulators (eg dtterm) use + * illegal values (eg first arg 1..9) for window changing + * and reports. + */ + if (term->esc_nargs <= 1 + && (term->esc_args[0] < 1 || + term->esc_args[0] >= 24)) { + compatibility(VT340TEXT); + if (!term->no_remote_resize) + request_resize(term->frontend, term->cols, + def(term->esc_args[0], 24)); + deselect(term); + } else if (term->esc_nargs >= 1 && + term->esc_args[0] >= 1 && + term->esc_args[0] < 24) { + compatibility(OTHER); + + switch (term->esc_args[0]) { + int x, y, len; + char buf[80], *p; + case 1: + set_iconic(term->frontend, FALSE); + break; + case 2: + set_iconic(term->frontend, TRUE); + break; + case 3: + if (term->esc_nargs >= 3) { + if (!term->no_remote_resize) + move_window(term->frontend, + def(term->esc_args[1], 0), + def(term->esc_args[2], 0)); + } + break; + case 4: + /* We should resize the window to a given + * size in pixels here, but currently our + * resizing code isn't healthy enough to + * manage it. */ + break; + case 5: + /* move to top */ + set_zorder(term->frontend, TRUE); + break; + case 6: + /* move to bottom */ + set_zorder(term->frontend, FALSE); + break; + case 7: + refresh_window(term->frontend); + break; + case 8: + if (term->esc_nargs >= 3) { + if (!term->no_remote_resize) + request_resize(term->frontend, + def(term->esc_args[2], term->conf_width), + def(term->esc_args[1], term->conf_height)); + } + break; + case 9: + if (term->esc_nargs >= 2) + set_zoomed(term->frontend, + term->esc_args[1] ? + TRUE : FALSE); + break; + case 11: + if (term->ldisc) + ldisc_send(term->ldisc, + is_iconic(term->frontend) ? + "\033[2t" : "\033[1t", 4, 0); + break; + case 13: + if (term->ldisc) { + get_window_pos(term->frontend, &x, &y); + len = sprintf(buf, "\033[3;%u;%ut", + (unsigned)x, + (unsigned)y); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 14: + if (term->ldisc) { + get_window_pixels(term->frontend, &x, &y); + len = sprintf(buf, "\033[4;%d;%dt", y, x); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 18: + if (term->ldisc) { + len = sprintf(buf, "\033[8;%d;%dt", + term->rows, term->cols); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 19: + /* + * Hmmm. Strictly speaking we + * should return `the size of the + * screen in characters', but + * that's not easy: (a) window + * furniture being what it is it's + * hard to compute, and (b) in + * resize-font mode maximising the + * window wouldn't change the + * number of characters. *shrug*. I + * think we'll ignore it for the + * moment and see if anyone + * complains, and then ask them + * what they would like it to do. + */ + break; + case 20: + if (term->ldisc && + term->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, TRUE); + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]L", 3, 0); + ldisc_send(term->ldisc, p, len, 0); + ldisc_send(term->ldisc, "\033\\", 2, 0); + } + break; + case 21: + if (term->ldisc && + term->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, FALSE); + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]l", 3, 0); + ldisc_send(term->ldisc, p, len, 0); + ldisc_send(term->ldisc, "\033\\", 2, 0); + } + break; + } + } + break; + case 'S': /* SU: Scroll up */ + CLAMP(term->esc_args[0], term->rows); + compatibility(SCOANSI); + scroll(term, term->marg_t, term->marg_b, + def(term->esc_args[0], 1), TRUE); + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'T': /* SD: Scroll down */ + CLAMP(term->esc_args[0], term->rows); + compatibility(SCOANSI); + scroll(term, term->marg_t, term->marg_b, + -def(term->esc_args[0], 1), TRUE); + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case ANSI('|', '*'): /* DECSNLS */ + /* + * Set number of lines on screen + * VT420 uses VGA like hardware and can + * support any size in reasonable range + * (24..49 AIUI) with no default specified. + */ + compatibility(VT420); + if (term->esc_nargs == 1 && term->esc_args[0] > 0) { + if (!term->no_remote_resize) + request_resize(term->frontend, term->cols, + def(term->esc_args[0], + term->conf_height)); + deselect(term); + } + break; + case ANSI('|', '$'): /* DECSCPP */ + /* + * Set number of columns per page + * Docs imply range is only 80 or 132, but + * I'll allow any. + */ + compatibility(VT340TEXT); + if (term->esc_nargs <= 1) { + if (!term->no_remote_resize) + request_resize(term->frontend, + def(term->esc_args[0], + term->conf_width), + term->rows); + deselect(term); + } + break; + case 'X': /* ECH: write N spaces w/o moving cursor */ + /* XXX VTTEST says this is vt220, vt510 manual + * says vt100 */ + compatibility(ANSIMIN); + CLAMP(term->esc_args[0], term->cols); + { + int n = def(term->esc_args[0], 1); + pos cursplus; + int p = term->curs.x; + termline *cline = scrlineptr(term->curs.y); + + if (n > term->cols - term->curs.x) + n = term->cols - term->curs.x; + cursplus = term->curs; + cursplus.x += n; + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+n, term->curs.y); + check_selection(term, term->curs, cursplus); + while (n--) + copy_termchar(cline, p++, + &term->erase_char); + seen_disp_event(term); + } + break; + case 'x': /* DECREQTPARM: report terminal characteristics */ + compatibility(VT100); + if (term->ldisc) { + char buf[32]; + int i = def(term->esc_args[0], 0); + if (i == 0 || i == 1) { + strcpy(buf, "\033[2;1;1;112;112;1;0x"); + buf[2] += i; + ldisc_send(term->ldisc, buf, 20, 0); + } + } + break; + case 'Z': /* CBT */ + compatibility(OTHER); + CLAMP(term->esc_args[0], term->cols); + { + int i = def(term->esc_args[0], 1); + pos old_curs = term->curs; + + for(;i>0 && term->curs.x>0; i--) { + do { + term->curs.x--; + } while (term->curs.x >0 && + !term->tabs[term->curs.x]); + } + check_selection(term, old_curs, term->curs); + } + break; + case ANSI('c', '='): /* Hide or Show Cursor */ + compatibility(SCOANSI); + switch(term->esc_args[0]) { + case 0: /* hide cursor */ + term->cursor_on = FALSE; + break; + case 1: /* restore cursor */ + term->big_cursor = FALSE; + term->cursor_on = TRUE; + break; + case 2: /* block cursor */ + term->big_cursor = TRUE; + term->cursor_on = TRUE; + break; + } + break; + case ANSI('C', '='): + /* + * set cursor start on scanline esc_args[0] and + * end on scanline esc_args[1].If you set + * the bottom scan line to a value less than + * the top scan line, the cursor will disappear. + */ + compatibility(SCOANSI); + if (term->esc_nargs >= 2) { + if (term->esc_args[0] > term->esc_args[1]) + term->cursor_on = FALSE; + else + term->cursor_on = TRUE; + } + break; + case ANSI('D', '='): + compatibility(SCOANSI); + term->blink_is_real = FALSE; + term_schedule_tblink(term); + if (term->esc_args[0]>=1) + term->curr_attr |= ATTR_BLINK; + else + term->curr_attr &= ~ATTR_BLINK; + break; + case ANSI('E', '='): + compatibility(SCOANSI); + term->blink_is_real = (term->esc_args[0] >= 1); + term_schedule_tblink(term); + break; + case ANSI('F', '='): /* set normal foreground */ + compatibility(SCOANSI); + if (term->esc_args[0] < 16) { + long colour = + (sco2ansicolour[term->esc_args[0] & 0x7] | + (term->esc_args[0] & 0x8)) << + ATTR_FGSHIFT; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= colour; + term->default_attr &= ~ATTR_FGMASK; + term->default_attr |= colour; + set_erase_char(term); + } + break; + case ANSI('G', '='): /* set normal background */ + compatibility(SCOANSI); + if (term->esc_args[0] < 16) { + long colour = + (sco2ansicolour[term->esc_args[0] & 0x7] | + (term->esc_args[0] & 0x8)) << + ATTR_BGSHIFT; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= colour; + term->default_attr &= ~ATTR_BGMASK; + term->default_attr |= colour; + set_erase_char(term); + } + break; + case ANSI('L', '='): + compatibility(SCOANSI); + term->use_bce = (term->esc_args[0] <= 0); + set_erase_char(term); + break; + case ANSI('p', '"'): /* DECSCL: set compat level */ + /* + * Allow the host to make this emulator a + * 'perfect' VT102. This first appeared in + * the VT220, but we do need to get back to + * PuTTY mode so I won't check it. + * + * The arg in 40..42,50 are a PuTTY extension. + * The 2nd arg, 8bit vs 7bit is not checked. + * + * Setting VT102 mode should also change + * the Fkeys to generate PF* codes as a + * real VT102 has no Fkeys. The VT220 does + * this, F11..F13 become ESC,BS,LF other + * Fkeys send nothing. + * + * Note ESC c will NOT change this! + */ + + switch (term->esc_args[0]) { + case 61: + term->compatibility_level &= ~TM_VTXXX; + term->compatibility_level |= TM_VT102; + break; + case 62: + term->compatibility_level &= ~TM_VTXXX; + term->compatibility_level |= TM_VT220; + break; + + default: + if (term->esc_args[0] > 60 && + term->esc_args[0] < 70) + term->compatibility_level |= TM_VTXXX; + break; + + case 40: + term->compatibility_level &= TM_VTXXX; + break; + case 41: + term->compatibility_level = TM_PUTTY; + break; + case 42: + term->compatibility_level = TM_SCOANSI; + break; + + case ARG_DEFAULT: + term->compatibility_level = TM_PUTTY; + break; + case 50: + break; + } + + /* Change the response to CSI c */ + if (term->esc_args[0] == 50) { + int i; + char lbuf[64]; + strcpy(term->id_string, "\033[?"); + for (i = 1; i < term->esc_nargs; i++) { + if (i != 1) + strcat(term->id_string, ";"); + sprintf(lbuf, "%d", term->esc_args[i]); + strcat(term->id_string, lbuf); + } + strcat(term->id_string, "c"); + } +#if 0 + /* Is this a good idea ? + * Well we should do a soft reset at this point ... + */ + if (!has_compat(VT420) && has_compat(VT100)) { + if (!term->no_remote_resize) { + if (term->reset_132) + request_resize(132, 24); + else + request_resize(80, 24); + } + } +#endif + break; + } + break; + case SEEN_OSC: + term->osc_w = FALSE; + switch (c) { + case 'P': /* Linux palette sequence */ + term->termstate = SEEN_OSC_P; + term->osc_strlen = 0; + break; + case 'R': /* Linux palette reset */ + palette_reset(term->frontend); + term_invalidate(term); + term->termstate = TOPLEVEL; + break; + case 'W': /* word-set */ + term->termstate = SEEN_OSC_W; + term->osc_w = TRUE; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (term->esc_args[0] <= UINT_MAX / 10 && + term->esc_args[0] * 10 <= UINT_MAX - c - '0') + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + else + term->esc_args[0] = UINT_MAX; + break; + case 'L': + /* + * Grotty hack to support xterm and DECterm title + * sequences concurrently. + */ + if (term->esc_args[0] == 2) { + term->esc_args[0] = 1; + break; + } + /* else fall through */ + default: + term->termstate = OSC_STRING; + term->osc_strlen = 0; + } + break; + case OSC_STRING: + /* + * This OSC stuff is EVIL. It takes just one character to get into + * sysline mode and it's not initially obvious how to get out. + * So I've added CR and LF as string aborts. + * This shouldn't effect compatibility as I believe embedded + * control characters are supposed to be interpreted (maybe?) + * and they don't display anything useful anyway. + * + * -- RDB + */ + if (c == '\012' || c == '\015') { + term->termstate = TOPLEVEL; + } else if (c == 0234 || c == '\007') { + /* + * These characters terminate the string; ST and BEL + * terminate the sequence and trigger instant + * processing of it, whereas ESC goes back to SEEN_ESC + * mode unless it is followed by \, in which case it is + * synonymous with ST in the first place. + */ + do_osc(term); + term->termstate = TOPLEVEL; + } else if (c == '\033') + term->termstate = OSC_MAYBE_ST; + else if (term->osc_strlen < OSC_STR_MAX) + term->osc_string[term->osc_strlen++] = (char)c; + break; + case SEEN_OSC_P: + { + int max = (term->osc_strlen == 0 ? 21 : 15); + int val; + if ((int)c >= '0' && (int)c <= '9') + val = c - '0'; + else if ((int)c >= 'A' && (int)c <= 'A' + max - 10) + val = c - 'A' + 10; + else if ((int)c >= 'a' && (int)c <= 'a' + max - 10) + val = c - 'a' + 10; + else { + term->termstate = TOPLEVEL; + break; + } + term->osc_string[term->osc_strlen++] = val; + if (term->osc_strlen >= 7) { + palette_set(term->frontend, term->osc_string[0], + term->osc_string[1] * 16 + term->osc_string[2], + term->osc_string[3] * 16 + term->osc_string[4], + term->osc_string[5] * 16 + term->osc_string[6]); + term_invalidate(term); + term->termstate = TOPLEVEL; + } + } + break; + case SEEN_OSC_W: + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (term->esc_args[0] <= UINT_MAX / 10 && + term->esc_args[0] * 10 <= UINT_MAX - c - '0') + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + else + term->esc_args[0] = UINT_MAX; + break; + default: + term->termstate = OSC_STRING; + term->osc_strlen = 0; + } + break; + case VT52_ESC: + term->termstate = TOPLEVEL; + seen_disp_event(term); + switch (c) { + case 'A': + move(term, term->curs.x, term->curs.y - 1, 1); + break; + case 'B': + move(term, term->curs.x, term->curs.y + 1, 1); + break; + case 'C': + move(term, term->curs.x + 1, term->curs.y, 1); + break; + case 'D': + move(term, term->curs.x - 1, term->curs.y, 1); + break; + /* + * From the VT100 Manual + * NOTE: The special graphics characters in the VT100 + * are different from those in the VT52 + * + * From VT102 manual: + * 137 _ Blank - Same + * 140 ` Reserved - Humm. + * 141 a Solid rectangle - Similar + * 142 b 1/ - Top half of fraction for the + * 143 c 3/ - subscript numbers below. + * 144 d 5/ + * 145 e 7/ + * 146 f Degrees - Same + * 147 g Plus or minus - Same + * 150 h Right arrow + * 151 i Ellipsis (dots) + * 152 j Divide by + * 153 k Down arrow + * 154 l Bar at scan 0 + * 155 m Bar at scan 1 + * 156 n Bar at scan 2 + * 157 o Bar at scan 3 - Similar + * 160 p Bar at scan 4 - Similar + * 161 q Bar at scan 5 - Similar + * 162 r Bar at scan 6 - Same + * 163 s Bar at scan 7 - Similar + * 164 t Subscript 0 + * 165 u Subscript 1 + * 166 v Subscript 2 + * 167 w Subscript 3 + * 170 x Subscript 4 + * 171 y Subscript 5 + * 172 z Subscript 6 + * 173 { Subscript 7 + * 174 | Subscript 8 + * 175 } Subscript 9 + * 176 ~ Paragraph + * + */ + case 'F': + term->cset_attr[term->cset = 0] = CSET_LINEDRW; + break; + case 'G': + term->cset_attr[term->cset = 0] = CSET_ASCII; + break; + case 'H': + move(term, 0, 0, 0); + break; + case 'I': + if (term->curs.y == 0) + scroll(term, 0, term->rows - 1, -1, TRUE); + else if (term->curs.y > 0) + term->curs.y--; + term->wrapnext = FALSE; + break; + case 'J': + erase_lots(term, FALSE, FALSE, TRUE); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 'K': + erase_lots(term, TRUE, FALSE, TRUE); + break; +#if 0 + case 'V': + /* XXX Print cursor line */ + break; + case 'W': + /* XXX Start controller mode */ + break; + case 'X': + /* XXX Stop controller mode */ + break; +#endif + case 'Y': + term->termstate = VT52_Y1; + break; + case 'Z': + if (term->ldisc) + ldisc_send(term->ldisc, "\033/Z", 3, 0); + break; + case '=': + term->app_keypad_keys = TRUE; + break; + case '>': + term->app_keypad_keys = FALSE; + break; + case '<': + /* XXX This should switch to VT100 mode not current or default + * VT mode. But this will only have effect in a VT220+ + * emulation. + */ + term->vt52_mode = FALSE; + term->blink_is_real = term->blinktext; + term_schedule_tblink(term); + break; +#if 0 + case '^': + /* XXX Enter auto print mode */ + break; + case '_': + /* XXX Exit auto print mode */ + break; + case ']': + /* XXX Print screen */ + break; +#endif + +#ifdef VT52_PLUS + case 'E': + /* compatibility(ATARI) */ + move(term, 0, 0, 0); + erase_lots(term, FALSE, FALSE, TRUE); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 'L': + /* compatibility(ATARI) */ + if (term->curs.y <= term->marg_b) + scroll(term, term->curs.y, term->marg_b, -1, FALSE); + break; + case 'M': + /* compatibility(ATARI) */ + if (term->curs.y <= term->marg_b) + scroll(term, term->curs.y, term->marg_b, 1, TRUE); + break; + case 'b': + /* compatibility(ATARI) */ + term->termstate = VT52_FG; + break; + case 'c': + /* compatibility(ATARI) */ + term->termstate = VT52_BG; + break; + case 'd': + /* compatibility(ATARI) */ + erase_lots(term, FALSE, TRUE, FALSE); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 'e': + /* compatibility(ATARI) */ + term->cursor_on = TRUE; + break; + case 'f': + /* compatibility(ATARI) */ + term->cursor_on = FALSE; + break; + /* case 'j': Save cursor position - broken on ST */ + /* case 'k': Restore cursor position */ + case 'l': + /* compatibility(ATARI) */ + erase_lots(term, TRUE, TRUE, TRUE); + term->curs.x = 0; + term->wrapnext = FALSE; + break; + case 'o': + /* compatibility(ATARI) */ + erase_lots(term, TRUE, TRUE, FALSE); + break; + case 'p': + /* compatibility(ATARI) */ + term->curr_attr |= ATTR_REVERSE; + break; + case 'q': + /* compatibility(ATARI) */ + term->curr_attr &= ~ATTR_REVERSE; + break; + case 'v': /* wrap Autowrap on - Wyse style */ + /* compatibility(ATARI) */ + term->wrap = 1; + break; + case 'w': /* Autowrap off */ + /* compatibility(ATARI) */ + term->wrap = 0; + break; + + case 'R': + /* compatibility(OTHER) */ + term->vt52_bold = FALSE; + term->curr_attr = ATTR_DEFAULT; + set_erase_char(term); + break; + case 'S': + /* compatibility(VI50) */ + term->curr_attr |= ATTR_UNDER; + break; + case 'W': + /* compatibility(VI50) */ + term->curr_attr &= ~ATTR_UNDER; + break; + case 'U': + /* compatibility(VI50) */ + term->vt52_bold = TRUE; + term->curr_attr |= ATTR_BOLD; + break; + case 'T': + /* compatibility(VI50) */ + term->vt52_bold = FALSE; + term->curr_attr &= ~ATTR_BOLD; + break; +#endif + } + break; + case VT52_Y1: + term->termstate = VT52_Y2; + move(term, term->curs.x, c - ' ', 0); + break; + case VT52_Y2: + term->termstate = TOPLEVEL; + move(term, c - ' ', term->curs.y, 0); + break; + +#ifdef VT52_PLUS + case VT52_FG: + term->termstate = TOPLEVEL; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr &= ~ATTR_BOLD; + term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT; + set_erase_char(term); + break; + case VT52_BG: + term->termstate = TOPLEVEL; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr &= ~ATTR_BLINK; + term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT; + set_erase_char(term); + break; +#endif + default: break; /* placate gcc warning about enum use */ + } + if (term->selstate != NO_SELECTION) { + pos cursplus = term->curs; + incpos(cursplus); + check_selection(term, term->curs, cursplus); + } + } + + term_print_flush(term); + if (term->logflush) + logflush(term->logctx); +} + +/* + * To prevent having to run the reasonably tricky bidi algorithm + * too many times, we maintain a cache of the last lineful of data + * fed to the algorithm on each line of the display. + */ +static int term_bidi_cache_hit(Terminal *term, int line, + termchar *lbefore, int width) +{ + int i; + + if (!term->pre_bidi_cache) + return FALSE; /* cache doesn't even exist yet! */ + + if (line >= term->bidi_cache_size) + return FALSE; /* cache doesn't have this many lines */ + + if (!term->pre_bidi_cache[line].chars) + return FALSE; /* cache doesn't contain _this_ line */ + + if (term->pre_bidi_cache[line].width != width) + return FALSE; /* line is wrong width */ + + for (i = 0; i < width; i++) + if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i)) + return FALSE; /* line doesn't match cache */ + + return TRUE; /* it didn't match. */ +} + +static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore, + termchar *lafter, bidi_char *wcTo, + int width, int size) +{ + int i; + + if (!term->pre_bidi_cache || term->bidi_cache_size <= line) { + int j = term->bidi_cache_size; + term->bidi_cache_size = line+1; + term->pre_bidi_cache = sresize(term->pre_bidi_cache, + term->bidi_cache_size, + struct bidi_cache_entry); + term->post_bidi_cache = sresize(term->post_bidi_cache, + term->bidi_cache_size, + struct bidi_cache_entry); + while (j < term->bidi_cache_size) { + term->pre_bidi_cache[j].chars = + term->post_bidi_cache[j].chars = NULL; + term->pre_bidi_cache[j].width = + term->post_bidi_cache[j].width = -1; + term->pre_bidi_cache[j].forward = + term->post_bidi_cache[j].forward = NULL; + term->pre_bidi_cache[j].backward = + term->post_bidi_cache[j].backward = NULL; + j++; + } + } + + sfree(term->pre_bidi_cache[line].chars); + sfree(term->post_bidi_cache[line].chars); + sfree(term->post_bidi_cache[line].forward); + sfree(term->post_bidi_cache[line].backward); + + term->pre_bidi_cache[line].width = width; + term->pre_bidi_cache[line].chars = snewn(size, termchar); + term->post_bidi_cache[line].width = width; + term->post_bidi_cache[line].chars = snewn(size, termchar); + term->post_bidi_cache[line].forward = snewn(width, int); + term->post_bidi_cache[line].backward = snewn(width, int); + + memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE); + memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE); + memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int)); + memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int)); + + for (i = 0; i < width; i++) { + int p = wcTo[i].index; + + assert(0 <= p && p < width); + + term->post_bidi_cache[line].backward[i] = p; + term->post_bidi_cache[line].forward[p] = i; + } +} + +/* + * Prepare the bidi information for a screen line. Returns the + * transformed list of termchars, or NULL if no transformation at + * all took place (because bidi is disabled). If return was + * non-NULL, auxiliary information such as the forward and reverse + * mappings of permutation position are available in + * term->post_bidi_cache[scr_y].*. + */ +static termchar *term_bidi_line(Terminal *term, struct termline *ldata, + int scr_y) +{ + termchar *lchars; + int it; + + /* Do Arabic shaping and bidi. */ + if(!term->bidi || !term->arabicshaping) { + + if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) { + + if (term->wcFromTo_size < term->cols) { + term->wcFromTo_size = term->cols; + term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size, + bidi_char); + term->wcTo = sresize(term->wcTo, term->wcFromTo_size, + bidi_char); + } + + for(it=0; itcols ; it++) + { + unsigned long uc = (ldata->chars[it].chr); + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->rawcnp) { + uc = term->ucsdata->unitab_xterm[uc & 0xFF]; + break; + } + case CSET_ASCII: + uc = term->ucsdata->unitab_line[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = term->ucsdata->unitab_scoacs[uc&0xFF]; + break; + } + switch (uc & CSET_MASK) { + case CSET_ACP: + uc = term->ucsdata->unitab_font[uc & 0xFF]; + break; + case CSET_OEMCP: + uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; + break; + } + + term->wcFrom[it].origwc = term->wcFrom[it].wc = + (unsigned int)uc; + term->wcFrom[it].index = it; + } + + if(!term->bidi) + do_bidi(term->wcFrom, term->cols); + + /* this is saved iff done from inside the shaping */ + if(!term->bidi && term->arabicshaping) + for(it=0; itcols; it++) + term->wcTo[it] = term->wcFrom[it]; + + if(!term->arabicshaping) + do_shape(term->wcFrom, term->wcTo, term->cols); + + if (term->ltemp_size < ldata->size) { + term->ltemp_size = ldata->size; + term->ltemp = sresize(term->ltemp, term->ltemp_size, + termchar); + } + + memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE); + + for(it=0; itcols ; it++) + { + term->ltemp[it] = ldata->chars[term->wcTo[it].index]; + if (term->ltemp[it].cc_next) + term->ltemp[it].cc_next -= + it - term->wcTo[it].index; + + if (term->wcTo[it].origwc != term->wcTo[it].wc) + term->ltemp[it].chr = term->wcTo[it].wc; + } + term_bidi_cache_store(term, scr_y, ldata->chars, + term->ltemp, term->wcTo, + term->cols, ldata->size); + + lchars = term->ltemp; + } else { + lchars = term->post_bidi_cache[scr_y].chars; + } + } else { + lchars = NULL; + } + + return lchars; +} + +/* + * Given a context, update the window. Out of paranoia, we don't + * allow WM_PAINT responses to do scrolling optimisations. + */ +static void do_paint(Terminal *term, Context ctx, int may_optimise) +{ + int i, j, our_curs_y, our_curs_x; + int rv, cursor; + pos scrpos; + wchar_t *ch; + int chlen; +#ifdef OPTIMISE_SCROLL + struct scrollregion *sr; +#endif /* OPTIMISE_SCROLL */ + termchar *newline; + + chlen = 1024; + ch = snewn(chlen, wchar_t); + + newline = snewn(term->cols, termchar); + + rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0); + + /* Depends on: + * screen array, disptop, scrtop, + * selection, rv, + * blinkpc, blink_is_real, tblinker, + * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext + */ + + /* Has the cursor position or type changed ? */ + if (term->cursor_on) { + if (term->has_focus) { + if (term->cblinker || !term->blink_cur) + cursor = TATTR_ACTCURS; + else + cursor = 0; + } else + cursor = TATTR_PASCURS; + if (term->wrapnext) + cursor |= TATTR_RIGHTCURS; + } else + cursor = 0; + our_curs_y = term->curs.y - term->disptop; + { + /* + * Adjust the cursor position: + * - for bidi + * - in the case where it's resting on the right-hand half + * of a CJK wide character. xterm's behaviour here, + * which seems adequate to me, is to display the cursor + * covering the _whole_ character, exactly as if it were + * one space to the left. + */ + termline *ldata = lineptr(term->curs.y); + termchar *lchars; + + our_curs_x = term->curs.x; + + if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) { + our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x]; + } else + lchars = ldata->chars; + + if (our_curs_x > 0 && + lchars[our_curs_x].chr == UCSWIDE) + our_curs_x--; + + unlineptr(ldata); + } + + /* + * If the cursor is not where it was last time we painted, and + * its previous position is visible on screen, invalidate its + * previous position. + */ + if (term->dispcursy >= 0 && + (term->curstype != cursor || + term->dispcursy != our_curs_y || + term->dispcursx != our_curs_x)) { + termchar *dispcurs = term->disptext[term->dispcursy]->chars + + term->dispcursx; + + if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE) + dispcurs[-1].attr |= ATTR_INVALID; + if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE) + dispcurs[1].attr |= ATTR_INVALID; + dispcurs->attr |= ATTR_INVALID; + + term->curstype = 0; + } + term->dispcursx = term->dispcursy = -1; + +#ifdef OPTIMISE_SCROLL + /* Do scrolls */ + sr = term->scrollhead; + while (sr) { + struct scrollregion *next = sr->next; + do_scroll(ctx, sr->topline, sr->botline, sr->lines); + sfree(sr); + sr = next; + } + term->scrollhead = term->scrolltail = NULL; +#endif /* OPTIMISE_SCROLL */ + + /* The normal screen data */ + for (i = 0; i < term->rows; i++) { + termline *ldata; + termchar *lchars; + int dirty_line, dirty_run, selected; + unsigned long attr = 0, cset = 0; + int start = 0; + int ccount = 0; + int last_run_dirty = 0; + int laststart, dirtyrect; + int *backward; + + scrpos.y = i + term->disptop; + ldata = lineptr(scrpos.y); + + /* Do Arabic shaping and bidi. */ + lchars = term_bidi_line(term, ldata, i); + if (lchars) { + backward = term->post_bidi_cache[i].backward; + } else { + lchars = ldata->chars; + backward = NULL; + } + + /* + * First loop: work along the line deciding what we want + * each character cell to look like. + */ + for (j = 0; j < term->cols; j++) { + unsigned long tattr, tchar; + termchar *d = lchars + j; + scrpos.x = backward ? backward[j] : j; + + tchar = d->chr; + tattr = d->attr; + + if (!term->ansi_colour) + tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | + ATTR_DEFFG | ATTR_DEFBG; + + if (!term->xterm_256_colour) { + int colour; + colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG; + colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG; + } + + switch (tchar & CSET_MASK) { + case CSET_ASCII: + tchar = term->ucsdata->unitab_line[tchar & 0xFF]; + break; + case CSET_LINEDRW: + tchar = term->ucsdata->unitab_xterm[tchar & 0xFF]; + break; + case CSET_SCOACS: + tchar = term->ucsdata->unitab_scoacs[tchar&0xFF]; + break; + } + if (j < term->cols-1 && d[1].chr == UCSWIDE) + tattr |= ATTR_WIDE; + + /* Video reversing things */ + if (term->selstate == DRAGGING || term->selstate == SELECTED) { + if (term->seltype == LEXICOGRAPHIC) + selected = (posle(term->selstart, scrpos) && + poslt(scrpos, term->selend)); + else + selected = (posPle(term->selstart, scrpos) && + posPlt(scrpos, term->selend)); + } else + selected = FALSE; + tattr = (tattr ^ rv + ^ (selected ? ATTR_REVERSE : 0)); + + /* 'Real' blinking ? */ + if (term->blink_is_real && (tattr & ATTR_BLINK)) { + if (term->has_focus && term->tblinker) { + tchar = term->ucsdata->unitab_line[(unsigned char)' ']; + } + tattr &= ~ATTR_BLINK; + } + + /* + * Check the font we'll _probably_ be using to see if + * the character is wide when we don't want it to be. + */ + if (tchar != term->disptext[i]->chars[j].chr || + tattr != (term->disptext[i]->chars[j].attr &~ + (ATTR_NARROW | DATTR_MASK))) { + if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2) + tattr |= ATTR_NARROW; + } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW) + tattr |= ATTR_NARROW; + + if (i == our_curs_y && j == our_curs_x) { + tattr |= cursor; + term->curstype = cursor; + term->dispcursx = j; + term->dispcursy = i; + } + + /* FULL-TERMCHAR */ + newline[j].attr = tattr; + newline[j].chr = tchar; + /* Combining characters are still read from lchars */ + newline[j].cc_next = 0; + } + + /* + * Now loop over the line again, noting where things have + * changed. + * + * During this loop, we keep track of where we last saw + * DATTR_STARTRUN. Any mismatch automatically invalidates + * _all_ of the containing run that was last printed: that + * is, any rectangle that was drawn in one go in the + * previous update should be either left completely alone + * or overwritten in its entirety. This, along with the + * expectation that front ends clip all text runs to their + * bounding rectangle, should solve any possible problems + * with fonts that overflow their character cells. + */ + laststart = 0; + dirtyrect = FALSE; + for (j = 0; j < term->cols; j++) { + if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) { + laststart = j; + dirtyrect = FALSE; + } + + if (term->disptext[i]->chars[j].chr != newline[j].chr || + (term->disptext[i]->chars[j].attr &~ DATTR_MASK) + != newline[j].attr) { + int k; + + if (!dirtyrect) { + for (k = laststart; k < j; k++) + term->disptext[i]->chars[k].attr |= ATTR_INVALID; + + dirtyrect = TRUE; + } + } + + if (dirtyrect) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + + /* + * Finally, loop once more and actually do the drawing. + */ + dirty_run = dirty_line = (ldata->lattr != + term->disptext[i]->lattr); + term->disptext[i]->lattr = ldata->lattr; + + for (j = 0; j < term->cols; j++) { + unsigned long tattr, tchar; + int break_run, do_copy; + termchar *d = lchars + j; + + tattr = newline[j].attr; + tchar = newline[j].chr; + + if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE) + dirty_line = TRUE; + + break_run = ((tattr ^ attr) & term->attr_mask) != 0; + +#ifdef USES_VTLINE_HACK + /* Special hack for VT100 Linedraw glyphs */ + if ((tchar >= 0x23BA && tchar <= 0x23BD) || + (j > 0 && (newline[j-1].chr >= 0x23BA && + newline[j-1].chr <= 0x23BD))) + break_run = TRUE; +#endif + + /* + * Separate out sequences of characters that have the + * same CSET, if that CSET is a magic one. + */ + if (CSET_OF(tchar) != cset) + break_run = TRUE; + + /* + * Break on both sides of any combined-character cell. + */ + if (d->cc_next != 0 || + (j > 0 && d[-1].cc_next != 0)) + break_run = TRUE; + + if (!term->ucsdata->dbcs_screenfont && !dirty_line) { + if (term->disptext[i]->chars[j].chr == tchar && + (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr) + break_run = TRUE; + else if (!dirty_run && ccount == 1) + break_run = TRUE; + } + + if (break_run) { + if ((dirty_run || last_run_dirty) && ccount > 0) { + do_text(ctx, start, i, ch, ccount, attr, + ldata->lattr); + if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) + do_cursor(ctx, start, i, ch, ccount, attr, + ldata->lattr); + } + start = j; + ccount = 0; + attr = tattr; + cset = CSET_OF(tchar); + if (term->ucsdata->dbcs_screenfont) + last_run_dirty = dirty_run; + dirty_run = dirty_line; + } + + do_copy = FALSE; + if (!termchars_equal_override(&term->disptext[i]->chars[j], + d, tchar, tattr)) { + do_copy = TRUE; + dirty_run = TRUE; + } + + if (ccount+2 > chlen) { + chlen = ccount + 256; + ch = sresize(ch, chlen, wchar_t); + } + +#ifdef PLATFORM_IS_UTF16 + if (tchar > 0x10000 && tchar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar); + } else +#endif /* PLATFORM_IS_UTF16 */ + ch[ccount++] = (wchar_t) tchar; + + if (d->cc_next) { + termchar *dd = d; + + while (dd->cc_next) { + unsigned long schar; + + dd += dd->cc_next; + + schar = dd->chr; + switch (schar & CSET_MASK) { + case CSET_ASCII: + schar = term->ucsdata->unitab_line[schar & 0xFF]; + break; + case CSET_LINEDRW: + schar = term->ucsdata->unitab_xterm[schar & 0xFF]; + break; + case CSET_SCOACS: + schar = term->ucsdata->unitab_scoacs[schar&0xFF]; + break; + } + + if (ccount+2 > chlen) { + chlen = ccount + 256; + ch = sresize(ch, chlen, wchar_t); + } + +#ifdef PLATFORM_IS_UTF16 + if (schar > 0x10000 && schar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar); + } else +#endif /* PLATFORM_IS_UTF16 */ + ch[ccount++] = (wchar_t) schar; + } + + attr |= TATTR_COMBINING; + } + + if (do_copy) { + copy_termchar(term->disptext[i], j, d); + term->disptext[i]->chars[j].chr = tchar; + term->disptext[i]->chars[j].attr = tattr; + if (start == j) + term->disptext[i]->chars[j].attr |= DATTR_STARTRUN; + } + + /* If it's a wide char step along to the next one. */ + if (tattr & ATTR_WIDE) { + if (++j < term->cols) { + d++; + /* + * By construction above, the cursor should not + * be on the right-hand half of this character. + * Ever. + */ + assert(!(i == our_curs_y && j == our_curs_x)); + if (!termchars_equal(&term->disptext[i]->chars[j], d)) + dirty_run = TRUE; + copy_termchar(term->disptext[i], j, d); + } + } + } + if (dirty_run && ccount > 0) { + do_text(ctx, start, i, ch, ccount, attr, + ldata->lattr); + if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) + do_cursor(ctx, start, i, ch, ccount, attr, + ldata->lattr); + } + + unlineptr(ldata); + } + + sfree(newline); + sfree(ch); +} + +/* + * Invalidate the whole screen so it will be repainted in full. + */ +void term_invalidate(Terminal *term) +{ + int i, j; + + for (i = 0; i < term->rows; i++) + for (j = 0; j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + + term_schedule_update(term); +} + +/* + * Paint the window in response to a WM_PAINT message. + */ +void term_paint(Terminal *term, Context ctx, + int left, int top, int right, int bottom, int immediately) +{ + int i, j; + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= term->cols) right = term->cols-1; + if (bottom >= term->rows) bottom = term->rows-1; + + for (i = top; i <= bottom && i < term->rows; i++) { + if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM) + for (j = left; j <= right && j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + else + for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + + if (immediately) { + do_paint (term, ctx, FALSE); + } else { + term_schedule_update(term); + } +} + +/* + * Attempt to scroll the scrollback. The second parameter gives the + * position we want to scroll to; the first is +1 to denote that + * this position is relative to the beginning of the scrollback, -1 + * to denote it is relative to the end, and 0 to denote that it is + * relative to the current position. + */ +void term_scroll(Terminal *term, int rel, int where) +{ + int sbtop = -sblines(term); +#ifdef OPTIMISE_SCROLL + int olddisptop = term->disptop; + int shift; +#endif /* OPTIMISE_SCROLL */ + + term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where; + if (term->disptop < sbtop) + term->disptop = sbtop; + if (term->disptop > 0) + term->disptop = 0; + update_sbar(term); +#ifdef OPTIMISE_SCROLL + shift = (term->disptop - olddisptop); + if (shift < term->rows && shift > -term->rows) + scroll_display(term, 0, term->rows - 1, shift); +#endif /* OPTIMISE_SCROLL */ + term_update(term); +} + +/* + * Scroll the scrollback to centre it on the beginning or end of the + * current selection, if any. + */ +void term_scroll_to_selection(Terminal *term, int which_end) +{ + pos target; + int y; + int sbtop = -sblines(term); + + if (term->selstate != SELECTED) + return; + if (which_end) + target = term->selend; + else + target = term->selstart; + + y = target.y - term->rows/2; + if (y < sbtop) + y = sbtop; + else if (y > 0) + y = 0; + term_scroll(term, -1, y); +} + +/* + * Helper routine for clipme(): growing buffer. + */ +typedef struct { + int buflen; /* amount of allocated space in textbuf/attrbuf */ + int bufpos; /* amount of actual data */ + wchar_t *textbuf; /* buffer for copied text */ + wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */ + int *attrbuf; /* buffer for copied attributes */ + int *attrptr; /* = attrbuf + bufpos */ +} clip_workbuf; + +static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) +{ + if (b->bufpos >= b->buflen) { + b->buflen += 128; + b->textbuf = sresize(b->textbuf, b->buflen, wchar_t); + b->textptr = b->textbuf + b->bufpos; + b->attrbuf = sresize(b->attrbuf, b->buflen, int); + b->attrptr = b->attrbuf + b->bufpos; + } + *b->textptr++ = chr; + *b->attrptr++ = attr; + b->bufpos++; +} + +static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) +{ + clip_workbuf buf; + int old_top_x; + int attr; + + buf.buflen = 5120; + buf.bufpos = 0; + buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t); + buf.attrptr = buf.attrbuf = snewn(buf.buflen, int); + + old_top_x = top.x; /* needed for rect==1 */ + + while (poslt(top, bottom)) { + int nl = FALSE; + termline *ldata = lineptr(top.y); + pos nlpos; + + /* + * nlpos will point at the maximum position on this line we + * should copy up to. So we start it at the end of the + * line... + */ + nlpos.y = top.y; + nlpos.x = term->cols; + + /* + * ... move it backwards if there's unused space at the end + * of the line (and also set `nl' if this is the case, + * because in normal selection mode this means we need a + * newline at the end)... + */ + if (!(ldata->lattr & LATTR_WRAPPED)) { + while (nlpos.x && + IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) && + !ldata->chars[nlpos.x - 1].cc_next && + poslt(top, nlpos)) + decpos(nlpos); + if (poslt(nlpos, bottom)) + nl = TRUE; + } else if (ldata->lattr & LATTR_WRAPPED2) { + /* Ignore the last char on the line in a WRAPPED2 line. */ + decpos(nlpos); + } + + /* + * ... and then clip it to the terminal x coordinate if + * we're doing rectangular selection. (In this case we + * still did the above, so that copying e.g. the right-hand + * column from a table doesn't fill with spaces on the + * right.) + */ + if (rect) { + if (nlpos.x > bottom.x) + nlpos.x = bottom.x; + nl = (top.y < bottom.y); + } + + while (poslt(top, bottom) && poslt(top, nlpos)) { +#if 0 + char cbuf[16], *p; + sprintf(cbuf, "", (ldata[top.x] & 0xFFFF)); +#else + wchar_t cbuf[16], *p; + int c; + int x = top.x; + + if (ldata->chars[x].chr == UCSWIDE) { + top.x++; + continue; + } + + while (1) { + int uc = ldata->chars[x].chr; + attr = ldata->chars[x].attr; + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->rawcnp) { + uc = term->ucsdata->unitab_xterm[uc & 0xFF]; + break; + } + case CSET_ASCII: + uc = term->ucsdata->unitab_line[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = term->ucsdata->unitab_scoacs[uc&0xFF]; + break; + } + switch (uc & CSET_MASK) { + case CSET_ACP: + uc = term->ucsdata->unitab_font[uc & 0xFF]; + break; + case CSET_OEMCP: + uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; + break; + } + + c = (uc & ~CSET_MASK); +#ifdef PLATFORM_IS_UTF16 + if (uc > 0x10000 && uc < 0x110000) { + cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10); + cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF); + cbuf[2] = 0; + } else +#endif + { + cbuf[0] = uc; + cbuf[1] = 0; + } + + if (DIRECT_FONT(uc)) { + if (c >= ' ' && c != 0x7F) { + char buf[4]; + WCHAR wbuf[4]; + int rv; + if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) { + buf[0] = c; + buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr); + rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4); + top.x++; + } else { + buf[0] = c; + rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4); + } + + if (rv > 0) { + memcpy(cbuf, wbuf, rv * sizeof(wchar_t)); + cbuf[rv] = 0; + } + } + } +#endif + + for (p = cbuf; *p; p++) + clip_addchar(&buf, *p, attr); + + if (ldata->chars[x].cc_next) + x += ldata->chars[x].cc_next; + else + break; + } + top.x++; + } + if (nl) { + int i; + for (i = 0; i < sel_nl_sz; i++) + clip_addchar(&buf, sel_nl[i], 0); + } + top.y++; + top.x = rect ? old_top_x : 0; + + unlineptr(ldata); + } +#if SELECTION_NUL_TERMINATED + clip_addchar(&buf, 0, 0); +#endif + /* Finally, transfer all that to the clipboard. */ + write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel); + sfree(buf.textbuf); + sfree(buf.attrbuf); +} + +void term_copyall(Terminal *term) +{ + pos top; + pos bottom; + tree234 *screen = term->screen; + top.y = -sblines(term); + top.x = 0; + bottom.y = find_last_nonempty_line(term, screen); + bottom.x = term->cols; + clipme(term, top, bottom, 0, TRUE); +} + +/* + * The wordness array is mainly for deciding the disposition of the + * US-ASCII characters. + */ +static int wordtype(Terminal *term, int uc) +{ + struct ucsword { + int start, end, ctype; + }; + static const struct ucsword ucs_words[] = { + { + 128, 160, 0}, { + 161, 191, 1}, { + 215, 215, 1}, { + 247, 247, 1}, { + 0x037e, 0x037e, 1}, /* Greek question mark */ + { + 0x0387, 0x0387, 1}, /* Greek ano teleia */ + { + 0x055a, 0x055f, 1}, /* Armenian punctuation */ + { + 0x0589, 0x0589, 1}, /* Armenian full stop */ + { + 0x0700, 0x070d, 1}, /* Syriac punctuation */ + { + 0x104a, 0x104f, 1}, /* Myanmar punctuation */ + { + 0x10fb, 0x10fb, 1}, /* Georgian punctuation */ + { + 0x1361, 0x1368, 1}, /* Ethiopic punctuation */ + { + 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */ + { + 0x17d4, 0x17dc, 1}, /* Khmer punctuation */ + { + 0x1800, 0x180a, 1}, /* Mongolian punctuation */ + { + 0x2000, 0x200a, 0}, /* Various spaces */ + { + 0x2070, 0x207f, 2}, /* superscript */ + { + 0x2080, 0x208f, 2}, /* subscript */ + { + 0x200b, 0x27ff, 1}, /* punctuation and symbols */ + { + 0x3000, 0x3000, 0}, /* ideographic space */ + { + 0x3001, 0x3020, 1}, /* ideographic punctuation */ + { + 0x303f, 0x309f, 3}, /* Hiragana */ + { + 0x30a0, 0x30ff, 3}, /* Katakana */ + { + 0x3300, 0x9fff, 3}, /* CJK Ideographs */ + { + 0xac00, 0xd7a3, 3}, /* Hangul Syllables */ + { + 0xf900, 0xfaff, 3}, /* CJK Ideographs */ + { + 0xfe30, 0xfe6b, 1}, /* punctuation forms */ + { + 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */ + { + 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */ + { + 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */ + { + 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */ + { + 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */ + { + 0, 0, 0} + }; + const struct ucsword *wptr; + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + uc = term->ucsdata->unitab_xterm[uc & 0xFF]; + break; + case CSET_ASCII: + uc = term->ucsdata->unitab_line[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = term->ucsdata->unitab_scoacs[uc&0xFF]; + break; + } + switch (uc & CSET_MASK) { + case CSET_ACP: + uc = term->ucsdata->unitab_font[uc & 0xFF]; + break; + case CSET_OEMCP: + uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; + break; + } + + /* For DBCS fonts I can't do anything useful. Even this will sometimes + * fail as there's such a thing as a double width space. :-( + */ + if (term->ucsdata->dbcs_screenfont && + term->ucsdata->font_codepage == term->ucsdata->line_codepage) + return (uc != ' '); + + if (uc < 0x80) + return term->wordness[uc]; + + for (wptr = ucs_words; wptr->start; wptr++) { + if (uc >= wptr->start && uc <= wptr->end) + return wptr->ctype; + } + + return 2; +} + +/* + * Spread the selection outwards according to the selection mode. + */ +static pos sel_spread_half(Terminal *term, pos p, int dir) +{ + termline *ldata; + short wvalue; + int topy = -sblines(term); + + ldata = lineptr(p.y); + + switch (term->selmode) { + case SM_CHAR: + /* + * In this mode, every character is a separate unit, except + * for runs of spaces at the end of a non-wrapping line. + */ + if (!(ldata->lattr & LATTR_WRAPPED)) { + termchar *q = ldata->chars + term->cols; + while (q > ldata->chars && + IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next) + q--; + if (q == ldata->chars + term->cols) + q--; + if (p.x >= q - ldata->chars) + p.x = (dir == -1 ? q - ldata->chars : term->cols - 1); + } + break; + case SM_WORD: + /* + * In this mode, the units are maximal runs of characters + * whose `wordness' has the same value. + */ + wvalue = wordtype(term, UCSGET(ldata->chars, p.x)); + if (dir == +1) { + while (1) { + int maxcols = (ldata->lattr & LATTR_WRAPPED2 ? + term->cols-1 : term->cols); + if (p.x < maxcols-1) { + if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue) + p.x++; + else + break; + } else { + if (p.y+1 < term->rows && + (ldata->lattr & LATTR_WRAPPED)) { + termline *ldata2; + ldata2 = lineptr(p.y+1); + if (wordtype(term, UCSGET(ldata2->chars, 0)) + == wvalue) { + p.x = 0; + p.y++; + unlineptr(ldata); + ldata = ldata2; + } else { + unlineptr(ldata2); + break; + } + } else + break; + } + } + } else { + while (1) { + if (p.x > 0) { + if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue) + p.x--; + else + break; + } else { + termline *ldata2; + int maxcols; + if (p.y <= topy) + break; + ldata2 = lineptr(p.y-1); + maxcols = (ldata2->lattr & LATTR_WRAPPED2 ? + term->cols-1 : term->cols); + if (ldata2->lattr & LATTR_WRAPPED) { + if (wordtype(term, UCSGET(ldata2->chars, maxcols-1)) + == wvalue) { + p.x = maxcols-1; + p.y--; + unlineptr(ldata); + ldata = ldata2; + } else { + unlineptr(ldata2); + break; + } + } else + break; + } + } + } + break; + case SM_LINE: + /* + * In this mode, every line is a unit. + */ + p.x = (dir == -1 ? 0 : term->cols - 1); + break; + } + + unlineptr(ldata); + return p; +} + +static void sel_spread(Terminal *term) +{ + if (term->seltype == LEXICOGRAPHIC) { + term->selstart = sel_spread_half(term, term->selstart, -1); + decpos(term->selend); + term->selend = sel_spread_half(term, term->selend, +1); + incpos(term->selend); + } +} + +static void term_paste_callback(void *vterm) +{ + Terminal *term = (Terminal *)vterm; + + if (term->paste_len == 0) + return; + + while (term->paste_pos < term->paste_len) { + int n = 0; + while (n + term->paste_pos < term->paste_len) { + if (term->paste_buffer[term->paste_pos + n++] == '\015') + break; + } + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0); + term->paste_pos += n; + + if (term->paste_pos < term->paste_len) { + queue_toplevel_callback(term_paste_callback, term); + return; + } + } + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} + +void term_do_paste(Terminal *term) +{ + wchar_t *data; + int len; + + get_clip(term->frontend, &data, &len); + if (data && len > 0) { + wchar_t *p, *q; + + term_seen_key_event(term); /* pasted data counts */ + + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_pos = term->paste_len = 0; + term->paste_buffer = snewn(len + 12, wchar_t); + + if (term->bracketed_paste) { + memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } + + p = q = data; + while (p < data + len) { + while (p < data + len && + !(p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl)))) + p++; + + { + int i; + for (i = 0; i < p - q; i++) { + term->paste_buffer[term->paste_len++] = q[i]; + } + } + + if (p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl))) { + term->paste_buffer[term->paste_len++] = '\015'; + p += sel_nl_sz; + } + q = p; + } + + if (term->bracketed_paste) { + memcpy(term->paste_buffer + term->paste_len, + L"\033[201~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } + + /* Assume a small paste will be OK in one go. */ + if (term->paste_len < 256) { + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_buffer = 0; + term->paste_pos = term->paste_len = 0; + } + } + get_clip(term->frontend, NULL, NULL); + + queue_toplevel_callback(term_paste_callback, term); +} + +void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, + Mouse_Action a, int x, int y, int shift, int ctrl, int alt) +{ + pos selpoint; + termline *ldata; + int raw_mouse = (term->xterm_mouse && + !term->no_mouse_rep && + !(term->mouse_override && shift)); + int default_seltype; + + if (y < 0) { + y = 0; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, -1); + } + if (y >= term->rows) { + y = term->rows - 1; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, +1); + } + if (x < 0) { + if (y > 0) { + x = term->cols - 1; + y--; + } else + x = 0; + } + if (x >= term->cols) + x = term->cols - 1; + + selpoint.y = y + term->disptop; + ldata = lineptr(selpoint.y); + + if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) + x /= 2; + + /* + * Transform x through the bidi algorithm to find the _logical_ + * click point from the physical one. + */ + if (term_bidi_line(term, ldata, y) != NULL) { + x = term->post_bidi_cache[y].backward[x]; + } + + selpoint.x = x; + unlineptr(ldata); + + /* + * If we're in the middle of a selection operation, we ignore raw + * mouse mode until it's done (we must have been not in raw mouse + * mode when it started). + * This makes use of Shift for selection reliable, and avoids the + * host seeing mouse releases for which they never saw corresponding + * presses. + */ + if (raw_mouse && + (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { + int encstate = 0, r, c, wheel; + char abuf[32]; + int len = 0; + + if (term->ldisc) { + + switch (braw) { + case MBT_LEFT: + encstate = 0x00; /* left button down */ + wheel = FALSE; + break; + case MBT_MIDDLE: + encstate = 0x01; + wheel = FALSE; + break; + case MBT_RIGHT: + encstate = 0x02; + wheel = FALSE; + break; + case MBT_WHEEL_UP: + encstate = 0x40; + wheel = TRUE; + break; + case MBT_WHEEL_DOWN: + encstate = 0x41; + wheel = TRUE; + break; + default: + return; + } + if (wheel) { + /* For mouse wheel buttons, we only ever expect to see + * MA_CLICK actions, and we don't try to keep track of + * the buttons being 'pressed' (since without matching + * click/release pairs that's pointless). */ + if (a != MA_CLICK) + return; + } else switch (a) { + case MA_DRAG: + if (term->xterm_mouse == 1) + return; + encstate += 0x20; + break; + case MA_RELEASE: + /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */ + if (!term->xterm_extended_mouse) + encstate = 0x03; + term->mouse_is_down = 0; + break; + case MA_CLICK: + if (term->mouse_is_down == braw) + return; + term->mouse_is_down = braw; + break; + default: + return; + } + if (shift) + encstate += 0x04; + if (ctrl) + encstate += 0x10; + r = y + 1; + c = x + 1; + + /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */ + if (term->xterm_extended_mouse) { + len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M'); + } else if (term->urxvt_extended_mouse) { + len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r); + } else if (c <= 223 && r <= 223) { + len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); + } + ldisc_send(term->ldisc, abuf, len, 0); + } + return; + } + + /* + * Set the selection type (rectangular or normal) at the start + * of a selection attempt, from the state of Alt. + */ + if (!alt ^ !term->rect_select) + default_seltype = RECTANGULAR; + else + default_seltype = LEXICOGRAPHIC; + + if (term->selstate == NO_SELECTION) { + term->seltype = default_seltype; + } + + if (bcooked == MBT_SELECT && a == MA_CLICK) { + deselect(term); + term->selstate = ABOUT_TO; + term->seltype = default_seltype; + term->selanchor = selpoint; + term->selmode = SM_CHAR; + } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) { + deselect(term); + term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE); + term->selstate = DRAGGING; + term->selstart = term->selanchor = selpoint; + term->selend = term->selstart; + incpos(term->selend); + sel_spread(term); + } else if ((bcooked == MBT_SELECT && a == MA_DRAG) || + (bcooked == MBT_EXTEND && a != MA_RELEASE)) { + if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) + return; + if (bcooked == MBT_EXTEND && a != MA_DRAG && + term->selstate == SELECTED) { + if (term->seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we extend by moving + * whichever end of the current selection is closer + * to the mouse. + */ + if (posdiff(selpoint, term->selstart) < + posdiff(term->selend, term->selstart) / 2) { + term->selanchor = term->selend; + decpos(term->selanchor); + } else { + term->selanchor = term->selstart; + } + } else { + /* + * For rectangular selection, we have a choice of + * _four_ places to put selanchor and selpoint: the + * four corners of the selection. + */ + if (2*selpoint.x < term->selstart.x + term->selend.x) + term->selanchor.x = term->selend.x-1; + else + term->selanchor.x = term->selstart.x; + + if (2*selpoint.y < term->selstart.y + term->selend.y) + term->selanchor.y = term->selend.y; + else + term->selanchor.y = term->selstart.y; + } + term->selstate = DRAGGING; + } + if (term->selstate != ABOUT_TO && term->selstate != DRAGGING) + term->selanchor = selpoint; + term->selstate = DRAGGING; + if (term->seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we set (selstart,selend) to + * (selpoint,selanchor) in some order. + */ + if (poslt(selpoint, term->selanchor)) { + term->selstart = selpoint; + term->selend = term->selanchor; + incpos(term->selend); + } else { + term->selstart = term->selanchor; + term->selend = selpoint; + incpos(term->selend); + } + } else { + /* + * For rectangular selection, we may need to + * interchange x and y coordinates (if the user has + * dragged in the -x and +y directions, or vice versa). + */ + term->selstart.x = min(term->selanchor.x, selpoint.x); + term->selend.x = 1+max(term->selanchor.x, selpoint.x); + term->selstart.y = min(term->selanchor.y, selpoint.y); + term->selend.y = max(term->selanchor.y, selpoint.y); + } + sel_spread(term); + } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) && + a == MA_RELEASE) { + if (term->selstate == DRAGGING) { + /* + * We've completed a selection. We now transfer the + * data to the clipboard. + */ + clipme(term, term->selstart, term->selend, + (term->seltype == RECTANGULAR), FALSE); + term->selstate = SELECTED; + } else + term->selstate = NO_SELECTION; + } else if (bcooked == MBT_PASTE + && (a == MA_CLICK +#if MULTICLICK_ONLY_EVENT + || a == MA_2CLK || a == MA_3CLK +#endif + )) { + request_paste(term->frontend); + } + + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); + term_update(term); +} + +int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl) +{ + char *p = buf; + + if (term->vt52_mode) + p += sprintf((char *) p, "\x1B%c", xkey); + else { + int app_flg = (term->app_cursor_keys && !term->no_applic_c); +#if 0 + /* + * RDB: VT100 & VT102 manuals both state the app cursor + * keys only work if the app keypad is on. + * + * SGT: That may well be true, but xterm disagrees and so + * does at least one application, so I've #if'ed this out + * and the behaviour is back to PuTTY's original: app + * cursor and app keypad are independently switchable + * modes. If anyone complains about _this_ I'll have to + * put in a configurable option. + */ + if (!term->app_keypad_keys) + app_flg = 0; +#endif + /* Useful mapping of Ctrl-arrows */ + if (ctrl) + app_flg = !app_flg; + + if (app_flg) + p += sprintf((char *) p, "\x1BO%c", xkey); + else + p += sprintf((char *) p, "\x1B[%c", xkey); + } + + return p - buf; +} + +void term_nopaste(Terminal *term) +{ + if (term->paste_len == 0) + return; + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} + +static void deselect(Terminal *term) +{ + term->selstate = NO_SELECTION; + term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0; +} + +void term_deselect(Terminal *term) +{ + deselect(term); + term_update(term); + + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); +} + +int term_ldisc(Terminal *term, int option) +{ + if (option == LD_ECHO) + return term->term_echoing; + if (option == LD_EDIT) + return term->term_editing; + return FALSE; +} + +int term_data(Terminal *term, int is_stderr, const char *data, int len) +{ + bufchain_add(&term->inbuf, data, len); + + if (!term->in_term_out) { + term->in_term_out = TRUE; + term_reset_cblink(term); + /* + * During drag-selects, we do not process terminal input, + * because the user will want the screen to hold still to + * be selected. + */ + if (term->selstate != DRAGGING) + term_out(term); + term->in_term_out = FALSE; + } + + /* + * term_out() always completely empties inbuf. Therefore, + * there's no reason at all to return anything other than zero + * from this function, because there _can't_ be a question of + * the remote side needing to wait until term_out() has cleared + * a backlog. + * + * This is a slightly suboptimal way to deal with SSH-2 - in + * principle, the window mechanism would allow us to continue + * to accept data on forwarded ports and X connections even + * while the terminal processing was going slowly - but we + * can't do the 100% right thing without moving the terminal + * processing into a separate thread, and that might hurt + * portability. So we manage stdout buffering the old SSH-1 way: + * if the terminal processing goes slowly, the whole SSH + * connection stops accepting data until it's ready. + * + * In practice, I can't imagine this causing serious trouble. + */ + return 0; +} + +/* + * Write untrusted data to the terminal. + * The only control character that should be honoured is \n (which + * will behave as a CRLF). + */ +int term_data_untrusted(Terminal *term, const char *data, int len) +{ + int i; + /* FIXME: more sophisticated checking? */ + for (i = 0; i < len; i++) { + if (data[i] == '\n') + term_data(term, 1, "\r\n", 2); + else if (data[i] & 0x60) + term_data(term, 1, data + i, 1); + } + return 0; /* assumes that term_data() always returns 0 */ +} + +void term_provide_logctx(Terminal *term, void *logctx) +{ + term->logctx = logctx; +} + +void term_set_focus(Terminal *term, int has_focus) +{ + term->has_focus = has_focus; + term_schedule_cblink(term); +} + +/* + * Provide "auto" settings for remote tty modes, suitable for an + * application with a terminal window. + */ +char *term_get_ttymode(Terminal *term, const char *mode) +{ + char *val = NULL; + if (strcmp(mode, "ERASE") == 0) { + val = term->bksp_is_delete ? "^?" : "^H"; + } + /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ + /* FIXME: or ECHO and friends based on local echo state? */ + return dupstr(val); +} + +struct term_userpass_state { + size_t curr_prompt; + int done_prompt; /* printed out prompt yet? */ + size_t pos; /* cursor position */ +}; + +/* + * Process some terminal data in the course of username/password + * input. + */ +int term_get_userpass_input(Terminal *term, prompts_t *p, + unsigned char *in, int inlen) +{ + struct term_userpass_state *s = (struct term_userpass_state *)p->data; + if (!s) { + /* + * First call. Set some stuff up. + */ + p->data = s = snew(struct term_userpass_state); + s->curr_prompt = 0; + s->done_prompt = 0; + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + size_t l = strlen(p->name); + term_data_untrusted(term, p->name, l); + if (p->name[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + term_data_untrusted(term, p->instruction, l); + if (p->instruction[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < (int)p->n_prompts; i++) + prompt_set_result(p->prompts[i], ""); + } + } + + while (s->curr_prompt < p->n_prompts) { + + prompt_t *pr = p->prompts[s->curr_prompt]; + int finished_prompt = 0; + + if (!s->done_prompt) { + term_data_untrusted(term, pr->prompt, strlen(pr->prompt)); + s->done_prompt = 1; + s->pos = 0; + } + + /* Breaking out here ensures that the prompt is printed even + * if we're now waiting for user data. */ + if (!in || !inlen) break; + + /* FIXME: should we be using local-line-editing code instead? */ + while (!finished_prompt && inlen) { + char c = *in++; + inlen--; + switch (c) { + case 10: + case 13: + term_data(term, 0, "\r\n", 2); + prompt_ensure_result_size(pr, s->pos + 1); + pr->result[s->pos] = '\0'; + /* go to next prompt, if any */ + s->curr_prompt++; + s->done_prompt = 0; + finished_prompt = 1; /* break out */ + break; + case 8: + case 127: + if (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 21: + case 27: + while (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 3: + case 4: + /* Immediate abort. */ + term_data(term, 0, "\r\n", 2); + sfree(s); + p->data = NULL; + return 0; /* user abort */ + default: + /* + * This simplistic check for printability is disabled + * when we're doing password input, because some people + * have control characters in their passwords. + */ + if (!pr->echo || (c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) { + prompt_ensure_result_size(pr, s->pos + 1); + pr->result[s->pos++] = c; + if (pr->echo) + term_data(term, 0, &c, 1); + } + break; + } + } + + } + + if (s->curr_prompt < p->n_prompts) { + return -1; /* more data required */ + } else { + sfree(s); + p->data = NULL; + return +1; /* all done */ + } +} diff --git a/netbox/libs/Putty/terminal.h b/netbox/libs/Putty/terminal.h new file mode 100644 index 000000000..01d5f57a2 --- /dev/null +++ b/netbox/libs/Putty/terminal.h @@ -0,0 +1,329 @@ +/* + * Internals of the Terminal structure, for those other modules + * which need to look inside it. It would be nice if this could be + * folded back into terminal.c in future, with an abstraction layer + * to handle everything that other modules need to know about it; + * but for the moment, this will do. + */ + +#ifndef PUTTY_TERMINAL_H +#define PUTTY_TERMINAL_H + +#include "tree234.h" + +struct beeptime { + struct beeptime *next; + unsigned long ticks; +}; + +typedef struct { + int y, x; +} pos; + +#ifdef OPTIMISE_SCROLL +struct scrollregion { + struct scrollregion *next; + int topline; /* Top line of scroll region. */ + int botline; /* Bottom line of scroll region. */ + int lines; /* Number of lines to scroll by - +ve is forwards. */ +}; +#endif /* OPTIMISE_SCROLL */ + +typedef struct termchar termchar; +typedef struct termline termline; + +struct termchar { + /* + * Any code in terminal.c which definitely needs to be changed + * when extra fields are added here is labelled with a comment + * saying FULL-TERMCHAR. + */ + unsigned long chr; + unsigned long attr; + + /* + * The cc_next field is used to link multiple termchars + * together into a list, so as to fit more than one character + * into a character cell (Unicode combining characters). + * + * cc_next is a relative offset into the current array of + * termchars. I.e. to advance to the next character in a list, + * one does `tc += tc->next'. + * + * Zero means end of list. + */ + int cc_next; +}; + +struct termline { + unsigned short lattr; + int cols; /* number of real columns on the line */ + int size; /* number of allocated termchars + * (cc-lists may make this > cols) */ + int temporary; /* TRUE if decompressed from scrollback */ + int cc_free; /* offset to first cc in free list */ + struct termchar *chars; +}; + +struct bidi_cache_entry { + int width; + struct termchar *chars; + int *forward, *backward; /* the permutations of line positions */ +}; + +struct terminal_tag { + + int compatibility_level; + + tree234 *scrollback; /* lines scrolled off top of screen */ + tree234 *screen; /* lines on primary screen */ + tree234 *alt_screen; /* lines on alternate screen */ + int disptop; /* distance scrolled back (0 or -ve) */ + int tempsblines; /* number of lines of .scrollback that + can be retrieved onto the terminal + ("temporary scrollback") */ + + termline **disptext; /* buffer of text on real screen */ + int dispcursx, dispcursy; /* location of cursor on real screen */ + int curstype; /* type of cursor on real screen */ + +#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */ + + struct beeptime *beephead, *beeptail; + int nbeeps; + int beep_overloaded; + long lastbeep; + +#define TTYPE termchar +#define TSIZE (sizeof(TTYPE)) + +#ifdef OPTIMISE_SCROLL + struct scrollregion *scrollhead, *scrolltail; +#endif /* OPTIMISE_SCROLL */ + + int default_attr, curr_attr, save_attr; + termchar basic_erase_char, erase_char; + + bufchain inbuf; /* terminal input buffer */ + pos curs; /* cursor */ + pos savecurs; /* saved cursor position */ + int marg_t, marg_b; /* scroll margins */ + int dec_om; /* DEC origin mode flag */ + int wrap, wrapnext; /* wrap flags */ + int insert; /* insert-mode flag */ + int cset; /* 0 or 1: which char set */ + int save_cset, save_csattr; /* saved with cursor position */ + int save_utf, save_wnext; /* saved with cursor position */ + int rvideo; /* global reverse video flag */ + unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ + int cursor_on; /* cursor enabled flag */ + int reset_132; /* Flag ESC c resets to 80 cols */ + int use_bce; /* Use Background coloured erase */ + int cblinker; /* When blinking is the cursor on ? */ + int tblinker; /* When the blinking text is on */ + int blink_is_real; /* Actually blink blinking text */ + int term_echoing; /* Does terminal want local echo? */ + int term_editing; /* Does terminal want local edit? */ + int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ + int vt52_bold; /* Force bold on non-bold colours */ + int utf; /* Are we in toggleable UTF-8 mode? */ + int utf_state; /* Is there a pending UTF-8 character */ + int utf_char; /* and what is it so far. */ + int utf_size; /* The size of the UTF character. */ + int printing, only_printing; /* Are we doing ANSI printing? */ + int print_state; /* state of print-end-sequence scan */ + bufchain printer_buf; /* buffered data for printer */ + printer_job *print_job; + + /* ESC 7 saved state for the alternate screen */ + pos alt_savecurs; + int alt_save_attr; + int alt_save_cset, alt_save_csattr; + int alt_save_utf, alt_save_wnext; + int alt_save_sco_acs; + + int rows, cols, savelines; + int has_focus; + int in_vbell; + long vbell_end; + int app_cursor_keys, app_keypad_keys, vt52_mode; + int repeat_off, cr_lf_return; + int seen_disp_event; + int big_cursor; + + int xterm_mouse; /* send mouse messages to host */ + int xterm_extended_mouse; + int urxvt_extended_mouse; + int mouse_is_down; /* used while tracking mouse buttons */ + + int bracketed_paste; + + int cset_attr[2]; + +/* + * Saved settings on the alternate screen. + */ + int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins; + int alt_cset, alt_sco_acs, alt_utf; + int alt_t, alt_b; + int alt_which; + int alt_sblines; /* # of lines on alternate screen that should be used for scrollback. */ + +#define ARGS_MAX 32 /* max # of esc sequence arguments */ +#define ARG_DEFAULT 0 /* if an arg isn't specified */ +#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) ) + unsigned esc_args[ARGS_MAX]; + int esc_nargs; + int esc_query; +#define ANSI(x,y) ((x)+((y)<<8)) +#define ANSI_QUE(x) ANSI(x,TRUE) + +#define OSC_STR_MAX 2048 + int osc_strlen; + char osc_string[OSC_STR_MAX + 1]; + int osc_w; + + char id_string[1024]; + + unsigned char *tabs; + + enum { + TOPLEVEL, + SEEN_ESC, + SEEN_CSI, + SEEN_OSC, + SEEN_OSC_W, + + DO_CTRLS, + + SEEN_OSC_P, + OSC_STRING, OSC_MAYBE_ST, + VT52_ESC, + VT52_Y1, + VT52_Y2, + VT52_FG, + VT52_BG + } termstate; + + enum { + NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED + } selstate; + enum { + LEXICOGRAPHIC, RECTANGULAR + } seltype; + enum { + SM_CHAR, SM_WORD, SM_LINE + } selmode; + pos selstart, selend, selanchor; + + short wordness[256]; + + /* Mask of attributes to pay attention to when painting. */ + int attr_mask; + + wchar_t *paste_buffer; + int paste_len, paste_pos; + + void (*resize_fn)(void *, int, int); + void *resize_ctx; + + void *ldisc; + + void *frontend; + + void *logctx; + + struct unicode_data *ucsdata; + + /* + * We maintain a full copy of a Conf here, not merely a pointer + * to it. That way, when we're passed a new one for + * reconfiguration, we can check the differences and adjust the + * _current_ setting of (e.g.) auto wrap mode rather than only + * the default. + */ + Conf *conf; + + /* + * from_backend calls term_out, but it can also be called from + * the ldisc if the ldisc is called _within_ term_out. So we + * have to guard against re-entrancy - if from_backend is + * called recursively like this, it will simply add data to the + * end of the buffer term_out is in the process of working + * through. + */ + int in_term_out; + + /* + * We schedule a window update shortly after receiving terminal + * data. This tracks whether one is currently pending. + */ + int window_update_pending; + long next_update; + + /* + * Track pending blinks and tblinks. + */ + int tblink_pending, cblink_pending; + long next_tblink, next_cblink; + + /* + * These are buffers used by the bidi and Arabic shaping code. + */ + termchar *ltemp; + int ltemp_size; + bidi_char *wcFrom, *wcTo; + int wcFromTo_size; + struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache; + int bidi_cache_size; + + /* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated + * tree234 lookups which would be involved in fetching them from + * the former every time. + */ + int ansi_colour; + char *answerback; + int answerbacklen; + int arabicshaping; + int beep; + int bellovl; + int bellovl_n; + int bellovl_s; + int bellovl_t; + int bidi; + int bksp_is_delete; + int blink_cur; + int blinktext; + int cjk_ambig_wide; + int conf_height; + int conf_width; + int crhaslf; + int erase_to_scrollback; + int funky_type; + int lfhascr; + int logflush; + int logtype; + int mouse_override; + int nethack_keypad; + int no_alt_screen; + int no_applic_c; + int no_applic_k; + int no_dbackspace; + int no_mouse_rep; + int no_remote_charset; + int no_remote_resize; + int no_remote_wintitle; + int rawcnp; + int rect_select; + int remote_qtitle_action; + int rxvt_homeend; + int scroll_on_disp; + int scroll_on_key; + int xterm_256_colour; +}; + +#define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) + +#endif diff --git a/netbox/libs/Putty/testback.c b/netbox/libs/Putty/testback.c new file mode 100644 index 000000000..bf3047efd --- /dev/null +++ b/netbox/libs/Putty/testback.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 1999 Simon Tatham + * Copyright (c) 1999 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* PuTTY test backends */ + +#include +#include + +#include "putty.h" + +static const char *null_init(void *, void **, Conf *, char *, int, char **, + int, int); +static const char *loop_init(void *, void **, Conf *, char *, int, char **, + int, int); +static void null_free(void *); +static void loop_free(void *); +static void null_reconfig(void *, Conf *); +static int null_send(void *, char *, int); +static int loop_send(void *, char *, int); +static int null_sendbuffer(void *); +static void null_size(void *, int, int); +static void null_special(void *, Telnet_Special); +static const struct telnet_special *null_get_specials(void *handle); +static int null_connected(void *); +static int null_exitcode(void *); +static int null_sendok(void *); +static int null_ldisc(void *, int); +static void null_provide_ldisc(void *, void *); +static void null_provide_logctx(void *, void *); +static void null_unthrottle(void *, int); +static int null_cfg_info(void *); + +Backend null_backend = { + null_init, null_free, null_reconfig, null_send, null_sendbuffer, null_size, + null_special, null_get_specials, null_connected, null_exitcode, null_sendok, + null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_cfg_info, "null", -1, 0 +}; + +Backend loop_backend = { + loop_init, loop_free, null_reconfig, loop_send, null_sendbuffer, null_size, + null_special, null_get_specials, null_connected, null_exitcode, null_sendok, + null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_cfg_info, "loop", -1, 0 +}; + +struct loop_state { + Terminal *term; +}; + +static const char *null_init(void *frontend_handle, void **backend_handle, + Conf *conf, char *host, int port, + char **realhost, int nodelay, int keepalive) { + + return NULL; +} + +static const char *loop_init(void *frontend_handle, void **backend_handle, + Conf *conf, char *host, int port, + char **realhost, int nodelay, int keepalive) { + struct loop_state *st = snew(struct loop_state); + + st->term = frontend_handle; + *backend_handle = st; + return NULL; +} + +static void null_free(void *handle) +{ + +} + +static void loop_free(void *handle) +{ + + sfree(handle); +} + +static void null_reconfig(void *handle, Conf *conf) { + +} + +static int null_send(void *handle, char *buf, int len) { + + return 0; +} + +static int loop_send(void *handle, char *buf, int len) { + struct loop_state *st = handle; + + return from_backend(st->term, 0, buf, len); +} + +static int null_sendbuffer(void *handle) { + + return 0; +} + +static void null_size(void *handle, int width, int height) { + +} + +static void null_special(void *handle, Telnet_Special code) { + +} + +static const struct telnet_special *null_get_specials (void *handle) { + + return NULL; +} + +static int null_connected(void *handle) { + + return 0; +} + +static int null_exitcode(void *handle) { + + return 0; +} + +static int null_sendok(void *handle) { + + return 1; +} + +static void null_unthrottle(void *handle, int backlog) { + +} + +static int null_ldisc(void *handle, int option) { + + return 0; +} + +static void null_provide_ldisc (void *handle, void *ldisc) { + +} + +static void null_provide_logctx(void *handle, void *logctx) { + +} + +static int null_cfg_info(void *handle) +{ + return 0; +} + + +/* + * Emacs magic: + * Local Variables: + * c-file-style: "simon" + * End: + */ diff --git a/netbox/libs/Putty/time.c b/netbox/libs/Putty/time.c new file mode 100644 index 000000000..75b89535d --- /dev/null +++ b/netbox/libs/Putty/time.c @@ -0,0 +1,16 @@ +/* + * Portable implementation of ltime() for any ISO-C platform where + * time_t behaves. (In practice, we've found that platforms such as + * Windows and Mac have needed their own specialised implementations.) + */ + +#include +#include + +struct tm ltime(void) +{ + time_t t; + time(&t); + assert (t != ((time_t)-1)); + return *localtime(&t); +} diff --git a/netbox/libs/Putty/timing.c b/netbox/libs/Putty/timing.c new file mode 100644 index 000000000..ccd76cd66 --- /dev/null +++ b/netbox/libs/Putty/timing.c @@ -0,0 +1,211 @@ +/* + * timing.c + * + * This module tracks any timers set up by schedule_timer(). It + * keeps all the currently active timers in a list; it informs the + * front end of when the next timer is due to go off if that + * changes; and, very importantly, it tracks the context pointers + * passed to schedule_timer(), so that if a context is freed all + * the timers associated with it can be immediately annulled. + * + * + * The problem is that computer clocks aren't perfectly accurate. + * The GETTICKCOUNT function returns a 32bit number that normally + * increases by about 1000 every second. On windows this uses the PC's + * interrupt timer and so is only accurate to around 20ppm. On unix it's + * a value that's calculated from the current UTC time and so is in theory + * accurate in the long term but may jitter and jump in the short term. + * + * What PuTTY needs from these timers is simply a way of delaying the + * calling of a function for a little while, if it's occasionally called a + * little early or late that's not a problem. So to protect against clock + * jumps schedule_timer records the time that it was called in the timer + * structure. With this information the run_timers function can see when + * the current GETTICKCOUNT value is after the time the event should be + * fired OR before the time it was set. In the latter case the clock must + * have jumped, the former is (probably) just the normal passage of time. + * + */ + +#include +#include + +#include "putty.h" +#include "tree234.h" + +struct timer { + timer_fn_t fn; + void *ctx; + unsigned long now; + unsigned long when_set; +}; + +static tree234 *timers = NULL; +static tree234 *timer_contexts = NULL; +static unsigned long now = 0L; + +static int compare_timers(void *av, void *bv) +{ + struct timer *a = (struct timer *)av; + struct timer *b = (struct timer *)bv; + long at = a->now - now; + long bt = b->now - now; + + if (at < bt) + return -1; + else if (at > bt) + return +1; + + /* + * Failing that, compare on the other two fields, just so that + * we don't get unwanted equality. + */ +#if defined(__LCC__) || defined(__clang__) + /* lcc won't let us compare function pointers. Legal, but annoying. */ + { + int c = memcmp(&a->fn, &b->fn, sizeof(a->fn)); + if (c) + return c; + } +#else + if (a->fn < b->fn) + return -1; + else if (a->fn > b->fn) + return +1; +#endif + + if (a->ctx < b->ctx) + return -1; + else if (a->ctx > b->ctx) + return +1; + + /* + * Failing _that_, the two entries genuinely are equal, and we + * never have a need to store them separately in the tree. + */ + return 0; +} + +static int compare_timer_contexts(void *av, void *bv) +{ + char *a = (char *)av; + char *b = (char *)bv; + if (a < b) + return -1; + else if (a > b) + return +1; + return 0; +} + +static void init_timers(void) +{ + if (!timers) { + timers = newtree234(compare_timers); + timer_contexts = newtree234(compare_timer_contexts); + now = GETTICKCOUNT(); + } +} + +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + unsigned long when; + struct timer *t, *first; + + init_timers(); + + now = GETTICKCOUNT(); + when = ticks + now; + + /* + * Just in case our various defences against timing skew fail + * us: if we try to schedule a timer that's already in the + * past, we instead schedule it for the immediate future. + */ + if (when - now <= 0) + when = now + 1; + + t = snew(struct timer); + t->fn = fn; + t->ctx = ctx; + t->now = when; + t->when_set = now; + + if (t != add234(timers, t)) { + sfree(t); /* identical timer already exists */ + } else { + add234(timer_contexts, t->ctx);/* don't care if this fails */ + } + + first = (struct timer *)index234(timers, 0); + if (first == t) { + /* + * This timer is the very first on the list, so we must + * notify the front end. + */ + timer_change_notify(first->now); + } + + return when; +} + +/* + * Call to run any timers whose time has reached the present. + * Returns the time (in ticks) expected until the next timer after + * that triggers. + */ +int run_timers(unsigned long anow, unsigned long *next) +{ + struct timer *first; + + init_timers(); + + now = GETTICKCOUNT(); + + while (1) { + first = (struct timer *)index234(timers, 0); + + if (!first) + return FALSE; /* no timers remaining */ + + if (find234(timer_contexts, first->ctx, NULL) == NULL) { + /* + * This timer belongs to a context that has been + * expired. Delete it without running. + */ + delpos234(timers, 0); + sfree(first); + } else if (now - (first->when_set - 10) > + first->now - (first->when_set - 10)) { + /* + * This timer is active and has reached its running + * time. Run it. + */ + delpos234(timers, 0); + first->fn(first->ctx, first->now); + sfree(first); + } else { + /* + * This is the first still-active timer that is in the + * future. Return how long it has yet to go. + */ + *next = first->now; + return TRUE; + } + } +} + +/* + * Call to expire all timers associated with a given context. + */ +void expire_timer_context(void *ctx) +{ + init_timers(); + + /* + * We don't bother to check the return value; if the context + * already wasn't in the tree (presumably because no timers + * ever actually got scheduled for it) then that's fine and we + * simply don't need to do anything. + */ + del234(timer_contexts, ctx); +} diff --git a/netbox/libs/Putty/tree234.c b/netbox/libs/Putty/tree234.c new file mode 100644 index 000000000..f1c0c2edb --- /dev/null +++ b/netbox/libs/Putty/tree234.c @@ -0,0 +1,1486 @@ +/* + * tree234.c: reasonably generic counted 2-3-4 tree routines. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include "tree234.h" + +#ifdef TEST +#define LOG(x) (printf x) +#define snew(type) ((type *)malloc(sizeof(type))) +#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n) * sizeof(type))) +#define sfree(ptr) free(ptr) +#else +#include "puttymem.h" +#define LOG(x) +#endif + +typedef struct node234_Tag node234; + +struct tree234_Tag { + node234 *root; + cmpfn234 cmp; +}; + +struct node234_Tag { + node234 *parent; + node234 *kids[4]; + int counts[4]; + void *elems[3]; +}; + +/* + * Create a 2-3-4 tree. + */ +tree234 *newtree234(cmpfn234 cmp) +{ + tree234 *ret = snew(tree234); + LOG(("created tree %p\n", ret)); + ret->root = NULL; + ret->cmp = cmp; + return ret; +} + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +static void freenode234(node234 * n) +{ + if (!n) + return; + freenode234(n->kids[0]); + freenode234(n->kids[1]); + freenode234(n->kids[2]); + freenode234(n->kids[3]); + sfree(n); +} + +void freetree234(tree234 * t) +{ + freenode234(t->root); + sfree(t); +} + +/* + * Internal function to count a node. + */ +static int countnode234(node234 * n) +{ + int count = 0; + int i; + if (!n) + return 0; + for (i = 0; i < 4; i++) + count += n->counts[i]; + for (i = 0; i < 3; i++) + if (n->elems[i]) + count++; + return count; +} + +/* + * Count the elements in a tree. + */ +int count234(tree234 * t) +{ + if (t->root) + return countnode234(t->root); + else + return 0; +} + +/* + * Add an element e to a 2-3-4 tree t. Returns e on success, or if + * an existing element compares equal, returns that. + */ +static void *add234_internal(tree234 * t, void *e, int index) +{ + node234 *n, **np, *left, *right; + void *orig_e = e; + int c, lcount, rcount; + + LOG(("adding node %p to tree %p\n", e, t)); + if (t->root == NULL) { + t->root = snew(node234); + t->root->elems[1] = t->root->elems[2] = NULL; + t->root->kids[0] = t->root->kids[1] = NULL; + t->root->kids[2] = t->root->kids[3] = NULL; + t->root->counts[0] = t->root->counts[1] = 0; + t->root->counts[2] = t->root->counts[3] = 0; + t->root->parent = NULL; + t->root->elems[0] = e; + LOG((" created root %p\n", t->root)); + return orig_e; + } + + n = NULL; /* placate gcc; will always be set below since t->root != NULL */ + np = &t->root; + while (*np) { + int childnum; + n = *np; + LOG((" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + if (index >= 0) { + if (!n->kids[0]) { + /* + * Leaf node. We want to insert at kid position + * equal to the index: + * + * 0 A 1 B 2 C 3 + */ + childnum = index; + } else { + /* + * Internal node. We always descend through it (add + * always starts at the bottom, never in the + * middle). + */ + do { /* this is a do ... while (0) to allow `break' */ + if (index <= n->counts[0]) { + childnum = 0; + break; + } + index -= n->counts[0] + 1; + if (index <= n->counts[1]) { + childnum = 1; + break; + } + index -= n->counts[1] + 1; + if (index <= n->counts[2]) { + childnum = 2; + break; + } + index -= n->counts[2] + 1; + if (index <= n->counts[3]) { + childnum = 3; + break; + } + return NULL; /* error: index out of range */ + } while (0); + } + } else { + if ((c = t->cmp(e, n->elems[0])) < 0) + childnum = 0; + else if (c == 0) + return n->elems[0]; /* already exists */ + else if (n->elems[1] == NULL + || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1; + else if (c == 0) + return n->elems[1]; /* already exists */ + else if (n->elems[2] == NULL + || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2; + else if (c == 0) + return n->elems[2]; /* already exists */ + else + childnum = 3; + } + np = &n->kids[childnum]; + LOG((" moving to child %d (%p)\n", childnum, *np)); + } + + /* + * We need to insert the new element in n at position np. + */ + left = NULL; + lcount = 0; + right = NULL; + rcount = 0; + while (n) { + LOG((" at %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n", + n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1], n->elems[1], + n->kids[2], n->counts[2], n->elems[2], + n->kids[3], n->counts[3])); + LOG((" need to insert %p/%d [%p] %p/%d at position %d\n", + left, lcount, e, right, rcount, (int)(np - n->kids))); + if (n->elems[1] == NULL) { + /* + * Insert in a 2-node; simple. + */ + if (np == &n->kids[0]) { + LOG((" inserting on left of 2-node\n")); + n->kids[2] = n->kids[1]; + n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; + n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; + n->counts[0] = lcount; + } else { /* np == &n->kids[1] */ + LOG((" inserting on right of 2-node\n")); + n->kids[2] = right; + n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; + n->counts[1] = lcount; + } + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + if (n->kids[2]) + n->kids[2]->parent = n; + LOG((" done\n")); + break; + } else if (n->elems[2] == NULL) { + /* + * Insert in a 3-node; simple. + */ + if (np == &n->kids[0]) { + LOG((" inserting on left of 3-node\n")); + n->kids[3] = n->kids[2]; + n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = n->kids[1]; + n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; + n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; + n->counts[0] = lcount; + } else if (np == &n->kids[1]) { + LOG((" inserting in middle of 3-node\n")); + n->kids[3] = n->kids[2]; + n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = right; + n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; + n->counts[1] = lcount; + } else { /* np == &n->kids[2] */ + LOG((" inserting on right of 3-node\n")); + n->kids[3] = right; + n->counts[3] = rcount; + n->elems[2] = e; + n->kids[2] = left; + n->counts[2] = lcount; + } + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + if (n->kids[2]) + n->kids[2]->parent = n; + if (n->kids[3]) + n->kids[3]->parent = n; + LOG((" done\n")); + break; + } else { + node234 *m = snew(node234); + m->parent = n->parent; + LOG((" splitting a 4-node; created new node %p\n", m)); + /* + * Insert in a 4-node; split into a 2-node and a + * 3-node, and move focus up a level. + * + * I don't think it matters which way round we put the + * 2 and the 3. For simplicity, we'll put the 3 first + * always. + */ + if (np == &n->kids[0]) { + m->kids[0] = left; + m->counts[0] = lcount; + m->elems[0] = e; + m->kids[1] = right; + m->counts[1] = rcount; + m->elems[1] = n->elems[0]; + m->kids[2] = n->kids[1]; + m->counts[2] = n->counts[1]; + e = n->elems[1]; + n->kids[0] = n->kids[2]; + n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else if (np == &n->kids[1]) { + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = left; + m->counts[1] = lcount; + m->elems[1] = e; + m->kids[2] = right; + m->counts[2] = rcount; + e = n->elems[1]; + n->kids[0] = n->kids[2]; + n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else if (np == &n->kids[2]) { + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; + m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = left; + m->counts[2] = lcount; + /* e = e; */ + n->kids[0] = right; + n->counts[0] = rcount; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else { /* np == &n->kids[3] */ + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; + m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = n->kids[2]; + m->counts[2] = n->counts[2]; + n->kids[0] = left; + n->counts[0] = lcount; + n->elems[0] = e; + n->kids[1] = right; + n->counts[1] = rcount; + e = n->elems[2]; + } + m->kids[3] = n->kids[3] = n->kids[2] = NULL; + m->counts[3] = n->counts[3] = n->counts[2] = 0; + m->elems[2] = n->elems[2] = n->elems[1] = NULL; + if (m->kids[0]) + m->kids[0]->parent = m; + if (m->kids[1]) + m->kids[1]->parent = m; + if (m->kids[2]) + m->kids[2]->parent = m; + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m, + m->kids[0], m->counts[0], m->elems[0], + m->kids[1], m->counts[1], m->elems[1], + m->kids[2], m->counts[2])); + LOG((" right (%p): %p/%d [%p] %p/%d\n", n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1])); + left = m; + lcount = countnode234(left); + right = n; + rcount = countnode234(right); + } + if (n->parent) + np = (n->parent->kids[0] == n ? &n->parent->kids[0] : + n->parent->kids[1] == n ? &n->parent->kids[1] : + n->parent->kids[2] == n ? &n->parent->kids[2] : + &n->parent->kids[3]); + n = n->parent; + } + + /* + * If we've come out of here by `break', n will still be + * non-NULL and all we need to do is go back up the tree + * updating counts. If we've come here because n is NULL, we + * need to create a new root for the tree because the old one + * has just split into two. */ + if (n) { + while (n->parent) { + int count = countnode234(n); + int childnum; + childnum = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n->parent->counts[childnum] = count; + n = n->parent; + } + } else { + LOG((" root is overloaded, split into two\n")); + t->root = snew(node234); + t->root->kids[0] = left; + t->root->counts[0] = lcount; + t->root->elems[0] = e; + t->root->kids[1] = right; + t->root->counts[1] = rcount; + t->root->elems[1] = NULL; + t->root->kids[2] = NULL; + t->root->counts[2] = 0; + t->root->elems[2] = NULL; + t->root->kids[3] = NULL; + t->root->counts[3] = 0; + t->root->parent = NULL; + if (t->root->kids[0]) + t->root->kids[0]->parent = t->root; + if (t->root->kids[1]) + t->root->kids[1]->parent = t->root; + LOG((" new root is %p/%d [%p] %p/%d\n", + t->root->kids[0], t->root->counts[0], + t->root->elems[0], t->root->kids[1], t->root->counts[1])); + } + + return orig_e; +} + +void *add234(tree234 * t, void *e) +{ + if (!t->cmp) /* tree is unsorted */ + return NULL; + + return add234_internal(t, e, -1); +} +void *addpos234(tree234 * t, void *e, int index) +{ + if (index < 0 || /* index out of range */ + t->cmp) /* tree is sorted */ + return NULL; /* return failure */ + + return add234_internal(t, e, index); /* this checks the upper bound */ +} + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + */ +void *index234(tree234 * t, int index) +{ + node234 *n; + + if (!t->root) + return NULL; /* tree is empty */ + + if (index < 0 || index >= countnode234(t->root)) + return NULL; /* out of range */ + + n = t->root; + + while (n) { + if (index < n->counts[0]) + n = n->kids[0]; + else if (index -= n->counts[0] + 1, index < 0) + return n->elems[0]; + else if (index < n->counts[1]) + n = n->kids[1]; + else if (index -= n->counts[1] + 1, index < 0) + return n->elems[1]; + else if (index < n->counts[2]) + n = n->kids[2]; + else if (index -= n->counts[2] + 1, index < 0) + return n->elems[2]; + else + n = n->kids[3]; + } + + /* We shouldn't ever get here. I wonder how we did. */ + return NULL; +} + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + */ +void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, + int relation, int *index) +{ + node234 *n; + void *ret; + int c; + int idx, ecount, kcount, cmpret; + + if (t->root == NULL) + return NULL; + + if (cmp == NULL) + cmp = t->cmp; + + n = t->root; + /* + * Attempt to find the element itself. + */ + idx = 0; + ecount = -1; + /* + * Prepare a fake `cmp' result if e is NULL. + */ + cmpret = 0; + if (e == NULL) { + assert(relation == REL234_LT || relation == REL234_GT); + if (relation == REL234_LT) + cmpret = +1; /* e is a max: always greater */ + else if (relation == REL234_GT) + cmpret = -1; /* e is a min: always smaller */ + } + while (1) { + for (kcount = 0; kcount < 4; kcount++) { + if (kcount >= 3 || n->elems[kcount] == NULL || + (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) { + break; + } + if (n->kids[kcount]) + idx += n->counts[kcount]; + if (c == 0) { + ecount = kcount; + break; + } + idx++; + } + if (ecount >= 0) + break; + if (n->kids[kcount]) + n = n->kids[kcount]; + else + break; + } + + if (ecount >= 0) { + /* + * We have found the element we're looking for. It's + * n->elems[ecount], at tree index idx. If our search + * relation is EQ, LE or GE we can now go home. + */ + if (relation != REL234_LT && relation != REL234_GT) { + if (index) + *index = idx; + return n->elems[ecount]; + } + + /* + * Otherwise, we'll do an indexed lookup for the previous + * or next element. (It would be perfectly possible to + * implement these search types in a non-counted tree by + * going back up from where we are, but far more fiddly.) + */ + if (relation == REL234_LT) + idx--; + else + idx++; + } else { + /* + * We've found our way to the bottom of the tree and we + * know where we would insert this node if we wanted to: + * we'd put it in in place of the (empty) subtree + * n->kids[kcount], and it would have index idx + * + * But the actual element isn't there. So if our search + * relation is EQ, we're doomed. + */ + if (relation == REL234_EQ) + return NULL; + + /* + * Otherwise, we must do an index lookup for index idx-1 + * (if we're going left - LE or LT) or index idx (if we're + * going right - GE or GT). + */ + if (relation == REL234_LT || relation == REL234_LE) { + idx--; + } + } + + /* + * We know the index of the element we want; just call index234 + * to do the rest. This will return NULL if the index is out of + * bounds, which is exactly what we want. + */ + ret = index234(t, idx); + if (ret && index) + *index = idx; + return ret; +} +void *find234(tree234 * t, void *e, cmpfn234 cmp) +{ + return findrelpos234(t, e, cmp, REL234_EQ, NULL); +} +void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation) +{ + return findrelpos234(t, e, cmp, relation, NULL); +} +void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index) +{ + return findrelpos234(t, e, cmp, REL234_EQ, index); +} + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + */ +static void *delpos234_internal(tree234 * t, int index) +{ + node234 *n; + void *retval; + int ei = -1; + + retval = 0; + + n = t->root; + LOG(("deleting item %d from tree %p\n", index, t)); + while (1) { + while (n) { + int ki; + node234 *sub; + + LOG( + (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n", + n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], + n->counts[1], n->elems[1], n->kids[2], n->counts[2], + n->elems[2], n->kids[3], n->counts[3], index)); + if (index < n->counts[0]) { + ki = 0; + } else if (index -= n->counts[0] + 1, index < 0) { + ei = 0; + break; + } else if (index < n->counts[1]) { + ki = 1; + } else if (index -= n->counts[1] + 1, index < 0) { + ei = 1; + break; + } else if (index < n->counts[2]) { + ki = 2; + } else if (index -= n->counts[2] + 1, index < 0) { + ei = 2; + break; + } else { + ki = 3; + } + /* + * Recurse down to subtree ki. If it has only one element, + * we have to do some transformation to start with. + */ + LOG((" moving to subtree %d\n", ki)); + sub = n->kids[ki]; + if (!sub->elems[1]) { + LOG((" subtree has only one element!\n", ki)); + if (ki > 0 && n->kids[ki - 1]->elems[1]) { + /* + * Case 3a, left-handed variant. Child ki has + * only one element, but child ki-1 has two or + * more. So we need to move a subtree from ki-1 + * to ki. + * + * . C . . B . + * / \ -> / \ + * [more] a A b B c d D e [more] a A b c C d D e + */ + node234 *sib = n->kids[ki - 1]; + int lastelem = (sib->elems[2] ? 2 : + sib->elems[1] ? 1 : 0); + sub->kids[2] = sub->kids[1]; + sub->counts[2] = sub->counts[1]; + sub->elems[1] = sub->elems[0]; + sub->kids[1] = sub->kids[0]; + sub->counts[1] = sub->counts[0]; + sub->elems[0] = n->elems[ki - 1]; + sub->kids[0] = sib->kids[lastelem + 1]; + sub->counts[0] = sib->counts[lastelem + 1]; + if (sub->kids[0]) + sub->kids[0]->parent = sub; + n->elems[ki - 1] = sib->elems[lastelem]; + sib->kids[lastelem + 1] = NULL; + sib->counts[lastelem + 1] = 0; + sib->elems[lastelem] = NULL; + n->counts[ki] = countnode234(sub); + LOG((" case 3a left\n")); + LOG( + (" index and left subtree count before adjustment: %d, %d\n", + index, n->counts[ki - 1])); + index += n->counts[ki - 1]; + n->counts[ki - 1] = countnode234(sib); + index -= n->counts[ki - 1]; + LOG( + (" index and left subtree count after adjustment: %d, %d\n", + index, n->counts[ki - 1])); + } else if (ki < 3 && n->kids[ki + 1] + && n->kids[ki + 1]->elems[1]) { + /* + * Case 3a, right-handed variant. ki has only + * one element but ki+1 has two or more. Move a + * subtree from ki+1 to ki. + * + * . B . . C . + * / \ -> / \ + * a A b c C d D e [more] a A b B c d D e [more] + */ + node234 *sib = n->kids[ki + 1]; + int j; + sub->elems[1] = n->elems[ki]; + sub->kids[2] = sib->kids[0]; + sub->counts[2] = sib->counts[0]; + if (sub->kids[2]) + sub->kids[2]->parent = sub; + n->elems[ki] = sib->elems[0]; + sib->kids[0] = sib->kids[1]; + sib->counts[0] = sib->counts[1]; + for (j = 0; j < 2 && sib->elems[j + 1]; j++) { + sib->kids[j + 1] = sib->kids[j + 2]; + sib->counts[j + 1] = sib->counts[j + 2]; + sib->elems[j] = sib->elems[j + 1]; + } + sib->kids[j + 1] = NULL; + sib->counts[j + 1] = 0; + sib->elems[j] = NULL; + n->counts[ki] = countnode234(sub); + n->counts[ki + 1] = countnode234(sib); + LOG((" case 3a right\n")); + } else { + /* + * Case 3b. ki has only one element, and has no + * neighbour with more than one. So pick a + * neighbour and merge it with ki, taking an + * element down from n to go in the middle. + * + * . B . . + * / \ -> | + * a A b c C d a A b B c C d + * + * (Since at all points we have avoided + * descending to a node with only one element, + * we can be sure that n is not reduced to + * nothingness by this move, _unless_ it was + * the very first node, ie the root of the + * tree. In that case we remove the now-empty + * root and replace it with its single large + * child as shown.) + */ + node234 *sib; + int j; + + if (ki > 0) { + ki--; + index += n->counts[ki] + 1; + } + sib = n->kids[ki]; + sub = n->kids[ki + 1]; + + sub->kids[3] = sub->kids[1]; + sub->counts[3] = sub->counts[1]; + sub->elems[2] = sub->elems[0]; + sub->kids[2] = sub->kids[0]; + sub->counts[2] = sub->counts[0]; + sub->elems[1] = n->elems[ki]; + sub->kids[1] = sib->kids[1]; + sub->counts[1] = sib->counts[1]; + if (sub->kids[1]) + sub->kids[1]->parent = sub; + sub->elems[0] = sib->elems[0]; + sub->kids[0] = sib->kids[0]; + sub->counts[0] = sib->counts[0]; + if (sub->kids[0]) + sub->kids[0]->parent = sub; + + n->counts[ki + 1] = countnode234(sub); + + sfree(sib); + + /* + * That's built the big node in sub. Now we + * need to remove the reference to sib in n. + */ + for (j = ki; j < 3 && n->kids[j + 1]; j++) { + n->kids[j] = n->kids[j + 1]; + n->counts[j] = n->counts[j + 1]; + n->elems[j] = j < 2 ? n->elems[j + 1] : NULL; + } + n->kids[j] = NULL; + n->counts[j] = 0; + if (j < 3) + n->elems[j] = NULL; + LOG((" case 3b ki=%d\n", ki)); + + if (!n->elems[0]) { + /* + * The root is empty and needs to be + * removed. + */ + LOG((" shifting root!\n")); + t->root = sub; + sub->parent = NULL; + sfree(n); + } + } + } + n = sub; + } + if (!retval) + retval = n->elems[ei]; + + if (ei == -1) + return NULL; /* although this shouldn't happen */ + + /* + * Treat special case: this is the one remaining item in + * the tree. n is the tree root (no parent), has one + * element (no elems[1]), and has no kids (no kids[0]). + */ + if (!n->parent && !n->elems[1] && !n->kids[0]) { + LOG((" removed last element in tree\n")); + sfree(n); + t->root = NULL; + return retval; + } + + /* + * Now we have the element we want, as n->elems[ei], and we + * have also arranged for that element not to be the only + * one in its node. So... + */ + + if (!n->kids[0] && n->elems[1]) { + /* + * Case 1. n is a leaf node with more than one element, + * so it's _really easy_. Just delete the thing and + * we're done. + */ + int i; + LOG((" case 1\n")); + for (i = ei; i < 2 && n->elems[i + 1]; i++) + n->elems[i] = n->elems[i + 1]; + n->elems[i] = NULL; + /* + * Having done that to the leaf node, we now go back up + * the tree fixing the counts. + */ + while (n->parent) { + int childnum; + childnum = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n->parent->counts[childnum]--; + n = n->parent; + } + return retval; /* finished! */ + } else if (n->kids[ei]->elems[1]) { + /* + * Case 2a. n is an internal node, and the root of the + * subtree to the left of e has more than one element. + * So find the predecessor p to e (ie the largest node + * in that subtree), place it where e currently is, and + * then start the deletion process over again on the + * subtree with p as target. + */ + node234 *m = n->kids[ei]; + void *target; + LOG((" case 2a\n")); + while (m->kids[0]) { + m = (m->kids[3] ? m->kids[3] : + m->kids[2] ? m->kids[2] : + m->kids[1] ? m->kids[1] : m->kids[0]); + } + target = (m->elems[2] ? m->elems[2] : + m->elems[1] ? m->elems[1] : m->elems[0]); + n->elems[ei] = target; + index = n->counts[ei] - 1; + n = n->kids[ei]; + } else if (n->kids[ei + 1]->elems[1]) { + /* + * Case 2b, symmetric to 2a but s/left/right/ and + * s/predecessor/successor/. (And s/largest/smallest/). + */ + node234 *m = n->kids[ei + 1]; + void *target; + LOG((" case 2b\n")); + while (m->kids[0]) { + m = m->kids[0]; + } + target = m->elems[0]; + n->elems[ei] = target; + n = n->kids[ei + 1]; + index = 0; + } else { + /* + * Case 2c. n is an internal node, and the subtrees to + * the left and right of e both have only one element. + * So combine the two subnodes into a single big node + * with their own elements on the left and right and e + * in the middle, then restart the deletion process on + * that subtree, with e still as target. + */ + node234 *a = n->kids[ei], *b = n->kids[ei + 1]; + int j; + + LOG((" case 2c\n")); + a->elems[1] = n->elems[ei]; + a->kids[2] = b->kids[0]; + a->counts[2] = b->counts[0]; + if (a->kids[2]) + a->kids[2]->parent = a; + a->elems[2] = b->elems[0]; + a->kids[3] = b->kids[1]; + a->counts[3] = b->counts[1]; + if (a->kids[3]) + a->kids[3]->parent = a; + sfree(b); + n->counts[ei] = countnode234(a); + /* + * That's built the big node in a, and destroyed b. Now + * remove the reference to b (and e) in n. + */ + for (j = ei; j < 2 && n->elems[j + 1]; j++) { + n->elems[j] = n->elems[j + 1]; + n->kids[j + 1] = n->kids[j + 2]; + n->counts[j + 1] = n->counts[j + 2]; + } + n->elems[j] = NULL; + n->kids[j + 1] = NULL; + n->counts[j + 1] = 0; + /* + * It's possible, in this case, that we've just removed + * the only element in the root of the tree. If so, + * shift the root. + */ + if (n->elems[0] == NULL) { + LOG((" shifting root!\n")); + t->root = a; + a->parent = NULL; + sfree(n); + } + /* + * Now go round the deletion process again, with n + * pointing at the new big node and e still the same. + */ + n = a; + index = a->counts[0] + a->counts[1] + 1; + } + } +} +void *delpos234(tree234 * t, int index) +{ + if (index < 0 || index >= countnode234(t->root)) + return NULL; + return delpos234_internal(t, index); +} +void *del234(tree234 * t, void *e) +{ + int index; + if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) + return NULL; /* it wasn't in there anyway */ + return delpos234_internal(t, index); /* it's there; delete it. */ +} + +#ifdef TEST + +/* + * Test code for the 2-3-4 tree. This code maintains an alternative + * representation of the data in the tree, in an array (using the + * obvious and slow insert and delete functions). After each tree + * operation, the verify() function is called, which ensures all + * the tree properties are preserved: + * - node->child->parent always equals node + * - tree->root->parent always equals NULL + * - number of kids == 0 or number of elements + 1; + * - tree has the same depth everywhere + * - every node has at least one element + * - subtree element counts are accurate + * - any NULL kid pointer is accompanied by a zero count + * - in a sorted tree: ordering property between elements of a + * node and elements of its children is preserved + * and also ensures the list represented by the tree is the same + * list it should be. (This last check also doubly verifies the + * ordering properties, because the `same list it should be' is by + * definition correctly ordered. It also ensures all nodes are + * distinct, because the enum functions would get caught in a loop + * if not.) + */ + +#include + +/* + * Error reporting function. + */ +void error(char *fmt, ...) +{ + va_list ap; + printf("ERROR: "); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf("\n"); +} + +/* The array representation of the data. */ +void **array; +int arraylen, arraysize; +cmpfn234 cmp; + +/* The tree representation of the same data. */ +tree234 *tree; + +typedef struct { + int treedepth; + int elemcount; +} chkctx; + +int chknode(chkctx * ctx, int level, node234 * node, + void *lowbound, void *highbound) +{ + int nkids, nelems; + int i; + int count; + + /* Count the non-NULL kids. */ + for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++); + /* Ensure no kids beyond the first NULL are non-NULL. */ + for (i = nkids; i < 4; i++) + if (node->kids[i]) { + error("node %p: nkids=%d but kids[%d] non-NULL", + node, nkids, i); + } else if (node->counts[i]) { + error("node %p: kids[%d] NULL but count[%d]=%d nonzero", + node, i, i, node->counts[i]); + } + + /* Count the non-NULL elements. */ + for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++); + /* Ensure no elements beyond the first NULL are non-NULL. */ + for (i = nelems; i < 3; i++) + if (node->elems[i]) { + error("node %p: nelems=%d but elems[%d] non-NULL", + node, nelems, i); + } + + if (nkids == 0) { + /* + * If nkids==0, this is a leaf node; verify that the tree + * depth is the same everywhere. + */ + if (ctx->treedepth < 0) + ctx->treedepth = level; /* we didn't know the depth yet */ + else if (ctx->treedepth != level) + error("node %p: leaf at depth %d, previously seen depth %d", + node, level, ctx->treedepth); + } else { + /* + * If nkids != 0, then it should be nelems+1, unless nelems + * is 0 in which case nkids should also be 0 (and so we + * shouldn't be in this condition at all). + */ + int shouldkids = (nelems ? nelems + 1 : 0); + if (nkids != shouldkids) { + error("node %p: %d elems should mean %d kids but has %d", + node, nelems, shouldkids, nkids); + } + } + + /* + * nelems should be at least 1. + */ + if (nelems == 0) { + error("node %p: no elems", node, nkids); + } + + /* + * Add nelems to the running element count of the whole tree. + */ + ctx->elemcount += nelems; + + /* + * Check ordering property: all elements should be strictly > + * lowbound, strictly < highbound, and strictly < each other in + * sequence. (lowbound and highbound are NULL at edges of tree + * - both NULL at root node - and NULL is considered to be < + * everything and > everything. IYSWIM.) + */ + if (cmp) { + for (i = -1; i < nelems; i++) { + void *lower = (i == -1 ? lowbound : node->elems[i]); + void *higher = + (i + 1 == nelems ? highbound : node->elems[i + 1]); + if (lower && higher && cmp(lower, higher) >= 0) { + error("node %p: kid comparison [%d=%s,%d=%s] failed", + node, i, lower, i + 1, higher); + } + } + } + + /* + * Check parent pointers: all non-NULL kids should have a + * parent pointer coming back to this node. + */ + for (i = 0; i < nkids; i++) + if (node->kids[i]->parent != node) { + error("node %p kid %d: parent ptr is %p not %p", + node, i, node->kids[i]->parent, node); + } + + + /* + * Now (finally!) recurse into subtrees. + */ + count = nelems; + + for (i = 0; i < nkids; i++) { + void *lower = (i == 0 ? lowbound : node->elems[i - 1]); + void *higher = (i >= nelems ? highbound : node->elems[i]); + int subcount = + chknode(ctx, level + 1, node->kids[i], lower, higher); + if (node->counts[i] != subcount) { + error("node %p kid %d: count says %d, subtree really has %d", + node, i, node->counts[i], subcount); + } + count += subcount; + } + + return count; +} + +void verify(void) +{ + chkctx ctx; + int i; + void *p; + + ctx.treedepth = -1; /* depth unknown yet */ + ctx.elemcount = 0; /* no elements seen yet */ + /* + * Verify validity of tree properties. + */ + if (tree->root) { + if (tree->root->parent != NULL) + error("root->parent is %p should be null", tree->root->parent); + chknode(&ctx, 0, tree->root, NULL, NULL); + } + printf("tree depth: %d\n", ctx.treedepth); + /* + * Enumerate the tree and ensure it matches up to the array. + */ + for (i = 0; NULL != (p = index234(tree, i)); i++) { + if (i >= arraylen) + error("tree contains more than %d elements", arraylen); + if (array[i] != p) + error("enum at position %d: array says %s, tree says %s", + i, array[i], p); + } + if (ctx.elemcount != i) { + error("tree really contains %d elements, enum gave %d", + ctx.elemcount, i); + } + if (i < arraylen) { + error("enum gave only %d elements, array has %d", i, arraylen); + } + i = count234(tree); + if (ctx.elemcount != i) { + error("tree really contains %d elements, count234 gave %d", + ctx.elemcount, i); + } +} + +void internal_addtest(void *elem, int index, void *realret) +{ + int i, j; + void *retval; + + if (arraysize < arraylen + 1) { + arraysize = arraylen + 1 + 256; + array = sresize(array, arraysize, void *); + } + + i = index; + /* now i points to the first element >= elem */ + retval = elem; /* expect elem returned (success) */ + for (j = arraylen; j > i; j--) + array[j] = array[j - 1]; + array[i] = elem; /* add elem to array */ + arraylen++; + + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + + verify(); +} + +void addtest(void *elem) +{ + int i; + void *realret; + + realret = add234(tree, elem); + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i < arraylen && !cmp(elem, array[i])) { + void *retval = array[i]; /* expect that returned not elem */ + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + } else + internal_addtest(elem, i, realret); +} + +void addpostest(void *elem, int i) +{ + void *realret; + + realret = addpos234(tree, elem, i); + + internal_addtest(elem, i, realret); +} + +void delpostest(int i) +{ + int index = i; + void *elem = array[i], *ret; + + /* i points to the right element */ + while (i < arraylen - 1) { + array[i] = array[i + 1]; + i++; + } + arraylen--; /* delete elem from array */ + + if (tree->cmp) + ret = del234(tree, elem); + else + ret = delpos234(tree, index); + + if (ret != elem) { + error("del returned %p, expected %p", ret, elem); + } + + verify(); +} + +void deltest(void *elem) +{ + int i; + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i >= arraylen || cmp(elem, array[i]) != 0) + return; /* don't do it! */ + delpostest(i); +} + +/* A sample data set and test utility. Designed for pseudo-randomness, + * and yet repeatability. */ + +/* + * This random number generator uses the `portable implementation' + * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; + * change it if not. + */ +int randomnumber(unsigned *seed) +{ + *seed *= 1103515245; + *seed += 12345; + return ((*seed) / 65536) % 32768; +} + +int mycmp(void *av, void *bv) +{ + char const *a = (char const *) av; + char const *b = (char const *) bv; + return strcmp(a, b); +} + +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +char *strings[] = { + "a", "ab", "absque", "coram", "de", + "palam", "clam", "cum", "ex", "e", + "sine", "tenus", "pro", "prae", + "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", + "penguin", "blancmange", "pangolin", "whale", "hedgehog", + "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", + "murfl", "spoo", "breen", "flarn", "octothorpe", + "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", + "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", + "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", + "wand", "ring", "amulet" +}; + +#define NSTR lenof(strings) + +int findtest(void) +{ + const static int rels[] = { + REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT + }; + const static char *const relnames[] = { + "EQ", "GE", "LE", "LT", "GT" + }; + int i, j, rel, index; + char *p, *ret, *realret, *realret2; + int lo, hi, mid, c; + + for (i = 0; i < NSTR; i++) { + p = strings[i]; + for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) { + rel = rels[j]; + + lo = 0; + hi = arraylen - 1; + while (lo <= hi) { + mid = (lo + hi) / 2; + c = strcmp(p, array[mid]); + if (c < 0) + hi = mid - 1; + else if (c > 0) + lo = mid + 1; + else + break; + } + + if (c == 0) { + if (rel == REL234_LT) + ret = (mid > 0 ? array[--mid] : NULL); + else if (rel == REL234_GT) + ret = (mid < arraylen - 1 ? array[++mid] : NULL); + else + ret = array[mid]; + } else { + assert(lo == hi + 1); + if (rel == REL234_LT || rel == REL234_LE) { + mid = hi; + ret = (hi >= 0 ? array[hi] : NULL); + } else if (rel == REL234_GT || rel == REL234_GE) { + mid = lo; + ret = (lo < arraylen ? array[lo] : NULL); + } else + ret = NULL; + } + + realret = findrelpos234(tree, p, NULL, rel, &index); + if (realret != ret) { + error("find(\"%s\",%s) gave %s should be %s", + p, relnames[j], realret, ret); + } + if (realret && index != mid) { + error("find(\"%s\",%s) gave %d should be %d", + p, relnames[j], index, mid); + } + if (realret && rel == REL234_EQ) { + realret2 = index234(tree, index); + if (realret2 != realret) { + error("find(\"%s\",%s) gave %s(%d) but %d -> %s", + p, relnames[j], realret, index, index, realret2); + } + } +#if 0 + printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], + realret, index); +#endif + } + } + + realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index); + if (arraylen && (realret != array[0] || index != 0)) { + error("find(NULL,GT) gave %s(%d) should be %s(0)", + realret, index, array[0]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,GT) gave %s(%d) should be NULL", realret, index); + } + + realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index); + if (arraylen + && (realret != array[arraylen - 1] || index != arraylen - 1)) { + error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index, + array[arraylen - 1]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); + } +} + +int main(void) +{ + int in[NSTR]; + int i, j, k; + unsigned seed = 0; + + for (i = 0; i < NSTR; i++) + in[i] = 0; + array = NULL; + arraylen = arraysize = 0; + tree = newtree234(mycmp); + cmp = mycmp; + + verify(); + for (i = 0; i < 10000; i++) { + j = randomnumber(&seed); + j %= NSTR; + printf("trial: %d\n", i); + if (in[j]) { + printf("deleting %s (%d)\n", strings[j], j); + deltest(strings[j]); + in[j] = 0; + } else { + printf("adding %s (%d)\n", strings[j], j); + addtest(strings[j]); + in[j] = 1; + } + findtest(); + } + + while (arraylen > 0) { + j = randomnumber(&seed); + j %= arraylen; + deltest(array[j]); + } + + freetree234(tree); + + /* + * Now try an unsorted tree. We don't really need to test + * delpos234 because we know del234 is based on it, so it's + * already been tested in the above sorted-tree code; but for + * completeness we'll use it to tear down our unsorted tree + * once we've built it. + */ + tree = newtree234(NULL); + cmp = NULL; + verify(); + for (i = 0; i < 1000; i++) { + printf("trial: %d\n", i); + j = randomnumber(&seed); + j %= NSTR; + k = randomnumber(&seed); + k %= count234(tree) + 1; + printf("adding string %s at index %d\n", strings[j], k); + addpostest(strings[j], k); + } + while (count234(tree) > 0) { + printf("cleanup: tree size %d\n", count234(tree)); + j = randomnumber(&seed); + j %= count234(tree); + printf("deleting string %s from index %d\n", + (const char *)array[j], j); + delpostest(j); + } + + return 0; +} + +#endif diff --git a/netbox/libs/Putty/tree234.h b/netbox/libs/Putty/tree234.h new file mode 100644 index 000000000..ba743087c --- /dev/null +++ b/netbox/libs/Putty/tree234.h @@ -0,0 +1,160 @@ +/* + * tree234.h: header defining functions in tree234.c. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TREE234_H +#define TREE234_H + +/* + * This typedef is opaque outside tree234.c itself. + */ +typedef struct tree234_Tag tree234; + +typedef int (*cmpfn234) (void *, void *); + +/* + * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and + * lookups by key will fail: you can only look things up by numeric + * index, and you have to use addpos234() and delpos234(). + */ +tree234 *newtree234(cmpfn234 cmp); + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +void freetree234(tree234 * t); + +/* + * Add an element e to a sorted 2-3-4 tree t. Returns e on success, + * or if an existing element compares equal, returns that. + */ +void *add234(tree234 * t, void *e); + +/* + * Add an element e to an unsorted 2-3-4 tree t. Returns e on + * success, NULL on failure. (Failure should only occur if the + * index is out of range or the tree is sorted.) + * + * Index range can be from 0 to the tree's current element count, + * inclusive. + */ +void *addpos234(tree234 * t, void *e, int index); + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + * + * One obvious use for this function is in iterating over the whole + * of a tree (sorted or unsorted): + * + * for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p); + * + * or + * + * int maxcount = count234(tree); + * for (i = 0; i < maxcount; i++) { + * p = index234(tree, i); + * assert(p != NULL); + * consume(p); + * } + */ +void *index234(tree234 * t, int index); + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + * + * Three of these functions are special cases of findrelpos234. The + * non-`pos' variants lack the `index' parameter: if the parameter + * is present and non-NULL, it must point to an integer variable + * which will be filled with the numeric index of the returned + * element. + * + * The non-`rel' variants lack the `relation' parameter. This + * parameter allows you to specify what relation the element you + * provide has to the element you're looking for. This parameter + * can be: + * + * REL234_EQ - find only an element that compares equal to e + * REL234_LT - find the greatest element that compares < e + * REL234_LE - find the greatest element that compares <= e + * REL234_GT - find the smallest element that compares > e + * REL234_GE - find the smallest element that compares >= e + * + * Non-`rel' variants assume REL234_EQ. + * + * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be + * NULL. In this case, REL234_GT will return the smallest element + * in the tree, and REL234_LT will return the greatest. This gives + * an alternative means of iterating over a sorted tree, instead of + * using index234: + * + * // to loop forwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;) + * consume(p); + * + * // to loop backwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;) + * consume(p); + */ +enum { + REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE +}; +void *find234(tree234 * t, void *e, cmpfn234 cmp); +void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation); +void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index); +void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, + int *index); + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + * + * delpos234 deletes the element at a particular tree index: it + * works on both sorted and unsorted trees. + * + * del234 deletes the element passed to it, so it only works on + * sorted trees. (It's equivalent to using findpos234 to determine + * the index of an element, and then passing that index to + * delpos234.) + * + * Both functions return a pointer to the element they delete, for + * the user to free or pass on elsewhere or whatever. If the index + * is out of range (delpos234) or the element is already not in the + * tree (del234) then they return NULL. + */ +void *del234(tree234 * t, void *e); +void *delpos234(tree234 * t, int index); + +/* + * Return the total element count of a tree234. + */ +int count234(tree234 * t); + +#endif /* TREE234_H */ diff --git a/netbox/libs/Putty/unix/gtkcfg.c b/netbox/libs/Putty/unix/gtkcfg.c new file mode 100644 index 000000000..958a3f665 --- /dev/null +++ b/netbox/libs/Putty/unix/gtkcfg.c @@ -0,0 +1,144 @@ +/* + * gtkcfg.c - the GTK-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void about_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) { + about_box(ctrl->generic.context.p); + } +} + +void gtk_setup_config_box(struct controlbox *b, int midsession, void *win) +{ + struct controlset *s, *s2; + union control *c; + int i; + + if (!midsession) { + /* + * Add the About button to the standard panel. + */ + s = ctrl_getset(b, "", "", ""); + c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), + about_handler, P(win)); + c->generic.column = 0; + } + + /* + * GTK makes it rather easier to put the scrollbar on the left + * than Windows does! + */ + s = ctrl_getset(b, "Window", "scrollback", + "Control the scrollback in the window"); + ctrl_checkbox(s, "Scrollbar on left", 'l', + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_scrollbar_on_left)); + /* + * Really this wants to go just after `Display scrollbar'. See + * if we can find that control, and do some shuffling. + */ + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_CHECKBOX && + c->generic.context.i == CONF_scrollbar) { + /* + * Control i is the scrollbar checkbox. + * Control s->ncontrols-1 is the scrollbar-on-left one. + */ + if (i < s->ncontrols-2) { + c = s->ctrls[s->ncontrols-1]; + memmove(s->ctrls+i+2, s->ctrls+i+1, + (s->ncontrols-i-2)*sizeof(union control *)); + s->ctrls[i+1] = c; + } + break; + } + } + + /* + * X requires three more fonts: bold, wide, and wide-bold; also + * we need the fiddly shadow-bold-offset control. This would + * make the Window/Appearance panel rather unwieldy and large, + * so I think the sensible thing here is to _move_ this + * controlset into a separate Window/Fonts panel! + */ + s2 = ctrl_getset(b, "Window/Appearance", "font", + "Font settings"); + /* Remove this controlset from b. */ + for (i = 0; i < b->nctrlsets; i++) { + if (b->ctrlsets[i] == s2) { + memmove(b->ctrlsets+i, b->ctrlsets+i+1, + (b->nctrlsets-i-1) * sizeof(*b->ctrlsets)); + b->nctrlsets--; + ctrl_free_set(s2); + break; + } + } + ctrl_settitle(b, "Window/Fonts", "Options controlling font usage"); + s = ctrl_getset(b, "Window/Fonts", "font", + "Fonts for displaying non-bold text"); + ctrl_fontsel(s, "Font used for ordinary text", 'f', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_font)); + ctrl_fontsel(s, "Font used for wide (CJK) text", 'w', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_widefont)); + s = ctrl_getset(b, "Window/Fonts", "fontbold", + "Fonts for displaying bolded text"); + ctrl_fontsel(s, "Font used for bolded text", 'b', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_boldfont)); + ctrl_fontsel(s, "Font used for bold wide text", 'i', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_wideboldfont)); + ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u', + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_shadowbold)); + ctrl_text(s, "(Note that bold fonts or shadow bolding are only" + " used if you have not requested bolding to be done by" + " changing the text colour.)", + HELPCTX(no_help)); + ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, + HELPCTX(no_help), conf_editbox_handler, + I(CONF_shadowboldoffset), I(-1)); + + /* + * Markus Kuhn feels, not totally unreasonably, that it's good + * for all applications to shift into UTF-8 mode if they notice + * that they've been started with a LANG setting dictating it, + * so that people don't have to keep remembering a separate + * UTF-8 option for every application they use. Therefore, + * here's an override option in the Translation panel. + */ + s = ctrl_getset(b, "Window/Translation", "trans", + "Character set translation on received data"); + ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l', + HELPCTX(translation_utf8_override), + conf_checkbox_handler, + I(CONF_utf8_override)); + + if (!midsession) { + /* + * Allow the user to specify the window class as part of the saved + * configuration, so that they can have their window manager treat + * different kinds of PuTTY and pterm differently if they want to. + */ + s = ctrl_getset(b, "Window/Behaviour", "x11", + "X Window System settings"); + ctrl_editbox(s, "Window class name:", 'z', 50, + HELPCTX(no_help), conf_editbox_handler, + I(CONF_winclass), I(1)); + } +} diff --git a/netbox/libs/Putty/unix/gtkcols.c b/netbox/libs/Putty/unix/gtkcols.c new file mode 100644 index 000000000..8cb5d14fc --- /dev/null +++ b/netbox/libs/Putty/unix/gtkcols.c @@ -0,0 +1,752 @@ +/* + * gtkcols.c - implementation of the `Columns' GTK layout container. + */ + +#include "gtkcols.h" +#include + +static void columns_init(Columns *cols); +static void columns_class_init(ColumnsClass *klass); +static void columns_map(GtkWidget *widget); +static void columns_unmap(GtkWidget *widget); +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_draw(GtkWidget *widget, GdkRectangle *area); +static gint columns_expose(GtkWidget *widget, GdkEventExpose *event); +#endif +static void columns_base_add(GtkContainer *container, GtkWidget *widget); +static void columns_remove(GtkContainer *container, GtkWidget *widget); +static void columns_forall(GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data); +#if !GTK_CHECK_VERSION(2,0,0) +static gint columns_focus(GtkContainer *container, GtkDirectionType dir); +#endif +static GtkType columns_child_type(GtkContainer *container); +static void columns_size_request(GtkWidget *widget, GtkRequisition *req); +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); + +static GtkContainerClass *parent_class = NULL; + +#if !GTK_CHECK_VERSION(2,0,0) +GtkType columns_get_type(void) +{ + static GtkType columns_type = 0; + + if (!columns_type) { + static const GtkTypeInfo columns_info = { + "Columns", + sizeof(Columns), + sizeof(ColumnsClass), + (GtkClassInitFunc) columns_class_init, + (GtkObjectInitFunc) columns_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info); + } + + return columns_type; +} +#else +GType columns_get_type(void) +{ + static GType columns_type = 0; + + if (!columns_type) { + static const GTypeInfo columns_info = { + sizeof(ColumnsClass), + NULL, + NULL, + (GClassInitFunc) columns_class_init, + NULL, + NULL, + sizeof(Columns), + 0, + (GInstanceInitFunc)columns_init, + }; + + columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns", + &columns_info, 0); + } + + return columns_type; +} +#endif + +#if !GTK_CHECK_VERSION(2,0,0) +static gint (*columns_inherited_focus)(GtkContainer *container, + GtkDirectionType direction); +#endif + +static void columns_class_init(ColumnsClass *klass) +{ +#if !GTK_CHECK_VERSION(2,0,0) + /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */ + GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; + GtkContainerClass *container_class = (GtkContainerClass *)klass; +#else + /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); +#endif + +#if !GTK_CHECK_VERSION(2,0,0) + parent_class = gtk_type_class(GTK_TYPE_CONTAINER); +#else + parent_class = g_type_class_peek_parent(klass); +#endif + + widget_class->map = columns_map; + widget_class->unmap = columns_unmap; +#if !GTK_CHECK_VERSION(2,0,0) + widget_class->draw = columns_draw; + widget_class->expose_event = columns_expose; +#endif + widget_class->size_request = columns_size_request; + widget_class->size_allocate = columns_size_allocate; + + container_class->add = columns_base_add; + container_class->remove = columns_remove; + container_class->forall = columns_forall; + container_class->child_type = columns_child_type; +#if !GTK_CHECK_VERSION(2,0,0) + /* Save the previous value of this method. */ + if (!columns_inherited_focus) + columns_inherited_focus = container_class->focus; + container_class->focus = columns_focus; +#endif +} + +static void columns_init(Columns *cols) +{ + GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW); + + cols->children = NULL; + cols->spacing = 0; +} + +/* + * These appear to be thoroughly tedious functions; the only reason + * we have to reimplement them at all is because we defined our own + * format for our GList of children... + */ +static void columns_map(GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_VISIBLE(child->widget) && + !GTK_WIDGET_MAPPED(child->widget)) + gtk_widget_map(child->widget); + } +} +static void columns_unmap(GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_VISIBLE(child->widget) && + GTK_WIDGET_MAPPED(child->widget)) + gtk_widget_unmap(child->widget); + } +} +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_draw(GtkWidget *widget, GdkRectangle *area) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + GdkRectangle child_area; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + if (GTK_WIDGET_DRAWABLE(widget)) { + cols = COLUMNS(widget); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_DRAWABLE(child->widget) && + gtk_widget_intersect(child->widget, area, &child_area)) + gtk_widget_draw(child->widget, &child_area); + } + } +} +static gint columns_expose(GtkWidget *widget, GdkEventExpose *event) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + GdkEventExpose child_event; + + g_return_val_if_fail(widget != NULL, FALSE); + g_return_val_if_fail(IS_COLUMNS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + if (GTK_WIDGET_DRAWABLE(widget)) { + cols = COLUMNS(widget); + child_event = *event; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_DRAWABLE(child->widget) && + GTK_WIDGET_NO_WINDOW(child->widget) && + gtk_widget_intersect(child->widget, &event->area, + &child_event.area)) + gtk_widget_event(child->widget, (GdkEvent *)&child_event); + } + } + return FALSE; +} +#endif + +static void columns_base_add(GtkContainer *container, GtkWidget *widget) +{ + Columns *cols; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(widget != NULL); + + cols = COLUMNS(container); + + /* + * Default is to add a new widget spanning all columns. + */ + columns_add(cols, widget, 0, 0); /* 0 means ncols */ +} + +static void columns_remove(GtkContainer *container, GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GtkWidget *childw; + GList *children; + gboolean was_visible; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(widget != NULL); + + cols = COLUMNS(container); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget != widget) + continue; + + was_visible = GTK_WIDGET_VISIBLE(widget); + gtk_widget_unparent(widget); + cols->children = g_list_remove_link(cols->children, children); + g_list_free(children); + g_free(child); + if (was_visible) + gtk_widget_queue_resize(GTK_WIDGET(container)); + break; + } + + for (children = cols->taborder; + children && (childw = children->data); + children = children->next) { + if (childw != widget) + continue; + + cols->taborder = g_list_remove_link(cols->taborder, children); + g_list_free(children); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(container, cols->taborder); +#endif + break; + } +} + +static void columns_forall(GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data) +{ + Columns *cols; + ColumnsChild *child; + GList *children, *next; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(callback != NULL); + + cols = COLUMNS(container); + + for (children = cols->children; + children && (child = children->data); + children = next) { + /* + * We can't wait until after the callback to assign + * `children = children->next', because the callback might + * be gtk_widget_destroy, which would remove the link + * `children' from the list! So instead we must get our + * hands on the value of the `next' pointer _before_ the + * callback. + */ + next = children->next; + if (child->widget) + callback(child->widget, callback_data); + } +} + +static GtkType columns_child_type(GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +GtkWidget *columns_new(gint spacing) +{ + Columns *cols; + +#if !GTK_CHECK_VERSION(2,0,0) + cols = gtk_type_new(columns_get_type()); +#else + cols = g_object_new(TYPE_COLUMNS, NULL); +#endif + + cols->spacing = spacing; + + return GTK_WIDGET(cols); +} + +void columns_set_cols(Columns *cols, gint ncols, const gint *percentages) +{ + ColumnsChild *childdata; + gint i; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(ncols > 0); + g_return_if_fail(percentages != NULL); + + childdata = g_new(ColumnsChild, 1); + childdata->widget = NULL; + childdata->ncols = ncols; + childdata->percentages = g_new(gint, ncols); + childdata->force_left = FALSE; + for (i = 0; i < ncols; i++) + childdata->percentages[i] = percentages[i]; + + cols->children = g_list_append(cols->children, childdata); +} + +void columns_add(Columns *cols, GtkWidget *child, + gint colstart, gint colspan) +{ + ColumnsChild *childdata; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(child != NULL); + g_return_if_fail(child->parent == NULL); + + childdata = g_new(ColumnsChild, 1); + childdata->widget = child; + childdata->colstart = colstart; + childdata->colspan = colspan; + childdata->force_left = FALSE; + + cols->children = g_list_append(cols->children, childdata); + cols->taborder = g_list_append(cols->taborder, child); + + gtk_widget_set_parent(child, GTK_WIDGET(cols)); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif + + if (GTK_WIDGET_REALIZED(cols)) + gtk_widget_realize(child); + + if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) { + if (GTK_WIDGET_MAPPED(cols)) + gtk_widget_map(child); + gtk_widget_queue_resize(child); + } +} + +void columns_force_left_align(Columns *cols, GtkWidget *widget) +{ + ColumnsChild *child; + GList *children; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget != widget) + continue; + + child->force_left = TRUE; + if (GTK_WIDGET_VISIBLE(widget)) + gtk_widget_queue_resize(GTK_WIDGET(cols)); + break; + } +} + +void columns_taborder_last(Columns *cols, GtkWidget *widget) +{ + GtkWidget *childw; + GList *children; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + for (children = cols->taborder; + children && (childw = children->data); + children = children->next) { + if (childw != widget) + continue; + + cols->taborder = g_list_remove_link(cols->taborder, children); + g_list_free(children); + cols->taborder = g_list_append(cols->taborder, widget); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif + break; + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +/* + * Override GtkContainer's focus movement so the user can + * explicitly specify the tab order. + */ +static gint columns_focus(GtkContainer *container, GtkDirectionType dir) +{ + Columns *cols; + GList *pos; + GtkWidget *focuschild; + + g_return_val_if_fail(container != NULL, FALSE); + g_return_val_if_fail(IS_COLUMNS(container), FALSE); + + cols = COLUMNS(container); + + if (!GTK_WIDGET_DRAWABLE(cols) || + !GTK_WIDGET_IS_SENSITIVE(cols)) + return FALSE; + + if (!GTK_WIDGET_CAN_FOCUS(container) && + (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { + + focuschild = container->focus_child; + gtk_container_set_focus_child(container, NULL); + + if (dir == GTK_DIR_TAB_FORWARD) + pos = cols->taborder; + else + pos = g_list_last(cols->taborder); + + while (pos) { + GtkWidget *child = pos->data; + + if (focuschild) { + if (focuschild == child) { + focuschild = NULL; /* now we can start looking in here */ + if (GTK_WIDGET_DRAWABLE(child) && + GTK_IS_CONTAINER(child) && + !GTK_WIDGET_HAS_FOCUS(child)) { + if (gtk_container_focus(GTK_CONTAINER(child), dir)) + return TRUE; + } + } + } else if (GTK_WIDGET_DRAWABLE(child)) { + if (GTK_IS_CONTAINER(child)) { + if (gtk_container_focus(GTK_CONTAINER(child), dir)) + return TRUE; + } else if (GTK_WIDGET_CAN_FOCUS(child)) { + gtk_widget_grab_focus(child); + return TRUE; + } + } + + if (dir == GTK_DIR_TAB_FORWARD) + pos = pos->next; + else + pos = pos->prev; + } + + return FALSE; + } else + return columns_inherited_focus(container, dir); +} +#endif + +/* + * Now here comes the interesting bit. The actual layout part is + * done in the following two functions: + * + * columns_size_request() examines the list of widgets held in the + * Columns, and returns a requisition stating the absolute minimum + * size it can bear to be. + * + * columns_size_allocate() is given an allocation telling it what + * size the whole container is going to be, and it calls + * gtk_widget_size_allocate() on all of its (visible) children to + * set their size and position relative to the top left of the + * container. + */ + +static void columns_size_request(GtkWidget *widget, GtkRequisition *req) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, *colypos; + const gint *percentages; + static const gint onecol[] = { 100 }; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(req != NULL); + + cols = COLUMNS(widget); + + req->width = 0; + req->height = cols->spacing; + + ncols = 1; + colypos = g_new(gint, 1); + colypos[0] = 0; + percentages = onecol; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + GtkRequisition creq; + + if (!child->widget) { + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + percentages = child->percentages; + colypos = g_renew(gint, colypos, ncols); + for (i = 1; i < ncols; i++) + colypos[i] = colypos[0]; + continue; + } + + /* Only take visible widgets into account. */ + if (!GTK_WIDGET_VISIBLE(child->widget)) + continue; + + gtk_widget_size_request(child->widget, &creq); + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * To compute width: we know that creq.width plus + * cols->spacing needs to equal a certain percentage of the + * full width of the container. So we work this value out, + * figure out how wide the container will need to be to + * make that percentage of it equal to that width, and + * ensure our returned width is at least that much. Very + * simple really. + */ + { + int percent, thiswid, fullwid; + + percent = 0; + for (i = 0; i < colspan; i++) + percent += percentages[child->colstart+i]; + + thiswid = creq.width + cols->spacing; + /* + * Since creq is the _minimum_ size the child needs, we + * must ensure that it gets _at least_ that size. + * Hence, when scaling thiswid up to fullwid, we must + * round up, which means adding percent-1 before + * dividing by percent. + */ + fullwid = (thiswid * 100 + percent - 1) / percent; + + /* + * The above calculation assumes every widget gets + * cols->spacing on the right. So we subtract + * cols->spacing here to account for the extra load of + * spacing on the right. + */ + if (req->width < fullwid - cols->spacing) + req->width = fullwid - cols->spacing; + } + + /* + * To compute height: the widget's top will be positioned + * at the largest y value so far reached in any of the + * columns it crosses. Then it will go down by creq.height + * plus padding; and the point it reaches at the bottom is + * the new y value in all those columns, and minus the + * padding it is also a lower bound on our own size + * request. + */ + { + int topy, boty; + + topy = 0; + for (i = 0; i < colspan; i++) { + if (topy < colypos[child->colstart+i]) + topy = colypos[child->colstart+i]; + } + boty = topy + creq.height + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + + if (req->height < boty - cols->spacing) + req->height = boty - cols->spacing; + } + } + + req->width += 2*GTK_CONTAINER(cols)->border_width; + req->height += 2*GTK_CONTAINER(cols)->border_width; + + g_free(colypos); +} + +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, border, *colxpos, *colypos; + const gint *percentages; + static const gint onecol[] = { 100 }; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(alloc != NULL); + + cols = COLUMNS(widget); + widget->allocation = *alloc; + border = GTK_CONTAINER(cols)->border_width; + + ncols = 1; + percentages = onecol; + /* colxpos gives the starting x position of each column. + * We supply n+1 of them, so that we can find the RH edge easily. + * All ending x positions are expected to be adjusted afterwards by + * subtracting the spacing. */ + colxpos = g_new(gint, 2); + colxpos[0] = 0; + colxpos[1] = alloc->width - 2*border + cols->spacing; + /* As in size_request, colypos is the lowest y reached in each column. */ + colypos = g_new(gint, 1); + colypos[0] = 0; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + GtkRequisition creq; + GtkAllocation call; + + if (!child->widget) { + gint percent; + + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + percentages = child->percentages; + colypos = g_renew(gint, colypos, ncols); + for (i = 1; i < ncols; i++) + colypos[i] = colypos[0]; + colxpos = g_renew(gint, colxpos, ncols + 1); + colxpos[0] = 0; + percent = 0; + for (i = 0; i < ncols; i++) { + percent += percentages[i]; + colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing) + * percent / 100); + } + continue; + } + + /* Only take visible widgets into account. */ + if (!GTK_WIDGET_VISIBLE(child->widget)) + continue; + + gtk_widget_get_child_requisition(child->widget, &creq); + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * Starting x position is cols[colstart]. + * Ending x position is cols[colstart+colspan] - spacing. + * + * Unless we're forcing left, in which case the width is + * exactly the requisition width. + */ + call.x = alloc->x + border + colxpos[child->colstart]; + if (child->force_left) + call.width = creq.width; + else + call.width = (colxpos[child->colstart+colspan] - + colxpos[child->colstart] - cols->spacing); + + /* + * To compute height: the widget's top will be positioned + * at the largest y value so far reached in any of the + * columns it crosses. Then it will go down by creq.height + * plus padding; and the point it reaches at the bottom is + * the new y value in all those columns. + */ + { + int topy, boty; + + topy = 0; + for (i = 0; i < colspan; i++) { + if (topy < colypos[child->colstart+i]) + topy = colypos[child->colstart+i]; + } + call.y = alloc->y + border + topy; + call.height = creq.height; + boty = topy + creq.height + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + } + + gtk_widget_size_allocate(child->widget, &call); + } + + g_free(colxpos); + g_free(colypos); +} diff --git a/netbox/libs/Putty/unix/gtkcols.h b/netbox/libs/Putty/unix/gtkcols.h new file mode 100644 index 000000000..cdbb15c64 --- /dev/null +++ b/netbox/libs/Putty/unix/gtkcols.h @@ -0,0 +1,62 @@ +/* + * gtkcols.h - header file for a columns-based widget container + * capable of supporting the PuTTY portable dialog box layout + * mechanism. + */ + +#ifndef COLUMNS_H +#define COLUMNS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TYPE_COLUMNS (columns_get_type()) +#define COLUMNS(obj) (GTK_CHECK_CAST((obj), TYPE_COLUMNS, Columns)) +#define COLUMNS_CLASS(klass) \ + (GTK_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass)) +#define IS_COLUMNS(obj) (GTK_CHECK_TYPE((obj), TYPE_COLUMNS)) +#define IS_COLUMNS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS)) + +typedef struct Columns_tag Columns; +typedef struct ColumnsClass_tag ColumnsClass; +typedef struct ColumnsChild_tag ColumnsChild; + +struct Columns_tag { + GtkContainer container; + /* private after here */ + GList *children; /* this holds ColumnsChild structures */ + GList *taborder; /* this just holds GtkWidgets */ + gint spacing; +}; + +struct ColumnsClass_tag { + GtkContainerClass parent_class; +}; + +struct ColumnsChild_tag { + /* If `widget' is non-NULL, this entry represents an actual widget. */ + GtkWidget *widget; + gint colstart, colspan; + gboolean force_left; /* for recalcitrant GtkLabels */ + /* Otherwise, this entry represents a change in the column setup. */ + gint ncols; + gint *percentages; +}; + +GtkType columns_get_type(void); +GtkWidget *columns_new(gint spacing); +void columns_set_cols(Columns *cols, gint ncols, const gint *percentages); +void columns_add(Columns *cols, GtkWidget *child, + gint colstart, gint colspan); +void columns_taborder_last(Columns *cols, GtkWidget *child); +void columns_force_left_align(Columns *cols, GtkWidget *child); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* COLUMNS_H */ diff --git a/netbox/libs/Putty/unix/gtkdlg.c b/netbox/libs/Putty/unix/gtkdlg.c new file mode 100644 index 000000000..aa97e0029 --- /dev/null +++ b/netbox/libs/Putty/unix/gtkdlg.c @@ -0,0 +1,3777 @@ +/* + * gtkdlg.c - GTK implementation of the PuTTY configuration box. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtkcols.h" +#include "gtkfont.h" + +#ifdef TESTMODE +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#endif + +#include "putty.h" +#include "storage.h" +#include "dialog.h" +#include "tree234.h" +#include "licence.h" + +struct Shortcut { + GtkWidget *widget; + struct uctrl *uc; + int action; +}; + +struct Shortcuts { + struct Shortcut sc[128]; +}; + +struct uctrl { + union control *ctrl; + GtkWidget *toplevel; + GtkWidget **buttons; int nbuttons; /* for radio buttons */ + GtkWidget *entry; /* for editbox, filesel, fontsel */ + GtkWidget *button; /* for filesel, fontsel */ +#if !GTK_CHECK_VERSION(2,4,0) + GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */ + GtkWidget *menu; /* for optionmenu (==droplist) */ + GtkWidget *optmenu; /* also for optionmenu */ +#else + GtkWidget *combo; /* for combo box (either editable or not) */ +#endif +#if GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */ + GtkListStore *listmodel; /* for all types of list box */ +#endif + GtkWidget *text; /* for text */ + GtkWidget *label; /* for dlg_label_change */ + GtkAdjustment *adj; /* for the scrollbar in a list box */ + guint entrysig; + guint textsig; + int nclicks; +}; + +struct dlgparam { + tree234 *byctrl, *bywidget; + void *data; + struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */ + /* `flags' are set to indicate when a GTK signal handler is being called + * due to automatic processing and should not flag a user event. */ + int flags; + struct Shortcuts *shortcuts; + GtkWidget *window, *cancelbutton; + union control *currfocus, *lastfocus; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *currtreeitem, **treeitems; + int ntreeitems; +#endif + int retval; +}; +#define FLAG_UPDATING_COMBO_LIST 1 +#define FLAG_UPDATING_LISTBOX 2 + +enum { /* values for Shortcut.action */ + SHORTCUT_EMPTY, /* no shortcut on this key */ + SHORTCUT_TREE, /* focus a tree item */ + SHORTCUT_FOCUS, /* focus the supplied widget */ + SHORTCUT_UCTRL, /* do something sane with uctrl */ + SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */ + SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */ +}; + +#if GTK_CHECK_VERSION(2,0,0) +enum { + TREESTORE_PATH, + TREESTORE_PARAMS, + TREESTORE_NUM +}; +#endif + +/* + * Forward references. + */ +static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, + gpointer data); +static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, + int chr, int action, void *ptr); +static void shortcut_highlight(GtkWidget *label, int chr); +#if !GTK_CHECK_VERSION(2,0,0) +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data); +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data); +#endif +#if !GTK_CHECK_VERSION(2,4,0) +static void menuitem_activate(GtkMenuItem *item, gpointer data); +#endif +static void coloursel_ok(GtkButton *button, gpointer data); +static void coloursel_cancel(GtkButton *button, gpointer data); +static void window_destroy(GtkWidget *widget, gpointer data); +int get_listitemheight(GtkWidget *widget); + +static int uctrl_cmp_byctrl(void *av, void *bv) +{ + struct uctrl *a = (struct uctrl *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a->ctrl < b->ctrl) + return -1; + else if (a->ctrl > b->ctrl) + return +1; + return 0; +} + +static int uctrl_cmp_byctrl_find(void *av, void *bv) +{ + union control *a = (union control *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a < b->ctrl) + return -1; + else if (a > b->ctrl) + return +1; + return 0; +} + +static int uctrl_cmp_bywidget(void *av, void *bv) +{ + struct uctrl *a = (struct uctrl *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a->toplevel < b->toplevel) + return -1; + else if (a->toplevel > b->toplevel) + return +1; + return 0; +} + +static int uctrl_cmp_bywidget_find(void *av, void *bv) +{ + GtkWidget *a = (GtkWidget *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a < b->toplevel) + return -1; + else if (a > b->toplevel) + return +1; + return 0; +} + +static void dlg_init(struct dlgparam *dp) +{ + dp->byctrl = newtree234(uctrl_cmp_byctrl); + dp->bywidget = newtree234(uctrl_cmp_bywidget); + dp->coloursel_result.ok = FALSE; + dp->window = dp->cancelbutton = NULL; +#if !GTK_CHECK_VERSION(2,0,0) + dp->treeitems = NULL; + dp->currtreeitem = NULL; +#endif + dp->flags = 0; + dp->currfocus = NULL; +} + +static void dlg_cleanup(struct dlgparam *dp) +{ + struct uctrl *uc; + + freetree234(dp->byctrl); /* doesn't free the uctrls inside */ + dp->byctrl = NULL; + while ( (uc = index234(dp->bywidget, 0)) != NULL) { + del234(dp->bywidget, uc); + sfree(uc->buttons); + sfree(uc); + } + freetree234(dp->bywidget); + dp->bywidget = NULL; +#if !GTK_CHECK_VERSION(2,0,0) + sfree(dp->treeitems); +#endif +} + +static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc) +{ + add234(dp->byctrl, uc); + add234(dp->bywidget, uc); +} + +static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl) +{ + if (!dp->byctrl) + return NULL; + return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find); +} + +static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w) +{ + struct uctrl *ret = NULL; + if (!dp->bywidget) + return NULL; + do { + ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find); + if (ret) + return ret; + w = w->parent; + } while (w); + return ret; +} + +union control *dlg_last_focused(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + if (dp->currfocus != ctrl) + return dp->currfocus; + else + return dp->lastfocus; +} + +void dlg_radiobutton_set(union control *ctrl, void *dlg, int which) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->buttons != NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE); +} + +int dlg_radiobutton_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + int i; + + assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->buttons != NULL); + for (i = 0; i < uc->nbuttons; i++) + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i]))) + return i; + return 0; /* got to return something */ +} + +void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); +} + +int dlg_checkbox_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); +} + +void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + GtkWidget *entry; + char *tmpstring; + assert(uc->ctrl->generic.type == CTRL_EDITBOX); + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) + entry = gtk_bin_get_child(GTK_BIN(uc->combo)); + else +#endif + entry = uc->entry; + + assert(entry != NULL); + + /* + * GTK 2 implements gtk_entry_set_text by means of two separate + * operations: first delete the previous text leaving the empty + * string, then insert the new text. This causes two calls to + * the "changed" signal. + * + * The first call to "changed", if allowed to proceed normally, + * will cause an EVENT_VALCHANGE event on the edit box, causing + * a call to dlg_editbox_get() which will read the empty string + * out of the GtkEntry - and promptly write it straight into the + * Conf structure, which is precisely where our `text' pointer + * is probably pointing, so the second editing operation will + * insert that instead of the string we originally asked for. + * + * Hence, we must take our own copy of the text before we do + * this. + */ + tmpstring = dupstr(text); + gtk_entry_set_text(GTK_ENTRY(entry), tmpstring); + sfree(tmpstring); +} + +char *dlg_editbox_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_EDITBOX); + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) { +#if GTK_CHECK_VERSION(2,6,0) + return dupstr(gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo))); +#else + return dupstr(gtk_entry_get_text + (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); +#endif + } +#endif + + if (uc->entry) { + return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + } + + assert(!"We shouldn't get here"); +} + +#if !GTK_CHECK_VERSION(2,4,0) +static void container_remove_and_destroy(GtkWidget *w, gpointer data) +{ + GtkContainer *cont = GTK_CONTAINER(data); + /* gtk_container_remove will unref the widget for us; we need not. */ + gtk_container_remove(cont, w); +} +#endif + +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + gtk_container_foreach(GTK_CONTAINER(uc->menu), + container_remove_and_destroy, + GTK_CONTAINER(uc->menu)); + return; + } + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + gtk_list_store_clear(uc->listmodel); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_listbox_del(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + gtk_container_remove + (GTK_CONTAINER(uc->menu), + g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); + return; + } + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + GtkTreePath *path; + GtkTreeIter iter; + assert(uc->listmodel != NULL); + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_list_store_remove(uc->listmodel, &iter); + gtk_tree_path_free(path); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) +{ + dlg_listbox_addwithid(ctrl, dlg, text, 0); +} + +/* + * Each listbox entry may have a numeric id associated with it. + * Note that some front ends only permit a string to be stored at + * each position, which means that _if_ you put two identical + * strings in any listbox then you MUST not assign them different + * IDs and expect to get meaningful results back. + */ +void dlg_listbox_addwithid(union control *ctrl, void *dlg, + char const *text, int id) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + + /* + * This routine is long and complicated in both GTK 1 and 2, + * and completely different. Sigh. + */ + dp->flags |= FLAG_UPDATING_COMBO_LIST; + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + /* + * List item in a drop-down (but non-combo) list. Tabs are + * ignored; we just provide a standard menu item with the + * text. + */ + GtkWidget *menuitem = gtk_menu_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->menu), menuitem); + gtk_widget_show(menuitem); + + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(id)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menuitem_activate), dp); + goto done; + } + if (uc->list && uc->entry) { + /* + * List item in a combo-box list, which means the sensible + * thing to do is make it a perfectly normal label. Hence + * tabs are disregarded. + */ + GtkWidget *listitem = gtk_list_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + goto done; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { + /* + * List item in a non-combo-box list box. We make all of + * these Columns containing GtkLabels. This allows us to do + * the nasty force_left hack irrespective of whether there + * are tabs in the thing. + */ + GtkWidget *listitem = gtk_list_item_new(); + GtkWidget *cols = columns_new(10); + gint *percents; + int i, ncols; + + /* Count the tabs in the text, and hence determine # of columns. */ + ncols = 1; + for (i = 0; text[i]; i++) + if (text[i] == '\t') + ncols++; + + assert(ncols <= + (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1)); + percents = snewn(ncols, gint); + percents[ncols-1] = 100; + for (i = 0; i < ncols-1; i++) { + percents[i] = uc->ctrl->listbox.percentages[i]; + percents[ncols-1] -= percents[i]; + } + columns_set_cols(COLUMNS(cols), ncols, percents); + sfree(percents); + + for (i = 0; i < ncols; i++) { + int len = strcspn(text, "\t"); + char *dup = dupprintf("%.*s", len, text); + GtkWidget *label; + + text += len; + if (*text) text++; + label = gtk_label_new(dup); + sfree(dup); + + columns_add(COLUMNS(cols), label, i, 1); + columns_force_left_align(COLUMNS(cols), label); + gtk_widget_show(label); + } + gtk_container_add(GTK_CONTAINER(listitem), cols); + gtk_widget_show(cols); + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + if (ctrl->listbox.multisel) { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj); + } else { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_single_key), uc->adj); + } + gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event", + GTK_SIGNAL_FUNC(listitem_button_press), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event", + GTK_SIGNAL_FUNC(listitem_button_release), dp); + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + goto done; + } +#else + if (uc->listmodel) { + GtkTreeIter iter; + int i, cols; + + dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */ + gtk_list_store_append(uc->listmodel, &iter); + dp->flags &= ~FLAG_UPDATING_LISTBOX; + gtk_list_store_set(uc->listmodel, &iter, 0, id, -1); + + /* + * Now go through text and divide it into columns at the tabs, + * as necessary. + */ + cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + int collen = strcspn(text, "\t"); + char *tmpstr = snewn(collen+1, char); + memcpy(tmpstr, text, collen); + tmpstr[collen] = '\0'; + gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); + sfree(tmpstr); + text += collen; + if (*text) text++; + } + goto done; + } +#endif + assert(!"We shouldn't get here"); + done: + dp->flags &= ~FLAG_UPDATING_COMBO_LIST; +} + +int dlg_listbox_getid(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkObject *item; + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_OBJECT(g_list_nth_data(children, index)); + g_list_free(children); + + return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + GtkTreePath *path; + GtkTreeIter iter; + int ret; + + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); + gtk_tree_path_free(path); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkWidget *item, *activeitem; + int i; + int selected = -1; + + if (uc->menu) + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + else + activeitem = NULL; /* unnecessarily placate gcc */ + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL; + i++, children = children->next) { + if (uc->menu ? activeitem == item : + GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) { + if (selected == -1) + selected = i; + else + selected = -2; + } + } + g_list_free(children); + return selected < 0 ? -1 : selected; + } +#else + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)); + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + GtkTreeModel *model; + GList *sellist; + gint *indices; + int ret; + + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + if (gtk_tree_selection_count_selected_rows(treesel) != 1) + return -1; + + sellist = gtk_tree_selection_get_selected_rows(treesel, &model); + + assert(sellist && sellist->data); + path = sellist->data; + + if (gtk_tree_path_get_depth(path) != 1) { + ret = -1; + } else { + indices = gtk_tree_path_get_indices(path); + if (!indices) { + ret = -1; + } else { + ret = indices[0]; + } + } + + g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL); + g_list_free(sellist); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +int dlg_listbox_issel(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkWidget *item, *activeitem; + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->menu != NULL || uc->list != NULL); + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_WIDGET(g_list_nth_data(children, index)); + g_list_free(children); + + if (uc->menu) { + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + return item == activeitem; + } else { + return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED; + } + } +#else + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + int ret; + + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + path = gtk_tree_path_new_from_indices(index, -1); + ret = gtk_tree_selection_path_is_selected(treesel, path); + gtk_tree_path_free(path); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +void dlg_listbox_select(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->optmenu) { + gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index); + return; + } + if (uc->list) { + int nitems; + GList *items; + gdouble newtop, newbot; + + gtk_list_select_item(GTK_LIST(uc->list), index); + + /* + * Scroll the list box if necessary to ensure the newly + * selected item is visible. + */ + items = gtk_container_children(GTK_CONTAINER(uc->list)); + nitems = g_list_length(items); + if (nitems > 0) { + int modified = FALSE; + g_list_free(items); + newtop = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * index / nitems; + newbot = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * (index+1) / nitems; + if (uc->adj->value > newtop) { + modified = TRUE; + uc->adj->value = newtop; + } else if (uc->adj->value < newbot - uc->adj->page_size) { + modified = TRUE; + uc->adj->value = newbot - uc->adj->page_size; + } + if (modified) + gtk_adjustment_value_changed(uc->adj); + } + return; + } +#else + if (uc->combo) { + gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_selection_select_path(treesel, path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview), + path, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(path); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_text_set(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_TEXT); + assert(uc->text != NULL); + + gtk_label_set_text(GTK_LABEL(uc->text), text); +} + +void dlg_label_change(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + switch (uc->ctrl->generic.type) { + case CTRL_BUTTON: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->button.shortcut); + break; + case CTRL_CHECKBOX: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut); + break; + case CTRL_RADIO: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->radio.shortcut); + break; + case CTRL_EDITBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->editbox.shortcut); + break; + case CTRL_FILESELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fileselect.shortcut); + break; + case CTRL_FONTSELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fontselect.shortcut); + break; + case CTRL_LISTBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->listbox.shortcut); + break; + default: + assert(!"This shouldn't happen"); + break; + } +} + +void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fn->path before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *duppath = dupstr(fn->path); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); + sfree(duppath); +} + +Filename *dlg_filesel_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); +} + +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fs->name before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *dupname = dupstr(fs->name); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); + sfree(dupname); +} + +FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); +} + +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, void *dlg) +{ + /* + * Apparently we can't do this at all in GTK. GtkCList supports + * freeze and thaw, but not GtkList. Bah. + */ +} + +void dlg_update_done(union control *ctrl, void *dlg) +{ + /* + * Apparently we can't do this at all in GTK. GtkCList supports + * freeze and thaw, but not GtkList. Bah. + */ +} + +void dlg_set_focus(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + switch (ctrl->generic.type) { + case CTRL_CHECKBOX: + case CTRL_BUTTON: + /* Check boxes and buttons get the focus _and_ get toggled. */ + gtk_widget_grab_focus(uc->toplevel); + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + case CTRL_EDITBOX: + if (uc->entry) { + /* Anything containing an edit box gets that focused. */ + gtk_widget_grab_focus(uc->entry); + } +#if GTK_CHECK_VERSION(2,4,0) + else if (uc->combo) { + /* Failing that, there'll be a combo box. */ + gtk_widget_grab_focus(uc->combo); + } +#endif + break; + case CTRL_RADIO: + /* + * Radio buttons: we find the currently selected button and + * focus it. + */ + { + int i; + for (i = 0; i < ctrl->radio.nbuttons; i++) + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(uc->buttons[i]))) { + gtk_widget_grab_focus(uc->buttons[i]); + } + } + break; + case CTRL_LISTBOX: +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->optmenu) { + gtk_widget_grab_focus(uc->optmenu); + break; + } +#else + if (uc->combo) { + gtk_widget_grab_focus(uc->combo); + break; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { + /* + * For GTK-1 style list boxes, we tell it to focus one + * of its children, which appears to do the Right + * Thing. + */ + gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); + break; + } +#else + if (uc->treeview) { + gtk_widget_grab_focus(uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); + break; + } +} + +/* + * During event processing, you might well want to give an error + * indication to the user. dlg_beep() is a quick and easy generic + * error; dlg_error() puts up a message-box or equivalent. + */ +void dlg_beep(void *dlg) +{ + gdk_beep(); +} + +static void errmsg_button_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(GTK_WIDGET(data)); +} + +static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) +{ + gint x, y, w, h, dx, dy; + GtkRequisition req; + gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE); + gtk_widget_size_request(GTK_WIDGET(child), &req); + + gdk_window_get_origin(GTK_WIDGET(parent)->window, &x, &y); + gdk_window_get_size(GTK_WIDGET(parent)->window, &w, &h); + + /* + * One corner of the transient will be offset inwards, by 1/4 + * of the parent window's size, from the corresponding corner + * of the parent window. The corner will be chosen so as to + * place the transient closer to the centre of the screen; this + * should avoid transients going off the edge of the screen on + * a regular basis. + */ + if (x + w/2 < gdk_screen_width() / 2) + dx = x + w/4; /* work from left edges */ + else + dx = x + 3*w/4 - req.width; /* work from right edges */ + if (y + h/2 < gdk_screen_height() / 2) + dy = y + h/4; /* work from top edges */ + else + dy = y + 3*h/4 - req.height; /* work from bottom edges */ + gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy); +} + +void dlg_error_msg(void *dlg, char *msg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + GtkWidget *window, *hbox, *text, *ok; + + window = gtk_dialog_new(); + text = gtk_label_new(msg); + gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0); + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + hbox, FALSE, FALSE, 20); + gtk_widget_show(text); + gtk_widget_show(hbox); + gtk_window_set_title(GTK_WINDOW(window), "Error"); + gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); + ok = gtk_button_new_with_label("OK"); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), + ok, FALSE, FALSE, 0); + gtk_widget_show(ok); + GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(window), ok); + gtk_signal_connect(GTK_OBJECT(ok), "clicked", + GTK_SIGNAL_FUNC(errmsg_button_clicked), window); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window)); + set_transient_window_pos(dp->window, window); + gtk_widget_show(window); + gtk_main(); +} + +/* + * This function signals to the front end that the dialog's + * processing is completed, and passes an integer value (typically + * a success status). + */ +void dlg_end(void *dlg, int value) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + dp->retval = value; + gtk_widget_destroy(dp->window); +} + +void dlg_refresh(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc; + + if (ctrl) { + if (ctrl->generic.handler != NULL) + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + } else { + int i; + + for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) { + assert(uc->ctrl != NULL); + if (uc->ctrl->generic.handler != NULL) + uc->ctrl->generic.handler(uc->ctrl, dp, + dp->data, EVENT_REFRESH); + } + } +} + +void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + gdouble cvals[4]; + + GtkWidget *coloursel = + gtk_color_selection_dialog_new("Select a colour"); + GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); + + dp->coloursel_result.ok = FALSE; + + gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE); +#if GTK_CHECK_VERSION(2,0,0) + gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#else + gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#endif + cvals[0] = r / 255.0; + cvals[1] = g / 255.0; + cvals[2] = b / 255.0; + cvals[3] = 1.0; /* fully opaque! */ + gtk_color_selection_set_color(GTK_COLOR_SELECTION(ccs->colorsel), cvals); + + gtk_object_set_data(GTK_OBJECT(ccs->ok_button), "user-data", + (gpointer)coloursel); + gtk_object_set_data(GTK_OBJECT(ccs->cancel_button), "user-data", + (gpointer)coloursel); + gtk_object_set_data(GTK_OBJECT(coloursel), "user-data", (gpointer)uc); + gtk_signal_connect(GTK_OBJECT(ccs->ok_button), "clicked", + GTK_SIGNAL_FUNC(coloursel_ok), (gpointer)dp); + gtk_signal_connect(GTK_OBJECT(ccs->cancel_button), "clicked", + GTK_SIGNAL_FUNC(coloursel_cancel), (gpointer)dp); + gtk_signal_connect_object(GTK_OBJECT(ccs->ok_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)coloursel); + gtk_signal_connect_object(GTK_OBJECT(ccs->cancel_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)coloursel); + gtk_widget_show(coloursel); +} + +int dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + if (dp->coloursel_result.ok) { + *r = dp->coloursel_result.r; + *g = dp->coloursel_result.g; + *b = dp->coloursel_result.b; + return 1; + } else + return 0; +} + +/* ---------------------------------------------------------------------- + * Signal handlers while the dialog box is active. + */ + +static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, widget); + union control *focus; + + if (uc && uc->ctrl) + focus = uc->ctrl; + else + focus = NULL; + + if (focus != dp->currfocus) { + dp->lastfocus = dp->currfocus; + dp->currfocus = focus; + } + + return FALSE; +} + +static void button_clicked(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); +} + +static void button_toggled(GtkToggleButton *tb, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); +} + +static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + /* + * GtkEntry has a nasty habit of eating the Return key, which + * is unhelpful since it doesn't actually _do_ anything with it + * (it calls gtk_widget_activate, but our edit boxes never need + * activating). So I catch Return before GtkEntry sees it, and + * pass it straight on to the parent widget. Effect: hitting + * Return in an edit box will now activate the default button + * in the dialog just like it will everywhere else. + */ + if (event->keyval == GDK_Return && widget->parent != NULL) { + gboolean return_val; + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); + gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event", + event, &return_val); + return return_val; + } + return FALSE; +} + +static void editbox_changed(GtkEditable *ed, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) { + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); + } +} + +static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); + return FALSE; +} + +#if !GTK_CHECK_VERSION(2,0,0) + +/* + * GTK 1 list box event handlers. + */ + +static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, + gpointer data, int multiple) +{ + GtkAdjustment *adj = GTK_ADJUSTMENT(data); + + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || + event->keyval == GDK_Down || event->keyval == GDK_KP_Down || + event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up || + event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) { + /* + * Up, Down, PgUp or PgDn have been pressed on a ListItem + * in a list box. So, if the list box is single-selection: + * + * - if the list item in question isn't already selected, + * we simply select it. + * - otherwise, we find the next one (or next + * however-far-away) in whichever direction we're going, + * and select that. + * + in this case, we must also fiddle with the + * scrollbar to ensure the newly selected item is + * actually visible. + * + * If it's multiple-selection, we do all of the above + * except actually selecting anything, so we move the focus + * and fiddle the scrollbar to follow it. + */ + GtkWidget *list = item->parent; + + gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event"); + + if (!multiple && + GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { + gtk_list_select_child(GTK_LIST(list), item); + } else { + int direction = + (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? -1 : +1; + int step = + (event->keyval==GDK_Page_Down || + event->keyval==GDK_KP_Page_Down || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? 2 : 1; + int i, n; + GList *children, *chead; + + chead = children = gtk_container_children(GTK_CONTAINER(list)); + + n = g_list_length(children); + + if (step == 2) { + /* + * Figure out how many list items to a screenful, + * and adjust the step appropriately. + */ + step = 0.5 + adj->page_size * n / (adj->upper - adj->lower); + step--; /* go by one less than that */ + } + + i = 0; + while (children != NULL) { + if (item == children->data) + break; + children = children->next; + i++; + } + + while (step > 0) { + if (direction < 0 && i > 0) + children = children->prev, i--; + else if (direction > 0 && i < n-1) + children = children->next, i++; + step--; + } + + if (children && children->data) { + if (!multiple) + gtk_list_select_child(GTK_LIST(list), + GTK_WIDGET(children->data)); + gtk_widget_grab_focus(GTK_WIDGET(children->data)); + gtk_adjustment_clamp_page + (adj, + adj->lower + (adj->upper-adj->lower) * i / n, + adj->lower + (adj->upper-adj->lower) * (i+1) / n); + } + + g_list_free(chead); + } + return TRUE; + } + + return FALSE; +} + +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, FALSE); +} + +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, TRUE); +} + +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + switch (event->type) { + default: + case GDK_BUTTON_PRESS: uc->nclicks = 1; break; + case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; + case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; + } + return FALSE; +} + +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + if (uc->nclicks>1) { + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + return TRUE; + } + return FALSE; +} + +static void list_selchange(GtkList *list, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list)); + if (!uc) return; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) +{ + int index = dlg_listbox_index(uc->ctrl, dp); + GList *children = gtk_container_children(GTK_CONTAINER(uc->list)); + GtkWidget *child; + + if ((index < 0) || + (index == 0 && direction < 0) || + (index == g_list_length(children)-1 && direction > 0)) { + gdk_beep(); + return; + } + + child = g_list_nth_data(children, index); + gtk_widget_ref(child); + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + g_list_free(children); + + children = NULL; + children = g_list_append(children, child); + gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction); + gtk_list_select_item(GTK_LIST(uc->list), index + direction); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); +} + +static void draglist_up(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, -1); +} + +static void draglist_down(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, +1); +} + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + +/* + * GTK 2 list box event handlers. + */ + +static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); +} + +static void listbox_selchange(GtkTreeSelection *treeselection, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +struct draglist_valchange_ctx { + struct uctrl *uc; + struct dlgparam *dp; +}; + +static gboolean draglist_valchange(gpointer data) +{ + struct draglist_valchange_ctx *ctx = + (struct draglist_valchange_ctx *)data; + + ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp, + ctx->dp->data, EVENT_VALCHANGE); + + sfree(ctx); + + return FALSE; +} + +static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer tree; + struct uctrl *uc; + + if (dp->flags & FLAG_UPDATING_LISTBOX) + return; /* not a user drag operation */ + + tree = g_object_get_data(G_OBJECT(treemodel), "user-data"); + uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) { + /* + * We should cause EVENT_VALCHANGE on the list box, now + * that its rows have been reordered. However, the GTK 2 + * docs say that at the point this signal is received the + * new row might not have actually been filled in yet. + * + * (So what smegging use is it then, eh? Don't suppose it + * occurred to you at any point that letting the + * application know _after_ the reordering was compelete + * might be helpful to someone?) + * + * To get round this, I schedule an idle function, which I + * hope won't be called until the main event loop is + * re-entered after the drag-and-drop handler has finished + * furtling with the list store. + */ + struct draglist_valchange_ctx *ctx = + snew(struct draglist_valchange_ctx); + ctx->uc = uc; + ctx->dp = dp; + g_idle_add(draglist_valchange, ctx); + } +} + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + +#if !GTK_CHECK_VERSION(2,4,0) + +static void menuitem_activate(GtkMenuItem *item, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkWidget *menushell = GTK_WIDGET(item)->parent; + gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#else + +static void droplist_selchange(GtkComboBox *combo, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#endif /* !GTK_CHECK_VERSION(2,4,0) */ + +static void filesel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data"); + const char *name = gtk_file_selection_get_filename + (GTK_FILE_SELECTION(filesel)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); +} + +static void fontsel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + +#if !GTK_CHECK_VERSION(2,0,0) + + gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data"); + const char *name = gtk_font_selection_dialog_get_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + +#else + + unifontsel *fontsel = (unifontsel *)gtk_object_get_data + (GTK_OBJECT(button), "user-data"); + struct uctrl *uc = (struct uctrl *)fontsel->user_data; + char *name = unifontsel_get_name(fontsel); + assert(name); /* should always be ok after OK pressed */ + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + sfree(name); + +#endif +} + +static void coloursel_ok(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data"); + gdouble cvals[4]; + gtk_color_selection_get_color + (GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(coloursel)->colorsel), + cvals); + dp->coloursel_result.r = (int) (255 * cvals[0]); + dp->coloursel_result.g = (int) (255 * cvals[1]); + dp->coloursel_result.b = (int) (255 * cvals[2]); + dp->coloursel_result.ok = TRUE; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); +} + +static void coloursel_cancel(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data"); + dp->coloursel_result.ok = FALSE; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); +} + +static void filefont_clicked(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + + if (uc->ctrl->generic.type == CTRL_FILESELECT) { + GtkWidget *filesel = + gtk_file_selection_new(uc->ctrl->fileselect.title); + gtk_window_set_modal(GTK_WINDOW(filesel), TRUE); + gtk_object_set_data + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", + (gpointer)filesel); + gtk_object_set_data(GTK_OBJECT(filesel), "user-data", (gpointer)uc); + gtk_signal_connect + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + GTK_SIGNAL_FUNC(filesel_ok), (gpointer)dp); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel); + gtk_widget_show(filesel); + } + + if (uc->ctrl->generic.type == CTRL_FONTSELECT) { + const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); + +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * Use the GTK 1 standard font selector. + */ + + gchar *spacings[] = { "c", "m", NULL }; + GtkWidget *fontsel = + gtk_font_selection_dialog_new("Select a font"); + gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); + gtk_font_selection_dialog_set_filter + (GTK_FONT_SELECTION_DIALOG(fontsel), + GTK_FONT_FILTER_BASE, GTK_FONT_ALL, + NULL, NULL, NULL, NULL, spacings, NULL); + if (!gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { + /* + * If the font name wasn't found as it was, try opening + * it and extracting its FONT property. This should + * have the effect of mapping short aliases into true + * XLFDs. + */ + GdkFont *font = gdk_font_load(fontname); + if (font) { + XFontStruct *xfs = GDK_FONT_XFONT(font); + Display *disp = GDK_FONT_XDISPLAY(font); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + gdk_font_ref(font); + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name) + gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), name); + } + gdk_font_unref(font); + } + } + gtk_object_set_data + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); + gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc); + gtk_signal_connect + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_widget_show(fontsel); + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + + /* + * Use the unifontsel code provided in gtkfont.c. + */ + + unifontsel *fontsel = unifontsel_new("Select a font"); + + gtk_window_set_modal(fontsel->window, TRUE); + unifontsel_set_name(fontsel, fontname); + + gtk_object_set_data(GTK_OBJECT(fontsel->ok_button), + "user-data", (gpointer)fontsel); + fontsel->user_data = uc; + gtk_signal_connect(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + gtk_signal_connect_object(GTK_OBJECT(fontsel->cancel_button),"clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + + gtk_widget_show(GTK_WIDGET(fontsel->window)); + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + + } +} + +static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, widget); + + gtk_widget_set_usize(uc->text, alloc->width, -1); + gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); + gtk_signal_disconnect(GTK_OBJECT(uc->text), uc->textsig); +} + +/* ---------------------------------------------------------------------- + * This function does the main layout work: it reads a controlset, + * it creates the relevant GTK controls, and returns a GtkWidget + * containing the result. (This widget might be a title of some + * sort, it might be a Columns containing many controls, or it + * might be a GtkFrame containing a Columns; whatever it is, it's + * definitely a GtkWidget and should probably be added to a + * GtkVbox.) + * + * `win' is required for setting the default button. If it is + * non-NULL, all buttons created will be default-capable (so they + * have extra space round them for the default highlight). + */ +GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, + struct controlset *s, GtkWindow *win) +{ + Columns *cols; + GtkWidget *ret; + int i; + + if (!s->boxname && s->boxtitle) { + /* This controlset is a panel title. */ + return gtk_label_new(s->boxtitle); + } + + /* + * Otherwise, we expect to be laying out actual controls, so + * we'll start by creating a Columns for the purpose. + */ + cols = COLUMNS(columns_new(4)); + ret = GTK_WIDGET(cols); + gtk_widget_show(ret); + + /* + * Create a containing frame if we have a box name. + */ + if (*s->boxname) { + ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */ + gtk_container_set_border_width(GTK_CONTAINER(cols), 4); + gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols)); + gtk_widget_show(ret); + } + + /* + * Now iterate through the controls themselves, create them, + * and add them to the Columns. + */ + for (i = 0; i < s->ncontrols; i++) { + union control *ctrl = s->ctrls[i]; + struct uctrl *uc; + int left = FALSE; + GtkWidget *w = NULL; + + switch (ctrl->generic.type) { + case CTRL_COLUMNS: + { + static const int simplecols[1] = { 100 }; + columns_set_cols(cols, ctrl->columns.ncols, + (ctrl->columns.percentages ? + ctrl->columns.percentages : simplecols)); + } + continue; /* no actual control created */ + case CTRL_TABDELAY: + { + struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl); + if (uc) + columns_taborder_last(cols, uc->toplevel); + } + continue; /* no actual control created */ + } + + uc = snew(struct uctrl); + uc->ctrl = ctrl; + uc->buttons = NULL; + uc->entry = NULL; +#if !GTK_CHECK_VERSION(2,4,0) + uc->list = uc->menu = uc->optmenu = NULL; +#else + uc->combo = NULL; +#endif +#if GTK_CHECK_VERSION(2,0,0) + uc->treeview = NULL; + uc->listmodel = NULL; +#endif + uc->button = uc->text = NULL; + uc->label = NULL; + uc->nclicks = 0; + + switch (ctrl->generic.type) { + case CTRL_BUTTON: + w = gtk_button_new_with_label(ctrl->generic.label); + if (win) { + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + if (ctrl->button.isdefault) + gtk_window_set_default(win, w); + if (ctrl->button.iscancel) + dp->cancelbutton = w; + } + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(button_clicked), dp); + gtk_signal_connect(GTK_OBJECT(w), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + shortcut_add(scs, GTK_BIN(w)->child, ctrl->button.shortcut, + SHORTCUT_UCTRL, uc); + break; + case CTRL_CHECKBOX: + w = gtk_check_button_new_with_label(ctrl->generic.label); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(button_toggled), dp); + gtk_signal_connect(GTK_OBJECT(w), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + shortcut_add(scs, GTK_BIN(w)->child, ctrl->checkbox.shortcut, + SHORTCUT_UCTRL, uc); + left = TRUE; + break; + case CTRL_RADIO: + /* + * Radio buttons get to go inside their own Columns, no + * matter what. + */ + { + gint i, *percentages; + GSList *group; + + w = columns_new(0); + if (ctrl->generic.label) { + GtkWidget *label = gtk_label_new(ctrl->generic.label); + columns_add(COLUMNS(w), label, 0, 1); + columns_force_left_align(COLUMNS(w), label); + gtk_widget_show(label); + shortcut_add(scs, label, ctrl->radio.shortcut, + SHORTCUT_UCTRL, uc); + uc->label = label; + } + percentages = g_new(gint, ctrl->radio.ncolumns); + for (i = 0; i < ctrl->radio.ncolumns; i++) { + percentages[i] = + ((100 * (i+1) / ctrl->radio.ncolumns) - + 100 * i / ctrl->radio.ncolumns); + } + columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns, + percentages); + g_free(percentages); + group = NULL; + + uc->nbuttons = ctrl->radio.nbuttons; + uc->buttons = snewn(uc->nbuttons, GtkWidget *); + + for (i = 0; i < ctrl->radio.nbuttons; i++) { + GtkWidget *b; + gint colstart; + + b = (gtk_radio_button_new_with_label + (group, ctrl->radio.buttons[i])); + uc->buttons[i] = b; + group = gtk_radio_button_group(GTK_RADIO_BUTTON(b)); + colstart = i % ctrl->radio.ncolumns; + columns_add(COLUMNS(w), b, colstart, + (i == ctrl->radio.nbuttons-1 ? + ctrl->radio.ncolumns - colstart : 1)); + columns_force_left_align(COLUMNS(w), b); + gtk_widget_show(b); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(button_toggled), dp); + gtk_signal_connect(GTK_OBJECT(b), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + if (ctrl->radio.shortcuts) { + shortcut_add(scs, GTK_BIN(b)->child, + ctrl->radio.shortcuts[i], + SHORTCUT_UCTRL, uc); + } + } + } + break; + case CTRL_EDITBOX: + { + GtkRequisition req; + GtkWidget *signalobject; + + if (ctrl->editbox.has_list) { +#if !GTK_CHECK_VERSION(2,4,0) + /* + * GTK 1 combo box. + */ + w = gtk_combo_new(); + gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE); + uc->entry = GTK_COMBO(w)->entry; + uc->list = GTK_COMBO(w)->list; + signalobject = uc->entry; +#else + /* + * GTK 2 combo box. + */ + uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, + G_TYPE_STRING); + w = gtk_combo_box_entry_new_with_model + (GTK_TREE_MODEL(uc->listmodel), 1); + /* We cannot support password combo boxes. */ + assert(!ctrl->editbox.password); + uc->combo = w; + signalobject = uc->combo; +#endif + } else { + w = gtk_entry_new(); + if (ctrl->editbox.password) + gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); + uc->entry = w; + signalobject = w; + } + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(signalobject), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + /* + * Edit boxes, for some strange reason, have a minimum + * width of 150 in GTK 1.2. We don't want this - we'd + * rather the edit boxes acquired their natural width + * from the column layout of the rest of the box. + * + * Also, while we're here, we'll squirrel away the + * edit box height so we can use that to centre its + * label vertically beside it. + */ + gtk_widget_size_request(w, &req); + gtk_widget_set_usize(w, 10, req.height); + + if (ctrl->generic.label) { + GtkWidget *label, *container; + + label = gtk_label_new(ctrl->generic.label); + + shortcut_add(scs, label, ctrl->editbox.shortcut, + SHORTCUT_FOCUS, uc->entry); + + container = columns_new(4); + if (ctrl->editbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->editbox.percentwidth; + percentages[0] = 100 - ctrl->editbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + /* Centre the label vertically. */ + gtk_widget_set_usize(label, -1, req.height); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + } + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + { + GtkWidget *ww; + GtkRequisition req; + char *browsebtn = + (ctrl->generic.type == CTRL_FILESELECT ? + "Browse..." : "Change..."); + + gint percentages[] = { 75, 25 }; + w = columns_new(4); + columns_set_cols(COLUMNS(w), 2, percentages); + + if (ctrl->generic.label) { + ww = gtk_label_new(ctrl->generic.label); + columns_add(COLUMNS(w), ww, 0, 2); + columns_force_left_align(COLUMNS(w), ww); + gtk_widget_show(ww); + shortcut_add(scs, ww, + (ctrl->generic.type == CTRL_FILESELECT ? + ctrl->fileselect.shortcut : + ctrl->fontselect.shortcut), + SHORTCUT_UCTRL, uc); + uc->label = ww; + } + + uc->entry = ww = gtk_entry_new(); + gtk_widget_size_request(ww, &req); + gtk_widget_set_usize(ww, 10, req.height); + columns_add(COLUMNS(w), ww, 0, 1); + gtk_widget_show(ww); + + uc->button = ww = gtk_button_new_with_label(browsebtn); + columns_add(COLUMNS(w), ww, 1, 1); + gtk_widget_show(ww); + + gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(ww), "clicked", + GTK_SIGNAL_FUNC(filefont_clicked), dp); + } + break; + case CTRL_LISTBOX: + +#if GTK_CHECK_VERSION(2,0,0) + /* + * First construct the list data store, with the right + * number of columns. + */ +# if !GTK_CHECK_VERSION(2,4,0) + /* (For GTK 2.0 to 2.3, we do this for full listboxes only, + * because combo boxes are still done the old GTK1 way.) */ + if (ctrl->listbox.height > 0) +# endif + { + GType *types; + int i; + int cols; + + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + types = snewn(1 + cols, GType); + + types[0] = G_TYPE_INT; + for (i = 0; i < cols; i++) + types[i+1] = G_TYPE_STRING; + + uc->listmodel = gtk_list_store_newv(1 + cols, types); + + sfree(types); + } +#endif + + /* + * See if it's a drop-down list (non-editable combo + * box). + */ + if (ctrl->listbox.height == 0) { +#if !GTK_CHECK_VERSION(2,4,0) + /* + * GTK1 and early-GTK2 option-menu style of + * drop-down list. + */ + uc->optmenu = w = gtk_option_menu_new(); + uc->menu = gtk_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu); + gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data", + (gpointer)uc->optmenu); + gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); +#else + /* + * Late-GTK2 style using a GtkComboBox. + */ + GtkCellRenderer *cr; + + /* + * Create a non-editable GtkComboBox (that is, not + * its subclass GtkComboBoxEntry). + */ + w = gtk_combo_box_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + uc->combo = w; + + /* + * Tell it how to render a list item (i.e. which + * column to look at in the list model). + */ + cr = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, + "text", 1, NULL); + + /* + * And tell it to notify us when the selection + * changes. + */ + g_signal_connect(G_OBJECT(w), "changed", + G_CALLBACK(droplist_selchange), dp); +#endif + } else { +#if !GTK_CHECK_VERSION(2,0,0) + /* + * GTK1-style full list box. + */ + uc->list = gtk_list_new(); + if (ctrl->listbox.multisel == 2) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_EXTENDED); + } else if (ctrl->listbox.multisel == 1) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_MULTIPLE); + } else { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_SINGLE); + } + w = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w), + uc->list); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + uc->adj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW(w)); + + gtk_widget_show(uc->list); + gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed", + GTK_SIGNAL_FUNC(list_selchange), dp); + gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + /* + * Adjust the height of the scrolled window to the + * minimum given by the height parameter. + * + * This piece of guesswork is a horrid hack based + * on looking inside the GTK 1.2 sources + * (specifically gtkviewport.c, which appears to be + * the widget which provides the border around the + * scrolling area). Anyone lets me know how I can + * do this in a way which isn't at risk from GTK + * upgrades, I'd be grateful. + */ + { + int edge; + edge = GTK_WIDGET(uc->list)->style->klass->ythickness; + gtk_widget_set_usize(w, 10, + 2*edge + (ctrl->listbox.height * + get_listitemheight(w))); + } + + if (ctrl->listbox.draglist) { + /* + * GTK doesn't appear to make it easy to + * implement a proper draggable list; so + * instead I'm just going to have to put an Up + * and a Down button to the right of the actual + * list box. Ah well. + */ + GtkWidget *cols, *button; + static const gint percentages[2] = { 80, 20 }; + + cols = columns_new(4); + columns_set_cols(COLUMNS(cols), 2, percentages); + columns_add(COLUMNS(cols), w, 0, 1); + gtk_widget_show(w); + button = gtk_button_new_with_label("Up"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_up), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + button = gtk_button_new_with_label("Down"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_down), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + w = cols; + } +#else + /* + * GTK2 treeview-based full list box. + */ + GtkTreeSelection *sel; + + /* + * Create the list box itself, its columns, and + * its containing scrolled window. + */ + w = gtk_tree_view_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + g_object_set_data(G_OBJECT(uc->listmodel), "user-data", + (gpointer)w); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); + gtk_tree_selection_set_mode + (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : + GTK_SELECTION_SINGLE); + uc->treeview = w; + gtk_signal_connect(GTK_OBJECT(w), "row-activated", + GTK_SIGNAL_FUNC(listbox_doubleclick), dp); + g_signal_connect(G_OBJECT(sel), "changed", + G_CALLBACK(listbox_selchange), dp); + + if (ctrl->listbox.draglist) { + gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE); + g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted", + G_CALLBACK(listbox_reorder), dp); + } + + { + int i; + int cols; + + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + GtkTreeViewColumn *column; + GtkCellRenderer *cellrend; + /* + * It appears that GTK 2 doesn't leave us any + * particularly sensible way to honour the + * "percentages" specification in the ctrl + * structure. + */ + cellrend = gtk_cell_renderer_text_new(); + if (!ctrl->listbox.hscroll) { + gtk_object_set(GTK_OBJECT(cellrend), + "ellipsize", PANGO_ELLIPSIZE_END, + "ellipsize-set", TRUE, + NULL); + } + column = gtk_tree_view_column_new_with_attributes + ("heading", cellrend, "text", i+1, (char *)NULL); + gtk_tree_view_column_set_sizing + (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + } + } + + { + GtkWidget *scroll; + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type + (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); + gtk_widget_show(w); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request + (scroll, -1, + ctrl->listbox.height * get_listitemheight(w)); + + w = scroll; + } +#endif + } + + if (ctrl->generic.label) { + GtkWidget *label, *container; + GtkRequisition req; + + label = gtk_label_new(ctrl->generic.label); + + shortcut_add(scs, label, ctrl->listbox.shortcut, + SHORTCUT_FOCUS, w); + + container = columns_new(4); + if (ctrl->listbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->listbox.percentwidth; + percentages[0] = 100 - ctrl->listbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + /* Centre the label vertically. */ + gtk_widget_size_request(w, &req); + gtk_widget_set_usize(label, -1, req.height); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + + break; + case CTRL_TEXT: + /* + * Wrapping text widgets don't sit well with the GTK + * layout model, in which widgets state a minimum size + * and the whole window then adjusts to the smallest + * size it can sensibly take given its contents. A + * wrapping text widget _has_ no clear minimum size; + * instead it has a range of possibilities. It can be + * one line deep but 2000 wide, or two lines deep and + * 1000 pixels, or three by 867, or four by 500 and so + * on. It can be as short as you like provided you + * don't mind it being wide, or as narrow as you like + * provided you don't mind it being tall. + * + * Therefore, it fits very badly into the layout model. + * Hence the only thing to do is pick a width and let + * it choose its own number of lines. To do this I'm + * going to cheat a little. All new wrapping text + * widgets will be created with a minimal text content + * "X"; then, after the rest of the dialog box is set + * up and its size calculated, the text widgets will be + * told their width and given their real text, which + * will cause the size to be recomputed in the y + * direction (because many of them will expand to more + * than one line). + */ + uc->text = w = gtk_label_new("X"); + gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.0); + gtk_label_set_line_wrap(GTK_LABEL(w), TRUE); + uc->textsig = + gtk_signal_connect(GTK_OBJECT(w), "size-allocate", + GTK_SIGNAL_FUNC(label_sizealloc), dp); + break; + } + + assert(w != NULL); + + columns_add(cols, w, + COLUMN_START(ctrl->generic.column), + COLUMN_SPAN(ctrl->generic.column)); + if (left) + columns_force_left_align(cols, w); + gtk_widget_show(w); + + uc->toplevel = w; + dlg_add_uctrl(dp, uc); + } + + return ret; +} + +struct selparam { + struct dlgparam *dp; + GtkNotebook *panels; + GtkWidget *panel; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeitem; +#else + int depth; + GtkTreePath *treepath; +#endif + struct Shortcuts shortcuts; +}; + +#if GTK_CHECK_VERSION(2,0,0) +static void treeselection_changed(GtkTreeSelection *treeselection, + gpointer data) +{ + struct selparam *sps = (struct selparam *)data, *sp; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + gint spindex; + gint page_num; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1); + sp = &sps[spindex]; + + page_num = gtk_notebook_page_num(sp->panels, sp->panel); + gtk_notebook_set_page(sp->panels, page_num); + + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; +} +#else +static void treeitem_sel(GtkItem *item, gpointer data) +{ + struct selparam *sp = (struct selparam *)data; + gint page_num; + + page_num = gtk_notebook_page_num(sp->panels, sp->panel); + gtk_notebook_set_page(sp->panels, page_num); + + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; + sp->dp->currtreeitem = sp->treeitem; +} +#endif + +static void window_destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +#if !GTK_CHECK_VERSION(2,0,0) +static int tree_grab_focus(struct dlgparam *dp) +{ + int i, f; + + /* + * See if any of the treeitems has the focus. + */ + f = -1; + for (i = 0; i < dp->ntreeitems; i++) + if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) { + f = i; + break; + } + + if (f >= 0) + return FALSE; + else { + gtk_widget_grab_focus(dp->currtreeitem); + return TRUE; + } +} + +gint tree_focus(GtkContainer *container, GtkDirectionType direction, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + gtk_signal_emit_stop_by_name(GTK_OBJECT(container), "focus"); + /* + * If there's a focused treeitem, we return FALSE to cause the + * focus to move on to some totally other control. If not, we + * focus the selected one. + */ + return tree_grab_focus(dp); +} +#endif + +int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + if (event->keyval == GDK_Escape && dp->cancelbutton) { + gtk_signal_emit_by_name(GTK_OBJECT(dp->cancelbutton), "clicked"); + return TRUE; + } + + if ((event->state & GDK_MOD1_MASK) && + (unsigned char)event->string[0] > 0 && + (unsigned char)event->string[0] <= 127) { + int schr = (unsigned char)event->string[0]; + struct Shortcut *sc = &dp->shortcuts->sc[schr]; + + switch (sc->action) { + case SHORTCUT_TREE: +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_grab_focus(sc->widget); +#else + tree_grab_focus(dp); +#endif + break; + case SHORTCUT_FOCUS: + gtk_widget_grab_focus(sc->widget); + break; + case SHORTCUT_UCTRL: + /* + * We must do something sensible with a uctrl. + * Precisely what this is depends on the type of + * control. + */ + switch (sc->uc->ctrl->generic.type) { + case CTRL_CHECKBOX: + case CTRL_BUTTON: + /* Check boxes and buttons get the focus _and_ get toggled. */ + gtk_widget_grab_focus(sc->uc->toplevel); + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->toplevel), + "clicked"); + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + /* File/font selectors have their buttons pressed (ooer), + * and focus transferred to the edit box. */ + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->button), + "clicked"); + gtk_widget_grab_focus(sc->uc->entry); + break; + case CTRL_RADIO: + /* + * Radio buttons are fun, because they have + * multiple shortcuts. We must find whether the + * activated shortcut is the shortcut for the whole + * group, or for a particular button. In the former + * case, we find the currently selected button and + * focus it; in the latter, we focus-and-click the + * button whose shortcut was pressed. + */ + if (schr == sc->uc->ctrl->radio.shortcut) { + int i; + for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) { + gtk_widget_grab_focus(sc->uc->buttons[i]); + } + } else if (sc->uc->ctrl->radio.shortcuts) { + int i; + for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) + if (schr == sc->uc->ctrl->radio.shortcuts[i]) { + gtk_widget_grab_focus(sc->uc->buttons[i]); + gtk_signal_emit_by_name + (GTK_OBJECT(sc->uc->buttons[i]), "clicked"); + } + } + break; + case CTRL_LISTBOX: + +#if !GTK_CHECK_VERSION(2,4,0) + if (sc->uc->optmenu) { + GdkEventButton bev; + gint returnval; + + gtk_widget_grab_focus(sc->uc->optmenu); + /* Option menus don't work using the "clicked" signal. + * We need to manufacture a button press event :-/ */ + bev.type = GDK_BUTTON_PRESS; + bev.button = 1; + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu), + "button_press_event", + &bev, &returnval); + break; + } +#else + if (sc->uc->combo) { + gtk_widget_grab_focus(sc->uc->combo); + gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo)); + break; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (sc->uc->list) { + /* + * For GTK-1 style list boxes, we tell it to + * focus one of its children, which appears to + * do the Right Thing. + */ + gtk_container_focus(GTK_CONTAINER(sc->uc->list), + GTK_DIR_TAB_FORWARD); + break; + } +#else + if (sc->uc->treeview) { + gtk_widget_grab_focus(sc->uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); + break; + } + break; + } + } + + return FALSE; +} + +#if !GTK_CHECK_VERSION(2,0,0) +int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || + event->keyval == GDK_Down || event->keyval == GDK_KP_Down) { + int dir, i, j = -1; + for (i = 0; i < dp->ntreeitems; i++) + if (widget == dp->treeitems[i]) + break; + if (i < dp->ntreeitems) { + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + dir = -1; + else + dir = +1; + + while (1) { + i += dir; + if (i < 0 || i >= dp->ntreeitems) + break; /* nothing in that dir to select */ + /* + * Determine if this tree item is visible. + */ + { + GtkWidget *w = dp->treeitems[i]; + int vis = TRUE; + while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) { + if (!GTK_WIDGET_VISIBLE(w)) { + vis = FALSE; + break; + } + w = w->parent; + } + if (vis) { + j = i; /* got one */ + break; + } + } + } + } + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + if (j >= 0) { + gtk_signal_emit_by_name(GTK_OBJECT(dp->treeitems[j]), "toggle"); + gtk_widget_grab_focus(dp->treeitems[j]); + } + return TRUE; + } + + /* + * It's nice for Left and Right to expand and collapse tree + * branches. + */ + if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + gtk_tree_item_collapse(GTK_TREE_ITEM(widget)); + return TRUE; + } + if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + gtk_tree_item_expand(GTK_TREE_ITEM(widget)); + return TRUE; + } + + return FALSE; +} +#endif + +static void shortcut_highlight(GtkWidget *labelw, int chr) +{ + GtkLabel *label = GTK_LABEL(labelw); + gchar *currstr, *pattern; + int i; + + gtk_label_get(label, &currstr); + for (i = 0; currstr[i]; i++) + if (tolower((unsigned char)currstr[i]) == chr) { + GtkRequisition req; + + pattern = dupprintf("%*s_", i, ""); + + gtk_widget_size_request(GTK_WIDGET(label), &req); + gtk_label_set_pattern(label, pattern); + gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height); + + sfree(pattern); + break; + } +} + +void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, + int chr, int action, void *ptr) +{ + if (chr == NO_SHORTCUT) + return; + + chr = tolower((unsigned char)chr); + + assert(scs->sc[chr].action == SHORTCUT_EMPTY); + + scs->sc[chr].action = action; + + if (action == SHORTCUT_FOCUS) { + scs->sc[chr].uc = NULL; + scs->sc[chr].widget = (GtkWidget *)ptr; + } else { + scs->sc[chr].widget = NULL; + scs->sc[chr].uc = (struct uctrl *)ptr; + } + + shortcut_highlight(labelw, chr); +} + +int get_listitemheight(GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *listitem = gtk_list_item_new_with_label("foo"); + GtkRequisition req; + gtk_widget_size_request(listitem, &req); + gtk_object_sink(GTK_OBJECT(listitem)); + return req.height; +#else + int height; + GtkCellRenderer *cr = gtk_cell_renderer_text_new(); + gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); + g_object_ref(G_OBJECT(cr)); + gtk_object_sink(GTK_OBJECT(cr)); + g_object_unref(G_OBJECT(cr)); + return height; +#endif +} + +void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * In GTK 1, laying out the buttons at the bottom of the + * configuration box is nice and easy, because a GtkDialog's + * action_area is a GtkHBox which stretches to cover the full + * width of the dialog. So we just put our Columns widget + * straight into that hbox, and it ends up just where we want + * it. + */ + gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0); + +#else + /* + * In GTK 2, the action area is now a GtkHButtonBox and its + * layout behaviour seems to be different: it doesn't stretch + * to cover the full width of the window, but instead finds its + * own preferred width and right-aligns that within the window. + * This isn't what we want, because we have both left-aligned + * and right-aligned buttons coming out of the above call to + * layout_ctrls(), and right-aligning the whole thing will + * result in the former being centred and looking weird. + * + * So instead we abandon the dialog's action area completely: + * we gtk_widget_hide() it in the below code, and we also call + * gtk_dialog_set_has_separator() to remove the separator above + * it. We then insert our own action area into the end of the + * dialog's main vbox, and add our own separator above that. + * + * (Ideally, if we were a native GTK app, we would use the + * GtkHButtonBox's _own_ innate ability to support one set of + * buttons being right-aligned and one left-aligned. But to do + * that here, we would have to either (a) pick apart our cross- + * platform layout structures and treat them specially for this + * particular set of controls, which would be painful, or else + * (b) develop a special and simpler cross-platform + * representation for these particular controls, and introduce + * special-case code into all the _other_ platforms to handle + * it. Neither appeals. Therefore, I regretfully discard the + * GTKHButtonBox and go it alone.) + */ + + GtkWidget *align; + align = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(align), w); + /* + * The purpose of this GtkAlignment is to provide padding + * around the buttons. The padding we use is twice the padding + * used in our GtkColumns, because we nest two GtkColumns most + * of the time (one separating the tree view from the main + * controls, and another for the main controls themselves). + */ +#if GTK_CHECK_VERSION(2,4,0) + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); +#endif + gtk_widget_show(align); + gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0); + w = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0); + gtk_widget_show(w); + gtk_widget_hide(dlg->action_area); + gtk_dialog_set_has_separator(dlg, FALSE); +#endif +} + +int do_config_box(const char *title, Conf *conf, int midsession, + int protcfginfo) +{ + GtkWidget *window, *hbox, *vbox, *cols, *label, + *tree, *treescroll, *panels, *panelvbox; + int index, level, protocol; + struct controlbox *ctrlbox; + char *path; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeStore *treestore; + GtkCellRenderer *treerenderer; + GtkTreeViewColumn *treecolumn; + GtkTreeSelection *treeselection; + GtkTreeIter treeiterlevels[8]; +#else + GtkTreeItem *treeitemlevels[8]; + GtkTree *treelevels[8]; +#endif + struct dlgparam dp; + struct Shortcuts scs; + + struct selparam *selparams = NULL; + int nselparams = 0, selparamsize = 0; + + dlg_init(&dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + window = gtk_dialog_new(); + + ctrlbox = ctrl_new_box(); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(ctrlbox, midsession, protocol, protcfginfo); + unix_setup_config_box(ctrlbox, midsession, protocol); + gtk_setup_config_box(ctrlbox, midsession, window); + + gtk_window_set_title(GTK_WINDOW(window), title); + hbox = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + gtk_widget_show(hbox); + vbox = gtk_vbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show(vbox); + cols = columns_new(4); + gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0); + gtk_widget_show(cols); + label = gtk_label_new("Category:"); + columns_add(COLUMNS(cols), label, 0, 1); + columns_force_left_align(COLUMNS(cols), label); + gtk_widget_show(label); + treescroll = gtk_scrolled_window_new(NULL, NULL); +#if GTK_CHECK_VERSION(2,0,0) + treestore = gtk_tree_store_new + (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT); + tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE); + treerenderer = gtk_cell_renderer_text_new(); + treecolumn = gtk_tree_view_column_new_with_attributes + ("Label", treerenderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn); + treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE); + gtk_container_add(GTK_CONTAINER(treescroll), tree); +#else + tree = gtk_tree_new(); + gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM); + gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE); + gtk_signal_connect(GTK_OBJECT(tree), "focus", + GTK_SIGNAL_FUNC(tree_focus), &dp); +#endif + gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), &dp); + shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree); + gtk_widget_show(treescroll); + gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0); + panels = gtk_notebook_new(); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0); + gtk_widget_show(panels); + + panelvbox = NULL; + path = NULL; + level = 0; + for (index = 0; index < ctrlbox->nctrlsets; index++) { + struct controlset *s = ctrlbox->ctrlsets[index]; + GtkWidget *w; + + if (!*s->pathname) { + w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); + + set_dialog_action_area(GTK_DIALOG(window), w); + } else { + int j = path ? ctrl_path_compare(s->pathname, path) : 0; + if (j != INT_MAX) { /* add to treeview, start new panel */ + char *c; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeIter treeiter; +#else + GtkWidget *treeitem; +#endif + int first; + + /* + * We expect never to find an implicit path + * component. For example, we expect never to see + * A/B/C followed by A/D/E, because that would + * _implicitly_ create A/D. All our path prefixes + * are expected to contain actual controls and be + * selectable in the treeview; so we would expect + * to see A/D _explicitly_ before encountering + * A/D/E. + */ + assert(j == ctrl_path_elements(s->pathname) - 1); + + c = strrchr(s->pathname, '/'); + if (!c) + c = s->pathname; + else + c++; + + path = s->pathname; + + first = (panelvbox == NULL); + + panelvbox = gtk_vbox_new(FALSE, 4); + gtk_widget_show(panelvbox); + gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox, + NULL); + if (first) { + gint page_num; + + page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), + panelvbox); + gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num); + } + + if (nselparams >= selparamsize) { + selparamsize += 16; + selparams = sresize(selparams, selparamsize, + struct selparam); + } + selparams[nselparams].dp = &dp; + selparams[nselparams].panels = GTK_NOTEBOOK(panels); + selparams[nselparams].panel = panelvbox; + selparams[nselparams].shortcuts = scs; /* structure copy */ + + assert(j-1 < level); + +#if GTK_CHECK_VERSION(2,0,0) + if (j > 0) + /* treeiterlevels[j-1] will always be valid because we + * don't allow implicit path components; see above. + */ + gtk_tree_store_append(treestore, &treeiter, + &treeiterlevels[j-1]); + else + gtk_tree_store_append(treestore, &treeiter, NULL); + gtk_tree_store_set(treestore, &treeiter, + TREESTORE_PATH, c, + TREESTORE_PARAMS, nselparams, + -1); + treeiterlevels[j] = treeiter; + + selparams[nselparams].depth = j; + if (j > 0) { + selparams[nselparams].treepath = + gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), + &treeiterlevels[j-1]); + /* + * We are going to collapse all tree branches + * at depth greater than 2, but not _yet_; see + * the comment at the call to + * gtk_tree_view_collapse_row below. + */ + gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), + selparams[nselparams].treepath, + FALSE); + } else { + selparams[nselparams].treepath = NULL; + } +#else + treeitem = gtk_tree_item_new_with_label(c); + if (j > 0) { + if (!treelevels[j-1]) { + treelevels[j-1] = GTK_TREE(gtk_tree_new()); + gtk_tree_item_set_subtree + (treeitemlevels[j-1], + GTK_WIDGET(treelevels[j-1])); + if (j < 2) + gtk_tree_item_expand(treeitemlevels[j-1]); + else + gtk_tree_item_collapse(treeitemlevels[j-1]); + } + gtk_tree_append(treelevels[j-1], treeitem); + } else { + gtk_tree_append(GTK_TREE(tree), treeitem); + } + treeitemlevels[j] = GTK_TREE_ITEM(treeitem); + treelevels[j] = NULL; + + gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event", + GTK_SIGNAL_FUNC(tree_key_press), &dp); + gtk_signal_connect(GTK_OBJECT(treeitem), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), &dp); + + gtk_widget_show(treeitem); + + if (first) + gtk_tree_select_child(GTK_TREE(tree), treeitem); + selparams[nselparams].treeitem = treeitem; +#endif + + level = j+1; + nselparams++; + } + + w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL); + gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); + gtk_widget_show(w); + } + } + +#if GTK_CHECK_VERSION(2,0,0) + { + GtkRequisition req; + int i; + + /* + * We want our tree view to come up with all branches at + * depth 2 or more collapsed. However, if we start off + * with those branches collapsed, then the tree view's + * size request will be calculated based on the width of + * the collapsed tree. So instead we start with them all + * expanded; then we ask for the current size request, + * collapse the relevant rows, and force the width to the + * value we just computed. This arranges that the tree + * view is wide enough to have all branches expanded + * safely. + */ + + gtk_widget_size_request(tree, &req); + + for (i = 0; i < nselparams; i++) + if (selparams[i].depth >= 2) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), + selparams[i].treepath); + + gtk_widget_set_size_request(tree, req.width, -1); + } +#endif + +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(treeselection), "changed", + G_CALLBACK(treeselection_changed), selparams); +#else + dp.ntreeitems = nselparams; + dp.treeitems = snewn(dp.ntreeitems, GtkWidget *); + + for (index = 0; index < nselparams; index++) { + gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select", + GTK_SIGNAL_FUNC(treeitem_sel), + &selparams[index]); + dp.treeitems[index] = selparams[index].treeitem; + } +#endif + + dp.data = conf; + dlg_refresh(NULL, &dp); + + dp.shortcuts = &selparams[0].shortcuts; +#if !GTK_CHECK_VERSION(2,0,0) + dp.currtreeitem = dp.treeitems[0]; +#endif + dp.lastfocus = NULL; + dp.retval = 0; + dp.window = window; + + { + /* in gtkwin.c */ + extern void set_window_icon(GtkWidget *window, + const char *const *const *icon, + int n_icon); + extern const char *const *const cfg_icon[]; + extern const int n_cfg_icon; + set_window_icon(window, cfg_icon, n_cfg_icon); + } + +#if !GTK_CHECK_VERSION(2,0,0) + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll), + tree); +#endif + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_show(tree); + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + + /* + * Set focus into the first available control. + */ + for (index = 0; index < ctrlbox->nctrlsets; index++) { + struct controlset *s = ctrlbox->ctrlsets[index]; + int done = 0; + int j; + + if (*s->pathname) { + for (j = 0; j < s->ncontrols; j++) + if (s->ctrls[j]->generic.type != CTRL_TABDELAY && + s->ctrls[j]->generic.type != CTRL_COLUMNS && + s->ctrls[j]->generic.type != CTRL_TEXT) { + dlg_set_focus(s->ctrls[j], &dp); + dp.lastfocus = s->ctrls[j]; + done = 1; + break; + } + } + if (done) + break; + } + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &dp); + + gtk_main(); + + dlg_cleanup(&dp); + sfree(selparams); + + return dp.retval; +} + +static void messagebox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dlg, ctrl->generic.context.i); +} +int messagebox(GtkWidget *parentwin, char *title, char *msg, + int minwid, int selectable, ...) +{ + GtkWidget *window, *w0, *w1; + struct controlbox *ctrlbox; + struct controlset *s0, *s1; + union control *c, *textctrl; + struct dlgparam dp; + struct Shortcuts scs; + int index, ncols; + va_list ap; + + dlg_init(&dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + ctrlbox = ctrl_new_box(); + + ncols = 0; + va_start(ap, selectable); + while (va_arg(ap, char *) != NULL) { + ncols++; + (void) va_arg(ap, int); /* shortcut */ + (void) va_arg(ap, int); /* normal/default/cancel */ + (void) va_arg(ap, int); /* end value */ + } + va_end(ap); + + s0 = ctrl_getset(ctrlbox, "", "", ""); + c = ctrl_columns(s0, 2, 50, 50); + c->columns.ncols = s0->ncolumns = ncols; + c->columns.percentages = sresize(c->columns.percentages, ncols, int); + for (index = 0; index < ncols; index++) + c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols; + va_start(ap, selectable); + index = 0; + while (1) { + char *title = va_arg(ap, char *); + int shortcut, type, value; + if (title == NULL) + break; + shortcut = va_arg(ap, int); + type = va_arg(ap, int); + value = va_arg(ap, int); + c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help), + messagebox_handler, I(value)); + c->generic.column = index++; + if (type > 0) + c->button.isdefault = TRUE; + else if (type < 0) + c->button.iscancel = TRUE; + } + va_end(ap); + + s1 = ctrl_getset(ctrlbox, "x", "", ""); + textctrl = ctrl_text(s1, msg, HELPCTX(no_help)); + + window = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(window), title); + w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window)); + set_dialog_action_area(GTK_DIALOG(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_usize(w1, minwid+20, -1); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + w1, TRUE, TRUE, 0); + gtk_widget_show(w1); + + dp.shortcuts = &scs; + dp.lastfocus = NULL; + dp.retval = 0; + dp.window = window; + + if (selectable) { +#if GTK_CHECK_VERSION(2,0,0) + struct uctrl *uc = dlg_find_byctrl(&dp, textctrl); + gtk_label_set_selectable(GTK_LABEL(uc->text), TRUE); + + /* + * GTK selectable labels have a habit of selecting their + * entire contents when they gain focus. It's ugly to have + * text in a message box start up all selected, so we suppress + * this by manually selecting none of it - but we must do this + * when the widget _already has_ focus, otherwise our work + * will be undone when it gains it shortly. + */ + gtk_widget_grab_focus(uc->text); + gtk_label_select_region(GTK_LABEL(uc->text), 0, 0); +#else + (void)textctrl; /* placate warning */ +#endif + } + + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + if (parentwin) { + set_transient_window_pos(parentwin, window); + gtk_window_set_transient_for(GTK_WINDOW(window), + GTK_WINDOW(parentwin)); + } else + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_container_set_focus_child(GTK_CONTAINER(window), NULL); + gtk_widget_show(window); + gtk_window_set_focus(GTK_WINDOW(window), NULL); + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &dp); + + gtk_main(); + + dlg_cleanup(&dp); + ctrl_free_box(ctrlbox); + + return dp.retval; +} + +int string_width(char *text) +{ + GtkWidget *label = gtk_label_new(text); + GtkRequisition req; + gtk_widget_size_request(label, &req); + gtk_object_sink(GTK_OBJECT(label)); + return req.width; +} + +int reallyclose(void *frontend) +{ + char *title = dupcat(appname, " Exit Confirmation", NULL); + int ret = messagebox(GTK_WIDGET(get_window(frontend)), + title, "Are you sure you want to close this session?", + string_width("Most of the width of the above text"), + FALSE, + "Yes", 'y', +1, 1, + "No", 'n', -1, 0, + NULL); + sfree(title); + return ret; +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char absenttxt[] = + "The server's host key is not cached. You have no guarantee " + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, press \"Accept\" to add the key to " + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without " + "adding the key to the cache, press \"Connect Once\".\n" + "If you do not trust this host, press \"Cancel\" to abandon the " + "connection."; + static const char wrongtxt[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has " + "cached. This means that either the server administrator " + "has changed the host key, or you have actually connected " + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key, " + "press \"Accept\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating " + "the cache, press \"Connect Once\".\n" + "If you want to abandon the connection completely, press " + "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " + "safe choice."; + char *text; + int ret; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint); + + ret = messagebox(GTK_WIDGET(get_window(frontend)), + "PuTTY Security Alert", text, + string_width(fingerprint), + TRUE, + "Accept", 'a', 0, 2, + "Connect Once", 'o', 0, 1, + "Cancel", 'c', -1, 0, + NULL); + + sfree(text); + + if (ret == 2) { + store_host_key(host, port, keytype, keystr); + return 1; /* continue with connection */ + } else if (ret == 1) + return 1; /* continue with connection */ + return 0; /* do not continue with connection */ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first %s supported by the server is " + "%s, which is below the configured warning threshold.\n" + "Continue with connection?"; + char *text; + int ret; + + text = dupprintf(msg, algtype, algname); + ret = messagebox(GTK_WIDGET(get_window(frontend)), + "PuTTY Security Alert", text, + string_width("Continue with connection?"), + FALSE, + "Yes", 'y', 0, 1, + "No", 'n', 0, 0, + NULL); + sfree(text); + + if (ret) { + return 1; + } else { + return 0; + } +} + +void old_keyfile_warning(void) +{ + /* + * This should never happen on Unix. We hope. + */ +} + +void fatal_message_box(void *window, char *msg) +{ + messagebox(window, "PuTTY Fatal Error", msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + FALSE, "OK", 'o', 1, 1, NULL); +} + +void nonfatal_message_box(void *window, char *msg) +{ + messagebox(window, "PuTTY Error", msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + FALSE, "OK", 'o', 1, 1, NULL); +} + +void fatalbox(char *p, ...) +{ + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + fatal_message_box(NULL, msg); + sfree(msg); + cleanup_exit(1); +} + +void nonfatal(char *p, ...) +{ + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + nonfatal_message_box(NULL, msg); + sfree(msg); +} + +static GtkWidget *aboutbox = NULL; + +static void about_close_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(aboutbox); + aboutbox = NULL; +} + +static void licence_clicked(GtkButton *button, gpointer data) +{ + char *title; + + title = dupcat(appname, " Licence", NULL); + assert(aboutbox != NULL); + messagebox(aboutbox, title, LICENCE_TEXT("\n\n"), + string_width("LONGISH LINE OF TEXT SO THE LICENCE" + " BOX ISN'T EXCESSIVELY TALL AND THIN"), + TRUE, "OK", 'o', 1, 1, NULL); + sfree(title); +} + +void about_box(void *window) +{ + GtkWidget *w; + char *title; + + if (aboutbox) { + gtk_widget_grab_focus(aboutbox); + return; + } + + aboutbox = gtk_dialog_new(); + gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); + title = dupcat("About ", appname, NULL); + gtk_window_set_title(GTK_WINDOW(aboutbox), title); + sfree(title); + + w = gtk_button_new_with_label("Close"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(aboutbox), w); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(about_close_clicked), NULL); + gtk_widget_show(w); + + w = gtk_button_new_with_label("View Licence"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(licence_clicked), NULL); + gtk_widget_show(w); + + { + char *label_text = dupprintf + ("%s\n\n%s\n\n%s", + appname, ver, + "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); + w = gtk_label_new(label_text); + gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER); +#if GTK_CHECK_VERSION(2,0,0) + gtk_label_set_selectable(GTK_LABEL(w), TRUE); +#endif + sfree(label_text); + } + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 0); +#if GTK_CHECK_VERSION(2,0,0) + /* + * Same precautions against initial select-all as in messagebox(). + */ + gtk_widget_grab_focus(w); + gtk_label_select_region(GTK_LABEL(w), 0, 0); +#endif + gtk_widget_show(w); + + set_transient_window_pos(GTK_WIDGET(window), aboutbox); + gtk_window_set_transient_for(GTK_WINDOW(aboutbox), + GTK_WINDOW(window)); + gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL); + gtk_widget_show(aboutbox); + gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL); +} + +struct eventlog_stuff { + GtkWidget *parentwin, *window; + struct controlbox *eventbox; + struct Shortcuts scs; + struct dlgparam dp; + union control *listctrl; + char **events; + int nevents, negsize; + char *seldata; + int sellen; + int ignore_selchange; +}; + +static void eventlog_destroy(GtkWidget *widget, gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + es->window = NULL; + sfree(es->seldata); + es->seldata = NULL; + dlg_cleanup(&es->dp); + ctrl_free_box(es->eventbox); +} +static void eventlog_ok_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dlg, 0); +} +static void eventlog_list_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + if (event == EVENT_REFRESH) { + int i; + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < es->nevents; i++) { + dlg_listbox_add(ctrl, dlg, es->events[i]); + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i; + int selsize = 0; + + /* + * If this SELCHANGE event is happening as a result of + * deliberate deselection because someone else has grabbed + * the selection, the last thing we want to do is pre-empt + * them. + */ + if (es->ignore_selchange) + return; + + /* + * Construct the data to use as the selection. + */ + sfree(es->seldata); + es->seldata = NULL; + es->sellen = 0; + for (i = 0; i < es->nevents; i++) { + if (dlg_listbox_issel(ctrl, dlg, i)) { + int extralen = strlen(es->events[i]); + + if (es->sellen + extralen + 2 > selsize) { + selsize = es->sellen + extralen + 512; + es->seldata = sresize(es->seldata, selsize, char); + } + + strcpy(es->seldata + es->sellen, es->events[i]); + es->sellen += extralen; + es->seldata[es->sellen++] = '\n'; + } + } + + if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME)) { + extern GdkAtom compound_text_atom; + + gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, 1); + gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, + compound_text_atom, 1); + } + + } +} + +void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)es->seldata, es->sellen); +} + +gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + struct uctrl *uc; + + /* + * Deselect everything in the list box. + */ + uc = dlg_find_byctrl(&es->dp, es->listctrl); + es->ignore_selchange = 1; +#if !GTK_CHECK_VERSION(2,0,0) + assert(uc->list); + gtk_list_unselect_all(GTK_LIST(uc->list)); +#else + assert(uc->treeview); + gtk_tree_selection_unselect_all + (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); +#endif + es->ignore_selchange = 0; + + sfree(es->seldata); + es->sellen = 0; + es->seldata = NULL; + return TRUE; +} + +void showeventlog(void *estuff, void *parentwin) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; + GtkWidget *window, *w0, *w1; + GtkWidget *parent = GTK_WIDGET(parentwin); + struct controlset *s0, *s1; + union control *c; + int index; + char *title; + + if (es->window) { + gtk_widget_grab_focus(es->window); + return; + } + + dlg_init(&es->dp); + + for (index = 0; index < lenof(es->scs.sc); index++) { + es->scs.sc[index].action = SHORTCUT_EMPTY; + } + + es->eventbox = ctrl_new_box(); + + s0 = ctrl_getset(es->eventbox, "", "", ""); + ctrl_columns(s0, 3, 33, 34, 33); + c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help), + eventlog_ok_handler, P(NULL)); + c->button.column = 1; + c->button.isdefault = TRUE; + + s1 = ctrl_getset(es->eventbox, "x", "", ""); + es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help), + eventlog_list_handler, P(es)); + c->listbox.height = 10; + c->listbox.multisel = 2; + c->listbox.ncols = 3; + c->listbox.percentages = snewn(3, int); + c->listbox.percentages[0] = 25; + c->listbox.percentages[1] = 10; + c->listbox.percentages[2] = 65; + + es->window = window = gtk_dialog_new(); + title = dupcat(appname, " Event Log", NULL); + gtk_window_set_title(GTK_WINDOW(window), title); + sfree(title); + w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window)); + set_dialog_action_area(GTK_DIALOG(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_usize(w1, 20 + + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG" + " IS QUITE LONG 'COS SSH LOG ENTRIES" + " ARE WIDE"), -1); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + w1, TRUE, TRUE, 0); + gtk_widget_show(w1); + + es->dp.data = es; + es->dp.shortcuts = &es->scs; + es->dp.lastfocus = NULL; + es->dp.retval = 0; + es->dp.window = window; + + dlg_refresh(NULL, &es->dp); + + if (parent) { + set_transient_window_pos(parent, window); + gtk_window_set_transient_for(GTK_WINDOW(window), + GTK_WINDOW(parent)); + } else + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(eventlog_destroy), es); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &es->dp); + gtk_signal_connect(GTK_OBJECT(window), "selection_get", + GTK_SIGNAL_FUNC(eventlog_selection_get), es); + gtk_signal_connect(GTK_OBJECT(window), "selection_clear_event", + GTK_SIGNAL_FUNC(eventlog_selection_clear), es); +} + +void *eventlogstuff_new(void) +{ + struct eventlog_stuff *es; + es = snew(struct eventlog_stuff); + memset(es, 0, sizeof(*es)); + return es; +} + +void logevent_dlg(void *estuff, const char *string) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; + + char timebuf[40]; + struct tm tm; + + if (es->nevents >= es->negsize) { + es->negsize += 64; + es->events = sresize(es->events, es->negsize, char *); + } + + tm=ltime(); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); + + es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); + strcpy(es->events[es->nevents], timebuf); + strcat(es->events[es->nevents], string); + if (es->window) { + dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]); + } + es->nevents++; +} + +int askappend(void *frontend, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%.*s\" already exists. " + "You can overwrite it with a new session log, " + "append your session log to the end of it, " + "or disable session logging for this session."; + char *message; + char *mbtitle; + int mbret; + + message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); + mbtitle = dupprintf("%s Log to File", appname); + + mbret = messagebox(get_window(frontend), mbtitle, message, + string_width("LINE OF TEXT SUITABLE FOR THE" + " ASKAPPEND WIDTH"), + FALSE, + "Overwrite", 'o', 1, 2, + "Append", 'a', 0, 1, + "Disable", 'd', -1, 0, + NULL); + + sfree(message); + sfree(mbtitle); + + return mbret; +} diff --git a/netbox/libs/Putty/unix/gtkfont.c b/netbox/libs/Putty/unix/gtkfont.c new file mode 100644 index 000000000..b152f2bbf --- /dev/null +++ b/netbox/libs/Putty/unix/gtkfont.c @@ -0,0 +1,2938 @@ +/* + * Unified font management for GTK. + * + * PuTTY is willing to use both old-style X server-side bitmap + * fonts _and_ GTK2/Pango client-side fonts. This requires us to + * do a bit of work to wrap the two wildly different APIs into + * forms the rest of the code can switch between seamlessly, and + * also requires a custom font selector capable of handling both + * types of font. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "gtkfont.h" +#include "tree234.h" + +/* + * Future work: + * + * - it would be nice to have a display of the current font name, + * and in particular whether it's client- or server-side, + * during the progress of the font selector. + * + * - it would be nice if we could move the processing of + * underline and VT100 double width into this module, so that + * instead of using the ghastly pixmap-stretching technique + * everywhere we could tell the Pango backend to scale its + * fonts to double size properly and at full resolution. + * However, this requires me to learn how to make Pango stretch + * text to an arbitrary aspect ratio (for double-width only + * text, which perversely is harder than DW+DH), and right now + * I haven't the energy. + */ + +#if !GLIB_CHECK_VERSION(1,3,7) +#define g_ascii_strcasecmp g_strcasecmp +#define g_ascii_strncasecmp g_strncasecmp +#endif + +/* + * Ad-hoc vtable mechanism to allow font structures to be + * polymorphic. + * + * Any instance of `unifont' used in the vtable functions will + * actually be the first element of a larger structure containing + * data specific to the subtype. This is permitted by the ISO C + * provision that one may safely cast between a pointer to a + * structure and a pointer to its first element. + */ + +#define FONTFLAG_CLIENTSIDE 0x0001 +#define FONTFLAG_SERVERSIDE 0x0002 +#define FONTFLAG_SERVERALIAS 0x0004 +#define FONTFLAG_NONMONOSPACED 0x0008 + +#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */ + +typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass); + +struct unifont_vtable { + /* + * `Methods' of the `class'. + */ + unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, + int shadowoffset, int shadowalways); + unifont *(*create_fallback)(GtkWidget *widget, int height, int wide, + int bold, int shadowoffset, int shadowalways); + void (*destroy)(unifont *font); + int (*has_glyph)(unifont *font, wchar_t glyph); + void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, int wide, + int bold, int cellwidth); + void (*enum_fonts)(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); + char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, + int *flags, int resolve_aliases); + char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); + + /* + * `Static data members' of the `class'. + */ + const char *prefix; +}; + +/* ---------------------------------------------------------------------- + * X11 font implementation, directly using Xlib calls. + */ + +static int x11font_has_glyph(unifont *font, wchar_t glyph); +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void x11font_destroy(unifont *font); +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size); + +struct x11font { + struct unifont u; + /* + * Actual font objects. We store a number of these, for + * automatically guessed bold and wide variants. + * + * The parallel array `allocated' indicates whether we've + * tried to fetch a subfont already (thus distinguishing NULL + * because we haven't tried yet from NULL because we tried and + * failed, so that we don't keep trying and failing + * subsequently). + */ + XFontStruct *fonts[4]; + int allocated[4]; + /* + * `sixteen_bit' is true iff the font object is indexed by + * values larger than a byte. That is, this flag tells us + * whether we use XDrawString or XDrawString16, etc. + */ + int sixteen_bit; + /* + * `variable' is true iff the font is non-fixed-pitch. This + * enables some code which takes greater care over character + * positioning during text drawing. + */ + int variable; + /* + * real_charset is the charset used when translating text into the + * font's internal encoding inside draw_text(). This need not be + * the same as the public_charset provided to the client; for + * example, public_charset might be CS_ISO8859_1 while + * real_charset is CS_ISO8859_1_X11. + */ + int real_charset; + /* + * Data passed in to unifont_create(). + */ + int wide, bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable x11font_vtable = { + x11font_create, + NULL, /* no fallback fonts in X11 */ + x11font_destroy, + x11font_has_glyph, + x11font_draw_text, + x11font_enum_fonts, + x11font_canonify_fontname, + x11font_scale_fontname, + "server", +}; + +static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide) +{ + Display *disp = GDK_DISPLAY(); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name && name[0] == '-') { + char *strings[13]; + char *dupname, *extrafree = NULL, *ret; + char *p, *q; + int nstr; + + p = q = dupname = dupstr(name); /* skip initial minus */ + nstr = 0; + + while (*p && nstr < lenof(strings)) { + if (*p == '-') { + *p = '\0'; + strings[nstr++] = p+1; + } + p++; + } + + if (nstr < lenof(strings)) { + sfree(dupname); + return NULL; /* XLFD was malformed */ + } + + if (bold) + strings[2] = "bold"; + + if (wide) { + /* 4 is `wideness', which obviously may have changed. */ + /* 5 is additional style, which may be e.g. `ja' or `ko'. */ + strings[4] = strings[5] = "*"; + strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11])); + } + + ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2], + "-", strings[ 3], "-", strings[ 4], "-", strings[ 5], + "-", strings[ 6], "-", strings[ 7], "-", strings[ 8], + "-", strings[ 9], "-", strings[10], "-", strings[11], + "-", strings[12], NULL); + sfree(extrafree); + sfree(dupname); + + return ret; + } + } + return NULL; +} + +static int x11_font_width(XFontStruct *xfs, int sixteen_bit) +{ + if (sixteen_bit) { + XChar2b space; + space.byte1 = 0; + space.byte2 = '0'; + return XTextWidth16(xfs, &space, 1); + } else { + return XTextWidth(xfs, "0", 1); + } +} + +static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2) +{ + int index; + + /* + * Not to be confused with x11font_has_glyph, which is a method of + * the x11font 'class' and hence takes a unifont as argument. This + * is the low-level function which grubs about in an actual + * XFontStruct to see if a given glyph exists. + * + * We must do this ourselves rather than letting Xlib's + * XTextExtents16 do the job, because XTextExtents will helpfully + * substitute the font's default_char for any missing glyph and + * not tell us it did so, which precisely won't help us find out + * which glyphs _are_ missing. + * + * The man page for XQueryFont is rather confusing about how the + * per_char array in the XFontStruct is laid out, because it gives + * formulae for determining the two-byte X character code _from_ + * an index into the per_char array. Going the other way, it's + * rather simpler: + * + * The valid character codes have byte1 between min_byte1 and + * max_byte1 inclusive, and byte2 between min_char_or_byte2 and + * max_char_or_byte2 inclusive. This gives a rectangle of size + * (max_byte2-min_byte1+1) by + * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the + * rectangle encoded in the per_char array. Hence, given a + * character code which is valid in the sense that it falls + * somewhere in that rectangle, its index in per_char is given by + * setting + * + * x = byte2 - min_char_or_byte2 + * y = byte1 - min_byte1 + * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x + * + * If min_byte1 and min_byte2 are both zero, that's a special case + * which can be treated as if min_byte2 was 1 instead, i.e. the + * per_char array just runs from min_char_or_byte2 to + * max_char_or_byte2 inclusive, and byte1 should always be zero. + */ + + if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) + return FALSE; + + if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) { + index = byte2 - xfs->min_char_or_byte2; + } else { + if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1) + return FALSE; + index = ((byte2 - xfs->min_char_or_byte2) + + ((byte1 - xfs->min_byte1) * + (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); + } + + if (!xfs->per_char) /* per_char NULL => everything in range exists */ + return TRUE; + + return (xfs->per_char[index].ascent + xfs->per_char[index].descent > 0 || + xfs->per_char[index].width > 0); +} + +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct x11font *xfont; + XFontStruct *xfs; + Display *disp = GDK_DISPLAY(); + Atom charset_registry, charset_encoding, spacing; + unsigned long registry_ret, encoding_ret, spacing_ret; + int pubcs, realcs, sixteen_bit, variable; + int i; + + xfs = XLoadQueryFont(disp, name); + if (!xfs) + return NULL; + + charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False); + charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); + + pubcs = realcs = CS_NONE; + sixteen_bit = FALSE; + variable = TRUE; + + if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && + XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { + char *reg, *enc; + reg = XGetAtomName(disp, (Atom)registry_ret); + enc = XGetAtomName(disp, (Atom)encoding_ret); + if (reg && enc) { + char *encoding = dupcat(reg, "-", enc, NULL); + pubcs = realcs = charset_from_xenc(encoding); + + /* + * iso10646-1 is the only wide font encoding we + * support. In this case, we expect clients to give us + * UTF-8, which this module must internally convert + * into 16-bit Unicode. + */ + if (!strcasecmp(encoding, "iso10646-1")) { + sixteen_bit = TRUE; + pubcs = realcs = CS_UTF8; + } + + /* + * Hack for X line-drawing characters: if the primary font + * is encoded as ISO-8859-1, and has valid glyphs in the + * low character positions, it is assumed that those + * glyphs are the VT100 line-drawing character set. + */ + if (pubcs == CS_ISO8859_1) { + int ch; + for (ch = 1; ch < 32; ch++) + if (!x11_font_has_glyph(xfs, 0, ch)) + break; + if (ch == 32) + realcs = CS_ISO8859_1_X11; + } + + sfree(encoding); + } + } + + spacing = XInternAtom(disp, "SPACING", False); + if (XGetFontProperty(xfs, spacing, &spacing_ret)) { + char *spc; + spc = XGetAtomName(disp, (Atom)spacing_ret); + + if (spc && strchr("CcMm", spc[0])) + variable = FALSE; + } + + xfont = snew(struct x11font); + xfont->u.vt = &x11font_vtable; + xfont->u.width = x11_font_width(xfs, sixteen_bit); + xfont->u.ascent = xfs->ascent; + xfont->u.descent = xfs->descent; + xfont->u.height = xfont->u.ascent + xfont->u.descent; + xfont->u.public_charset = pubcs; + xfont->u.want_fallback = TRUE; + xfont->real_charset = realcs; + xfont->fonts[0] = xfs; + xfont->allocated[0] = TRUE; + xfont->sixteen_bit = sixteen_bit; + xfont->variable = variable; + xfont->wide = wide; + xfont->bold = bold; + xfont->shadowoffset = shadowoffset; + xfont->shadowalways = shadowalways; + + for (i = 1; i < lenof(xfont->fonts); i++) { + xfont->fonts[i] = NULL; + xfont->allocated[i] = FALSE; + } + + return (unifont *)xfont; +} + +static void x11font_destroy(unifont *font) +{ + Display *disp = GDK_DISPLAY(); + struct x11font *xfont = (struct x11font *)font; + int i; + + for (i = 0; i < lenof(xfont->fonts); i++) + if (xfont->fonts[i]) + XFreeFont(disp, xfont->fonts[i]); + sfree(font); +} + +static void x11_alloc_subfont(struct x11font *xfont, int sfid) +{ + Display *disp = GDK_DISPLAY(); + char *derived_name = x11_guess_derived_font_name + (xfont->fonts[0], sfid & 1, !!(sfid & 2)); + xfont->fonts[sfid] = XLoadQueryFont(disp, derived_name); + xfont->allocated[sfid] = TRUE; + sfree(derived_name); + /* Note that xfont->fonts[sfid] may still be NULL, if XLQF failed. */ +} + +static int x11font_has_glyph(unifont *font, wchar_t glyph) +{ + struct x11font *xfont = (struct x11font *)font; + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input value. + */ + return x11_font_has_glyph(xfont->fonts[0], glyph >> 8, glyph & 0xFF); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char sbstring[2]; + int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, + sbstring, 2, "", NULL, NULL); + if (sblen == 0 || !sbstring[0]) + return FALSE; /* not even in the charset */ + + return x11_font_has_glyph(xfont->fonts[0], 0, + (unsigned char)sbstring[0]); + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */ +#endif + +static void x11font_really_draw_text_16(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const XChar2b *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = 1; + nsteps = nchars; + centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - XTextWidth16(xfs, string, step)) / 2; + + XDrawString16(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString16(disp, GDK_DRAWABLE_XID(target), gc, + X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + +static void x11font_really_draw_text(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const char *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = 1; + nsteps = nchars; + centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - XTextWidth(xfs, string, step)) / 2; + + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + struct x11font *xfont = (struct x11font *)font; + GC gc = GDK_GC_XGC(gdkgc); + int sfid; + int shadowoffset = 0; + int mult = (wide ? 2 : 1); + + wide -= xfont->wide; + bold -= xfont->bold; + + /* + * Decide which subfont we're using, and whether we have to + * use shadow bold. + */ + if (xfont->shadowalways && bold) { + shadowoffset = xfont->shadowoffset; + bold = 0; + } + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + if (bold && !xfont->fonts[sfid]) { + bold = 0; + shadowoffset = xfont->shadowoffset; + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + } + + if (!xfont->fonts[sfid]) + return; /* we've tried our best, but no luck */ + + XSetFont(disp, gc, xfont->fonts[sfid]->fid); + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input string. + */ + XChar2b *xcs; + int i; + + xcs = snewn(len, XChar2b); + for (i = 0; i < len; i++) { + xcs[i].byte1 = string[i] >> 8; + xcs[i].byte2 = string[i]; + } + + x11font_really_draw_text_16(target, xfont->fonts[sfid], gc, x, y, + xcs, len, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(xcs); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char *sbstring = snewn(len+1, char); + int sblen = wc_to_mb(xfont->real_charset, 0, string, len, + sbstring, len+1, ".", NULL, NULL); + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + sbstring, sblen, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(sbstring); + } +} + +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx) +{ + char **fontnames; + char *tmp = NULL; + int nnames, i, max, tmpsize; + + max = 32768; + while (1) { + fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + tmpsize = 0; + + for (i = 0; i < nnames; i++) { + if (fontnames[i][0] == '-') { + /* + * Dismember an XLFD and convert it into the format + * we'll be using in the font selector. + */ + char *components[14]; + char *p, *font, *style, *stylekey, *charset; + int j, weightkey, slantkey, setwidthkey; + int thistmpsize, fontsize, flags; + + thistmpsize = 4 * strlen(fontnames[i]) + 256; + if (tmpsize < thistmpsize) { + tmpsize = thistmpsize; + tmp = sresize(tmp, tmpsize, char); + } + strcpy(tmp, fontnames[i]); + + p = tmp; + for (j = 0; j < 14; j++) { + if (*p) + *p++ = '\0'; + components[j] = p; + while (*p && *p != '-') + p++; + } + *p++ = '\0'; + + /* + * Font name is made up of fields 0 and 1, in reverse + * order with parentheses. (This is what the GTK 1.2 X + * font selector does, and it seems to come out + * looking reasonably sensible.) + */ + font = p; + p += 1 + sprintf(p, "%s (%s)", components[1], components[0]); + + /* + * Charset is made up of fields 12 and 13. + */ + charset = p; + p += 1 + sprintf(p, "%s-%s", components[12], components[13]); + + /* + * Style is a mixture of quite a lot of the fields, + * with some strange formatting. + */ + style = p; + p += sprintf(p, "%s", components[2][0] ? components[2] : + "regular"); + if (!g_ascii_strcasecmp(components[3], "i")) + p += sprintf(p, " italic"); + else if (!g_ascii_strcasecmp(components[3], "o")) + p += sprintf(p, " oblique"); + else if (!g_ascii_strcasecmp(components[3], "ri")) + p += sprintf(p, " reverse italic"); + else if (!g_ascii_strcasecmp(components[3], "ro")) + p += sprintf(p, " reverse oblique"); + else if (!g_ascii_strcasecmp(components[3], "ot")) + p += sprintf(p, " other-slant"); + if (components[4][0] && g_ascii_strcasecmp(components[4], "normal")) + p += sprintf(p, " %s", components[4]); + if (!g_ascii_strcasecmp(components[10], "m")) + p += sprintf(p, " [M]"); + if (!g_ascii_strcasecmp(components[10], "c")) + p += sprintf(p, " [C]"); + if (components[5][0]) + p += sprintf(p, " %s", components[5]); + + /* + * Style key is the same stuff as above, but with a + * couple of transformations done on it to make it + * sort more sensibly. + */ + p++; + stylekey = p; + if (!g_ascii_strcasecmp(components[2], "medium") || + !g_ascii_strcasecmp(components[2], "regular") || + !g_ascii_strcasecmp(components[2], "normal") || + !g_ascii_strcasecmp(components[2], "book")) + weightkey = 0; + else if (!g_ascii_strncasecmp(components[2], "demi", 4) || + !g_ascii_strncasecmp(components[2], "semi", 4)) + weightkey = 1; + else + weightkey = 2; + if (!g_ascii_strcasecmp(components[3], "r")) + slantkey = 0; + else if (!g_ascii_strncasecmp(components[3], "r", 1)) + slantkey = 2; + else + slantkey = 1; + if (!g_ascii_strcasecmp(components[4], "normal")) + setwidthkey = 0; + else + setwidthkey = 1; + + p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", + weightkey, + (int)strlen(components[2]), components[2], + slantkey, + (int)strlen(components[3]), components[3], + setwidthkey, + (int)strlen(components[4]), components[4], + (int)strlen(components[10]), components[10], + (int)strlen(components[5]), components[5]); + + assert(p - tmp < thistmpsize); + + /* + * Size is in pixels, for our application, so we + * derive it directly from the pixel size field, + * number 6. + */ + fontsize = atoi(components[6]); + + /* + * Flags: we need to know whether this is a monospaced + * font, which we do by examining the spacing field + * again. + */ + flags = FONTFLAG_SERVERSIDE; + if (!strchr("CcMm", components[10][0])) + flags |= FONTFLAG_NONMONOSPACED; + + /* + * Not sure why, but sometimes the X server will + * deliver dummy font types in which fontsize comes + * out as zero. Filter those out. + */ + if (fontsize) + callback(callback_ctx, fontnames[i], font, charset, + style, stylekey, fontsize, flags, &x11font_vtable); + } else { + /* + * This isn't an XLFD, so it must be an alias. + * Transmit it with mostly null data. + * + * It would be nice to work out if it's monospaced + * here, but at the moment I can't see that being + * anything but computationally hideous. Ah well. + */ + callback(callback_ctx, fontnames[i], fontnames[i], NULL, + NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); + } + } + XFreeFontNames(fontnames); +} + +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given an X11 font name to try to make sense of for a + * font selector, we must attempt to load it (to see if it + * exists), and then canonify it by extracting its FONT + * property, which should give its full XLFD even if what we + * originally had was a wildcard. + * + * However, we must carefully avoid canonifying font + * _aliases_, unless specifically asked to, because the font + * selector treats them as worthwhile in their own right. + */ + XFontStruct *xfs; + Display *disp = GDK_DISPLAY(); + Atom fontprop, fontprop2; + unsigned long ret; + + xfs = XLoadQueryFont(disp, name); + + if (!xfs) + return NULL; /* didn't make sense to us, sorry */ + + fontprop = XInternAtom(disp, "FONT", False); + + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *newname = XGetAtomName(disp, (Atom)ret); + if (newname) { + unsigned long fsize = 12; + + fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); + if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { + *size = fsize; + XFreeFont(disp, xfs); + if (flags) { + if (name[0] == '-' || resolve_aliases) + *flags = FONTFLAG_SERVERSIDE; + else + *flags = FONTFLAG_SERVERALIAS; + } + return dupstr(name[0] == '-' || resolve_aliases ? + newname : name); + } + } + } + + XFreeFont(disp, xfs); + + return NULL; /* something went wrong */ +} + +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + return NULL; /* shan't */ +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Pango font implementation (for GTK 2 only). + */ + +#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6 +#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ +#endif + +static int pangofont_has_glyph(unifont *font, wchar_t glyph); +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + int wide, int bold, + int shadowoffset, int shadowalways); +static void pangofont_destroy(unifont *font); +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx); +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size); + +struct pangofont { + struct unifont u; + /* + * Pango objects. + */ + PangoFontDescription *desc; + PangoFontset *fset; + /* + * The containing widget. + */ + GtkWidget *widget; + /* + * Data passed in to unifont_create(). + */ + int bold, shadowoffset, shadowalways; + /* + * Cache of character widths, indexed by Unicode code point. In + * pixels; -1 means we haven't asked Pango about this character + * before. + */ + int *widthcache; + unsigned nwidthcache; +}; + +static const struct unifont_vtable pangofont_vtable = { + pangofont_create, + pangofont_create_fallback, + pangofont_destroy, + pangofont_has_glyph, + pangofont_draw_text, + pangofont_enum_fonts, + pangofont_canonify_fontname, + pangofont_scale_fontname, + "client", +}; + +/* + * This function is used to rigorously validate a + * PangoFontDescription. Later versions of Pango have a nasty + * habit of accepting _any_ old string as input to + * pango_font_description_from_string and returning a font + * description which can actually be used to display text, even if + * they have to do it by falling back to their most default font. + * This is doubtless helpful in some situations, but not here, + * because we need to know if a Pango font string actually _makes + * sense_ in order to fall back to treating it as an X font name + * if it doesn't. So we check that the font family is actually one + * supported by Pango. + */ +static int pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) +{ +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies, matched; + + /* + * Ask Pango for a list of font families, and iterate through + * them to see if one of them matches the family in the + * PangoFontDescription. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return FALSE; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + + matched = FALSE; + for (i = 0; i < nfamilies; i++) { + if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { + matched = TRUE; + break; + } + } + g_free(families); + + return matched; +} + +static unifont *pangofont_create_internal(GtkWidget *widget, + PangoContext *ctx, + PangoFontDescription *desc, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct pangofont *pfont; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontset *fset; + PangoFontMetrics *metrics; + +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + pfont = snew(struct pangofont); + pfont->u.vt = &pangofont_vtable; + pfont->u.width = + PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics)); + pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); + pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); + pfont->u.height = pfont->u.ascent + pfont->u.descent; + pfont->u.want_fallback = FALSE; + /* The Pango API is hardwired to UTF-8 */ + pfont->u.public_charset = CS_UTF8; + pfont->desc = desc; + pfont->fset = fset; + pfont->widget = widget; + pfont->bold = bold; + pfont->shadowoffset = shadowoffset; + pfont->shadowalways = shadowalways; + pfont->widthcache = NULL; + pfont->nwidthcache = 0; + + pango_font_metrics_unref(metrics); + + return (unifont *)pfont; +} + +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string("Monospace"); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + pango_font_description_set_absolute_size(desc, height * PANGO_SCALE); + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static void pangofont_destroy(unifont *font) +{ + struct pangofont *pfont = (struct pangofont *)font; + pango_font_description_free(pfont->desc); + sfree(pfont->widthcache); + g_object_unref(pfont->fset); + sfree(font); +} + +static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, + wchar_t uchr, const char *utfchr, int utflen) +{ + /* + * Here we check whether a character has the same width as the + * character cell it'll be drawn in. Because profiling showed that + * pango_layout_get_pixel_extents() was a huge bottleneck when we + * were calling it every time we needed to know this, we instead + * call it only on characters we don't already know about, and + * cache the results. + */ + + if ((unsigned)uchr >= pfont->nwidthcache) { + unsigned newsize = ((int)uchr + 0x100) & ~0xFF; + pfont->widthcache = sresize(pfont->widthcache, newsize, int); + while (pfont->nwidthcache < newsize) + pfont->widthcache[pfont->nwidthcache++] = -1; + } + + if (pfont->widthcache[uchr] < 0) { + PangoRectangle rect; + pango_layout_set_text(layout, utfchr, utflen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + pfont->widthcache[uchr] = rect.width; + } + + return pfont->widthcache[uchr]; +} + +static int pangofont_has_glyph(unifont *font, wchar_t glyph) +{ + /* Pango implements font fallback, so assume it has everything */ + return TRUE; +} + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + struct pangofont *pfont = (struct pangofont *)font; + PangoLayout *layout; + PangoRectangle rect; + char *utfstring, *utfptr; + int utflen; + int shadowbold = FALSE; + + if (wide) + cellwidth *= 2; + + y -= pfont->u.ascent; + + layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget)); + pango_layout_set_font_description(layout, pfont->desc); + if (bold > pfont->bold) { + if (pfont->shadowalways) + shadowbold = TRUE; + else { + PangoFontDescription *desc2 = + pango_font_description_copy_static(pfont->desc); + pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD); + pango_layout_set_font_description(layout, desc2); + } + } + + /* + * Pango always expects UTF-8, so convert the input wide character + * string to UTF-8. + */ + utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ + utflen = wc_to_mb(CS_UTF8, 0, string, len, + utfstring, len*6+1, ".", NULL, NULL); + + utfptr = utfstring; + while (utflen > 0) { + int clen, n; + + /* + * We want to display every character from this string in + * the centre of its own character cell. In the worst case, + * this requires a separate text-drawing call for each + * character; but in the common case where the font is + * properly fixed-width, we can draw many characters in one + * go which is much faster. + * + * This still isn't really ideal. If you look at what + * happens in the X protocol as a result of all of this, you + * find - naturally enough - that each call to + * gdk_draw_layout() generates a separate set of X RENDER + * operations involving creating a picture, setting a clip + * rectangle, doing some drawing and undoing the whole lot. + * In an ideal world, we should _always_ be able to turn the + * contents of this loop into a single RenderCompositeGlyphs + * operation which internally specifies inter-character + * deltas to get the spacing right, which would give us full + * speed _even_ in the worst case of a non-fixed-width font. + * However, Pango's architecture and documentation are so + * unhelpful that I have no idea how if at all to persuade + * them to do that. + */ + + /* + * Start by extracting a single UTF-8 character from the + * string. + */ + clen = 1; + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n = 1; + + if (is_rtl(string[0]) || + pangofont_char_width(layout, pfont, string[n-1], + utfptr, clen) != cellwidth) { + /* + * If this character is a right-to-left one, or has an + * unusual width, then we must display it on its own. + */ + } else { + /* + * Try to amalgamate a contiguous string of characters + * with the expected sensible width, for the common case + * in which we're using a monospaced font and everything + * works as expected. + */ + while (clen < utflen) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n++; + if (pangofont_char_width(layout, pfont, + string[n-1], utfptr + oldclen, + clen - oldclen) != cellwidth) { + clen = oldclen; + n--; + break; + } + } + } + + pango_layout_set_text(layout, utfptr, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2, + y + (pfont->u.height - rect.height)/2, layout); + if (shadowbold) + gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, + y + (pfont->u.height - rect.height)/2, layout); + + utflen -= clen; + utfptr += clen; + string += n; + x += n * cellwidth; + } + + sfree(utfstring); + + g_object_unref(layout); +} + +/* + * Dummy size value to be used when converting a + * PangoFontDescription of a scalable font to a string for + * internal use. + */ +#define PANGO_DUMMY_SIZE 12 + +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx) +{ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies; + + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) + return; + + /* + * Ask Pango for a list of font families, and iterate through + * them. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + for (i = 0; i < nfamilies; i++) { + PangoFontFamily *family = families[i]; + const char *familyname; + int flags; + PangoFontFace **faces; + int j, nfaces; + + /* + * Set up our flags for this font family, and get the name + * string. + */ + flags = FONTFLAG_CLIENTSIDE; +#ifndef PANGO_PRE_1POINT4 + /* + * In very early versions of Pango, we can't tell + * monospaced fonts from non-monospaced. + */ + if (!pango_font_family_is_monospace(family)) + flags |= FONTFLAG_NONMONOSPACED; +#endif + familyname = pango_font_family_get_name(family); + + /* + * Go through the available font faces in this family. + */ + pango_font_family_list_faces(family, &faces, &nfaces); + for (j = 0; j < nfaces; j++) { + PangoFontFace *face = faces[j]; + PangoFontDescription *desc; + const char *facename; + int *sizes; + int k, nsizes, dummysize; + + /* + * Get the face name string. + */ + facename = pango_font_face_get_face_name(face); + + /* + * Set up a font description with what we've got so + * far. We'll fill in the size field manually and then + * call pango_font_description_to_string() to give the + * full real name of the specific font. + */ + desc = pango_font_face_describe(face); + + /* + * See if this font has a list of specific sizes. + */ +#ifndef PANGO_PRE_1POINT4 + pango_font_face_list_sizes(face, &sizes, &nsizes); +#else + /* + * In early versions of Pango, that call wasn't + * supported; we just have to assume everything is + * scalable. + */ + sizes = NULL; +#endif + if (!sizes) { + /* + * Write a single entry with a dummy size. + */ + dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE; + sizes = &dummysize; + nsizes = 1; + } + + /* + * If so, go through them one by one. + */ + for (k = 0; k < nsizes; k++) { + char *fullname; + char stylekey[128]; + + pango_font_description_set_size(desc, sizes[k]); + + fullname = pango_font_description_to_string(desc); + + /* + * Construct the sorting key for font styles. + */ + { + char *p = stylekey; + int n; + + n = pango_font_description_get_weight(desc); + /* Weight: normal, then lighter, then bolder */ + if (n <= PANGO_WEIGHT_NORMAL) + n = PANGO_WEIGHT_NORMAL - n; + p += sprintf(p, "%4d", n); + + n = pango_font_description_get_style(desc); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_stretch(desc); + /* Stretch: closer to normal sorts earlier */ + n = 2 * abs(PANGO_STRETCH_NORMAL - n) + + (n < PANGO_STRETCH_NORMAL); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_variant(desc); + p += sprintf(p, " %2d", n); + + } + + /* + * Got everything. Hand off to the callback. + * (The charset string is NULL, because only + * server-side X fonts use it.) + */ + callback(callback_ctx, fullname, familyname, NULL, facename, + stylekey, + (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), + flags, &pangofont_vtable); + + g_free(fullname); + } + if (sizes != &dummysize) + g_free(sizes); + + pango_font_description_free(desc); + } + g_free(faces); + } + g_free(families); +} + +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given a Pango font name to try to make sense of for a + * font selector, we must normalise it to PANGO_DUMMY_SIZE and + * extract its original size (in pixels) into the `size' field. + */ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + *size = PANGO_PIXELS(pango_font_description_get_size(desc)); + *flags = FONTFLAG_CLIENTSIDE; + pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + + pango_font_metrics_unref(metrics); + pango_font_description_free(desc); + g_object_unref(fset); + + return retname; +} + +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + PangoFontDescription *desc; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + pango_font_description_set_size(desc, size * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + pango_font_description_free(desc); + + return retname; +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ + +/* ---------------------------------------------------------------------- + * Outermost functions which do the vtable dispatch. + */ + +/* + * Complete list of font-type subclasses. Listed in preference + * order for unifont_create(). (That is, in the extremely unlikely + * event that the same font name is valid as both a Pango and an + * X11 font, it will be interpreted as the former in the absence + * of an explicit type-disambiguating prefix.) + * + * The 'multifont' subclass is omitted here, as discussed above. + */ +static const struct unifont_vtable *unifont_types[] = { +#if GTK_CHECK_VERSION(2,0,0) + &pangofont_vtable, +#endif + &x11font_vtable, +}; + +/* + * Function which takes a font name and processes the optional + * scheme prefix. Returns the tail of the font name suitable for + * passing to individual font scheme functions, and also provides + * a subrange of the unifont_types[] array above. + * + * The return values `start' and `end' denote a half-open interval + * in unifont_types[]; that is, the correct way to iterate over + * them is + * + * for (i = start; i < end; i++) {...} + */ +static const char *unifont_do_prefix(const char *name, int *start, int *end) +{ + int colonpos = strcspn(name, ":"); + int i; + + if (name[colonpos]) { + /* + * There's a colon prefix on the font name. Use it to work + * out which subclass to use. + */ + for (i = 0; i < lenof(unifont_types); i++) { + if (strlen(unifont_types[i]->prefix) == colonpos && + !strncmp(unifont_types[i]->prefix, name, colonpos)) { + *start = i; + *end = i+1; + return name + colonpos + 1; + } + } + /* + * None matched, so return an empty scheme list to prevent + * any scheme from being called at all. + */ + *start = *end = 0; + return name + colonpos + 1; + } else { + /* + * No colon prefix, so just use all the subclasses. + */ + *start = 0; + *end = lenof(unifont_types); + return name; + } +} + +unifont *unifont_create(GtkWidget *widget, const char *name, int wide, + int bold, int shadowoffset, int shadowalways) +{ + int i, start, end; + + name = unifont_do_prefix(name, &start, &end); + + for (i = start; i < end; i++) { + unifont *ret = unifont_types[i]->create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (ret) + return ret; + } + return NULL; /* font not found in any scheme */ +} + +void unifont_destroy(unifont *font) +{ + font->vt->destroy(font); +} + +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + font->vt->draw_text(target, gc, font, x, y, string, len, + wide, bold, cellwidth); +} + +/* ---------------------------------------------------------------------- + * Multiple-font wrapper. This is a type of unifont which encapsulates + * up to two other unifonts, permitting missing glyphs in the main + * font to be filled in by a fallback font. + * + * This is a type of unifont just like the previous two, but it has a + * separate constructor which is manually called by the client, so it + * doesn't appear in the list of available font types enumerated by + * unifont_create. This means it's not used by unifontsel either, so + * it doesn't need to support any methods except draw_text and + * destroy. + */ + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); +static void multifont_destroy(unifont *font); + +struct multifont { + struct unifont u; + unifont *main; + unifont *fallback; +}; + +static const struct unifont_vtable multifont_vtable = { + NULL, /* creation is done specially */ + NULL, + multifont_destroy, + NULL, + multifont_draw_text, + NULL, + NULL, + NULL, + "client", +}; + +unifont *multifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + int i; + unifont *font, *fallback; + struct multifont *mfont; + + font = unifont_create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (!font) + return NULL; + + fallback = NULL; + if (font->want_fallback) { + for (i = 0; i < lenof(unifont_types); i++) { + if (unifont_types[i]->create_fallback) { + fallback = unifont_types[i]->create_fallback + (widget, font->height, wide, bold, + shadowoffset, shadowalways); + if (fallback) + break; + } + } + } + + /* + * Construct our multifont. Public members are all copied from the + * primary font we're wrapping. + */ + mfont = snew(struct multifont); + mfont->u.vt = &multifont_vtable; + mfont->u.width = font->width; + mfont->u.ascent = font->ascent; + mfont->u.descent = font->descent; + mfont->u.height = font->height; + mfont->u.public_charset = font->public_charset; + mfont->u.want_fallback = FALSE; /* shouldn't be needed, but just in case */ + mfont->main = font; + mfont->fallback = fallback; + + return (unifont *)mfont; +} + +static void multifont_destroy(unifont *font) +{ + struct multifont *mfont = (struct multifont *)font; + unifont_destroy(mfont->main); + if (mfont->fallback) + unifont_destroy(mfont->fallback); + sfree(font); +} + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + struct multifont *mfont = (struct multifont *)font; + int ok, i; + + while (len > 0) { + /* + * Find a maximal sequence of characters which are, or are + * not, supported by our main font. + */ + ok = mfont->main->vt->has_glyph(mfont->main, string[0]); + for (i = 1; + i < len && + !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok; + i++); + + /* + * Now display it. + */ + unifont_draw_text(target, gc, ok ? mfont->main : mfont->fallback, + x, y, string, i, wide, bold, cellwidth); + string += i; + len -= i; + x += i * cellwidth; + } +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Implementation of a unified font selector. Used on GTK 2 only; + * for GTK 1 we still use the standard font selector. + */ + +typedef struct fontinfo fontinfo; + +typedef struct unifontsel_internal { + /* This must be the structure's first element, for cross-casting */ + unifontsel u; + GtkListStore *family_model, *style_model, *size_model; + GtkWidget *family_list, *style_list, *size_entry, *size_list; + GtkWidget *filter_buttons[4]; + GtkWidget *preview_area; + GdkPixmap *preview_pixmap; + int preview_width, preview_height; + GdkColor preview_fg, preview_bg; + int filter_flags; + tree234 *fonts_by_realname, *fonts_by_selorder; + fontinfo *selected; + int selsize, intendedsize; + int inhibit_response; /* inhibit callbacks when we change GUI controls */ +} unifontsel_internal; + +/* + * The structure held in the tree234s. All the string members are + * part of the same allocated area, so don't need freeing + * separately. + */ +struct fontinfo { + char *realname; + char *family, *charset, *style, *stylekey; + int size, flags; + /* + * Fallback sorting key, to permit multiple identical entries + * to exist in the selorder tree. + */ + int index; + /* + * Indices mapping fontinfo structures to indices in the list + * boxes. sizeindex is irrelevant if the font is scalable + * (size==0). + */ + int familyindex, styleindex, sizeindex; + /* + * The class of font. + */ + const struct unifont_vtable *fontclass; +}; + +struct fontinfo_realname_find { + const char *realname; + int flags; +}; + +static int strnullcasecmp(const char *a, const char *b) +{ + int i; + + /* + * If exactly one of the inputs is NULL, it compares before + * the other one. + */ + if ((i = (!b) - (!a)) != 0) + return i; + + /* + * NULL compares equal. + */ + if (!a) + return 0; + + /* + * Otherwise, ordinary strcasecmp. + */ + return g_ascii_strcasecmp(a, b); +} + +static int fontinfo_realname_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_realname_find(void *av, void *bv) +{ + struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_selorder_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + if ((i = strnullcasecmp(a->family, b->family)) != 0) + return i; + /* + * Font class comes immediately after family, so that fonts + * from different classes with the same family + */ + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + if ((i = strnullcasecmp(a->charset, b->charset)) != 0) + return i; + if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) + return i; + if ((i = strnullcasecmp(a->style, b->style)) != 0) + return i; + if (a->size != b->size) + return (a->size < b->size ? -1 : +1); + if (a->index != b->index) + return (a->index < b->index ? -1 : +1); + return 0; +} + +static void unifontsel_deselect(unifontsel_internal *fs) +{ + fs->selected = NULL; + gtk_list_store_clear(fs->style_model); + gtk_list_store_clear(fs->size_model); + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + gtk_widget_set_sensitive(fs->size_entry, FALSE); +} + +static void unifontsel_setup_familylist(unifontsel_internal *fs) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1; + char *currfamily = NULL; + int currflags = -1; + fontinfo *info; + + gtk_list_store_clear(fs->family_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = 0 ;; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * tree. We must still do a processing pass in that + * situation, in case we had an unfinished font record in + * progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->familyindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || strnullcasecmp(currfamily, info->family) || + currflags != (info->flags & FONTFLAG_SORT_MASK)) { + /* + * We've either finished a family, or started a new + * one, or both. + */ + if (currfamily) { + gtk_list_store_append(fs->family_model, &iter); + gtk_list_store_set(fs->family_model, &iter, + 0, currfamily, 1, minpos, 2, maxpos+1, -1); + listindex++; + } + if (info) { + minpos = i; + currfamily = info->family; + currflags = info->flags & FONTFLAG_SORT_MASK; + } + } + if (!info) + break; /* now we're done */ + info->familyindex = listindex; + maxpos = i; + } + + /* + * If we've just filtered out the previously selected font, + * deselect it thoroughly. + */ + if (fs->selected && fs->selected->familyindex < 0) + unifontsel_deselect(fs); +} + +static void unifontsel_setup_stylelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1, started = FALSE; + char *currcs = NULL, *currstyle = NULL; + fontinfo *info; + + gtk_list_store_clear(fs->style_model); + listindex = 0; + started = FALSE; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its charset + * and/or style name to the list box. + */ + for (i = start; i <= end; i++) { + if (i == end) + info = NULL; + else + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * relevant data. We must still do a processing pass in + * that situation, in case we had an unfinished font + * record in progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->styleindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || !started || strnullcasecmp(currcs, info->charset) || + strnullcasecmp(currstyle, info->style)) { + /* + * We've either finished a style/charset, or started a + * new one, or both. + */ + started = TRUE; + if (currstyle) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, currstyle, 1, minpos, 2, maxpos+1, + 3, TRUE, -1); + listindex++; + } + if (info) { + minpos = i; + if (info->charset && strnullcasecmp(currcs, info->charset)) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, info->charset, 1, -1, 2, -1, + 3, FALSE, -1); + listindex++; + } + currcs = info->charset; + currstyle = info->style; + } + } + if (!info) + break; /* now we're done */ + info->styleindex = listindex; + maxpos = i; + } +} + +static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 }; + +static void unifontsel_setup_sizelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex; + char sizetext[40]; + fontinfo *info; + + gtk_list_store_clear(fs->size_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = start; i < end; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + if (info->flags &~ fs->filter_flags) { + info->sizeindex = -1; + continue; /* we're filtering out this font */ + } + if (info->size) { + sprintf(sizetext, "%d", info->size); + info->sizeindex = listindex; + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, + 0, sizetext, 1, i, 2, info->size, -1); + listindex++; + } else { + int j; + + assert(i == start); + assert(i+1 == end); + + for (j = 0; j < lenof(unifontsel_default_sizes); j++) { + sprintf(sizetext, "%d", unifontsel_default_sizes[j]); + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i, + 2, unifontsel_default_sizes[j], -1); + listindex++; + } + } + } +} + +static void unifontsel_set_filter_buttons(unifontsel_internal *fs) +{ + int i; + + for (i = 0; i < lenof(fs->filter_buttons); i++) { + int flagbit = GPOINTER_TO_INT(gtk_object_get_data + (GTK_OBJECT(fs->filter_buttons[i]), + "user-data")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), + !!(fs->filter_flags & flagbit)); + } +} + +static void unifontsel_draw_preview_text(unifontsel_internal *fs) +{ + unifont *font; + char *sizename = NULL; + fontinfo *info = fs->selected; + + if (info) { + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + } else + font = NULL; + + if (fs->preview_pixmap) { + GdkGC *gc = gdk_gc_new(fs->preview_pixmap); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(gc, &fs->preview_fg); + if (font) { + /* + * The pangram used here is rather carefully + * constructed: it contains a sequence of very narrow + * letters (`jil') and a pair of adjacent very wide + * letters (`wm'). + * + * If the user selects a proportional font, it will be + * coerced into fixed-width character cells when used + * in the actual terminal window. We therefore display + * it the same way in the preview pane, so as to show + * it the way it will actually be displayed - and we + * deliberately pick a pangram which will show the + * resulting miskerning at its worst. + * + * We aren't trying to sell people these fonts; we're + * trying to let them make an informed choice. Better + * that they find out the problems with using + * proportional fonts in terminal windows here than + * that they go to the effort of selecting their font + * and _then_ realise it was a mistake. + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent, + L"bankrupt jilted showmen quiz convex fogey", + 41, FALSE, FALSE, font->width); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height, + L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, FALSE, FALSE, font->width); + /* + * The ordering of punctuation here is also selected + * with some specific aims in mind. I put ` and ' + * together because some software (and people) still + * use them as matched quotes no matter what Unicode + * might say on the matter, so people can quickly + * check whether they look silly in a candidate font. + * The sequence #_@ is there to let people judge the + * suitability of the underscore as an effectively + * alphabetic character (since that's how it's often + * used in practice, at least by programmers). + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height * 2, + L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + 42, FALSE, FALSE, font->width); + } + gdk_gc_unref(gc); + gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); + } + if (font) + info->fontclass->destroy(font); + + sfree(sizename); +} + +static void unifontsel_select_font(unifontsel_internal *fs, + fontinfo *info, int size, int leftlist, + int size_is_explicit) +{ + int index; + int minval, maxval; + GtkTreePath *treepath; + GtkTreeIter iter; + + fs->inhibit_response = TRUE; + + fs->selected = info; + fs->selsize = size; + if (size_is_explicit) + fs->intendedsize = size; + + gtk_widget_set_sensitive(fs->u.ok_button, TRUE); + + /* + * Find the index of this fontinfo in the selorder list. + */ + index = -1; + findpos234(fs->fonts_by_selorder, info, NULL, &index); + assert(index >= 0); + + /* + * Adjust the font selector flags and redo the font family + * list box, if necessary. + */ + if (leftlist <= 0 && + (fs->filter_flags | info->flags) != fs->filter_flags) { + fs->filter_flags |= info->flags; + unifontsel_set_filter_buttons(fs); + unifontsel_setup_familylist(fs); + } + + /* + * Find the appropriate family name and select it in the list. + */ + assert(info->familyindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * Now set up the font style list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 1) + unifontsel_setup_stylelist(fs, minval, maxval); + + /* + * Find the appropriate style name and select it in the list. + */ + if (info->style) { + assert(info->styleindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), + &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * And set up the size list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, minval, maxval); + + /* + * Find the appropriate size, and select it in the list. + */ + if (info->size) { + assert(info->sizeindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(treepath); + size = info->size; + } else { + int j; + for (j = 0; j < lenof(unifontsel_default_sizes); j++) + if (unifontsel_default_sizes[j] == size) { + treepath = gtk_tree_path_new_from_indices(j, -1); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, + 0.0); + gtk_tree_path_free(treepath); + } + } + + /* + * And set up the font size text entry box. + */ + { + char sizetext[40]; + sprintf(sizetext, "%d", size); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext); + } + } else { + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, 0, 0); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), ""); + } + + /* + * Grey out the font size edit box if we're not using a + * scalable font. + */ + gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); + gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); + + unifontsel_draw_preview_text(fs); + + fs->inhibit_response = FALSE; +} + +static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int newstate = gtk_toggle_button_get_active(tb); + int newflags; + int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb), + "user-data")); + + if (newstate) + newflags = fs->filter_flags | flagbit; + else + newflags = fs->filter_flags & ~flagbit; + + if (fs->filter_flags != newflags) { + fs->filter_flags = newflags; + unifontsel_setup_familylist(fs); + } +} + +static void unifontsel_add_entry(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass) +{ + unifontsel_internal *fs = (unifontsel_internal *)ctx; + fontinfo *info; + int totalsize; + char *p; + + totalsize = sizeof(fontinfo) + strlen(realfontname) + + (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + + (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10; + info = (fontinfo *)smalloc(totalsize); + info->fontclass = fontclass; + p = (char *)info + sizeof(fontinfo); + info->realname = p; + strcpy(p, realfontname); + p += 1+strlen(p); + if (family) { + info->family = p; + strcpy(p, family); + p += 1+strlen(p); + } else + info->family = NULL; + if (charset) { + info->charset = p; + strcpy(p, charset); + p += 1+strlen(p); + } else + info->charset = NULL; + if (style) { + info->style = p; + strcpy(p, style); + p += 1+strlen(p); + } else + info->style = NULL; + if (stylekey) { + info->stylekey = p; + strcpy(p, stylekey); + p += 1+strlen(p); + } else + info->stylekey = NULL; + assert(p - (char *)info <= totalsize); + info->size = size; + info->flags = flags; + info->index = count234(fs->fonts_by_selorder); + + /* + * It's just conceivable that a misbehaving font enumerator + * might tell us about the same font real name more than once, + * in which case we should silently drop the new one. + */ + if (add234(fs->fonts_by_realname, info) != info) { + sfree(info); + return; + } + /* + * However, we should never get a duplicate key in the + * selorder tree, because the index field carefully + * disambiguates otherwise identical records. + */ + add234(fs->fonts_by_selorder, info); +} + +static fontinfo *update_for_intended_size(unifontsel_internal *fs, + fontinfo *info) +{ + fontinfo info2, *below, *above; + int pos; + + /* + * Copy the info structure. This doesn't copy its dynamic + * string fields, but that's unimportant because all we're + * going to do is to adjust the size field and use it in one + * tree search. + */ + info2 = *info; + info2.size = fs->intendedsize; + + /* + * Search in the tree to find the fontinfo structure which + * best approximates the size the user last requested. + */ + below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, + REL234_LE, &pos); + if (!below) + pos = -1; + above = index234(fs->fonts_by_selorder, pos+1); + + /* + * See if we've found it exactly, which is an easy special + * case. If we have, it'll be in `below' and not `above', + * because we did a REL234_LE rather than REL234_LT search. + */ + if (below && !fontinfo_selorder_compare(&info2, below)) + return below; + + /* + * Now we've either found two suitable fonts, one smaller and + * one larger, or we're at one or other extreme end of the + * scale. Find out which, by NULLing out either of below and + * above if it differs from this one in any respect but size + * (and the disambiguating index field). Bear in mind, also, + * that either one might _already_ be NULL if we're at the + * extreme ends of the font list. + */ + if (below) { + info2.size = below->size; + info2.index = below->index; + if (fontinfo_selorder_compare(&info2, below)) + below = NULL; + } + if (above) { + info2.size = above->size; + info2.index = above->index; + if (fontinfo_selorder_compare(&info2, above)) + above = NULL; + } + + /* + * Now return whichever of above and below is non-NULL, if + * that's unambiguous. + */ + if (!above) + return below; + if (!below) + return above; + + /* + * And now we really do have to make a choice about whether to + * round up or down. We'll do it by rounding to nearest, + * breaking ties by rounding up. + */ + if (above->size - fs->intendedsize <= fs->intendedsize - below->size) + return above; + else + return below; +} + +static void family_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 1, FALSE); +} + +static void style_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + if (minval < 0) + return; /* somehow a charset heading got clicked */ + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 2, FALSE); +} + +static void size_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval, size; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); +} + +static void size_entry_changed(GtkEditable *ed, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + const char *text; + int size; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + text = gtk_entry_get_text(GTK_ENTRY(ed)); + size = atoi(text); + + if (size > 0) { + assert(fs->selected->size == 0); + unifontsel_select_font(fs, fs->selected, size, 3, TRUE); + } +} + +static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeIter iter; + int minval, newsize; + fontinfo *info, *newinfo; + char *newname; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + if (info) { + int flags; + struct fontinfo_realname_find f; + + newname = info->fontclass->canonify_fontname + (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); + + f.realname = newname; + f.flags = flags; + newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + + sfree(newname); + if (!newinfo) + return; /* font name not in our index */ + if (newinfo == info) + return; /* didn't change under canonification => not an alias */ + unifontsel_select_font(fs, newinfo, + newinfo->size ? newinfo->size : newsize, + 1, TRUE); + } +} + +static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + + if (fs->preview_pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + fs->preview_pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +static gint unifontsel_configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int ox, oy, nx, ny, x, y; + + /* + * Enlarge the pixmap, but never shrink it. + */ + ox = fs->preview_width; + oy = fs->preview_height; + x = event->width; + y = event->height; + if (x > ox || y > oy) { + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + nx = (x > ox ? x : ox); + ny = (y > oy ? y : oy); + fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1); + fs->preview_width = nx; + fs->preview_height = ny; + + unifontsel_draw_preview_text(fs); + } + + gdk_window_invalidate_rect(widget->window, NULL, FALSE); + + return TRUE; +} + +unifontsel *unifontsel_new(const char *wintitle) +{ + unifontsel_internal *fs = snew(unifontsel_internal); + GtkWidget *table, *label, *w, *ww, *scroll; + GtkListStore *model; + GtkTreeViewColumn *column; + int lists_height, preview_height, font_width, style_width, size_width; + int i; + + fs->inhibit_response = FALSE; + fs->selected = NULL; + + { + /* + * Invent some magic size constants. + */ + GtkRequisition req; + label = gtk_label_new("Quite Long Font Name (Foundry)"); + gtk_widget_size_request(label, &req); + font_width = req.width; + lists_height = 14 * req.height; + preview_height = 5 * req.height; + gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); + gtk_widget_size_request(label, &req); + style_width = req.width; + gtk_label_set_text(GTK_LABEL(label), "48000"); + gtk_widget_size_request(label, &req); + size_width = req.width; +#if GTK_CHECK_VERSION(2,10,0) + g_object_ref_sink(label); + g_object_unref(label); +#else + gtk_object_sink(GTK_OBJECT(label)); +#endif + } + + /* + * Create the dialog box and initialise the user-visible + * fields in the returned structure. + */ + fs->u.user_data = NULL; + fs->u.window = GTK_WINDOW(gtk_dialog_new()); + gtk_window_set_title(fs->u.window, wintitle); + fs->u.cancel_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_widget_grab_default(fs->u.ok_button); + + /* + * Now set up the internal fields, including in particular all + * the controls that actually allow the user to select fonts. + */ + table = gtk_table_new(8, 3, FALSE); + gtk_widget_show(table); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); +#if GTK_CHECK_VERSION(2,4,0) + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), table); + gtk_widget_show(w); +#else + w = table; +#endif + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), + w, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic("_Font:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Font list box displays only a string, but additionally + * stores two integers which give the limits within the + * tree234 of the font entries covered by this list entry. + */ + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Font", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(family_changed), fs); + g_signal_connect(G_OBJECT(w), "row-activated", + G_CALLBACK(alias_resolve), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, font_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->family_model = model; + fs->family_list = w; + + label = gtk_label_new_with_mnemonic("_Style:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Style list box can contain insensitive elements + * (character set headings for server-side fonts), so we add + * an extra column to the list store to hold that information. + */ + model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, + G_TYPE_BOOLEAN); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(style_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, style_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->style_model = model; + fs->style_list = w; + + label = gtk_label_new_with_mnemonic("Si_ze:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Size label attaches primarily to a text input box so + * that the user can select a size of their choice. The list + * of available sizes is secondary. + */ + fs->size_entry = w = gtk_entry_new(); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_set_size_request(w, size_width, -1); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed), + fs); + + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Size", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(size_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->size_model = model; + fs->size_list = w; + + /* + * Preview widget. + */ + fs->preview_area = gtk_drawing_area_new(); + fs->preview_pixmap = NULL; + fs->preview_width = 0; + fs->preview_height = 0; + fs->preview_fg.pixel = fs->preview_bg.pixel = 0; + fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000; + fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF; + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg, + FALSE, FALSE); + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, + FALSE, FALSE); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event", + GTK_SIGNAL_FUNC(unifontsel_expose_area), fs); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event", + GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); + gtk_widget_set_size_request(fs->preview_area, 1, preview_height); + gtk_widget_show(fs->preview_area); + ww = fs->preview_area; + w = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#if GTK_CHECK_VERSION(2,4,0) + ww = w; + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#endif + ww = w; + w = gtk_frame_new("Preview of font"); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); + + i = 0; + w = gtk_check_button_new_with_label("Show client-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side font aliases"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show non-monospaced fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); + + assert(i == lenof(fs->filter_buttons)); + fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE | + FONTFLAG_SERVERALIAS; + unifontsel_set_filter_buttons(fs); + + /* + * Go and find all the font names, and set up our master font + * list. + */ + fs->fonts_by_realname = newtree234(fontinfo_realname_compare); + fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare); + for (i = 0; i < lenof(unifont_types); i++) + unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window), + unifontsel_add_entry, fs); + + /* + * And set up the initial font names list. + */ + unifontsel_setup_familylist(fs); + + fs->selsize = fs->intendedsize = 13; /* random default */ + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + + return (unifontsel *)fs; +} + +void unifontsel_destroy(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + fontinfo *info; + + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + freetree234(fs->fonts_by_selorder); + while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) + sfree(info); + freetree234(fs->fonts_by_realname); + + gtk_widget_destroy(GTK_WIDGET(fs->u.window)); + sfree(fs); +} + +void unifontsel_set_name(unifontsel *fontsel, const char *fontname) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + int i, start, end, size, flags; + const char *fontname2 = NULL; + fontinfo *info; + + /* + * Provide a default if given an empty or null font name. + */ + if (!fontname || !*fontname) + fontname = "server:fixed"; + + /* + * Call the canonify_fontname function. + */ + fontname = unifont_do_prefix(fontname, &start, &end); + for (i = start; i < end; i++) { + fontname2 = unifont_types[i]->canonify_fontname + (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE); + if (fontname2) + break; + } + if (i == end) + return; /* font name not recognised */ + + /* + * Now look up the canonified font name in our index. + */ + { + struct fontinfo_realname_find f; + f.realname = fontname2; + f.flags = flags; + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + } + + /* + * If we've found the font, and its size field is either + * correct or zero (the latter indicating a scalable font), + * then we're done. Otherwise, try looking up the original + * font name instead. + */ + if (!info || (info->size != size && info->size != 0)) { + struct fontinfo_realname_find f; + f.realname = fontname; + f.flags = flags; + + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + if (!info || info->size != size) + return; /* font name not in our index */ + } + + /* + * Now we've got a fontinfo structure and a font size, so we + * know everything we need to fill in all the fields in the + * dialog. + */ + unifontsel_select_font(fs, info, size, 0, TRUE); +} + +char *unifontsel_get_name(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + char *name; + + if (!fs->selected) + return NULL; + + if (fs->selected->size == 0) { + name = fs->selected->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + if (name) { + char *ret = dupcat(fs->selected->fontclass->prefix, ":", + name, NULL); + sfree(name); + return ret; + } + } + + return dupcat(fs->selected->fontclass->prefix, ":", + fs->selected->realname, NULL); +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/netbox/libs/Putty/unix/gtkfont.h b/netbox/libs/Putty/unix/gtkfont.h new file mode 100644 index 000000000..1ed202bfc --- /dev/null +++ b/netbox/libs/Putty/unix/gtkfont.h @@ -0,0 +1,81 @@ +/* + * Header file for gtkfont.c. Has to be separate from unix.h + * because it depends on GTK data types, hence can't be included + * from cross-platform code (which doesn't go near GTK). + */ + +#ifndef PUTTY_GTKFONT_H +#define PUTTY_GTKFONT_H + +/* + * Exports from gtkfont.c. + */ +struct unifont_vtable; /* contents internal to gtkfont.c */ +typedef struct unifont { + const struct unifont_vtable *vt; + /* + * `Non-static data members' of the `class', accessible to + * external code. + */ + + /* + * public_charset is the charset used when the user asks for + * `Use font encoding'. + */ + int public_charset; + + /* + * Font dimensions needed by clients. + */ + int width, height, ascent, descent; + + /* + * Indicates whether this font is capable of handling all glyphs + * (Pango fonts can do this because Pango automatically supplies + * missing glyphs from other fonts), or whether it would like a + * fallback font to cope with missing glyphs. + */ + int want_fallback; +} unifont; + +unifont *unifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +void unifont_destroy(unifont *font); +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); + +/* + * This function behaves exactly like the low-level unifont_create, + * except that as well as the requested font it also allocates (if + * necessary) a fallback font for filling in replacement glyphs. + * + * Return value is usable with unifont_destroy and unifont_draw_text + * as if it were an ordinary unifont. + */ +unifont *multifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); + +/* + * Unified font selector dialog. I can't be bothered to do a + * proper GTK subclassing today, so this will just be an ordinary + * data structure with some useful members. + * + * (Of course, these aren't the only members; this structure is + * contained within a bigger one which holds data visible only to + * the implementation.) + */ +typedef struct unifontsel { + void *user_data; /* settable by the user */ + GtkWindow *window; + GtkWidget *ok_button, *cancel_button; +} unifontsel; + +unifontsel *unifontsel_new(const char *wintitle); +void unifontsel_destroy(unifontsel *fontsel); +void unifontsel_set_name(unifontsel *fontsel, const char *fontname); +char *unifontsel_get_name(unifontsel *fontsel); + +#endif /* PUTTY_GTKFONT_H */ diff --git a/netbox/libs/Putty/unix/gtkwin.c b/netbox/libs/Putty/unix/gtkwin.c new file mode 100644 index 000000000..3d3789f8e --- /dev/null +++ b/netbox/libs/Putty/unix/gtkwin.c @@ -0,0 +1,3953 @@ +/* + * gtkwin.c: the main code that runs a PuTTY terminal emulator and + * backend in a GTK window. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if GTK_CHECK_VERSION(2,0,0) +#include +#endif + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "terminal.h" +#include "gtkfont.h" + +#define CAT2(x,y) x ## y +#define CAT(x,y) CAT2(x,y) +#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)} + +#if GTK_CHECK_VERSION(2,0,0) +ASSERT(sizeof(long) <= sizeof(gsize)); +#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l) +#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p) +#else /* Gtk 1.2 */ +ASSERT(sizeof(long) <= sizeof(gpointer)); +#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) +#define GPOINTER_TO_LONG(p) ((long)(p)) +#endif + +/* Colours come in two flavours: configurable, and xterm-extended. */ +#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ +#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) + +GdkAtom compound_text_atom, utf8_string_atom; + +extern char **pty_argv; /* declared in pty.c */ +extern int use_pty_argv; + +/* + * Timers are global across all sessions (even if we were handling + * multiple sessions, which we aren't), so the current timer ID is + * a global variable. + */ +static guint timer_id = 0; + +struct gui_data { + GtkWidget *window, *area, *sbar; + GtkBox *hbox; + GtkAdjustment *sbar_adjust; + GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, + *restartitem; + GtkWidget *sessionsmenu; + GdkPixmap *pixmap; +#if GTK_CHECK_VERSION(2,0,0) + GtkIMContext *imc; +#endif + unifont *fonts[4]; /* normal, bold, wide, widebold */ + int xpos, ypos, gotpos, gravity; + GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; + GdkColor cols[NALLCOLOURS]; + GdkColormap *colmap; + wchar_t *pastein_data; + int direct_to_font; + int pastein_data_len; + char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; + int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; + int font_width, font_height; + int width, height; + int ignore_sbar; + int mouseptr_visible; + int busy_status; + guint toplevel_callback_idle_id; + int idle_fn_scheduled, quit_fn_scheduled; + int alt_keycode; + int alt_digits; + char *wintitle; + char *icontitle; + int master_fd, master_func_id; + void *ldisc; + Backend *back; + void *backhandle; + Terminal *term; + void *logctx; + int exited; + struct unicode_data ucsdata; + Conf *conf; + void *eventlogstuff; + char *progname, **gtkargvstart; + int ngtkargs; + guint32 input_event_time; /* Timestamp of the most recent input event. */ + int reconfiguring; + /* Cached things out of conf that we refer to a lot */ + int bold_style; + int window_border; + int cursor_type; +}; + +static void cache_conf_values(struct gui_data *inst) +{ + inst->bold_style = conf_get_int(inst->conf, CONF_bold_style); + inst->window_border = conf_get_int(inst->conf, CONF_window_border); + inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type); +} + +struct draw_ctx { + GdkGC *gc; + struct gui_data *inst; +}; + +static int send_raw_mouse; + +static char *app_name = "pterm"; + +static void start_backend(struct gui_data *inst); +static void exit_callback(void *vinst); + +char *x_get_default(const char *key) +{ + return XGetDefault(GDK_DISPLAY(), app_name, key); +} + +void connection_fatal(void *frontend, char *p, ...) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + fatal_message_box(inst->window, msg); + sfree(msg); + + queue_toplevel_callback(exit_callback, inst); +} + +/* + * Default settings that are specific to pterm. + */ +FontSpec *platform_default_fontspec(const char *name) +{ + if (!strcmp(name, "Font")) + return fontspec_new("server:fixed"); + else + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + if (!strcmp(name, "CloseOnExit")) + return 2; /* maps to FORCE_ON after painful rearrangement :-( */ + if (!strcmp(name, "WinNameAlways")) + return 0; /* X natively supports icon titles, so use 'em by default */ + return def; +} + +/* Dummy routine, only required in plink. */ +void ldisc_update(void *frontend, int echo, int edit) +{ +} + +char *get_ttymode(void *frontend, const char *mode) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_get_ttymode(inst->term, mode); +} + +int from_backend(void *frontend, int is_stderr, const char *data, int len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_data(inst->term, is_stderr, data, len); +} + +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_data_untrusted(inst->term, data, len); +} + +int from_backend_eof(void *frontend) +{ + return TRUE; /* do respond to incoming EOF with outgoing */ +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + struct gui_data *inst = (struct gui_data *)p->frontend; + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = term_get_userpass_input(inst->term, p, in, inlen); + return ret; +} + +void logevent(void *frontend, const char *string) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + log_eventlog(inst->logctx, string); + + logevent_dlg(inst->eventlogstuff, string); +} + +int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */ +{ + struct gui_data *inst = (struct gui_data *)frontend; + + if (which) + return inst->font_height; + else + return inst->font_width; +} + +/* + * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) + * into a cooked one (SELECT, EXTEND, PASTE). + * + * In Unix, this is not configurable; the X button arrangement is + * rock-solid across all applications, everyone has a three-button + * mouse or a means of faking it, and there is no need to switch + * buttons around at all. + */ +static Mouse_Button translate_button(Mouse_Button button) +{ + /* struct gui_data *inst = (struct gui_data *)frontend; */ + + if (button == MBT_LEFT) + return MBT_SELECT; + if (button == MBT_MIDDLE) + return MBT_PASTE; + if (button == MBT_RIGHT) + return MBT_EXTEND; + return 0; /* shouldn't happen */ +} + +/* + * Return the top-level GtkWindow associated with a particular + * front end instance. + */ +void *get_window(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return inst->window; +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +void set_iconic(void *frontend, int iconic) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + struct gui_data *inst = (struct gui_data *)frontend; + if (iconic) + gtk_window_iconify(GTK_WINDOW(inst->window)); + else + gtk_window_deiconify(GTK_WINDOW(inst->window)); +#endif +} + +/* + * Move the window in response to a server-side request. + */ +void move_window(void *frontend, int x, int y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_move(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_move(inst->window->window, x, y); +#endif +} + +/* + * Move the window to the top or bottom of the z-order in response + * to a server-side request. + */ +void set_zorder(void *frontend, int top) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (top) + gdk_window_raise(inst->window->window); + else + gdk_window_lower(inst->window->window); +} + +/* + * Refresh the window in response to a server-side request. + */ +void refresh_window(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + term_invalidate(inst->term); +} + +/* + * Maximise or restore the window in response to a server-side + * request. + */ +void set_zoomed(void *frontend, int zoomed) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + struct gui_data *inst = (struct gui_data *)frontend; + if (zoomed) + gtk_window_maximize(GTK_WINDOW(inst->window)); + else + gtk_window_unmaximize(GTK_WINDOW(inst->window)); +#endif +} + +/* + * Report whether the window is iconic, for terminal reports. + */ +int is_iconic(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return !gdk_window_is_viewable(inst->window->window); +} + +/* + * Report the window's position, for terminal reports. + */ +void get_window_pos(void *frontend, int *x, int *y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_get_position(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_get_position(inst->window->window, x, y); +#endif +} + +/* + * Report the window's pixel size, for terminal reports. + */ +void get_window_pixels(void *frontend, int *x, int *y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_get_size(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_get_size(inst->window->window, x, y); +#endif +} + +/* + * Return the window or icon title. + */ +char *get_window_title(void *frontend, int icon) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return icon ? inst->icontitle : inst->wintitle; +} + +gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (!inst->exited && conf_get_int(inst->conf, CONF_warn_on_close)) { + if (!reallyclose(inst)) + return TRUE; + } + return FALSE; +} + +static void update_mouseptr(struct gui_data *inst) +{ + switch (inst->busy_status) { + case BUSY_NOT: + if (!inst->mouseptr_visible) { + gdk_window_set_cursor(inst->area->window, inst->blankcursor); + } else if (send_raw_mouse) { + gdk_window_set_cursor(inst->area->window, inst->rawcursor); + } else { + gdk_window_set_cursor(inst->area->window, inst->textcursor); + } + break; + case BUSY_WAITING: /* XXX can we do better? */ + case BUSY_CPU: + /* We always display these cursors. */ + gdk_window_set_cursor(inst->area->window, inst->waitcursor); + break; + default: + assert(0); + } +} + +static void show_mouseptr(struct gui_data *inst, int show) +{ + if (!conf_get_int(inst->conf, CONF_hide_mouseptr)) + show = 1; + inst->mouseptr_visible = show; + update_mouseptr(inst); +} + +void draw_backing_rect(struct gui_data *inst) +{ + GdkGC *gc = gdk_gc_new(inst->area->window); + gdk_gc_set_foreground(gc, &inst->cols[258]); /* default background */ + gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0, + inst->width * inst->font_width + 2*inst->window_border, + inst->height * inst->font_height + 2*inst->window_border); + gdk_gc_unref(gc); +} + +gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int w, h, need_size = 0; + + /* + * See if the terminal size has changed, in which case we must + * let the terminal know. + */ + w = (event->width - 2*inst->window_border) / inst->font_width; + h = (event->height - 2*inst->window_border) / inst->font_height; + if (w != inst->width || h != inst->height) { + inst->width = w; + inst->height = h; + conf_set_int(inst->conf, CONF_width, inst->width); + conf_set_int(inst->conf, CONF_height, inst->height); + need_size = 1; + } + + if (inst->pixmap) { + gdk_pixmap_unref(inst->pixmap); + inst->pixmap = NULL; + } + + inst->pixmap = gdk_pixmap_new(widget->window, + (w * inst->font_width + 2*inst->window_border), + (h * inst->font_height + 2*inst->window_border), -1); + + draw_backing_rect(inst); + + if (need_size && inst->term) { + term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); + } + + if (inst->term) + term_invalidate(inst->term); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_im_context_set_client_window(inst->imc, widget->window); +#endif + + return TRUE; +} + +gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + /* + * Pass the exposed rectangle to terminal.c, which will call us + * back to do the actual painting. + */ + if (inst->pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + inst->pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +#define KEY_PRESSED(k) \ + (inst->keystate[(k) / 32] & (1 << ((k) % 32))) + +gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + char output[256]; + wchar_t ucsoutput[2]; + int ucsval, start, end, special, output_charset, use_ucsoutput; + int nethack_mode, app_keypad_mode; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + /* By default, nothing is generated. */ + end = start = 0; + special = use_ucsoutput = FALSE; + output_charset = CS_ISO8859_1; + + /* + * If Alt is being released after typing an Alt+numberpad + * sequence, we should generate the code that was typed. + * + * Note that we only do this if more than one key was actually + * pressed - I don't think Alt+NumPad4 should be ^D or that + * Alt+NumPad3 should be ^C, for example. There's no serious + * inconvenience in having to type a zero before a single-digit + * character code. + */ + if (event->type == GDK_KEY_RELEASE) { + if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || + event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) && + inst->alt_keycode >= 0 && inst->alt_digits > 1) { +#ifdef KEY_DEBUGGING + printf("Alt key up, keycode = %d\n", inst->alt_keycode); +#endif + /* + * FIXME: we might usefully try to do something clever here + * about interpreting the generated key code in a way that's + * appropriate to the line code page. + */ + output[0] = inst->alt_keycode; + end = 1; + goto done; + } +#if GTK_CHECK_VERSION(2,0,0) + if (gtk_im_context_filter_keypress(inst->imc, event)) + return TRUE; +#endif + } + + if (event->type == GDK_KEY_PRESS) { +#ifdef KEY_DEBUGGING + { + int i; + printf("keypress: keyval = %04x, state = %08x; string =", + event->keyval, event->state); + for (i = 0; event->string[i]; i++) + printf(" %02x", (unsigned char) event->string[i]); + printf("\n"); + } +#endif + + /* + * NYI: Compose key (!!! requires Unicode faff before even trying) + */ + + /* + * If Alt has just been pressed, we start potentially + * accumulating an Alt+numberpad code. We do this by + * setting alt_keycode to -1 (nothing yet but plausible). + */ + if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || + event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R)) { + inst->alt_keycode = -1; + inst->alt_digits = 0; + goto done; /* this generates nothing else */ + } + + /* + * If we're seeing a numberpad key press with Mod1 down, + * consider adding it to alt_keycode if that's sensible. + * Anything _else_ with Mod1 down cancels any possibility + * of an ALT keycode: we set alt_keycode to -2. + */ + if ((event->state & GDK_MOD1_MASK) && inst->alt_keycode != -2) { + int digit = -1; + switch (event->keyval) { + case GDK_KP_0: case GDK_KP_Insert: digit = 0; break; + case GDK_KP_1: case GDK_KP_End: digit = 1; break; + case GDK_KP_2: case GDK_KP_Down: digit = 2; break; + case GDK_KP_3: case GDK_KP_Page_Down: digit = 3; break; + case GDK_KP_4: case GDK_KP_Left: digit = 4; break; + case GDK_KP_5: case GDK_KP_Begin: digit = 5; break; + case GDK_KP_6: case GDK_KP_Right: digit = 6; break; + case GDK_KP_7: case GDK_KP_Home: digit = 7; break; + case GDK_KP_8: case GDK_KP_Up: digit = 8; break; + case GDK_KP_9: case GDK_KP_Page_Up: digit = 9; break; + } + if (digit < 0) + inst->alt_keycode = -2; /* it's invalid */ + else { +#ifdef KEY_DEBUGGING + printf("Adding digit %d to keycode %d", digit, + inst->alt_keycode); +#endif + if (inst->alt_keycode == -1) + inst->alt_keycode = digit; /* one-digit code */ + else + inst->alt_keycode = inst->alt_keycode * 10 + digit; + inst->alt_digits++; +#ifdef KEY_DEBUGGING + printf(" gives new code %d\n", inst->alt_keycode); +#endif + /* Having used this digit, we now do nothing more with it. */ + goto done; + } + } + + /* + * Shift-PgUp and Shift-PgDn don't even generate keystrokes + * at all. + */ + if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) { + term_scroll(inst->term, 0, -inst->height/2); + return TRUE; + } + if (event->keyval == GDK_Page_Up && (event->state & GDK_CONTROL_MASK)) { + term_scroll(inst->term, 0, -1); + return TRUE; + } + if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) { + term_scroll(inst->term, 0, +inst->height/2); + return TRUE; + } + if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) { + term_scroll(inst->term, 0, +1); + return TRUE; + } + + /* + * Neither does Shift-Ins. + */ + if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) { + request_paste(inst); + return TRUE; + } + + special = FALSE; + use_ucsoutput = FALSE; + + nethack_mode = conf_get_int(inst->conf, CONF_nethack_keypad); + app_keypad_mode = (inst->term->app_keypad_keys && + !conf_get_int(inst->conf, CONF_no_applic_k)); + + /* ALT+things gives leading Escape. */ + output[0] = '\033'; +#if !GTK_CHECK_VERSION(2,0,0) + /* + * In vanilla X, and hence also GDK 1.2, the string received + * as part of a keyboard event is assumed to be in + * ISO-8859-1. (Seems woefully shortsighted in i18n terms, + * but it's true: see the man page for XLookupString(3) for + * confirmation.) + */ + output_charset = CS_ISO8859_1; + strncpy(output+1, event->string, lenof(output)-1); +#else + /* + * Most things can now be passed to + * gtk_im_context_filter_keypress without breaking anything + * below this point. An exception is the numeric keypad if + * we're in Nethack or application mode: the IM will eat + * numeric keypad presses if Num Lock is on, but we don't want + * it to. + */ + if (app_keypad_mode && + (event->keyval == GDK_Num_Lock || + event->keyval == GDK_KP_Divide || + event->keyval == GDK_KP_Multiply || + event->keyval == GDK_KP_Subtract || + event->keyval == GDK_KP_Add || + event->keyval == GDK_KP_Enter || + event->keyval == GDK_KP_0 || + event->keyval == GDK_KP_Insert || + event->keyval == GDK_KP_1 || + event->keyval == GDK_KP_End || + event->keyval == GDK_KP_2 || + event->keyval == GDK_KP_Down || + event->keyval == GDK_KP_3 || + event->keyval == GDK_KP_Page_Down || + event->keyval == GDK_KP_4 || + event->keyval == GDK_KP_Left || + event->keyval == GDK_KP_5 || + event->keyval == GDK_KP_Begin || + event->keyval == GDK_KP_6 || + event->keyval == GDK_KP_Right || + event->keyval == GDK_KP_7 || + event->keyval == GDK_KP_Home || + event->keyval == GDK_KP_8 || + event->keyval == GDK_KP_Up || + event->keyval == GDK_KP_9 || + event->keyval == GDK_KP_Page_Up || + event->keyval == GDK_KP_Decimal || + event->keyval == GDK_KP_Delete)) { + /* app keypad; do nothing */ + } else if (nethack_mode && + (event->keyval == GDK_KP_1 || + event->keyval == GDK_KP_End || + event->keyval == GDK_KP_2 || + event->keyval == GDK_KP_Down || + event->keyval == GDK_KP_3 || + event->keyval == GDK_KP_Page_Down || + event->keyval == GDK_KP_4 || + event->keyval == GDK_KP_Left || + event->keyval == GDK_KP_5 || + event->keyval == GDK_KP_Begin || + event->keyval == GDK_KP_6 || + event->keyval == GDK_KP_Right || + event->keyval == GDK_KP_7 || + event->keyval == GDK_KP_Home || + event->keyval == GDK_KP_8 || + event->keyval == GDK_KP_Up || + event->keyval == GDK_KP_9 || + event->keyval == GDK_KP_Page_Up)) { + /* nethack mode; do nothing */ + } else { + if (gtk_im_context_filter_keypress(inst->imc, event)) + return TRUE; + } + + /* + * GDK 2.0 arranges to have done some translation for us: in + * GDK 2.0, event->string is encoded in the current locale. + * + * So we use the standard C library function mbstowcs() to + * convert from the current locale into Unicode; from there + * we can convert to whatever PuTTY is currently working in. + * (In fact I convert straight back to UTF-8 from + * wide-character Unicode, for the sake of simplicity: that + * way we can still use exactly the same code to manipulate + * the string, such as prefixing ESC.) + */ + output_charset = CS_UTF8; + { + wchar_t widedata[32]; + const wchar_t *wp; + int wlen; + int ulen; + + wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, + event->string, strlen(event->string), + widedata, lenof(widedata)-1); + + wp = widedata; + ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, + CS_UTF8, NULL, NULL, 0); + output[1+ulen] = '\0'; + } +#endif + + if (!output[1] && + (ucsval = keysym_to_unicode(event->keyval)) >= 0) { + ucsoutput[0] = '\033'; + ucsoutput[1] = ucsval; + use_ucsoutput = TRUE; + end = 2; + } else { + output[lenof(output)-1] = '\0'; + end = strlen(output); + } + if (event->state & GDK_MOD1_MASK) { + start = 0; + if (end == 1) end = 0; + } else + start = 1; + + /* Control-` is the same as Control-\ (unless gtk has a better idea) */ + if (!output[1] && event->keyval == '`' && + (event->state & GDK_CONTROL_MASK)) { + output[1] = '\x1C'; + use_ucsoutput = FALSE; + end = 2; + } + + /* Control-Break sends a Break special to the backend */ + if (event->keyval == GDK_Break && + (event->state & GDK_CONTROL_MASK)) { + if (inst->back) + inst->back->special(inst->backhandle, TS_BRK); + return TRUE; + } + + /* We handle Return ourselves, because it needs to be flagged as + * special to ldisc. */ + if (event->keyval == GDK_Return) { + output[1] = '\015'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + + /* Control-2, Control-Space and Control-@ are NUL */ + if (!output[1] && + (event->keyval == ' ' || event->keyval == '2' || + event->keyval == '@') && + (event->state & (GDK_SHIFT_MASK | + GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) { + output[1] = '\0'; + use_ucsoutput = FALSE; + end = 2; + } + + /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ + if (!output[1] && event->keyval == ' ' && + (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == + (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { + output[1] = '\240'; + output_charset = CS_ISO8859_1; + use_ucsoutput = FALSE; + end = 2; + } + + /* We don't let GTK tell us what Backspace is! We know better. */ + if (event->keyval == GDK_BackSpace && + !(event->state & GDK_SHIFT_MASK)) { + output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ? + '\x7F' : '\x08'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + /* For Shift Backspace, do opposite of what is configured. */ + if (event->keyval == GDK_BackSpace && + (event->state & GDK_SHIFT_MASK)) { + output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ? + '\x08' : '\x7F'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + + /* Shift-Tab is ESC [ Z */ + if (event->keyval == GDK_ISO_Left_Tab || + (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) { + end = 1 + sprintf(output+1, "\033[Z"); + use_ucsoutput = FALSE; + } + /* And normal Tab is Tab, if the keymap hasn't already told us. + * (Curiously, at least one version of the MacOS 10.5 X server + * doesn't translate Tab for us. */ + if (event->keyval == GDK_Tab && end <= 1) { + output[1] = '\t'; + end = 2; + } + + /* + * NetHack keypad mode. + */ + if (nethack_mode) { + char *keys = NULL; + switch (event->keyval) { + case GDK_KP_1: case GDK_KP_End: keys = "bB\002"; break; + case GDK_KP_2: case GDK_KP_Down: keys = "jJ\012"; break; + case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN\016"; break; + case GDK_KP_4: case GDK_KP_Left: keys = "hH\010"; break; + case GDK_KP_5: case GDK_KP_Begin: keys = "..."; break; + case GDK_KP_6: case GDK_KP_Right: keys = "lL\014"; break; + case GDK_KP_7: case GDK_KP_Home: keys = "yY\031"; break; + case GDK_KP_8: case GDK_KP_Up: keys = "kK\013"; break; + case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU\025"; break; + } + if (keys) { + end = 2; + if (event->state & GDK_CONTROL_MASK) + output[1] = keys[2]; + else if (event->state & GDK_SHIFT_MASK) + output[1] = keys[1]; + else + output[1] = keys[0]; + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Application keypad mode. + */ + if (app_keypad_mode) { + int xkey = 0; + switch (event->keyval) { + case GDK_Num_Lock: xkey = 'P'; break; + case GDK_KP_Divide: xkey = 'Q'; break; + case GDK_KP_Multiply: xkey = 'R'; break; + case GDK_KP_Subtract: xkey = 'S'; break; + /* + * Keypad + is tricky. It covers a space that would + * be taken up on the VT100 by _two_ keys; so we + * let Shift select between the two. Worse still, + * in xterm function key mode we change which two... + */ + case GDK_KP_Add: + if (conf_get_int(inst->conf, CONF_funky_type) == FUNKY_XTERM) { + if (event->state & GDK_SHIFT_MASK) + xkey = 'l'; + else + xkey = 'k'; + } else if (event->state & GDK_SHIFT_MASK) + xkey = 'm'; + else + xkey = 'l'; + break; + case GDK_KP_Enter: xkey = 'M'; break; + case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break; + case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break; + case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break; + case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break; + case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break; + case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break; + case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break; + case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break; + case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break; + case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break; + case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break; + } + if (xkey) { + if (inst->term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') + end = 1 + sprintf(output+1, "\033%c", xkey); + else + end = 1 + sprintf(output+1, "\033?%c", xkey); + } else + end = 1 + sprintf(output+1, "\033O%c", xkey); + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Next, all the keys that do tilde codes. (ESC '[' nn '~', + * for integer decimal nn.) + * + * We also deal with the weird ones here. Linux VCs replace F1 + * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but + * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w + * respectively. + */ + { + int code = 0; + int funky_type = conf_get_int(inst->conf, CONF_funky_type); + switch (event->keyval) { + case GDK_F1: + code = (event->state & GDK_SHIFT_MASK ? 23 : 11); + break; + case GDK_F2: + code = (event->state & GDK_SHIFT_MASK ? 24 : 12); + break; + case GDK_F3: + code = (event->state & GDK_SHIFT_MASK ? 25 : 13); + break; + case GDK_F4: + code = (event->state & GDK_SHIFT_MASK ? 26 : 14); + break; + case GDK_F5: + code = (event->state & GDK_SHIFT_MASK ? 28 : 15); + break; + case GDK_F6: + code = (event->state & GDK_SHIFT_MASK ? 29 : 17); + break; + case GDK_F7: + code = (event->state & GDK_SHIFT_MASK ? 31 : 18); + break; + case GDK_F8: + code = (event->state & GDK_SHIFT_MASK ? 32 : 19); + break; + case GDK_F9: + code = (event->state & GDK_SHIFT_MASK ? 33 : 20); + break; + case GDK_F10: + code = (event->state & GDK_SHIFT_MASK ? 34 : 21); + break; + case GDK_F11: + code = 23; + break; + case GDK_F12: + code = 24; + break; + case GDK_F13: + code = 25; + break; + case GDK_F14: + code = 26; + break; + case GDK_F15: + code = 28; + break; + case GDK_F16: + code = 29; + break; + case GDK_F17: + code = 31; + break; + case GDK_F18: + code = 32; + break; + case GDK_F19: + code = 33; + break; + case GDK_F20: + code = 34; + break; + } + if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) { + case GDK_Home: case GDK_KP_Home: + code = 1; + break; + case GDK_Insert: case GDK_KP_Insert: + code = 2; + break; + case GDK_Delete: case GDK_KP_Delete: + code = 3; + break; + case GDK_End: case GDK_KP_End: + code = 4; + break; + case GDK_Page_Up: case GDK_KP_Page_Up: + code = 5; + break; + case GDK_Page_Down: case GDK_KP_Page_Down: + code = 6; + break; + } + /* Reorder edit keys to physical order */ + if (funky_type == FUNKY_VT400 && code <= 6) + code = "\0\2\1\4\5\3\6"[code]; + + if (inst->term->vt52_mode && code > 0 && code <= 6) { + end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]); + use_ucsoutput = FALSE; + goto done; + } + + if (funky_type == FUNKY_SCO && /* SCO function keys */ + code >= 11 && code <= 34) { + char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; + int index = 0; + switch (event->keyval) { + case GDK_F1: index = 0; break; + case GDK_F2: index = 1; break; + case GDK_F3: index = 2; break; + case GDK_F4: index = 3; break; + case GDK_F5: index = 4; break; + case GDK_F6: index = 5; break; + case GDK_F7: index = 6; break; + case GDK_F8: index = 7; break; + case GDK_F9: index = 8; break; + case GDK_F10: index = 9; break; + case GDK_F11: index = 10; break; + case GDK_F12: index = 11; break; + } + if (event->state & GDK_SHIFT_MASK) index += 12; + if (event->state & GDK_CONTROL_MASK) index += 24; + end = 1 + sprintf(output+1, "\x1B[%c", codes[index]); + use_ucsoutput = FALSE; + goto done; + } + if (funky_type == FUNKY_SCO && /* SCO small keypad */ + code >= 1 && code <= 6) { + char codes[] = "HL.FIG"; + if (code == 3) { + output[1] = '\x7F'; + end = 2; + } else { + end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]); + } + use_ucsoutput = FALSE; + goto done; + } + if ((inst->term->vt52_mode || funky_type == FUNKY_VT100P) && + code >= 11 && code <= 24) { + int offt = 0; + if (code > 15) + offt++; + if (code > 21) + offt++; + if (inst->term->vt52_mode) + end = 1 + sprintf(output+1, + "\x1B%c", code + 'P' - 11 - offt); + else + end = 1 + sprintf(output+1, + "\x1BO%c", code + 'P' - 11 - offt); + use_ucsoutput = FALSE; + goto done; + } + if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11); + use_ucsoutput = FALSE; + goto done; + } + if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (inst->term->vt52_mode) + end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11); + else + end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11); + use_ucsoutput = FALSE; + goto done; + } + if ((code == 1 || code == 4) && + conf_get_int(inst->conf, CONF_rxvt_homeend)) { + end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw"); + use_ucsoutput = FALSE; + goto done; + } + if (code) { + end = 1 + sprintf(output+1, "\x1B[%d~", code); + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Cursor keys. (This includes the numberpad cursor keys, + * if we haven't already done them due to app keypad mode.) + * + * Here we also process un-numlocked un-appkeypadded KP5, + * which sends ESC [ G. + */ + { + int xkey = 0; + switch (event->keyval) { + case GDK_Up: case GDK_KP_Up: xkey = 'A'; break; + case GDK_Down: case GDK_KP_Down: xkey = 'B'; break; + case GDK_Right: case GDK_KP_Right: xkey = 'C'; break; + case GDK_Left: case GDK_KP_Left: xkey = 'D'; break; + case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break; + } + if (xkey) { + end = 1 + format_arrow_key(output+1, inst->term, xkey, + event->state & GDK_CONTROL_MASK); + use_ucsoutput = FALSE; + goto done; + } + } + goto done; + } + + done: + + if (end-start > 0) { +#ifdef KEY_DEBUGGING + int i; + printf("generating sequence:"); + for (i = start; i < end; i++) + printf(" %02x", (unsigned char) output[i]); + printf("\n"); +#endif + + if (special) { + /* + * For special control characters, the character set + * should never matter. + */ + output[end] = '\0'; /* NUL-terminate */ + if (inst->ldisc) + ldisc_send(inst->ldisc, output+start, -2, 1); + } else if (!inst->direct_to_font) { + if (!use_ucsoutput) { + if (inst->ldisc) + lpage_send(inst->ldisc, output_charset, output+start, + end-start, 1); + } else { + /* + * We generated our own Unicode key data from the + * keysym, so use that instead. + */ + if (inst->ldisc) + luni_send(inst->ldisc, ucsoutput+start, end-start, 1); + } + } else { + /* + * In direct-to-font mode, we just send the string + * exactly as we received it. + */ + if (inst->ldisc) + ldisc_send(inst->ldisc, output+start, end-start, 1); + } + + show_mouseptr(inst, 0); + term_seen_key_event(inst->term); + } + + return TRUE; +} + +#if GTK_CHECK_VERSION(2,0,0) +void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (inst->ldisc) + lpage_send(inst->ldisc, CS_UTF8, str, strlen(str), 1); + show_mouseptr(inst, 0); + term_seen_key_event(inst->term); +} +#endif + +gboolean button_internal(struct gui_data *inst, guint32 timestamp, + GdkEventType type, guint ebutton, guint state, + gdouble ex, gdouble ey) +{ + int shift, ctrl, alt, x, y, button, act, raw_mouse_mode; + + /* Remember the timestamp. */ + inst->input_event_time = timestamp; + + show_mouseptr(inst, 1); + + shift = state & GDK_SHIFT_MASK; + ctrl = state & GDK_CONTROL_MASK; + alt = state & GDK_MOD1_MASK; + + raw_mouse_mode = + send_raw_mouse && !(shift && conf_get_int(inst->conf, + CONF_mouse_override)); + + if (!raw_mouse_mode) { + if (ebutton == 4 && type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, -5); + return TRUE; + } + if (ebutton == 5 && type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, +5); + return TRUE; + } + } + + if (ebutton == 3 && ctrl) { + gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, + ebutton, timestamp); + return TRUE; + } + + if (ebutton == 1) + button = MBT_LEFT; + else if (ebutton == 2) + button = MBT_MIDDLE; + else if (ebutton == 3) + button = MBT_RIGHT; + else if (ebutton == 4) + button = MBT_WHEEL_UP; + else if (ebutton == 5) + button = MBT_WHEEL_DOWN; + else + return FALSE; /* don't even know what button! */ + + switch (type) { + case GDK_BUTTON_PRESS: act = MA_CLICK; break; + case GDK_BUTTON_RELEASE: act = MA_RELEASE; break; + case GDK_2BUTTON_PRESS: act = MA_2CLK; break; + case GDK_3BUTTON_PRESS: act = MA_3CLK; break; + default: return FALSE; /* don't know this event type */ + } + + if (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE) + return TRUE; /* we ignore these in raw mouse mode */ + + x = (ex - inst->window_border) / inst->font_width; + y = (ey - inst->window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), act, + x, y, shift, ctrl, alt); + + return TRUE; +} + +gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + return button_internal(inst, event->time, event->type, event->button, + event->state, event->x, event->y); +} + +#if GTK_CHECK_VERSION(2,0,0) +/* + * In GTK 2, mouse wheel events have become a new type of event. + * This handler translates them back into button-4 and button-5 + * presses so that I don't have to change my old code too much :-) + */ +gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + guint button; + + if (event->direction == GDK_SCROLL_UP) + button = 4; + else if (event->direction == GDK_SCROLL_DOWN) + button = 5; + else + return FALSE; + + return button_internal(inst, event->time, GDK_BUTTON_PRESS, + button, event->state, event->x, event->y); +} +#endif + +gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int shift, ctrl, alt, x, y, button; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + show_mouseptr(inst, 1); + + shift = event->state & GDK_SHIFT_MASK; + ctrl = event->state & GDK_CONTROL_MASK; + alt = event->state & GDK_MOD1_MASK; + if (event->state & GDK_BUTTON1_MASK) + button = MBT_LEFT; + else if (event->state & GDK_BUTTON2_MASK) + button = MBT_MIDDLE; + else if (event->state & GDK_BUTTON3_MASK) + button = MBT_RIGHT; + else + return FALSE; /* don't even know what button! */ + + x = (event->x - inst->window_border) / inst->font_width; + y = (event->y - inst->window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), MA_DRAG, + x, y, shift, ctrl, alt); + + return TRUE; +} + +void frontend_keypress(void *handle) +{ + struct gui_data *inst = (struct gui_data *)handle; + + /* + * If our child process has exited but not closed, terminate on + * any keypress. + */ + if (inst->exited) + cleanup_exit(0); +} + +static void exit_callback(void *vinst) +{ + struct gui_data *inst = (struct gui_data *)vinst; + int exitcode, close_on_exit; + + if (!inst->exited && + (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { + inst->exited = TRUE; + close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_ON || + (close_on_exit == AUTO && exitcode == 0)) + gtk_main_quit(); /* just go */ + if (inst->ldisc) { + ldisc_free(inst->ldisc); + inst->ldisc = NULL; + } + inst->back->free(inst->backhandle); + inst->backhandle = NULL; + inst->back = NULL; + term_provide_resize_fn(inst->term, NULL, NULL); + update_specials_menu(inst); + gtk_widget_set_sensitive(inst->restartitem, TRUE); + } +} + +void notify_remote_exit(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + queue_toplevel_callback(exit_callback, inst); +} + +static void notify_toplevel_callback(void *frontend); + +static gint quit_toplevel_callback_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + notify_toplevel_callback(inst); + + inst->quit_fn_scheduled = FALSE; + + return 0; +} + +static gint idle_toplevel_callback_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (gtk_main_level() > 1) { + /* + * We don't run the callbacks if we're in the middle of a + * subsidiary gtk_main. Instead, ask for a callback when we + * get back out of the subsidiary main loop (if we haven't + * already arranged one), so we can reschedule ourself then. + */ + if (!inst->quit_fn_scheduled) { + gtk_quit_add(2, quit_toplevel_callback_func, inst); + inst->quit_fn_scheduled = TRUE; + } + /* + * And unschedule this idle function, since we've now done + * everything we can until the innermost gtk_main has quit and + * can reschedule us with a chance of actually taking action. + */ + if (inst->idle_fn_scheduled) { /* double-check, just in case */ + gtk_idle_remove(inst->toplevel_callback_idle_id); + inst->idle_fn_scheduled = FALSE; + } + } else { + run_toplevel_callbacks(); + } + + /* + * If we've emptied our toplevel callback queue, unschedule + * ourself. Otherwise, leave ourselves pending so we'll be called + * again to deal with more callbacks after another round of the + * event loop. + */ + if (!toplevel_callback_pending() && inst->idle_fn_scheduled) { + gtk_idle_remove(inst->toplevel_callback_idle_id); + inst->idle_fn_scheduled = FALSE; + } + + return TRUE; +} + +static void notify_toplevel_callback(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + if (!inst->idle_fn_scheduled) { + inst->toplevel_callback_idle_id = + gtk_idle_add(idle_toplevel_callback_func, inst); + inst->idle_fn_scheduled = TRUE; + } +} + +static gint timer_trigger(gpointer data) +{ + unsigned long now = GPOINTER_TO_LONG(data); + unsigned long next, then; + long ticks; + + /* + * Destroy the timer we got here on. + */ + if (timer_id) { + gtk_timeout_remove(timer_id); + timer_id = 0; + } + + /* + * run_timers() may cause a call to timer_change_notify, in which + * case a new timer will already have been set up and left in + * timer_id. If it hasn't, and run_timers reports that some timing + * still needs to be done, we do it ourselves. + */ + if (run_timers(now, &next) && !timer_id) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + timer_id = gtk_timeout_add(ticks, timer_trigger, + LONG_TO_GPOINTER(next)); + } + + /* + * Returning FALSE means 'don't call this timer again', which + * _should_ be redundant given that we removed it above, but just + * in case, return FALSE anyway. + */ + return FALSE; +} + +void timer_change_notify(unsigned long next) +{ + long ticks; + + if (timer_id) + gtk_timeout_remove(timer_id); + + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + + timer_id = gtk_timeout_add(ticks, timer_trigger, + LONG_TO_GPOINTER(next)); +} + +void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) +{ + /* + * We must process exceptional notifications before ordinary + * readability ones, or we may go straight past the urgent + * marker. + */ + if (condition & GDK_INPUT_EXCEPTION) + select_result(sourcefd, 4); + if (condition & GDK_INPUT_READ) + select_result(sourcefd, 1); + if (condition & GDK_INPUT_WRITE) + select_result(sourcefd, 2); +} + +void destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_set_focus(inst->term, event->in); + term_update(inst->term); + show_mouseptr(inst, 1); + return FALSE; +} + +void set_busy_status(void *frontend, int status) +{ + struct gui_data *inst = (struct gui_data *)frontend; + inst->busy_status = status; + update_mouseptr(inst); +} + +/* + * set or clear the "raw mouse message" mode + */ +void set_raw_mouse_mode(void *frontend, int activate) +{ + struct gui_data *inst = (struct gui_data *)frontend; + activate = activate && !conf_get_int(inst->conf, CONF_no_mouse_rep); + send_raw_mouse = activate; + update_mouseptr(inst); +} + +void request_resize(void *frontend, int w, int h) +{ + struct gui_data *inst = (struct gui_data *)frontend; + int large_x, large_y; + int offset_x, offset_y; + int area_x, area_y; + GtkRequisition inner, outer; + + /* + * This is a heinous hack dreamed up by the gnome-terminal + * people to get around a limitation in gtk. The problem is + * that in order to set the size correctly we really need to be + * calling gtk_window_resize - but that needs to know the size + * of the _whole window_, not the drawing area. So what we do + * is to set an artificially huge size request on the drawing + * area, recompute the resulting size request on the window, + * and look at the difference between the two. That gives us + * the x and y offsets we need to translate drawing area size + * into window size for real, and then we call + * gtk_window_resize. + */ + + /* + * We start by retrieving the current size of the whole window. + * Adding a bit to _that_ will give us a value we can use as a + * bogus size request which guarantees to be bigger than the + * current size of the drawing area. + */ + get_window_pixels(inst, &large_x, &large_y); + large_x += 32; + large_y += 32; + +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_size_request(inst->area, large_x, large_y); +#else + gtk_widget_set_usize(inst->area, large_x, large_y); +#endif + gtk_widget_size_request(inst->area, &inner); + gtk_widget_size_request(inst->window, &outer); + + offset_x = outer.width - inner.width; + offset_y = outer.height - inner.height; + + area_x = inst->font_width * w + 2*inst->window_border; + area_y = inst->font_height * h + 2*inst->window_border; + + /* + * Now we must set the size request on the drawing area back to + * something sensible before we commit the real resize. Best + * way to do this, I think, is to set it to what the size is + * really going to end up being. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_size_request(inst->area, area_x, area_y); + gtk_window_resize(GTK_WINDOW(inst->window), + area_x + offset_x, area_y + offset_y); +#else + gtk_widget_set_usize(inst->area, area_x, area_y); + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); + /* + * I can no longer remember what this call to + * gtk_container_dequeue_resize_handler is for. It was + * introduced in r3092 with no comment, and the commit log + * message was uninformative. I'm _guessing_ its purpose is to + * prevent gratuitous resize processing on the window given + * that we're about to resize it anyway, but I have no idea + * why that's so incredibly vital. + * + * I've tried removing the call, and nothing seems to go + * wrong. I've backtracked to r3092 and tried removing the + * call there, and still nothing goes wrong. So I'm going to + * adopt the working hypothesis that it's superfluous; I won't + * actually remove it from the GTK 1.2 code, but I won't + * attempt to replicate its functionality in the GTK 2 code + * above. + */ + gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); + gdk_window_resize(inst->window->window, + area_x + offset_x, area_y + offset_y); +#endif +} + +static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b) +{ + gboolean success[1]; + + inst->cols[n].red = r * 0x0101; + inst->cols[n].green = g * 0x0101; + inst->cols[n].blue = b * 0x0101; + + gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1); + gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1, + FALSE, TRUE, success); + if (!success[0]) + g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname, + n, r, g, b); +} + +void set_window_background(struct gui_data *inst) +{ + if (inst->area && inst->area->window) + gdk_window_set_background(inst->area->window, &inst->cols[258]); + if (inst->window && inst->window->window) + gdk_window_set_background(inst->window->window, &inst->cols[258]); +} + +void palette_set(void *frontend, int n, int r, int g, int b) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (n >= 16) + n += 256 - 16; + if (n >= NALLCOLOURS) + return; + real_palette_set(inst, n, r, g, b); + if (n == 258) { + /* Default Background changed. Ensure space between text area and + * window border is redrawn */ + set_window_background(inst); + draw_backing_rect(inst); + gtk_widget_queue_draw(inst->area); + } +} + +void palette_reset(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* This maps colour indices in inst->conf to those used in inst->cols. */ + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + gboolean success[NALLCOLOURS]; + int i; + + assert(lenof(ww) == NCFGCOLOURS); + + if (!inst->colmap) { + inst->colmap = gdk_colormap_get_system(); + } else { + gdk_colormap_free_colors(inst->colmap, inst->cols, NALLCOLOURS); + } + + for (i = 0; i < NCFGCOLOURS; i++) { + inst->cols[ww[i]].red = + conf_get_int_int(inst->conf, CONF_colours, i*3+0) * 0x0101; + inst->cols[ww[i]].green = + conf_get_int_int(inst->conf, CONF_colours, i*3+1) * 0x0101; + inst->cols[ww[i]].blue = + conf_get_int_int(inst->conf, CONF_colours, i*3+2) * 0x0101; + } + + for (i = 0; i < NEXTCOLOURS; i++) { + if (i < 216) { + int r = i / 36, g = (i / 6) % 6, b = i % 6; + inst->cols[i+16].red = r ? r * 0x2828 + 0x3737 : 0; + inst->cols[i+16].green = g ? g * 0x2828 + 0x3737 : 0; + inst->cols[i+16].blue = b ? b * 0x2828 + 0x3737 : 0; + } else { + int shade = i - 216; + shade = shade * 0x0a0a + 0x0808; + inst->cols[i+16].red = inst->cols[i+16].green = + inst->cols[i+16].blue = shade; + } + } + + gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS, + FALSE, TRUE, success); + for (i = 0; i < NALLCOLOURS; i++) { + if (!success[i]) + g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", + appname, i, + conf_get_int_int(inst->conf, CONF_colours, i*3+0), + conf_get_int_int(inst->conf, CONF_colours, i*3+1), + conf_get_int_int(inst->conf, CONF_colours, i*3+2)); + } + + /* Since Default Background may have changed, ensure that space + * between text area and window border is refreshed. */ + set_window_background(inst); + if (inst->area && inst->area->window) { + draw_backing_rect(inst); + gtk_widget_queue_draw(inst->area); + } +} + +/* Ensure that all the cut buffers exist - according to the ICCCM, we must + * do this before we start using cut buffers. + */ +void init_cutbuffers() +{ + unsigned char empty[] = ""; + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); +} + +/* Store the data in a cut-buffer. */ +void store_cutbuffer(char * ptr, int len) +{ + /* ICCCM says we must rotate the buffers before storing to buffer 0. */ + XRotateBuffers(GDK_DISPLAY(), 1); + XStoreBytes(GDK_DISPLAY(), ptr, len); +} + +/* Retrieve data from a cut-buffer. + * Returned data needs to be freed with XFree(). + */ +char * retrieve_cutbuffer(int * nbytes) +{ + char * ptr; + ptr = XFetchBytes(GDK_DISPLAY(), nbytes); + if (*nbytes <= 0 && ptr != 0) { + XFree(ptr); + ptr = 0; + } + return ptr; +} + +void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (inst->pasteout_data) + sfree(inst->pasteout_data); + if (inst->pasteout_data_ctext) + sfree(inst->pasteout_data_ctext); + if (inst->pasteout_data_utf8) + sfree(inst->pasteout_data_utf8); + + /* + * Set up UTF-8 and compound text paste data. This only happens + * if we aren't in direct-to-font mode using the D800 hack. + */ + if (!inst->direct_to_font) { + const wchar_t *tmp = data; + int tmplen = len; + XTextProperty tp; + char *list[1]; + + inst->pasteout_data_utf8 = snewn(len*6, char); + inst->pasteout_data_utf8_len = len*6; + inst->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len, + CS_UTF8, NULL, NULL, 0); + if (inst->pasteout_data_utf8_len == 0) { + sfree(inst->pasteout_data_utf8); + inst->pasteout_data_utf8 = NULL; + } else { + inst->pasteout_data_utf8 = + sresize(inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len + 1, char); + inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0'; + } + + /* + * Now let Xlib convert our UTF-8 data into compound text. + */ + list[0] = inst->pasteout_data_utf8; + if (Xutf8TextListToTextProperty(GDK_DISPLAY(), list, 1, + XCompoundTextStyle, &tp) == 0) { + inst->pasteout_data_ctext = snewn(tp.nitems+1, char); + memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems); + inst->pasteout_data_ctext_len = tp.nitems; + XFree(tp.value); + } else { + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + } + } else { + inst->pasteout_data_utf8 = NULL; + inst->pasteout_data_utf8_len = 0; + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + } + + inst->pasteout_data = snewn(len*6, char); + inst->pasteout_data_len = len*6; + inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, + data, len, inst->pasteout_data, + inst->pasteout_data_len, + NULL, NULL, NULL); + if (inst->pasteout_data_len == 0) { + sfree(inst->pasteout_data); + inst->pasteout_data = NULL; + } else { + inst->pasteout_data = + sresize(inst->pasteout_data, inst->pasteout_data_len, char); + } + + store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len); + + if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, + inst->input_event_time)) { +#if GTK_CHECK_VERSION(2,0,0) + gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY); +#endif + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, 1); + if (inst->pasteout_data_ctext) + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + compound_text_atom, 1); + if (inst->pasteout_data_utf8) + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + utf8_string_atom, 1); + } + + if (must_deselect) + term_deselect(inst->term); +} + +void selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (seldata->target == utf8_string_atom) + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len); + else if (seldata->target == compound_text_atom) + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data_ctext, + inst->pasteout_data_ctext_len); + else + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data, + inst->pasteout_data_len); +} + +gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + term_deselect(inst->term); + if (inst->pasteout_data) + sfree(inst->pasteout_data); + if (inst->pasteout_data_ctext) + sfree(inst->pasteout_data_ctext); + if (inst->pasteout_data_utf8) + sfree(inst->pasteout_data_utf8); + inst->pasteout_data = NULL; + inst->pasteout_data_len = 0; + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + inst->pasteout_data_utf8 = NULL; + inst->pasteout_data_utf8_len = 0; + return TRUE; +} + +void request_paste(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * In Unix, pasting is asynchronous: all we can do at the + * moment is to call gtk_selection_convert(), and when the data + * comes back _then_ we can call term_do_paste(). + */ + + if (!inst->direct_to_font) { + /* + * First we attempt to retrieve the selection as a UTF-8 + * string (which we will convert to the correct code page + * before sending to the session, of course). If that + * fails, selection_received() will be informed and will + * fall back to an ordinary string. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + utf8_string_atom, + inst->input_event_time); + } else { + /* + * If we're in direct-to-font mode, we disable UTF-8 + * pasting, and go straight to ordinary string data. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + } +} + +gint idle_paste_func(gpointer data); /* forward ref */ + +void selection_received(GtkWidget *widget, GtkSelectionData *seldata, + guint time, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + XTextProperty tp; + char **list; + char *text; + int length, count, ret; + int free_list_required = 0; + int free_required = 0; + int charset; + + if (seldata->target == utf8_string_atom && seldata->length <= 0) { + /* + * Failed to get a UTF-8 selection string. Try compound + * text next. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + compound_text_atom, + inst->input_event_time); + return; + } + + if (seldata->target == compound_text_atom && seldata->length <= 0) { + /* + * Failed to get UTF-8 or compound text. Try an ordinary + * string. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + return; + } + + /* + * If we have data, but it's not of a type we can deal with, + * we have to ignore the data. + */ + if (seldata->length > 0 && + seldata->type != GDK_SELECTION_TYPE_STRING && + seldata->type != compound_text_atom && + seldata->type != utf8_string_atom) + return; + + /* + * If we have no data, try looking in a cut buffer. + */ + if (seldata->length <= 0) { + text = retrieve_cutbuffer(&length); + if (length == 0) + return; + /* Xterm is rumoured to expect Latin-1, though I havn't checked the + * source, so use that as a de-facto standard. */ + charset = CS_ISO8859_1; + free_required = 1; + } else { + /* + * Convert COMPOUND_TEXT into UTF-8. + */ + if (seldata->type == compound_text_atom) { + tp.value = seldata->data; + tp.encoding = (Atom) seldata->type; + tp.format = seldata->format; + tp.nitems = seldata->length; + ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp, + &list, &count); + if (ret != 0 || count != 1) { + /* + * Compound text failed; fall back to STRING. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + return; + } + text = list[0]; + length = strlen(list[0]); + charset = CS_UTF8; + free_list_required = 1; + } else { + text = (char *)seldata->data; + length = seldata->length; + charset = (seldata->type == utf8_string_atom ? + CS_UTF8 : inst->ucsdata.line_codepage); + } + } + + if (inst->pastein_data) + sfree(inst->pastein_data); + + inst->pastein_data = snewn(length, wchar_t); + inst->pastein_data_len = length; + inst->pastein_data_len = + mb_to_wc(charset, 0, text, length, + inst->pastein_data, inst->pastein_data_len); + + term_do_paste(inst->term); + + if (free_list_required) + XFreeStringList(list); + if (free_required) + XFree(text); +} + +void get_clip(void *frontend, wchar_t ** p, int *len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + if (p) { + *p = inst->pastein_data; + *len = inst->pastein_data_len; + } +} + +static void set_window_titles(struct gui_data *inst) +{ + /* + * We must always call set_icon_name after calling set_title, + * since set_title will write both names. Irritating, but such + * is life. + */ + gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle); + if (!conf_get_int(inst->conf, CONF_win_name_always)) + gdk_window_set_icon_name(inst->window->window, inst->icontitle); +} + +void set_title(void *frontend, char *title) +{ + struct gui_data *inst = (struct gui_data *)frontend; + sfree(inst->wintitle); + inst->wintitle = dupstr(title); + set_window_titles(inst); +} + +void set_icon(void *frontend, char *title) +{ + struct gui_data *inst = (struct gui_data *)frontend; + sfree(inst->icontitle); + inst->icontitle = dupstr(title); + set_window_titles(inst); +} + +void set_title_and_icon(void *frontend, char *title, char *icon) +{ + struct gui_data *inst = (struct gui_data *)frontend; + sfree(inst->wintitle); + inst->wintitle = dupstr(title); + sfree(inst->icontitle); + inst->icontitle = dupstr(icon); + set_window_titles(inst); +} + +void set_sbar(void *frontend, int total, int start, int page) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (!conf_get_int(inst->conf, CONF_scrollbar)) + return; + inst->sbar_adjust->lower = 0; + inst->sbar_adjust->upper = total; + inst->sbar_adjust->value = start; + inst->sbar_adjust->page_size = page; + inst->sbar_adjust->step_increment = 1; + inst->sbar_adjust->page_increment = page/2; + inst->ignore_sbar = TRUE; + gtk_adjustment_changed(inst->sbar_adjust); + inst->ignore_sbar = FALSE; +} + +void scrollbar_moved(GtkAdjustment *adj, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (!conf_get_int(inst->conf, CONF_scrollbar)) + return; + if (!inst->ignore_sbar) + term_scroll(inst->term, 1, (int)adj->value); +} + +void sys_cursor(void *frontend, int x, int y) +{ + /* + * This is meaningless under X. + */ +} + +/* + * This is still called when mode==BELL_VISUAL, even though the + * visual bell is handled entirely within terminal.c, because we + * may want to perform additional actions on any kind of bell (for + * example, taskbar flashing in Windows). + */ +void do_beep(void *frontend, int mode) +{ + if (mode == BELL_DEFAULT) + gdk_beep(); +} + +int char_width(Context ctx, int uc) +{ + /* + * Under X, any fixed-width font really _is_ fixed-width. + * Double-width characters will be dealt with using a separate + * font. For the moment we can simply return 1. + * + * FIXME: but is that also true of Pango? + */ + return 1; +} + +Context get_ctx(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + struct draw_ctx *dctx; + + if (!inst->area->window) + return NULL; + + dctx = snew(struct draw_ctx); + dctx->inst = inst; + dctx->gc = gdk_gc_new(inst->area->window); + return dctx; +} + +void free_ctx(Context ctx) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + /* struct gui_data *inst = dctx->inst; */ + GdkGC *gc = dctx->gc; + gdk_gc_unref(gc); + sfree(dctx); +} + +/* + * Draw a line of text in the window, at given character + * coordinates, in given attributes. + * + * We are allowed to fiddle with the contents of `text'. + */ +void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + int ncombining, combining; + int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold; + int monochrome = gtk_widget_get_visual(inst->area)->depth == 1; + + if (attr & TATTR_COMBINING) { + ncombining = len; + len = 1; + } else + ncombining = 1; + + nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT); + nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT); + if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) { + t = nfg; + nfg = nbg; + nbg = t; + } + if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } + if ((attr & TATTR_ACTCURS) && !monochrome) { + nfg = 260; + nbg = 261; + } + + fontid = shadow = 0; + + if (attr & ATTR_WIDE) { + widefactor = 2; + fontid |= 2; + } else { + widefactor = 1; + } + + if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { + bold = 1; + fontid |= 1; + } else { + bold = 0; + } + + if (!inst->fonts[fontid]) { + int i; + /* + * Fall back through font ids with subsets of this one's + * set bits, in order. + */ + for (i = fontid; i-- > 0 ;) { + if (i & ~fontid) + continue; /* some other bit is set */ + if (inst->fonts[i]) { + fontid = i; + break; + } + } + assert(inst->fonts[fontid]); /* we should at least have hit zero */ + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + rlen = len * 2; + } else + rlen = len; + + { + GdkRectangle r; + + r.x = x*inst->font_width+inst->window_border; + r.y = y*inst->font_height+inst->window_border; + r.width = rlen*widefactor*inst->font_width; + r.height = inst->font_height; + gdk_gc_set_clip_rectangle(gc, &r); + } + + gdk_gc_set_foreground(gc, &inst->cols[nbg]); + gdk_draw_rectangle(inst->pixmap, gc, 1, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + rlen*widefactor*inst->font_width, inst->font_height); + + gdk_gc_set_foreground(gc, &inst->cols[nfg]); + for (combining = 0; combining < ncombining; combining++) { + unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+inst->fonts[0]->ascent, + text + combining, len, widefactor > 1, + bold, inst->font_width); + } + + if (attr & ATTR_UNDER) { + int uheight = inst->fonts[0]->ascent + 1; + if (uheight >= inst->font_height) + uheight = inst->font_height - 1; + gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->window_border, + y*inst->font_height + uheight + inst->window_border, + (x+len)*widefactor*inst->font_width-1+inst->window_border, + y*inst->font_height + uheight + inst->window_border); + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + /* + * I can't find any plausible StretchBlt equivalent in the + * X server, so I'm going to do this the slow and painful + * way. This will involve repeated calls to + * gdk_draw_pixmap() to stretch the text horizontally. It's + * O(N^2) in time and O(N) in network bandwidth, but you + * try thinking of a better way. :-( + */ + int i; + for (i = 0; i < len * widefactor * inst->font_width; i++) { + gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, + x*inst->font_width+inst->window_border + 2*i, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border + 2*i+1, + y*inst->font_height+inst->window_border, + len * widefactor * inst->font_width - i, inst->font_height); + } + len *= 2; + if ((lattr & LATTR_MODE) != LATTR_WIDE) { + int dt, db; + /* Now stretch vertically, in the same way. */ + if ((lattr & LATTR_MODE) == LATTR_BOT) + dt = 0, db = 1; + else + dt = 1, db = 0; + for (i = 0; i < inst->font_height; i+=2) { + gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+dt*i+db, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+dt*(i+1), + len * widefactor * inst->font_width, inst->font_height-i-1); + } + } + } +} + +void do_text(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + int widefactor; + + do_text_internal(ctx, x, y, text, len, attr, lattr); + + if (attr & ATTR_WIDE) { + widefactor = 2; + } else { + widefactor = 1; + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + len *= 2; + } + + gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width, inst->font_height); +} + +void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + + int active, passive, widefactor; + + if (attr & TATTR_PASCURS) { + attr &= ~TATTR_PASCURS; + passive = 1; + } else + passive = 0; + if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { + attr &= ~TATTR_ACTCURS; + active = 1; + } else + active = 0; + do_text_internal(ctx, x, y, text, len, attr, lattr); + + if (attr & TATTR_COMBINING) + len = 1; + + if (attr & ATTR_WIDE) { + widefactor = 2; + } else { + widefactor = 1; + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + len *= 2; + } + + if (inst->cursor_type == 0) { + /* + * An active block cursor will already have been done by + * the above do_text call, so we only need to do anything + * if it's passive. + */ + if (passive) { + gdk_gc_set_foreground(gc, &inst->cols[261]); + gdk_draw_rectangle(inst->pixmap, gc, 0, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width-1, inst->font_height-1); + } + } else { + int uheight; + int startx, starty, dx, dy, length, i; + + int char_width; + + if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM) + char_width = 2*inst->font_width; + else + char_width = inst->font_width; + + if (inst->cursor_type == 1) { + uheight = inst->fonts[0]->ascent + 1; + if (uheight >= inst->font_height) + uheight = inst->font_height - 1; + + startx = x * inst->font_width + inst->window_border; + starty = y * inst->font_height + inst->window_border + uheight; + dx = 1; + dy = 0; + length = len * widefactor * char_width; + } else { + int xadjust = 0; + if (attr & TATTR_RIGHTCURS) + xadjust = char_width - 1; + startx = x * inst->font_width + inst->window_border + xadjust; + starty = y * inst->font_height + inst->window_border; + dx = 0; + dy = 1; + length = inst->font_height; + } + + gdk_gc_set_foreground(gc, &inst->cols[261]); + if (passive) { + for (i = 0; i < length; i++) { + if (i % 2 == 0) { + gdk_draw_point(inst->pixmap, gc, startx, starty); + } + startx += dx; + starty += dy; + } + } else if (active) { + gdk_draw_line(inst->pixmap, gc, startx, starty, + startx + (length-1) * dx, starty + (length-1) * dy); + } /* else no cursor (e.g., blinked off) */ + } + + gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width, inst->font_height); + +#if GTK_CHECK_VERSION(2,0,0) + { + GdkRectangle cursorrect; + cursorrect.x = x*inst->font_width+inst->window_border; + cursorrect.y = y*inst->font_height+inst->window_border; + cursorrect.width = len*widefactor*inst->font_width; + cursorrect.height = inst->font_height; + gtk_im_context_set_cursor_location(inst->imc, &cursorrect); + } +#endif +} + +GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val) +{ + /* + * Truly hideous hack: GTK doesn't allow us to set the mouse + * cursor foreground and background colours unless we've _also_ + * created our own cursor from bitmaps. Therefore, I need to + * load the `cursor' font and draw glyphs from it on to + * pixmaps, in order to construct my cursors with the fg and bg + * I want. This is a gross hack, but it's more self-contained + * than linking in Xlib to find the X window handle to + * inst->area and calling XRecolorCursor, and it's more + * futureproof than hard-coding the shapes as bitmap arrays. + */ + static GdkFont *cursor_font = NULL; + GdkPixmap *source, *mask; + GdkGC *gc; + GdkColor cfg = { 0, 65535, 65535, 65535 }; + GdkColor cbg = { 0, 0, 0, 0 }; + GdkColor dfg = { 1, 65535, 65535, 65535 }; + GdkColor dbg = { 0, 0, 0, 0 }; + GdkCursor *ret; + gchar text[2]; + gint lb, rb, wid, asc, desc, w, h, x, y; + + if (cursor_val == -2) { + gdk_font_unref(cursor_font); + return NULL; + } + + if (cursor_val >= 0 && !cursor_font) { + cursor_font = gdk_font_load("cursor"); + if (cursor_font) + gdk_font_ref(cursor_font); + } + + /* + * Get the text extent of the cursor in question. We use the + * mask character for this, because it's typically slightly + * bigger than the main character. + */ + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val + 1; + gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc); + w = rb-lb; h = asc+desc; x = -lb; y = asc; + } else { + w = h = 1; + x = y = 0; + } + + source = gdk_pixmap_new(NULL, w, h, 1); + mask = gdk_pixmap_new(NULL, w, h, 1); + + /* + * Draw the mask character on the mask pixmap. + */ + gc = gdk_gc_new(mask); + gdk_gc_set_foreground(gc, &dbg); + gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h); + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val + 1; + gdk_gc_set_foreground(gc, &dfg); + gdk_draw_text(mask, cursor_font, gc, x, y, text, 1); + } + gdk_gc_unref(gc); + + /* + * Draw the main character on the source pixmap. + */ + gc = gdk_gc_new(source); + gdk_gc_set_foreground(gc, &dbg); + gdk_draw_rectangle(source, gc, 1, 0, 0, w, h); + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val; + gdk_gc_set_foreground(gc, &dfg); + gdk_draw_text(source, cursor_font, gc, x, y, text, 1); + } + gdk_gc_unref(gc); + + /* + * Create the cursor. + */ + ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y); + + /* + * Clean up. + */ + gdk_pixmap_unref(source); + gdk_pixmap_unref(mask); + + return ret; +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", appname); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +char *get_x_display(void *frontend) +{ + return gdk_get_display(); +} + +long get_windowid(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return (long)GDK_WINDOW_XWINDOW(inst->area->window); +} + +static void help(FILE *fp) { + if(fprintf(fp, +"pterm option summary:\n" +"\n" +" --display DISPLAY Specify X display to use (note '--')\n" +" -name PREFIX Prefix when looking up resources (default: pterm)\n" +" -fn FONT Normal text font\n" +" -fb FONT Bold text font\n" +" -geometry GEOMETRY Position and size of window (size in characters)\n" +" -sl LINES Number of lines of scrollback\n" +" -fg COLOUR, -bg COLOUR Foreground/background colour\n" +" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n" +" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n" +" -T TITLE Window title\n" +" -ut, +ut Do(default) or do not update utmp\n" +" -ls, +ls Do(default) or do not make shell a login shell\n" +" -sb, +sb Do(default) or do not display a scrollbar\n" +" -log PATH, -sessionlog PATH Log all output to a file\n" +" -nethack Map numeric keypad to hjklyubn direction keys\n" +" -xrm RESOURCE-STRING Set an X resource\n" +" -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n" + ) < 0 || fflush(fp) < 0) { + perror("output error"); + exit(1); + } +} + +static void version(FILE *fp) { + if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) { + perror("output error"); + exit(1); + } +} + +int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, + struct gui_data *inst, Conf *conf) +{ + int err = 0; + char *val; + + /* + * Macros to make argument handling easier. Note that because + * they need to call `continue', they cannot be contained in + * the usual do {...} while (0) wrapper to make them + * syntactically single statements; hence it is not legal to + * use one of these macros as an unbraced statement between + * `if' and `else'. + */ +#define EXPECTS_ARG { \ + if (--argc <= 0) { \ + err = 1; \ + fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ + continue; \ + } else \ + val = *++argv; \ +} +#define SECOND_PASS_ONLY { if (!do_everything) continue; } + + while (--argc > 0) { + char *p = *++argv; + int ret; + + /* + * Shameless cheating. Debian requires all X terminal + * emulators to support `-T title'; but + * cmdline_process_param will eat -T (it means no-pty) and + * complain that pterm doesn't support it. So, in pterm + * only, we convert -T into -title. + */ + if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) && + !strcmp(p, "-T")) + p = "-title"; + + ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + do_everything ? 1 : -1, conf); + + if (ret == -2) { + cmdline_error("option \"%s\" requires an argument", p); + } else if (ret == 2) { + --argc, ++argv; /* skip next argument */ + continue; + } else if (ret == 1) { + continue; + } + + if (!strcmp(p, "-fn") || !strcmp(p, "-font")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_font, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fb")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_boldfont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fw")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_widefont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fwb")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_wideboldfont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-cs")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_str(conf, CONF_line_codepage, val); + + } else if (!strcmp(p, "-geometry")) { + int flags, x, y; + unsigned int w, h; + EXPECTS_ARG; + SECOND_PASS_ONLY; + + flags = XParseGeometry(val, &x, &y, &w, &h); + if (flags & WidthValue) + conf_set_int(conf, CONF_width, w); + if (flags & HeightValue) + conf_set_int(conf, CONF_height, h); + + if (flags & (XValue | YValue)) { + inst->xpos = x; + inst->ypos = y; + inst->gotpos = TRUE; + inst->gravity = ((flags & XNegative ? 1 : 0) | + (flags & YNegative ? 2 : 0)); + } + + } else if (!strcmp(p, "-sl")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_savelines, atoi(val)); + + } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || + !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || + !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { + GdkColor col; + + EXPECTS_ARG; + SECOND_PASS_ONLY; + if (!gdk_color_parse(val, &col)) { + err = 1; + fprintf(stderr, "%s: unable to parse colour \"%s\"\n", + appname, val); + } else { + int index; + index = (!strcmp(p, "-fg") ? 0 : + !strcmp(p, "-bg") ? 2 : + !strcmp(p, "-bfg") ? 1 : + !strcmp(p, "-bbg") ? 3 : + !strcmp(p, "-cfg") ? 4 : + !strcmp(p, "-cbg") ? 5 : -1); + assert(index != -1); + conf_set_int_int(conf, CONF_colours, index*3+0, col.red / 256); + conf_set_int_int(conf, CONF_colours, index*3+1,col.green/ 256); + conf_set_int_int(conf, CONF_colours, index*3+2, col.blue/ 256); + } + + } else if (use_pty_argv && !strcmp(p, "-e")) { + /* This option swallows all further arguments. */ + if (!do_everything) + break; + + if (--argc > 0) { + int i; + pty_argv = snewn(argc+1, char *); + ++argv; + for (i = 0; i < argc; i++) + pty_argv[i] = argv[i]; + pty_argv[argc] = NULL; + break; /* finished command-line processing */ + } else + err = 1, fprintf(stderr, "%s: -e expects an argument\n", + appname); + + } else if (!strcmp(p, "-title")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_str(conf, CONF_wintitle, val); + + } else if (!strcmp(p, "-log")) { + Filename *fn; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fn = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); + filename_free(fn); + + } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_stamp_utmp, 0); + + } else if (!strcmp(p, "-ut")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_stamp_utmp, 1); + + } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_login_shell, 0); + + } else if (!strcmp(p, "-ls")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_login_shell, 1); + + } else if (!strcmp(p, "-nethack")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_nethack_keypad, 1); + + } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_scrollbar, 0); + + } else if (!strcmp(p, "-sb")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_scrollbar, 1); + + } else if (!strcmp(p, "-name")) { + EXPECTS_ARG; + app_name = val; + + } else if (!strcmp(p, "-xrm")) { + EXPECTS_ARG; + provide_xrm_string(val); + + } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { + help(stdout); + exit(0); + + } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { + version(stdout); + exit(0); + + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + + } else if(p[0] != '-' && (!do_everything || + process_nonoption_arg(p, conf, + allow_launch))) { + /* do nothing */ + + } else { + err = 1; + fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); + } + } + + return err; +} + +int uxsel_input_add(int fd, int rwx) { + int flags = 0; + if (rwx & 1) flags |= GDK_INPUT_READ; + if (rwx & 2) flags |= GDK_INPUT_WRITE; + if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; + assert(flags); + return gdk_input_add(fd, flags, fd_input_func, NULL); +} + +void uxsel_input_remove(int id) { + gdk_input_remove(id); +} + +int frontend_is_utf8(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return inst->ucsdata.line_codepage == CS_UTF8; +} + +char *setup_fonts_ucs(struct gui_data *inst) +{ + int shadowbold = conf_get_int(inst->conf, CONF_shadowbold); + int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); + FontSpec *fs; + unifont *fonts[4]; + int i; + + fs = conf_get_fontspec(inst->conf, CONF_font); + fonts[0] = multifont_create(inst->area, fs->name, FALSE, FALSE, + shadowboldoffset, shadowbold); + if (!fonts[0]) { + return dupprintf("unable to load font \"%s\"", fs->name); + } + + fs = conf_get_fontspec(inst->conf, CONF_boldfont); + if (shadowbold || !fs->name[0]) { + fonts[1] = NULL; + } else { + fonts[1] = multifont_create(inst->area, fs->name, FALSE, TRUE, + shadowboldoffset, shadowbold); + if (!fonts[1]) { + if (fonts[0]) + unifont_destroy(fonts[0]); + return dupprintf("unable to load bold font \"%s\"", fs->name); + } + } + + fs = conf_get_fontspec(inst->conf, CONF_widefont); + if (fs->name[0]) { + fonts[2] = multifont_create(inst->area, fs->name, TRUE, FALSE, + shadowboldoffset, shadowbold); + if (!fonts[2]) { + for (i = 0; i < 2; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide font \"%s\"", fs->name); + } + } else { + fonts[2] = NULL; + } + + fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); + if (shadowbold || !fs->name[0]) { + fonts[3] = NULL; + } else { + fonts[3] = multifont_create(inst->area, fs->name, TRUE, TRUE, + shadowboldoffset, shadowbold); + if (!fonts[3]) { + for (i = 0; i < 3; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide bold font \"%s\"", fs->name); + } + } + + /* + * Now we've got past all the possible error conditions, we can + * actually update our state. + */ + + for (i = 0; i < 4; i++) { + if (inst->fonts[i]) + unifont_destroy(inst->fonts[i]); + inst->fonts[i] = fonts[i]; + } + + inst->font_width = inst->fonts[0]->width; + inst->font_height = inst->fonts[0]->height; + + inst->direct_to_font = init_ucs(&inst->ucsdata, + conf_get_str(inst->conf, CONF_line_codepage), + conf_get_int(inst->conf, CONF_utf8_override), + inst->fonts[0]->public_charset, + conf_get_int(inst->conf, CONF_vtmode)); + + return NULL; +} + +void set_geom_hints(struct gui_data *inst) +{ + GdkGeometry geom; + geom.min_width = inst->font_width + 2*inst->window_border; + geom.min_height = inst->font_height + 2*inst->window_border; + geom.max_width = geom.max_height = -1; + geom.base_width = 2*inst->window_border; + geom.base_height = 2*inst->window_border; + geom.width_inc = inst->font_width; + geom.height_inc = inst->font_height; + geom.min_aspect = geom.max_aspect = 0; + gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom, + GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | + GDK_HINT_RESIZE_INC); +} + +void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_clrsb(inst->term); +} + +void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_pwron(inst->term, TRUE); + if (inst->ldisc) + ldisc_send(inst->ldisc, NULL, 0, 0); +} + +void copy_all_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_copyall(inst->term); +} + +void special_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); + + if (inst->back) + inst->back->special(inst->backhandle, code); +} + +void about_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + about_box(inst->window); +} + +void event_log_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + showeventlog(inst->eventlogstuff, inst->window); +} + +void change_settings_menuitem(GtkMenuItem *item, gpointer data) +{ + /* This maps colour indices in inst->conf to those used in inst->cols. */ + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + struct gui_data *inst = (struct gui_data *)data; + char *title; + Conf *oldconf, *newconf; + int i, j, need_size; + + assert(lenof(ww) == NCFGCOLOURS); + + if (inst->reconfiguring) + return; + else + inst->reconfiguring = TRUE; + + title = dupcat(appname, " Reconfiguration", NULL); + + oldconf = inst->conf; + newconf = conf_copy(inst->conf); + + if (do_config_box(title, newconf, 1, + inst->back?inst->back->cfg_info(inst->backhandle):0)) { + inst->conf = newconf; + + /* Pass new config data to the logging module */ + log_reconfig(inst->logctx, inst->conf); + /* + * Flush the line discipline's edit buffer in the case + * where local editing has just been disabled. + */ + if (inst->ldisc) { + ldisc_configure(inst->ldisc, inst->conf); + ldisc_send(inst->ldisc, NULL, 0, 0); + } + /* Pass new config data to the terminal */ + term_reconfig(inst->term, inst->conf); + /* Pass new config data to the back end */ + if (inst->back) + inst->back->reconfig(inst->backhandle, inst->conf); + + cache_conf_values(inst); + + /* + * Just setting inst->conf is sufficient to cause colour + * setting changes to appear on the next ESC]R palette + * reset. But we should also check whether any colour + * settings have been changed, and revert the ones that have + * to the new default, on the assumption that the user is + * most likely to want an immediate update. + */ + for (i = 0; i < NCFGCOLOURS; i++) { + for (j = 0; j < 3; j++) + if (conf_get_int_int(oldconf, CONF_colours, i*3+j) != + conf_get_int_int(newconf, CONF_colours, i*3+j)) + break; + if (j < 3) { + real_palette_set(inst, ww[i], + conf_get_int_int(newconf,CONF_colours,i*3+0), + conf_get_int_int(newconf,CONF_colours,i*3+1), + conf_get_int_int(newconf,CONF_colours,i*3+2)); + + /* + * If the default background has changed, we must + * repaint the space in between the window border + * and the text area. + */ + if (ww[i] == 258) { + set_window_background(inst); + draw_backing_rect(inst); + } + } + } + + /* + * If the scrollbar needs to be shown, hidden, or moved + * from one end to the other of the window, do so now. + */ + if (conf_get_int(oldconf, CONF_scrollbar) != + conf_get_int(newconf, CONF_scrollbar)) { + if (conf_get_int(newconf, CONF_scrollbar)) + gtk_widget_show(inst->sbar); + else + gtk_widget_hide(inst->sbar); + } + if (conf_get_int(oldconf, CONF_scrollbar_on_left) != + conf_get_int(newconf, CONF_scrollbar_on_left)) { + gtk_box_reorder_child(inst->hbox, inst->sbar, + conf_get_int(newconf, CONF_scrollbar_on_left) + ? 0 : 1); + } + + /* + * Change the window title, if required. + */ + if (strcmp(conf_get_str(oldconf, CONF_wintitle), + conf_get_str(newconf, CONF_wintitle))) + set_title(inst, conf_get_str(newconf, CONF_wintitle)); + set_window_titles(inst); + + /* + * Redo the whole tangled fonts and Unicode mess if + * necessary. + */ + need_size = FALSE; + if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name, + conf_get_fontspec(newconf, CONF_font)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name, + conf_get_fontspec(newconf, CONF_boldfont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name, + conf_get_fontspec(newconf, CONF_widefont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name, + conf_get_fontspec(newconf, CONF_wideboldfont)->name) || + strcmp(conf_get_str(oldconf, CONF_line_codepage), + conf_get_str(newconf, CONF_line_codepage)) || + conf_get_int(oldconf, CONF_utf8_override) != + conf_get_int(newconf, CONF_utf8_override) || + conf_get_int(oldconf, CONF_vtmode) != + conf_get_int(newconf, CONF_vtmode) || + conf_get_int(oldconf, CONF_shadowbold) != + conf_get_int(newconf, CONF_shadowbold) || + conf_get_int(oldconf, CONF_shadowboldoffset) != + conf_get_int(newconf, CONF_shadowboldoffset)) { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + char *msgboxtext = + dupprintf("Could not change fonts in terminal window: %s\n", + errmsg); + messagebox(inst->window, "Font setup error", msgboxtext, + string_width("Could not change fonts in terminal window:"), + FALSE, "OK", 'o', +1, 1, + NULL); + sfree(msgboxtext); + sfree(errmsg); + } else { + need_size = TRUE; + } + } + + /* + * Resize the window. + */ + if (conf_get_int(oldconf, CONF_width) != + conf_get_int(newconf, CONF_width) || + conf_get_int(oldconf, CONF_height) != + conf_get_int(newconf, CONF_height) || + conf_get_int(oldconf, CONF_window_border) != + conf_get_int(newconf, CONF_window_border) || + need_size) { + set_geom_hints(inst); + request_resize(inst, conf_get_int(newconf, CONF_width), + conf_get_int(newconf, CONF_height)); + } else { + /* + * The above will have caused a call to term_size() for + * us if it happened. If the user has fiddled with only + * the scrollback size, the above will not have + * happened and we will need an explicit term_size() + * here. + */ + if (conf_get_int(oldconf, CONF_savelines) != + conf_get_int(newconf, CONF_savelines)) + term_size(inst->term, inst->term->rows, inst->term->cols, + conf_get_int(newconf, CONF_savelines)); + } + + term_invalidate(inst->term); + + /* + * We do an explicit full redraw here to ensure the window + * border has been redrawn as well as the text area. + */ + gtk_widget_queue_draw(inst->area); + + conf_free(oldconf); + } else { + conf_free(newconf); + } + sfree(title); + inst->reconfiguring = FALSE; +} + +void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...) +{ + /* + * Re-execing ourself is not an exact science under Unix. I do + * the best I can by using /proc/self/exe if available and by + * assuming argv[0] can be found on $PATH if not. + * + * Note that we also have to reconstruct the elements of the + * original argv which gtk swallowed, since the user wants the + * new session to appear on the same X display as the old one. + */ + char **args; + va_list ap; + int i, n; + int pid; + + /* + * Collect the arguments with which to re-exec ourself. + */ + va_start(ap, fd_to_close); + n = 2; /* progname and terminating NULL */ + n += inst->ngtkargs; + while (va_arg(ap, char *) != NULL) + n++; + va_end(ap); + + args = snewn(n, char *); + args[0] = inst->progname; + args[n-1] = NULL; + for (i = 0; i < inst->ngtkargs; i++) + args[i+1] = inst->gtkargvstart[i]; + + i++; + va_start(ap, fd_to_close); + while ((args[i++] = va_arg(ap, char *)) != NULL); + va_end(ap); + + assert(i == n); + + /* + * Do the double fork. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + sfree(args); + return; + } + + if (pid == 0) { + int pid2 = fork(); + if (pid2 < 0) { + perror("fork"); + _exit(1); + } else if (pid2 > 0) { + /* + * First child has successfully forked second child. My + * Work Here Is Done. Note the use of _exit rather than + * exit: the latter appears to cause destroy messages + * to be sent to the X server. I suspect gtk uses + * atexit. + */ + _exit(0); + } + + /* + * If we reach here, we are the second child, so we now + * actually perform the exec. + */ + if (fd_to_close >= 0) + close(fd_to_close); + + execv("/proc/self/exe", args); + execvp(inst->progname, args); + perror("exec"); + _exit(127); + + } else { + int status; + sfree(args); + waitpid(pid, &status, 0); + } + +} + +void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) +{ + struct gui_data *inst = (struct gui_data *)gdata; + /* + * For this feature we must marshal conf and (possibly) pty_argv + * into a byte stream, create a pipe, and send this byte stream + * to the child through the pipe. + */ + int i, ret, sersize, size; + char *data; + char option[80]; + int pipefd[2]; + + if (pipe(pipefd) < 0) { + perror("pipe"); + return; + } + + size = sersize = conf_serialised_size(inst->conf); + if (use_pty_argv && pty_argv) { + for (i = 0; pty_argv[i]; i++) + size += strlen(pty_argv[i]) + 1; + } + + data = snewn(size, char); + conf_serialise(inst->conf, data); + if (use_pty_argv && pty_argv) { + int p = sersize; + for (i = 0; pty_argv[i]; i++) { + strcpy(data + p, pty_argv[i]); + p += strlen(pty_argv[i]) + 1; + } + assert(p == size); + } + + sprintf(option, "---[%d,%d]", pipefd[0], size); + noncloexec(pipefd[0]); + fork_and_exec_self(inst, pipefd[1], option, NULL); + close(pipefd[0]); + + i = ret = 0; + while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0) + i += ret; + if (ret < 0) + perror("write to pipe"); + close(pipefd[1]); + sfree(data); +} + +int read_dupsession_data(struct gui_data *inst, Conf *conf, char *arg) +{ + int fd, i, ret, size, size_used; + char *data; + + if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) { + fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg); + exit(1); + } + + data = snewn(size, char); + i = ret = 0; + while (i < size && (ret = read(fd, data + i, size - i)) > 0) + i += ret; + if (ret < 0) { + perror("read from pipe"); + exit(1); + } else if (i < size) { + fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n", + appname); + exit(1); + } + + size_used = conf_deserialise(conf, data, size); + if (use_pty_argv && size > size_used) { + int n = 0; + i = size_used; + while (i < size) { + while (i < size && data[i]) i++; + if (i >= size) { + fprintf(stderr, "%s: malformed Duplicate Session data\n", + appname); + exit(1); + } + i++; + n++; + } + pty_argv = snewn(n+1, char *); + pty_argv[n] = NULL; + n = 0; + i = size_used; + while (i < size) { + char *p = data + i; + while (i < size && data[i]) i++; + assert(i < size); + i++; + pty_argv[n++] = dupstr(p); + } + } + + sfree(data); + + return 0; +} + +void new_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + fork_and_exec_self(inst, -1, NULL); +} + +void restart_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (!inst->back) { + logevent(inst, "----- Session restarted -----"); + term_pwron(inst->term, FALSE); + start_backend(inst); + inst->exited = FALSE; + } +} + +void saved_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data"); + + fork_and_exec_self(inst, -1, "-load", str, NULL); +} + +void saved_session_freedata(GtkMenuItem *item, gpointer data) +{ + char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data"); + + sfree(str); +} + +static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + struct sesslist sesslist; + int i; + + gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu), + (GtkCallback)gtk_widget_destroy, NULL); + + get_sesslist(&sesslist, TRUE); + /* skip sesslist.sessions[0] == Default Settings */ + for (i = 1; i < sesslist.nsessions; i++) { + GtkWidget *menuitem = + gtk_menu_item_new_with_label(sesslist.sessions[i]); + gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); + gtk_widget_show(menuitem); + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + dupstr(sesslist.sessions[i])); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(saved_session_menuitem), + inst); + gtk_signal_connect(GTK_OBJECT(menuitem), "destroy", + GTK_SIGNAL_FUNC(saved_session_freedata), + inst); + } + if (sesslist.nsessions <= 1) { + GtkWidget *menuitem = + gtk_menu_item_new_with_label("(No sessions)"); + gtk_widget_set_sensitive(menuitem, FALSE); + gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); + gtk_widget_show(menuitem); + } + get_sesslist(&sesslist, FALSE); /* free up */ +} + +void set_window_icon(GtkWidget *window, const char *const *const *icon, + int n_icon) +{ + GdkPixmap *iconpm; + GdkBitmap *iconmask; +#if GTK_CHECK_VERSION(2,0,0) + GList *iconlist; + int n; +#endif + + if (!n_icon) + return; + + gtk_widget_realize(window); + iconpm = gdk_pixmap_create_from_xpm_d(window->window, &iconmask, + NULL, (gchar **)icon[0]); + gdk_window_set_icon(window->window, NULL, iconpm, iconmask); + +#if GTK_CHECK_VERSION(2,0,0) + iconlist = NULL; + for (n = 0; n < n_icon; n++) { + iconlist = + g_list_append(iconlist, + gdk_pixbuf_new_from_xpm_data((const gchar **) + icon[n])); + } + gdk_window_set_icon_list(window->window, iconlist); +#endif +} + +void update_specials_menu(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + const struct telnet_special *specials; + + if (inst->back) + specials = inst->back->get_specials(inst->backhandle); + else + specials = NULL; + + /* I believe this disposes of submenus too. */ + gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu), + (GtkCallback)gtk_widget_destroy, NULL); + if (specials) { + int i; + GtkWidget *menu = inst->specialsmenu; + /* A lame "stack" for submenus that will do for now. */ + GtkWidget *saved_menu = NULL; + int nesting = 1; + for (i = 0; nesting > 0; i++) { + GtkWidget *menuitem = NULL; + switch (specials[i].code) { + case TS_SUBMENU: + assert (nesting < 2); + saved_menu = menu; /* XXX lame stacking */ + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label(specials[i].name); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_container_add(GTK_CONTAINER(saved_menu), menuitem); + gtk_widget_show(menuitem); + menuitem = NULL; + nesting++; + break; + case TS_EXITMENU: + nesting--; + if (nesting) { + menu = saved_menu; /* XXX lame stacking */ + saved_menu = NULL; + } + break; + case TS_SEP: + menuitem = gtk_menu_item_new(); + break; + default: + menuitem = gtk_menu_item_new_with_label(specials[i].name); + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(specials[i].code)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(special_menuitem), inst); + break; + } + if (menuitem) { + gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_widget_show(menuitem); + } + } + gtk_widget_show(inst->specialsitem1); + gtk_widget_show(inst->specialsitem2); + } else { + gtk_widget_hide(inst->specialsitem1); + gtk_widget_hide(inst->specialsitem2); + } +} + +static void start_backend(struct gui_data *inst) +{ + extern Backend *select_backend(Conf *conf); + char *realhost; + const char *error; + char *s; + + inst->back = select_backend(inst->conf); + + error = inst->back->init((void *)inst, &inst->backhandle, + inst->conf, + conf_get_str(inst->conf, CONF_host), + conf_get_int(inst->conf, CONF_port), + &realhost, + conf_get_int(inst->conf, CONF_tcp_nodelay), + conf_get_int(inst->conf, CONF_tcp_keepalives)); + + if (error) { + char *msg = dupprintf("Unable to open connection to %s:\n%s", + conf_get_str(inst->conf, CONF_host), error); + inst->exited = TRUE; + fatal_message_box(inst->window, msg); + sfree(msg); + exit(0); + } + + s = conf_get_str(inst->conf, CONF_wintitle); + if (s[0]) { + set_title_and_icon(inst, s, s); + } else { + char *title = make_default_wintitle(realhost); + set_title_and_icon(inst, title, title); + sfree(title); + } + sfree(realhost); + + inst->back->provide_logctx(inst->backhandle, inst->logctx); + + term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle); + + inst->ldisc = + ldisc_create(inst->conf, inst->term, inst->back, inst->backhandle, + inst); + + gtk_widget_set_sensitive(inst->restartitem, FALSE); +} + +int pt_main(int argc, char **argv) +{ + extern int cfgbox(Conf *conf); + struct gui_data *inst; + + setlocale(LC_CTYPE, ""); + + /* + * Create an instance structure and initialise to zeroes + */ + inst = snew(struct gui_data); + memset(inst, 0, sizeof(*inst)); + inst->alt_keycode = -1; /* this one needs _not_ to be zero */ + inst->busy_status = BUSY_NOT; + inst->conf = conf_new(); + inst->wintitle = inst->icontitle = NULL; + inst->quit_fn_scheduled = FALSE; + inst->idle_fn_scheduled = FALSE; + + /* defer any child exit handling until we're ready to deal with + * it */ + block_signal(SIGCHLD, 1); + + inst->progname = argv[0]; + /* + * Copy the original argv before letting gtk_init fiddle with + * it. It will be required later. + */ + { + int i, oldargc; + inst->gtkargvstart = snewn(argc-1, char *); + for (i = 1; i < argc; i++) + inst->gtkargvstart[i-1] = dupstr(argv[i]); + oldargc = argc; + gtk_init(&argc, &argv); + inst->ngtkargs = oldargc - argc; + } + + if (argc > 1 && !strncmp(argv[1], "---", 3)) { + read_dupsession_data(inst, inst->conf, argv[1]); + /* Splatter this argument so it doesn't clutter a ps listing */ + smemclr(argv[1], strlen(argv[1])); + } else { + /* By default, we bring up the config dialog, rather than launching + * a session. This gets set to TRUE if something happens to change + * that (e.g., a hostname is specified on the command-line). */ + int allow_launch = FALSE; + if (do_cmdline(argc, argv, 0, &allow_launch, inst, inst->conf)) + exit(1); /* pre-defaults pass to get -class */ + do_defaults(NULL, inst->conf); + if (do_cmdline(argc, argv, 1, &allow_launch, inst, inst->conf)) + exit(1); /* post-defaults, do everything */ + + cmdline_run_saved(inst->conf); + + if (loaded_session) + allow_launch = TRUE; + + if ((!allow_launch || !conf_launchable(inst->conf)) && + !cfgbox(inst->conf)) + exit(0); /* config box hit Cancel */ + } + + if (!compound_text_atom) + compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE); + if (!utf8_string_atom) + utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); + + inst->area = gtk_drawing_area_new(); + +#if GTK_CHECK_VERSION(2,0,0) + inst->imc = gtk_im_multicontext_new(); +#endif + + { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + fprintf(stderr, "%s: %s\n", appname, errmsg); + exit(1); + } + } + init_cutbuffers(); + + inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + { + const char *winclass = conf_get_str(inst->conf, CONF_winclass); + if (*winclass) + gtk_window_set_wmclass(GTK_WINDOW(inst->window), + winclass, winclass); + } + + /* + * Set up the colour map. + */ + palette_reset(inst); + + inst->width = conf_get_int(inst->conf, CONF_width); + inst->height = conf_get_int(inst->conf, CONF_height); + cache_conf_values(inst); + + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), + inst->font_width * inst->width + 2*inst->window_border, + inst->font_height * inst->height + 2*inst->window_border); + inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0)); + inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust); + inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); + /* + * We always create the scrollbar; it remains invisible if + * unwanted, so we can pop it up quickly if it suddenly becomes + * desirable. + */ + if (conf_get_int(inst->conf, CONF_scrollbar_on_left)) + gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); + gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0); + if (!conf_get_int(inst->conf, CONF_scrollbar_on_left)) + gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); + + gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); + + set_geom_hints(inst); + + gtk_widget_show(inst->area); + if (conf_get_int(inst->conf, CONF_scrollbar)) + gtk_widget_show(inst->sbar); + else + gtk_widget_hide(inst->sbar); + gtk_widget_show(GTK_WIDGET(inst->hbox)); + + if (inst->gotpos) { + int x = inst->xpos, y = inst->ypos; + GtkRequisition req; + gtk_widget_size_request(GTK_WIDGET(inst->window), &req); + if (inst->gravity & 1) x += gdk_screen_width() - req.width; + if (inst->gravity & 2) y += gdk_screen_height() - req.height; + gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE); + gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y); + } + + gtk_signal_connect(GTK_OBJECT(inst->window), "destroy", + GTK_SIGNAL_FUNC(destroy), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event", + GTK_SIGNAL_FUNC(delete_window), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event", + GTK_SIGNAL_FUNC(key_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "key_release_event", + GTK_SIGNAL_FUNC(key_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event", + GTK_SIGNAL_FUNC(focus_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event", + GTK_SIGNAL_FUNC(focus_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event", + GTK_SIGNAL_FUNC(configure_area), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event", + GTK_SIGNAL_FUNC(expose_area), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event", + GTK_SIGNAL_FUNC(button_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event", + GTK_SIGNAL_FUNC(button_event), inst); +#if GTK_CHECK_VERSION(2,0,0) + gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event", + GTK_SIGNAL_FUNC(scroll_event), inst); +#endif + gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event", + GTK_SIGNAL_FUNC(motion_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received", + GTK_SIGNAL_FUNC(selection_received), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get", + GTK_SIGNAL_FUNC(selection_get), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event", + GTK_SIGNAL_FUNC(selection_clear), inst); +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(inst->imc), "commit", + G_CALLBACK(input_method_commit_event), inst); +#endif + if (conf_get_int(inst->conf, CONF_scrollbar)) + gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", + GTK_SIGNAL_FUNC(scrollbar_moved), inst); + gtk_widget_add_events(GTK_WIDGET(inst->area), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK); + + { + extern const char *const *const main_icon[]; + extern const int n_main_icon; + set_window_icon(inst->window, main_icon, n_main_icon); + } + + gtk_widget_show(inst->window); + + set_window_background(inst); + + /* + * Set up the Ctrl+rightclick context menu. + */ + { + GtkWidget *menuitem; + char *s; + extern const int use_event_log, new_session, saved_sessions; + + inst->menu = gtk_menu_new(); + +#define MKMENUITEM(title, func) do \ + { \ + menuitem = gtk_menu_item_new_with_label(title); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", \ + GTK_SIGNAL_FUNC(func), inst); \ + } while (0) + +#define MKSUBMENU(title) do \ + { \ + menuitem = gtk_menu_item_new_with_label(title); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + } while (0) + +#define MKSEP() do \ + { \ + menuitem = gtk_menu_item_new(); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + } while (0) + + if (new_session) + MKMENUITEM("New Session...", new_session_menuitem); + MKMENUITEM("Restart Session", restart_session_menuitem); + inst->restartitem = menuitem; + gtk_widget_set_sensitive(inst->restartitem, FALSE); + MKMENUITEM("Duplicate Session", dup_session_menuitem); + if (saved_sessions) { + inst->sessionsmenu = gtk_menu_new(); + /* sessionsmenu will be updated when it's invoked */ + /* XXX is this the right way to do dynamic menus in Gtk? */ + MKMENUITEM("Saved Sessions", update_savedsess_menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), + inst->sessionsmenu); + } + MKSEP(); + MKMENUITEM("Change Settings...", change_settings_menuitem); + MKSEP(); + if (use_event_log) + MKMENUITEM("Event Log", event_log_menuitem); + MKSUBMENU("Special Commands"); + inst->specialsmenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu); + inst->specialsitem1 = menuitem; + MKSEP(); + inst->specialsitem2 = menuitem; + gtk_widget_hide(inst->specialsitem1); + gtk_widget_hide(inst->specialsitem2); + MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem); + MKMENUITEM("Reset Terminal", reset_terminal_menuitem); + MKMENUITEM("Copy All", copy_all_menuitem); + MKSEP(); + s = dupcat("About ", appname, NULL); + MKMENUITEM(s, about_menuitem); + sfree(s); +#undef MKMENUITEM +#undef MKSUBMENU +#undef MKSEP + } + + inst->textcursor = make_mouse_ptr(inst, GDK_XTERM); + inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR); + inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH); + inst->blankcursor = make_mouse_ptr(inst, -1); + make_mouse_ptr(inst, -2); /* clean up cursor font */ + inst->currcursor = inst->textcursor; + show_mouseptr(inst, 1); + + inst->eventlogstuff = eventlogstuff_new(); + + request_callback_notifications(notify_toplevel_callback, inst); + + inst->term = term_init(inst->conf, &inst->ucsdata, inst); + inst->logctx = log_init(inst, inst->conf); + term_provide_logctx(inst->term, inst->logctx); + + uxsel_init(); + + term_size(inst->term, inst->height, inst->width, + conf_get_int(inst->conf, CONF_savelines)); + + start_backend(inst); + + ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + + /* now we're reday to deal with the child exit handler being + * called */ + block_signal(SIGCHLD, 0); + + /* + * Block SIGPIPE: if we attempt Duplicate Session or similar + * and it falls over in some way, we certainly don't want + * SIGPIPE terminating the main pterm/PuTTY. Note that we do + * this _after_ (at least pterm) forks off its child process, + * since the child wants SIGPIPE handled in the usual way. + */ + block_signal(SIGPIPE, 1); + + inst->exited = FALSE; + + gtk_main(); + + return 0; +} diff --git a/netbox/libs/Putty/unix/unix.h b/netbox/libs/Putty/unix/unix.h new file mode 100644 index 000000000..1968a423f --- /dev/null +++ b/netbox/libs/Putty/unix/unix.h @@ -0,0 +1,194 @@ +#ifndef PUTTY_UNIX_H +#define PUTTY_UNIX_H + +#ifdef HAVE_CONFIG_H +# include "uxconfig.h" /* Space to hide it from mkfiles.pl */ +#endif + +#include /* for FILENAME_MAX */ +#include /* C99 int types */ +#ifndef NO_LIBDL +#include /* Dynamic library loading */ +#endif /* NO_LIBDL */ +#include "charset.h" + +struct Filename { + char *path; +}; +FILE *f_open(const struct Filename *, char const *, int); + +struct FontSpec { + char *name; /* may be "" to indicate no selected font at all */ +}; +struct FontSpec *fontspec_new(const char *name); + +typedef void *Context; /* FIXME: probably needs changing */ + +extern Backend pty_backend; + +typedef uint32_t uint32; /* C99: uint32_t defined in stdint.h */ +#define PUTTY_UINT32_DEFINED + +/* + * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_ + * MA_3CLK, when a button is pressed for the second or third time. + */ +#define MULTICLICK_ONLY_EVENT 0 + +/* + * Under GTK, there is no context help available. + */ +#define HELPCTX(x) P(NULL) +#define FILTER_KEY_FILES NULL /* FIXME */ +#define FILTER_DYNLIB_FILES NULL /* FIXME */ + +/* + * Under X, selection data must not be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 0 + +/* + * Under X, copying to the clipboard terminates lines with just LF. + */ +#define SEL_NL { 10 } + +/* Simple wraparound timer function */ +unsigned long getticks(void); /* based on gettimeofday(2) */ +#define GETTICKCOUNT getticks +#define TICKSPERSEC 1000 /* we choose to use milliseconds */ +#define CURSORBLINK 450 /* no standard way to set this */ + +#define WCHAR wchar_t +#define BYTE unsigned char + +/* + * Unix-specific global flag + * + * FLAG_STDERR_TTY indicates that standard error might be a terminal and + * might get its configuration munged, so anything trying to output plain + * text (i.e. with newlines in it) will need to put it back into cooked + * mode first. Applications setting this flag should also call + * stderr_tty_init() before messing with any terminal modes, and can call + * premsg() before outputting text to stderr and postmsg() afterwards. + */ +#define FLAG_STDERR_TTY 0x1000 + +/* Things pty.c needs from pterm.c */ +char *get_x_display(void *frontend); +int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */ +long get_windowid(void *frontend); +int frontend_is_utf8(void *frontend); + +/* Things gtkdlg.c needs from pterm.c */ +void *get_window(void *frontend); /* void * to avoid depending on gtk.h */ + +/* Things pterm.c needs from gtkdlg.c */ +int do_config_box(const char *title, Conf *conf, + int midsession, int protcfginfo); +void fatal_message_box(void *window, char *msg); +void nonfatal_message_box(void *window, char *msg); +void about_box(void *window); +void *eventlogstuff_new(void); +void showeventlog(void *estuff, void *parentwin); +void logevent_dlg(void *estuff, const char *string); +int reallyclose(void *frontend); +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +int messagebox(GtkWidget *parentwin, char *title, + char *msg, int minwid, int selectable, ...); +int string_width(char *text); +#endif + +/* Things pterm.c needs from {ptermm,uxputty}.c */ +char *make_default_wintitle(char *hostname); +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch); + +/* pterm.c needs this special function in xkeysym.c */ +int keysym_to_unicode(int keysym); + +/* Things uxstore.c needs from pterm.c */ +char *x_get_default(const char *key); + +/* Things uxstore.c provides to pterm.c */ +void provide_xrm_string(char *string); + +/* Things provided by uxcons.c */ +struct termios; +void stderr_tty_init(void); +void premsg(struct termios *); +void postmsg(struct termios *); + +/* The interface used by uxsel.c */ +void uxsel_init(void); +typedef int (*uxsel_callback_fn)(int fd, int event); +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); +void uxsel_del(int fd); +int select_result(int fd, int event); +int first_fd(int *state, int *rwx); +int next_fd(int *state, int *rwx); +/* The following are expected to be provided _to_ uxsel.c by the frontend */ +int uxsel_input_add(int fd, int rwx); /* returns an id */ +void uxsel_input_remove(int id); + +/* uxcfg.c */ +struct controlbox; +void unix_setup_config_box(struct controlbox *b, int midsession, int protocol); + +/* gtkcfg.c */ +void gtk_setup_config_box(struct controlbox *b, int midsession, void *window); + +/* + * In the Unix Unicode layer, DEFAULT_CODEPAGE is a special value + * which causes mb_to_wc and wc_to_mb to call _libc_ rather than + * libcharset. That way, we can interface the various charsets + * supported by libcharset with the one supported by mbstowcs and + * wcstombs (which will be the character set in which stuff read + * from the command line or config files is assumed to be encoded). + */ +#define DEFAULT_CODEPAGE 0xFFFF +#define CP_UTF8 CS_UTF8 /* from libcharset */ + +#define strnicmp strncasecmp +#define stricmp strcasecmp + +/* BSD-semantics version of signal(), and another helpful function */ +void (*putty_signal(int sig, void (*func)(int)))(int); +void block_signal(int sig, int block_it); + +/* uxmisc.c */ +void cloexec(int); +void noncloexec(int); +int nonblock(int); +int no_nonblock(int); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +int init_ucs(struct unicode_data *ucsdata, char *line_codepage, + int utf8_override, int font_charset, int vtmode); + +/* + * Spare function exported directly from uxnet.c. + */ +void *sk_getxdmdata(void *sock, int *lenp); + +/* + * General helpful Unix stuff: more helpful version of the FD_SET + * macro, which also handles maxfd. + */ +#define FD_SET_MAX(fd, max, set) do { \ + FD_SET(fd, &set); \ + if (max < fd + 1) max = fd + 1; \ +} while (0) + +/* + * Exports from winser.c. + */ +extern Backend serial_backend; + +/* + * uxpeer.c, wrapping getsockopt(SO_PEERCRED). + */ +int so_peercred(int fd, int *pid, int *uid, int *gid); + +#endif diff --git a/netbox/libs/Putty/unix/ux_x11.c b/netbox/libs/Putty/unix/ux_x11.c new file mode 100644 index 000000000..63a92b585 --- /dev/null +++ b/netbox/libs/Putty/unix/ux_x11.c @@ -0,0 +1,40 @@ +/* + * ux_x11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "network.h" + +void platform_get_x11_auth(struct X11Display *disp, Conf *conf) +{ + char *xauthfile; + int needs_free; + + /* + * Find the .Xauthority file. + */ + needs_free = FALSE; + xauthfile = getenv("XAUTHORITY"); + if (!xauthfile) { + xauthfile = getenv("HOME"); + if (xauthfile) { + xauthfile = dupcat(xauthfile, "/.Xauthority", NULL); + needs_free = TRUE; + } + } + + if (xauthfile) { + x11_get_auth_from_authfile(disp, xauthfile); + if (needs_free) + sfree(xauthfile); + } +} + +const int platform_uses_x11_unix_by_default = TRUE; diff --git a/netbox/libs/Putty/unix/uxagentc.c b/netbox/libs/Putty/unix/uxagentc.c new file mode 100644 index 000000000..5734a7b0e --- /dev/null +++ b/netbox/libs/Putty/unix/uxagentc.c @@ -0,0 +1,162 @@ +/* + * SSH agent client code. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "misc.h" +#include "tree234.h" +#include "puttymem.h" + +int agent_exists(void) +{ + const char *p = getenv("SSH_AUTH_SOCK"); + if (p && *p) + return TRUE; + return FALSE; +} + +static tree234 *agent_connections; +struct agent_connection { + int fd; + char *retbuf; + char sizebuf[4]; + int retsize, retlen; + void (*callback)(void *, void *, int); + void *callback_ctx; +}; +static int agent_conncmp(void *av, void *bv) +{ + struct agent_connection *a = (struct agent_connection *) av; + struct agent_connection *b = (struct agent_connection *) bv; + if (a->fd < b->fd) + return -1; + if (a->fd > b->fd) + return +1; + return 0; +} +static int agent_connfind(void *av, void *bv) +{ + int afd = *(int *) av; + struct agent_connection *b = (struct agent_connection *) bv; + if (afd < b->fd) + return -1; + if (afd > b->fd) + return +1; + return 0; +} + +static int agent_select_result(int fd, int event) +{ + int ret; + struct agent_connection *conn; + + assert(event == 1); /* not selecting for anything but R */ + + conn = find234(agent_connections, &fd, agent_connfind); + if (!conn) { + uxsel_del(fd); + return 1; + } + + ret = read(fd, conn->retbuf+conn->retlen, conn->retsize-conn->retlen); + if (ret <= 0) { + if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf); + conn->retbuf = NULL; + conn->retlen = 0; + goto done; + } + conn->retlen += ret; + if (conn->retsize == 4 && conn->retlen == 4) { + conn->retsize = toint(GET_32BIT(conn->retbuf) + 4); + if (conn->retsize <= 0) { + conn->retbuf = NULL; + conn->retlen = 0; + goto done; + } + assert(conn->retbuf == conn->sizebuf); + conn->retbuf = snewn(conn->retsize, char); + memcpy(conn->retbuf, conn->sizebuf, 4); + } + + if (conn->retlen < conn->retsize) + return 0; /* more data to come */ + + done: + /* + * We have now completed the agent query. Do the callback, and + * clean up. (Of course we don't free retbuf, since ownership + * of that passes to the callback.) + */ + conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen); + uxsel_del(fd); + close(fd); + del234(agent_connections, conn); + sfree(conn); + return 0; +} + +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + char *name; + int sock; + struct sockaddr_un addr; + int done; + struct agent_connection *conn; + + name = getenv("SSH_AUTH_SOCK"); + if (!name) + goto failure; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket(PF_UNIX)"); + exit(1); + } + + cloexec(sock); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, name, sizeof(addr.sun_path)); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(sock); + goto failure; + } + + for (done = 0; done < inlen ;) { + int ret = write(sock, (char *)in + done, inlen - done); + if (ret <= 0) { + close(sock); + goto failure; + } + done += ret; + } + + if (!agent_connections) + agent_connections = newtree234(agent_conncmp); + + conn = snew(struct agent_connection); + conn->fd = sock; + conn->retbuf = conn->sizebuf; + conn->retsize = 4; + conn->retlen = 0; + conn->callback = callback; + conn->callback_ctx = callback_ctx; + add234(agent_connections, conn); + + uxsel_set(sock, 1, agent_select_result); + return 0; + + failure: + *out = NULL; + *outlen = 0; + return 1; +} diff --git a/netbox/libs/Putty/unix/uxcfg.c b/netbox/libs/Putty/unix/uxcfg.c new file mode 100644 index 000000000..e48a9b691 --- /dev/null +++ b/netbox/libs/Putty/unix/uxcfg.c @@ -0,0 +1,79 @@ +/* + * uxcfg.c - the Unix-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +void unix_setup_config_box(struct controlbox *b, int midsession, int protocol) +{ + struct controlset *s; + union control *c; + + /* + * The Conf structure contains two Unix-specific elements which + * are not configured in here: stamp_utmp and login_shell. This + * is because pterm does not put up a configuration box right at + * the start, which is the only time when these elements would + * be useful to configure. + */ + + /* + * On Unix, we don't have a drop-down list for the printer + * control. + */ + s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); + assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX); + s->ctrls[0]->editbox.has_list = 0; + + /* + * Unix supports a local-command proxy. This also means we must + * adjust the text on the `Telnet command' control. + */ + if (!midsession) { + int i; + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.context.i == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Local"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); + break; + } + } + + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_EDITBOX && + c->generic.context.i == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } + + /* + * Serial back end is available on Unix. However, we have to + * mask out a couple of the configuration options: mark and + * space parity are not conveniently supported, and neither is + * DSR/DTR flow control. + */ + if (!midsession || (protocol == PROT_SERIAL)) + ser_setup_config_box(b, midsession, 0x07, 0x07); +} diff --git a/netbox/libs/Putty/unix/uxcons.c b/netbox/libs/Putty/unix/uxcons.c new file mode 100644 index 000000000..fa1c43f2e --- /dev/null +++ b/netbox/libs/Putty/unix/uxcons.c @@ -0,0 +1,509 @@ +/* + * uxcons.c: various interactive-prompt routines shared between the + * Unix console PuTTY tools + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" + +int console_batch_mode = FALSE; + +static void *console_logctx = NULL; + +static struct termios orig_termios_stderr; +static int stderr_is_a_tty; + +void stderr_tty_init() +{ + /* Ensure that if stderr is a tty, we can get it back to a sane state. */ + if ((flags & FLAG_STDERR_TTY) && isatty(STDERR_FILENO)) { + stderr_is_a_tty = TRUE; + tcgetattr(STDERR_FILENO, &orig_termios_stderr); + } +} + +void premsg(struct termios *cf) +{ + if (stderr_is_a_tty) { + tcgetattr(STDERR_FILENO, cf); + tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr); + } +} +void postmsg(struct termios *cf) +{ + if (stderr_is_a_tty) + tcsetattr(STDERR_FILENO, TCSADRAIN, cf); +} + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +void set_busy_status(void *frontend, int status) +{ +} + +void update_specials_menu(void *frontend) +{ +} + +void notify_remote_exit(void *frontend) +{ +} + +void timer_change_notify(unsigned long next) +{ +} + +/* + * Wrapper around Unix read(2), suitable for use on a file descriptor + * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK + * by means of doing a one-fd select and then trying again; all other + * errors (including errors from select) are returned to the caller. + */ +static int block_and_read(int fd, void *buf, size_t len) +{ + int ret; + + while ((ret = read(fd, buf, len)) < 0 && ( +#ifdef EAGAIN + (errno == EAGAIN) || +#endif +#ifdef EWOULDBLOCK + (errno == EWOULDBLOCK) || +#endif + 0)) { + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + ret = select(fd+1, &rfds, NULL, NULL, NULL); + assert(ret != 0); + if (ret < 0) + return ret; + assert(FD_ISSET(fd, &rfds)); + } + + return ret; +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + static const char absentmsg_batch[] = + "The server's host key is not cached. You have no guarantee\n" + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char absentmsg[] = + "The server's host key is not cached. You have no guarantee\n" + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, enter \"y\" to add the key to\n" + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without\n" + "adding the key to the cache, enter \"n\".\n" + "If you do not trust this host, press Return to abandon the\n" + "connection.\n" + "Store key in cache? (y/n) "; + + static const char wrongmsg_batch[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached. This means that either the server administrator\n" + "has changed the host key, or you have actually connected\n" + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char wrongmsg[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached. This means that either the server administrator\n" + "has changed the host key, or you have actually connected\n" + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key,\n" + "enter \"y\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating\n" + "the cache, enter \"n\".\n" + "If you want to abandon the connection completely, press\n" + "Return to cancel. Pressing Return is the ONLY guaranteed\n" + "safe choice.\n" + "Update cached key? (y/n, Return cancels connection) "; + + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + struct termios cf; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + premsg(&cf); + if (ret == 2) { /* key was different */ + if (console_batch_mode) { + fprintf(stderr, wrongmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, wrongmsg, keytype, fingerprint); + fflush(stderr); + } + if (ret == 1) { /* key was absent */ + if (console_batch_mode) { + fprintf(stderr, absentmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, absentmsg, keytype, fingerprint); + fflush(stderr); + } + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (block_and_read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + postmsg(&cf); + return 1; + } else { + fprintf(stderr, abandoned); + postmsg(&cf); + return 0; + } +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Continue with connection? (y/n) "; + static const char msg_batch[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Connection abandoned.\n"; + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + struct termios cf; + + premsg(&cf); + if (console_batch_mode) { + fprintf(stderr, msg_batch, algtype, algname); + return 0; + } + + fprintf(stderr, msg, algtype, algname); + fflush(stderr); + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (block_and_read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + if (line[0] == 'y' || line[0] == 'Y') { + postmsg(&cf); + return 1; + } else { + fprintf(stderr, abandoned); + postmsg(&cf); + return 0; + } +} + +/* + * Ask whether to wipe a session log file before writing to it. + * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). + */ +int askappend(void *frontend, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%.*s\" already exists.\n" + "You can overwrite it with a new session log,\n" + "append your session log to the end of it,\n" + "or disable session logging for this session.\n" + "Enter \"y\" to wipe the file, \"n\" to append to it,\n" + "or just press Return to disable logging.\n" + "Wipe the log file? (y/n, Return cancels logging) "; + + static const char msgtemplate_batch[] = + "The session log file \"%.*s\" already exists.\n" + "Logging will not be enabled.\n"; + + char line[32]; + struct termios cf; + + premsg(&cf); + if (console_batch_mode) { + fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path); + fflush(stderr); + return 0; + } + fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path); + fflush(stderr); + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (block_and_read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + postmsg(&cf); + if (line[0] == 'y' || line[0] == 'Y') + return 2; + else if (line[0] == 'n' || line[0] == 'N') + return 1; + else + return 0; +} + +/* + * Warn about the obsolescent key file format. + * + * Uniquely among these functions, this one does _not_ expect a + * frontend handle. This means that if PuTTY is ported to a + * platform which requires frontend handles, this function will be + * an anomaly. Fortunately, the problem it addresses will not have + * been present on that platform, so it can plausibly be + * implemented as an empty function. + */ +void old_keyfile_warning(void) +{ + static const char message[] = + "You are loading an SSH-2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again.\n"; + + struct termios cf; + premsg(&cf); + fputs(message, stderr); + postmsg(&cf); +} + +void console_provide_logctx(void *logctx) +{ + console_logctx = logctx; +} + +void logevent(void *frontend, const char *string) +{ + struct termios cf; + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) + premsg(&cf); + if (console_logctx) + log_eventlog(console_logctx, string); + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) + postmsg(&cf); +} + +/* + * Special functions to read and print to the console for password + * prompts and the like. Uses /dev/tty or stdin/stderr, in that order + * of preference; also sanitises escape sequences out of the text, on + * the basis that it might have been sent by a hostile SSH server + * doing malicious keyboard-interactive. + */ +static void console_open(FILE **outfp, int *infd) +{ + int fd; + + if ((fd = open("/dev/tty", O_RDWR)) >= 0) { + *infd = fd; + *outfp = fdopen(*infd, "w"); + } else { + *infd = 0; + *outfp = stderr; + } +} +static void console_close(FILE *outfp, int infd) +{ + if (outfp != stderr) + fclose(outfp); /* will automatically close infd too */ +} + +static void console_prompt_text(FILE *outfp, const char *data, int len) +{ + int i; + + for (i = 0; i < len; i++) + if ((data[i] & 0x60) || (data[i] == '\n')) + fputc(data[i], outfp); + fflush(outfp); +} + +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t curr_prompt; + FILE *outfp = NULL; + int infd; + + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + prompt_set_result(p->prompts[i], ""); + } + + if (p->n_prompts && console_batch_mode) + return 0; + + console_open(&outfp, &infd); + + /* + * Preamble. + */ + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + size_t l = strlen(p->name); + console_prompt_text(outfp, p->name, l); + if (p->name[l-1] != '\n') + console_prompt_text(outfp, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + console_prompt_text(outfp, p->instruction, l); + if (p->instruction[l-1] != '\n') + console_prompt_text(outfp, "\n", 1); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + struct termios oldmode, newmode; + int len; + prompt_t *pr = p->prompts[curr_prompt]; + + tcgetattr(infd, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ISIG | ICANON; + if (!pr->echo) + newmode.c_lflag &= ~ECHO; + else + newmode.c_lflag |= ECHO; + tcsetattr(infd, TCSANOW, &newmode); + + console_prompt_text(outfp, pr->prompt, strlen(pr->prompt)); + + len = 0; + while (1) { + int ret; + + prompt_ensure_result_size(pr, len * 5 / 4 + 512); + ret = read(infd, pr->result + len, pr->resultsize - len - 1); + if (ret <= 0) { + len = -1; + break; + } + len += ret; + if (pr->result[len - 1] == '\n') { + len--; + break; + } + } + + tcsetattr(infd, TCSANOW, &oldmode); + + if (!pr->echo) + console_prompt_text(outfp, "\n", 1); + + if (len < 0) { + console_close(outfp, infd); + return 0; /* failure due to read error */ + } + + pr->result[len] = '\0'; + } + + console_close(outfp, infd); + + return 1; /* success */ +} + +void frontend_keypress(void *handle) +{ + /* + * This is nothing but a stub, in console code. + */ + return; +} + +int is_interactive(void) +{ + return isatty(0); +} + +/* + * X11-forwarding-related things suitable for console. + */ + +char *platform_get_x_display(void) { + return dupstr(getenv("DISPLAY")); +} diff --git a/netbox/libs/Putty/unix/uxgen.c b/netbox/libs/Putty/unix/uxgen.c new file mode 100644 index 000000000..156d4efe3 --- /dev/null +++ b/netbox/libs/Putty/unix/uxgen.c @@ -0,0 +1,39 @@ +/* + * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. + */ + +#include +#include +#include + +#include "putty.h" + +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + int fd; + int ngot, ret; + + fd = open("/dev/random", O_RDONLY); + if (fd < 0) { + sfree(buf); + perror("puttygen: unable to open /dev/random"); + return NULL; + } + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + sfree(buf); + perror("puttygen: unable to read /dev/random"); + return NULL; + } + ngot += ret; + } + + close(fd); + + return buf; +} diff --git a/netbox/libs/Putty/unix/uxgss.c b/netbox/libs/Putty/unix/uxgss.c new file mode 100644 index 000000000..2a9e12962 --- /dev/null +++ b/netbox/libs/Putty/unix/uxgss.c @@ -0,0 +1,169 @@ +#include "putty.h" +#ifndef NO_GSSAPI +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.h" + +/* Unix code to set up the GSSAPI library list. */ + +#if !defined NO_LIBDL && !defined NO_GSSAPI + +const int ngsslibs = 4; +const char *const gsslibnames[4] = { + "libgssapi (Heimdal)", + "libgssapi_krb5 (MIT Kerberos)", + "libgss (Sun)", + "User-specified GSSAPI library", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "libgssapi", 0, -1, -1 }, + { "libgssapi_krb5", 1, -1, -1 }, + { "libgss", 2, -1, -1 }, + { "custom", 3, -1, -1 }, +}; + +/* + * Run-time binding against a choice of GSSAPI implementations. We + * try loading several libraries, and produce an entry in + * ssh_gss_libraries[] for each one. + */ + +static void gss_init(struct ssh_gss_library *lib, void *dlhandle, + int id, const char *msg) +{ + lib->id = id; + lib->gsslogmsg = msg; + lib->handle = dlhandle; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); +} + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + void *gsslib; + char *gsspath; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snewn(4, struct ssh_gss_library); + list->nlibraries = 0; + + /* Heimdal's GSSAPI Library */ + if ((gsslib = dlopen("libgssapi.so.2", RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 0, "Using GSSAPI from libgssapi.so.2"); + + /* MIT Kerberos's GSSAPI Library */ + if ((gsslib = dlopen("libgssapi_krb5.so.2", RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 1, "Using GSSAPI from libgssapi_krb5.so.2"); + + /* Sun's GSSAPI Library */ + if ((gsslib = dlopen("libgss.so.1", RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 2, "Using GSSAPI from libgss.so.1"); + + /* User-specified GSSAPI library */ + gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 3, dupprintf("Using GSSAPI from user-specified" + " library '%s'", gsspath)); + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + int i; + + /* + * dlopen and dlclose are defined to employ reference counting + * in the case where the same library is repeatedly dlopened, so + * even in a multiple-sessions-per-process context it's safe to + * naively dlclose everything here without worrying about + * destroying it under the feet of another SSH instance still + * using it. + */ + for (i = 0; i < list->nlibraries; i++) { + dlclose(list->libraries[i].handle); + if (list->libraries[i].id == 3) { + /* The 'custom' id involves a dynamically allocated message. + * Note that we must cast away the 'const' to free it. */ + sfree((char *)list->libraries[i].gsslogmsg); + } + } + sfree(list->libraries); + sfree(list); +} + +#elif !defined NO_GSSAPI + +const int ngsslibs = 1; +const char *const gsslibnames[1] = { + "static", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "static", 0, -1, -1 }, +}; + +/* + * Link-time binding against GSSAPI. Here we just construct a single + * library structure containing pointers to the functions we linked + * against. + */ + +#include + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snew(struct ssh_gss_library); + list->nlibraries = 1; + + list->libraries[0].gsslogmsg = "Using statically linked GSSAPI"; + +#define BIND_GSS_FN(name) \ + list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(&list->libraries[0]); + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list->libraries); + sfree(list); +} + +#endif /* NO_LIBDL */ + +#endif /* NO_GSSAPI */ diff --git a/netbox/libs/Putty/unix/uxmisc.c b/netbox/libs/Putty/unix/uxmisc.c new file mode 100644 index 000000000..b7727cb21 --- /dev/null +++ b/netbox/libs/Putty/unix/uxmisc.c @@ -0,0 +1,292 @@ +/* + * PuTTY miscellaneous Unix stuff + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" + +unsigned long getticks(void) +{ + /* + * We want to use milliseconds rather than the microseconds or + * nanoseconds given by the underlying clock functions, because we + * need a decent number of them to fit into a 32-bit word so it + * can be used for keepalives. + */ +#if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_MONOTONIC + { + /* Use CLOCK_MONOTONIC if available, so as to be unconfused if + * the system clock changes. */ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec * TICKSPERSEC + + ts.tv_nsec / (1000000000 / TICKSPERSEC); + } +#endif + { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); + } +} + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +int filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +int filename_is_null(const Filename *fn) +{ + return !fn->path[0]; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +int filename_serialise(const Filename *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->path) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->path); + } + return len; +} +Filename *filename_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end; + end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + end++; + *used = end - data; + return filename_from_str(data); +} + +char filename_char_sanitise(char c) +{ + if (c == '/') + return '.'; + return c; +} + +#ifdef DEBUG +static FILE *debug_fp = NULL; + +void dputs(char *buf) +{ + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ + + fputs(buf, debug_fp); + fflush(debug_fp); +} +#endif + +char *get_username(void) +{ + struct passwd *p; + uid_t uid = getuid(); + char *user, *ret = NULL; + + /* + * First, find who we think we are using getlogin. If this + * agrees with our uid, we'll go along with it. This should + * allow sharing of uids between several login names whilst + * coping correctly with people who have su'ed. + */ + user = getlogin(); + setpwent(); + if (user) + p = getpwnam(user); + else + p = NULL; + if (p && p->pw_uid == uid) { + /* + * The result of getlogin() really does correspond to + * our uid. Fine. + */ + ret = user; + } else { + /* + * If that didn't work, for whatever reason, we'll do + * the simpler version: look up our uid in the password + * file and map it straight to a name. + */ + p = getpwuid(uid); + if (!p) + return NULL; + ret = p->pw_name; + } + endpwent(); + + return dupstr(ret); +} + +/* + * Display the fingerprints of the PGP Master Keys to the user. + * (This is here rather than in uxcons because it's appropriate even for + * Unix GUI apps.) + */ +void pgp_fingerprints(void) +{ + fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n" + "be used to establish a trust path from this executable to another\n" + "one. See the manual for more information.\n" + "(Note: these fingerprints have nothing to do with SSH!)\n" + "\n" + "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Original PuTTY Master Key (RSA, 1024-bit):\n" + " " PGP_RSA_MASTER_KEY_FP "\n" + "Original PuTTY Master Key (DSA, 1024-bit):\n" + " " PGP_DSA_MASTER_KEY_FP "\n", stdout); +} + +/* + * Set and clear fcntl options on a file descriptor. We don't + * realistically expect any of these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. However, nonblock and no_nonblock do return the + * previous state of O_NONBLOCK. + */ +void cloexec(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} +void noncloexec(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} +int nonblock(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} +int no_nonblock(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} + +FILE *f_open(const Filename *filename, char const *mode, int is_private) +{ + if (!is_private) { + return fopen(filename->path, mode); + } else { + int fd; + assert(mode[0] == 'w'); /* is_private is meaningless for read, + and tricky for append */ + fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + return NULL; + return fdopen(fd, mode); + } +} + +FontSpec *fontspec_new(const char *name) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + return f; +} +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name); +} +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} +int fontspec_serialise(FontSpec *f, void *data) +{ + int len = strlen(f->name); + if (data) + strcpy(data, f->name); + return len + 1; /* include trailing NUL */ +} +FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + *used = end - data + 1; + return fontspec_new(data); +} diff --git a/netbox/libs/Putty/unix/uxnet.c b/netbox/libs/Putty/unix/uxnet.c new file mode 100644 index 000000000..181274bf7 --- /dev/null +++ b/netbox/libs/Putty/unix/uxnet.c @@ -0,0 +1,1649 @@ +/* + * Unix networking abstraction. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "tree234.h" + +/* Solaris needs for SIOCATMARK. */ +#ifndef SIOCATMARK +#include +#endif + +#ifndef X11_UNIX_PATH +# define X11_UNIX_PATH "/tmp/.X11-unix/X" +#endif + +/* + * Access to sockaddr types without breaking C strict aliasing rules. + */ +union sockaddr_union { + struct sockaddr_storage storage; + struct sockaddr sa; + struct sockaddr_in sin; +#ifndef NO_IPV6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr_un su; +}; + +/* + * We used to typedef struct Socket_tag *Socket. + * + * Since we have made the networking abstraction slightly more + * abstract, Socket no longer means a tcp socket (it could mean + * an ssl socket). So now we must use Actual_Socket when we know + * we are talking about a tcp socket. + */ +typedef struct Socket_tag *Actual_Socket; + +/* + * Mutable state that goes with a SockAddr: stores information + * about where in the list of candidate IP(v*) addresses we've + * currently got to. + */ +typedef struct SockAddrStep_tag SockAddrStep; +struct SockAddrStep_tag { +#ifndef NO_IPV6 + struct addrinfo *ai; /* steps along addr->ais */ +#endif + int curraddr; +}; + +struct Socket_tag { + struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + const char *error; + int s; + Plug plug; + bufchain output_data; + int connected; /* irrelevant for listening sockets */ + int writable; + int frozen; /* this causes readability notifications to be ignored */ + int localhost_only; /* for listening sockets */ + char oobdata[1]; + int sending_oob; + int oobpending; /* is there OOB data available to read? */ + int oobinline; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + int incomingeof; + int pending_error; /* in case send() returns error */ + int listener; + int nodelay, keepalive; /* for connect()-type sockets */ + int privport, port; /* and again */ + SockAddr addr; + SockAddrStep step; + /* + * We sometimes need pairs of Socket structures to be linked: + * if we are listening on the same IPv6 and v4 port, for + * example. So here we define `parent' and `child' pointers to + * track this link. + */ + Actual_Socket parent, child; +}; + +struct SockAddr_tag { + int refcount; + const char *error; + enum { UNRESOLVED, UNIX, IP } superfamily; +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#else + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; +#endif + char hostname[512]; /* Store an unresolved host name. */ +}; + +/* + * Which address family this address belongs to. AF_INET for IPv4; + * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has + * not been done and a simple host name is held in this SockAddr + * structure. + */ +#ifndef NO_IPV6 +#define SOCKADDR_FAMILY(addr, step) \ + ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ + (addr)->superfamily == UNIX ? AF_UNIX : \ + (step).ai ? (step).ai->ai_family : AF_INET) +#else +/* Here we gratuitously reference 'step' to avoid gcc warnings about + * 'set but not used' when compiling -DNO_IPV6 */ +#define SOCKADDR_FAMILY(addr, step) \ + ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ + (addr)->superfamily == UNIX ? AF_UNIX : \ + (step).curraddr ? AF_INET : AF_INET) +#endif + +/* + * Start a SockAddrStep structure to step through multiple + * addresses. + */ +#ifndef NO_IPV6 +#define START_STEP(addr, step) \ + ((step).ai = (addr)->ais, (step).curraddr = 0) +#else +#define START_STEP(addr, step) \ + ((step).curraddr = 0) +#endif + +static tree234 *sktree; + +static void uxsel_tell(Actual_Socket s); + +static int cmpfortree(void *av, void *bv) +{ + Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv; + int as = a->s, bs = b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + if (a < b) + return -1; + if (a > b) + return +1; + return 0; +} + +static int cmpforsearch(void *av, void *bv) +{ + Actual_Socket b = (Actual_Socket) bv; + int as = *(int *)av, bs = b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + return 0; +} + +void sk_init(void) +{ + sktree = newtree234(cmpfortree); +} + +void sk_cleanup(void) +{ + Actual_Socket s; + int i; + + if (sktree) { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + close(s->s); + } + } +} + +SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family) +{ + SockAddr ret = snew(struct SockAddr_tag); +#ifndef NO_IPV6 + struct addrinfo hints; + int err; +#else + unsigned long a; + struct hostent *h = NULL; + int n; +#endif + char realhost[8192]; + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(struct SockAddr_tag)); + ret->superfamily = UNRESOLVED; + *realhost = '\0'; + ret->error = NULL; + ret->refcount = 1; + +#ifndef NO_IPV6 + hints.ai_flags = AI_CANONNAME; + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + { + char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ + err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + sfree(trimmed_host); + } + if (err != 0) { + ret->error = gai_strerror(err); + return ret; + } + ret->superfamily = IP; + *realhost = '\0'; + if (ret->ais->ai_canonname != NULL) + strncat(realhost, ret->ais->ai_canonname, sizeof(realhost) - 1); + else + strncat(realhost, host, sizeof(realhost) - 1); +#else + if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) { + /* + * Otherwise use the IPv4-only gethostbyname... (NOTE: + * we don't use gethostbyname as a fallback!) + */ + if (ret->superfamily == UNRESOLVED) { + /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */ + if ( (h = gethostbyname(host)) ) + ret->superfamily = IP; + } + if (ret->superfamily == UNRESOLVED) { + ret->error = (h_errno == HOST_NOT_FOUND || + h_errno == NO_DATA || + h_errno == NO_ADDRESS ? "Host does not exist" : + h_errno == TRY_AGAIN ? + "Temporary name service failure" : + "gethostbyname: unknown error"); + return ret; + } + /* This way we are always sure the h->h_name is valid :) */ + strncpy(realhost, h->h_name, sizeof(realhost)); + for (n = 0; h->h_addr_list[n]; n++); + ret->addresses = snewn(n, unsigned long); + ret->naddresses = n; + for (n = 0; n < ret->naddresses; n++) { + memcpy(&a, h->h_addr_list[n], sizeof(a)); + ret->addresses[n] = ntohl(a); + } + } else { + /* + * This must be a numeric IPv4 address because it caused a + * success return from inet_addr. + */ + ret->superfamily = IP; + strncpy(realhost, host, sizeof(realhost)); + ret->addresses = snew(unsigned long); + ret->naddresses = 1; + ret->addresses[0] = ntohl(a); + } +#endif + realhost[lenof(realhost)-1] = '\0'; + *canonicalname = snewn(1+strlen(realhost), char); + strcpy(*canonicalname, realhost); + return ret; +} + +SockAddr sk_nonamelookup(const char *host) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->superfamily = UNRESOLVED; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; +#endif + ret->refcount = 1; + return ret; +} + +static int sk_nextaddr(SockAddr addr, SockAddrStep *step) +{ +#ifndef NO_IPV6 + if (step->ai && step->ai->ai_next) { + step->ai = step->ai->ai_next; + return TRUE; + } else + return FALSE; +#else + if (step->curraddr+1 < addr->naddresses) { + step->curraddr++; + return TRUE; + } else { + return FALSE; + } +#endif +} + +void sk_getaddr(SockAddr addr, char *buf, int buflen) +{ + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { + strncpy(buf, addr->hostname, buflen); + buf[buflen-1] = '\0'; + } else { +#ifndef NO_IPV6 + if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, + NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + strncat(buf, "", buflen - 1); + } +#else + struct in_addr a; + SockAddrStep step; + START_STEP(addr, step); + assert(SOCKADDR_FAMILY(addr, step) == AF_INET); + a.s_addr = htonl(addr->addresses[0]); + strncpy(buf, inet_ntoa(a), buflen); + buf[buflen-1] = '\0'; +#endif + } +} + +int sk_addr_needs_port(SockAddr addr) +{ + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { + return FALSE; + } else { + return TRUE; + } +} + +int sk_hostname_is_local(const char *name) +{ + return !strcmp(name, "localhost") || + !strcmp(name, "::1") || + !strncmp(name, "127.", 4); +} + +#define ipv4_is_loopback(addr) \ + (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000)) + +static int sockaddr_is_loopback(struct sockaddr *sa) +{ + union sockaddr_union *u = (union sockaddr_union *)sa; + switch (u->sa.sa_family) { + case AF_INET: + return ipv4_is_loopback(u->sin.sin_addr); +#ifndef NO_IPV6 + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr); +#endif + case AF_UNIX: + return TRUE; + default: + return FALSE; + } +} + +int sk_address_is_local(SockAddr addr) +{ + if (addr->superfamily == UNRESOLVED) + return 0; /* we don't know; assume not */ + else if (addr->superfamily == UNIX) + return 1; + else { +#ifndef NO_IPV6 + return sockaddr_is_loopback(addr->ais->ai_addr); +#else + struct in_addr a; + SockAddrStep step; + START_STEP(addr, step); + assert(SOCKADDR_FAMILY(addr, step) == AF_INET); + a.s_addr = htonl(addr->addresses[0]); + return ipv4_is_loopback(a); +#endif + } +} + +int sk_address_is_special_local(SockAddr addr) +{ + return addr->superfamily == UNIX; +} + +int sk_addrtype(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + return (family == AF_INET ? ADDRTYPE_IPV4 : +#ifndef NO_IPV6 + family == AF_INET6 ? ADDRTYPE_IPV6 : +#endif + ADDRTYPE_NAME); +} + +void sk_addrcopy(SockAddr addr, char *buf) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + +#ifndef NO_IPV6 + if (family == AF_INET) + memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr, + sizeof(struct in_addr)); + else if (family == AF_INET6) + memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + else + assert(FALSE); +#else + struct in_addr a; + + assert(family == AF_INET); + a.s_addr = htonl(addr->addresses[step.curraddr]); + memcpy(buf, (char*) &a.s_addr, 4); +#endif +} + +void sk_addr_free(SockAddr addr) +{ + if (--addr->refcount > 0) + return; +#ifndef NO_IPV6 + if (addr->ais != NULL) + freeaddrinfo(addr->ais); +#else + sfree(addr->addresses); +#endif + sfree(addr); +} + +SockAddr sk_addr_dup(SockAddr addr) +{ + addr->refcount++; + return addr; +} + +static Plug sk_tcp_plug(Socket sock, Plug p) +{ + Actual_Socket s = (Actual_Socket) sock; + Plug ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_tcp_flush(Socket s) +{ + /* + * We send data to the socket as soon as we can anyway, + * so we don't need to do anything here. :-) + */ +} + +static void sk_tcp_close(Socket s); +static int sk_tcp_write(Socket s, const char *data, int len); +static int sk_tcp_write_oob(Socket s, const char *data, int len); +static void sk_tcp_write_eof(Socket s); +static void sk_tcp_set_frozen(Socket s, int is_frozen); +static char *sk_tcp_peer_info(Socket s); +static const char *sk_tcp_socket_error(Socket s); + +static struct socket_function_table tcp_fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_write_eof, + sk_tcp_flush, + sk_tcp_set_frozen, + sk_tcp_socket_error, + sk_tcp_peer_info, +}; + +static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) +{ + int sockfd = ctx.i; + Actual_Socket ret; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 1; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 1; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; + ret->listener = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + ret->connected = 1; + + ret->s = sockfd; + + if (ret->s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + ret->oobinline = 0; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} + +static int try_connect(Actual_Socket sock) +{ + int s; + union sockaddr_union u; + const union sockaddr_union *sa; + int err = 0; + short localport; + int salen, family; + + /* + * Remove the socket from the tree before we overwrite its + * internal socket id, because that forms part of the tree's + * sorting criterion. We'll add it back before exiting this + * function, whether we changed anything or not. + */ + del234(sktree, sock); + + if (sock->s >= 0) + close(sock->s); + + plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0); + + /* + * Open socket. + */ + family = SOCKADDR_FAMILY(sock->addr, sock->step); + assert(family != AF_UNSPEC); + s = socket(family, SOCK_STREAM, 0); + sock->s = s; + + if (s < 0) { + err = errno; + goto ret; + } + + cloexec(s); + + if (sock->oobinline) { + int b = TRUE; + if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + if (sock->nodelay) { + int b = TRUE; + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + if (sock->keepalive) { + int b = TRUE; + if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + /* + * Bind to local address. + */ + if (sock->privport) + localport = 1023; /* count from 1023 downwards */ + else + localport = 0; /* just use port 0 (ie kernel picks) */ + + /* BSD IP stacks need sockaddr_in zeroed before filling in */ + memset(&u,'\0',sizeof(u)); + + /* We don't try to bind to a local address for UNIX domain sockets. (Why + * do we bother doing the bind when localport == 0 anyway?) */ + if (family != AF_UNIX) { + /* Loop round trying to bind */ + while (1) { + int retcode; + +#ifndef NO_IPV6 + if (family == AF_INET6) { + /* XXX use getaddrinfo to get a local address? */ + u.sin6.sin6_family = AF_INET6; + u.sin6.sin6_addr = in6addr_any; + u.sin6.sin6_port = htons(localport); + retcode = bind(s, &u.sa, sizeof(u.sin6)); + } else +#endif + { + assert(family == AF_INET); + u.sin.sin_family = AF_INET; + u.sin.sin_addr.s_addr = htonl(INADDR_ANY); + u.sin.sin_port = htons(localport); + retcode = bind(s, &u.sa, sizeof(u.sin)); + } + if (retcode >= 0) { + err = 0; + break; /* done */ + } else { + err = errno; + if (err != EADDRINUSE) /* failed, for a bad reason */ + break; + } + + if (localport == 0) + break; /* we're only looping once */ + localport--; + if (localport == 0) + break; /* we might have got to the end */ + } + + if (err) + goto ret; + } + + /* + * Connect to remote address. + */ + switch(family) { +#ifndef NO_IPV6 + case AF_INET: + /* XXX would be better to have got getaddrinfo() to fill in the port. */ + ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = + htons(sock->port); + sa = (const union sockaddr_union *)sock->step.ai->ai_addr; + salen = sock->step.ai->ai_addrlen; + break; + case AF_INET6: + ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = + htons(sock->port); + sa = (const union sockaddr_union *)sock->step.ai->ai_addr; + salen = sock->step.ai->ai_addrlen; + break; +#else + case AF_INET: + u.sin.sin_family = AF_INET; + u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]); + u.sin.sin_port = htons((short) sock->port); + sa = &u; + salen = sizeof u.sin; + break; +#endif + case AF_UNIX: + assert(sock->port == 0); /* to catch confused people */ + assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path); + u.su.sun_family = AF_UNIX; + strcpy(u.su.sun_path, sock->addr->hostname); + sa = &u; + salen = sizeof u.su; + break; + + default: + assert(0 && "unknown address family"); + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + nonblock(s); + + if ((connect(s, &(sa->sa), salen)) < 0) { + if ( errno != EINPROGRESS ) { + err = errno; + goto ret; + } + } else { + /* + * If we _don't_ get EWOULDBLOCK, the connect has completed + * and we should set the socket as connected and writable. + */ + sock->connected = 1; + sock->writable = 1; + } + + uxsel_tell(sock); + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) + plug_log(sock->plug, 1, sock->addr, sock->port, strerror(err), err); + return err; +} + +Socket putty_sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug plug) +{ + Actual_Socket ret; + int err; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = 0; /* to start with */ + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; + ret->listener = 0; + ret->addr = addr; + START_STEP(ret->addr, ret->step); + ret->s = -1; + ret->oobinline = oobinline; + ret->nodelay = nodelay; + ret->keepalive = keepalive; + ret->privport = privport; + ret->port = port; + + err = 0; + do { + err = try_connect(ret); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + if (err) + ret->error = strerror(err); + + return (Socket) ret; +} + +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family) +{ + int s; +#ifndef NO_IPV6 + struct addrinfo hints, *ai = NULL; + char portstr[6]; +#endif + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + Actual_Socket ret; + int retcode; + int address_family; + int on = 1; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; + ret->listener = 1; + ret->addr = NULL; + ret->s = -1; + + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + +#ifndef NO_IPV6 + /* Let's default to IPv6. + * If the stack doesn't support IPv6, we will fall back to IPv4. */ + if (address_family == AF_UNSPEC) address_family = AF_INET6; +#else + /* No other choice, default to IPv4 */ + if (address_family == AF_UNSPEC) address_family = AF_INET; +#endif + + /* + * Open socket. + */ + s = socket(address_family, SOCK_STREAM, 0); + +#ifndef NO_IPV6 + /* If the host doesn't support IPv6 try fallback to IPv4. */ + if (s < 0 && address_family == AF_INET6) { + address_family = AF_INET; + s = socket(address_family, SOCK_STREAM, 0); + } +#endif + + if (s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + cloexec(s); + + ret->oobinline = 0; + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const char *)&on, sizeof(on)) < 0) { + ret->error = strerror(errno); + close(s); + return (Socket) ret; + } + + retcode = -1; + addr = NULL; addrlen = -1; /* placate optimiser */ + + if (srcaddr != NULL) { +#ifndef NO_IPV6 + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = address_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + assert(port >= 0 && port <= 99999); + sprintf(portstr, "%d", port); + { + char *trimmed_addr = host_strduptrim(srcaddr); + retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai); + sfree(trimmed_addr); + } + if (retcode == 0) { + addr = (union sockaddr_union *)ai->ai_addr; + addrlen = ai->ai_addrlen; + } +#else + memset(&u,'\0',sizeof u); + u.sin.sin_family = AF_INET; + u.sin.sin_port = htons(port); + u.sin.sin_addr.s_addr = inet_addr(srcaddr); + if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr); + } + addr = &u; + addrlen = sizeof(u.sin); + retcode = 0; +#endif + } + + if (retcode != 0) { + memset(&u,'\0',sizeof u); +#ifndef NO_IPV6 + if (address_family == AF_INET6) { + u.sin6.sin6_family = AF_INET6; + u.sin6.sin6_port = htons(port); + if (local_host_only) + u.sin6.sin6_addr = in6addr_loopback; + else + u.sin6.sin6_addr = in6addr_any; + addr = &u; + addrlen = sizeof(u.sin6); + } else +#endif + { + u.sin.sin_family = AF_INET; + u.sin.sin_port = htons(port); + if (local_host_only) + u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + else + u.sin.sin_addr.s_addr = htonl(INADDR_ANY); + addr = &u; + addrlen = sizeof(u.sin); + } + } + + retcode = bind(s, &addr->sa, addrlen); + +#ifndef NO_IPV6 + if (ai) + freeaddrinfo(ai); +#endif + + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + +#ifndef NO_IPV6 + /* + * If we were given ADDRTYPE_UNSPEC, we must also create an + * IPv4 listening socket and link it to this one. + */ + if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) { + Actual_Socket other; + + other = (Actual_Socket) sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV4); + + if (other) { + if (!other->error) { + other->parent = ret; + ret->child = other; + } else { + /* If we couldn't create a listening socket on IPv4 as well + * as IPv6, we must return an error overall. */ + close(s); + sfree(ret); + return (Socket) other; + } + } + } +#endif + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} + +static void sk_tcp_close(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + if (s->child) + sk_tcp_close((Socket)s->child); + + uxsel_del(s->s); + del234(sktree, s); + close(s->s); + if (s->addr) + sk_addr_free(s->addr); + sfree(s); +} + +void *sk_getxdmdata(void *sock, int *lenp) +{ + Actual_Socket s = (Actual_Socket) sock; + union sockaddr_union u; + socklen_t addrlen; + char *buf; + static unsigned int unix_addr = 0xFFFFFFFF; + + /* + * We must check that this socket really _is_ an Actual_Socket. + */ + if (s->fn != &tcp_fn_table) + return NULL; /* failure */ + + addrlen = sizeof(u); + if (getsockname(s->s, &u.sa, &addrlen) < 0) + return NULL; + switch(u.sa.sa_family) { + case AF_INET: + *lenp = 6; + buf = snewn(*lenp, char); + PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr)); + PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port)); + break; +#ifndef NO_IPV6 + case AF_INET6: + *lenp = 6; + buf = snewn(*lenp, char); + if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) { + memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4); + PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port)); + } else + /* This is stupid, but it's what XLib does. */ + memset(buf, 0, 6); + break; +#endif + case AF_UNIX: + *lenp = 6; + buf = snewn(*lenp, char); + PUT_32BIT_MSB_FIRST(buf, unix_addr--); + PUT_16BIT_MSB_FIRST(buf+4, getpid()); + break; + + /* XXX IPV6 */ + + default: + return NULL; + } + + return buf; +} + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + Actual_Socket s = (Actual_Socket)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(Actual_Socket s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + int err; + void *data; + int len, urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + bufchain_prefix(&s->output_data, &data, &len); + } + nsent = send(s->s, data, len, urgentflag); + noise_ultralight(nsent); + if (nsent <= 0) { + err = (nsent < 0 ? errno : 0); + if (err == EWOULDBLOCK) { + /* + * Perfectly normal: we've sent all we can for the moment. + */ + s->writable = FALSE; + return; + } else { + /* + * We unfortunately can't just call plug_closing(), + * because it's quite likely that we're currently + * _in_ a call from the code we'd be calling back + * to, so we'd have to make half the SSH code + * reentrant. Instead we flag a pending error on + * the socket, to be dealt with (by calling + * plug_closing()) at some suitable future moment. + */ + s->pending_error = err; + /* + * Immediately cease selecting on this socket, so that + * we don't tight-loop repeatedly trying to do + * whatever it was that went wrong. + */ + uxsel_tell(s); + /* + * Arrange to be called back from the top level to + * deal with the error condition on this socket. + */ + queue_toplevel_callback(socket_error_callback, s); + return; + } + } else { + if (s->sending_oob) { + if (nsent < len) { + memmove(s->oobdata, s->oobdata+nsent, len-nsent); + s->sending_oob = len - nsent; + } else { + s->sending_oob = 0; + } + } else { + bufchain_consume(&s->output_data, nsent); + } + } + } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + shutdown(s->s, SHUT_WR); + s->outgoingeof = EOF_SENT; + } + + /* + * Also update the select status, because we don't need to select + * for writing any more. + */ + uxsel_tell(s); +} + +static int sk_tcp_write(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Add the data to the buffer list on the socket. + */ + bufchain_add(&s->output_data, buf, len); + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); + + return bufchain_size(&s->output_data); +} + +static int sk_tcp_write_oob(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Replace the buffer list on the socket with the data. + */ + bufchain_clear(&s->output_data); + assert(len <= sizeof(s->oobdata)); + memcpy(s->oobdata, buf, len); + s->sending_oob = len; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); + + return s->sending_oob; +} + +static void sk_tcp_write_eof(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); +} + +static int net_select_result(int fd, int event) +{ + int ret; + char buf[20480]; /* nice big buffer for plenty of speed */ + Actual_Socket s; + u_long atmark; + + /* Find the Socket structure */ + s = find234(sktree, &fd, cmpforsearch); + if (!s) + return 1; /* boggle */ + + noise_ultralight(event); + + switch (event) { + case 4: /* exceptional */ + if (!s->oobinline) { + /* + * On a non-oobinline socket, this indicates that we + * can immediately perform an OOB read and get back OOB + * data, which we will send to the back end with + * type==2 (urgent data). + */ + ret = recv(s->s, buf, sizeof(buf), MSG_OOB); + noise_ultralight(ret); + if (ret <= 0) { + return plug_closing(s->plug, + ret == 0 ? "Internal networking trouble" : + strerror(errno), errno, 0); + } else { + /* + * Receiving actual data on a socket means we can + * stop falling back through the candidate + * addresses to connect to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + return plug_receive(s->plug, 2, buf, ret); + } + break; + } + + /* + * If we reach here, this is an oobinline socket, which + * means we should set s->oobpending and then deal with it + * when we get called for the readability event (which + * should also occur). + */ + s->oobpending = TRUE; + break; + case 1: /* readable; also acceptance */ + if (s->listener) { + /* + * On a listening socket, the readability event means a + * connection is ready to be accepted. + */ + union sockaddr_union su; + socklen_t addrlen = sizeof(su); + accept_ctx_t actx; + int t; /* socket of connection */ + + memset(&su, 0, addrlen); + t = accept(s->s, &su.sa, &addrlen); + if (t < 0) { + break; + } + + nonblock(t); + actx.i = t; + + if ((!s->addr || s->addr->superfamily != UNIX) && + s->localhost_only && !sockaddr_is_loopback(&su.sa)) { + close(t); /* someone let nonlocal through?! */ + } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) { + close(t); /* denied or error */ + } + break; + } + + /* + * If we reach here, this is not a listening socket, so + * readability really means readability. + */ + + /* In the case the socket is still frozen, we don't even bother */ + if (s->frozen) + break; + + /* + * We have received data on the socket. For an oobinline + * socket, this might be data _before_ an urgent pointer, + * in which case we send it to the back end with type==1 + * (data prior to urgent). + */ + if (s->oobinline && s->oobpending) { + atmark = 1; + if (ioctl(s->s, SIOCATMARK, &atmark) == 0 && atmark) + s->oobpending = FALSE; /* clear this indicator */ + } else + atmark = 1; + + ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); + noise_ultralight(ret); + if (ret < 0) { + if (errno == EWOULDBLOCK) { + break; + } + } + if (ret < 0) { + /* + * An error at this point _might_ be an error reported + * by a non-blocking connect(). So before we return a + * panic status to the user, let's just see whether + * that's the case. + */ + int err = errno; + if (s->addr) { + plug_log(s->plug, 1, s->addr, s->port, strerror(err), err); + while (s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + } + if (err != 0) + return plug_closing(s->plug, strerror(err), err, 0); + } else if (0 == ret) { + s->incomingeof = TRUE; /* stop trying to read now */ + uxsel_tell(s); + return plug_closing(s->plug, NULL, 0, 0); + } else { + /* + * Receiving actual data on a socket means we can + * stop falling back through the candidate + * addresses to connect to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + return plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + break; + case 2: /* writable */ + if (!s->connected) { + /* + * select() reports a socket as _writable_ when an + * asynchronous connection is completed. + */ + s->connected = s->writable = 1; + uxsel_tell(s); + break; + } else { + int bufsize_before, bufsize_after; + s->writable = 1; + bufsize_before = s->sending_oob + bufchain_size(&s->output_data); + try_send(s); + bufsize_after = s->sending_oob + bufchain_size(&s->output_data); + if (bufsize_after < bufsize_before) + plug_sent(s->plug, bufsize_after); + } + break; + } + + return 1; +} + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr) +{ + return addr->error; +} +static const char *sk_tcp_socket_error(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->error; +} + +static void sk_tcp_set_frozen(Socket sock, int is_frozen) +{ + Actual_Socket s = (Actual_Socket) sock; + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + uxsel_tell(s); +} + +static char *sk_tcp_peer_info(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + union sockaddr_union addr; + socklen_t addrlen = sizeof(addr); +#ifndef NO_IPV6 + char buf[INET6_ADDRSTRLEN]; +#endif + + if (getpeername(s->s, &addr.sa, &addrlen) < 0) + return NULL; + if (addr.storage.ss_family == AF_INET) { + return dupprintf + ("%s:%d", + inet_ntoa(addr.sin.sin_addr), + (int)ntohs(addr.sin.sin_port)); +#ifndef NO_IPV6 + } else if (addr.storage.ss_family == AF_INET6) { + return dupprintf + ("[%s]:%d", + inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf)), + (int)ntohs(addr.sin6.sin6_port)); +#endif + } else if (addr.storage.ss_family == AF_UNIX) { + /* + * For Unix sockets, the source address is unlikely to be + * helpful. Instead, we try SO_PEERCRED and try to get the + * source pid. + */ + int pid, uid, gid; + if (so_peercred(s->s, &pid, &uid, &gid)) { + char uidbuf[64], gidbuf[64]; + sprintf(uidbuf, "%d", uid); + sprintf(gidbuf, "%d", gid); + struct passwd *pw = getpwuid(uid); + struct group *gr = getgrgid(gid); + return dupprintf("pid %d (%s:%s)", pid, + pw ? pw->pw_name : uidbuf, + gr ? gr->gr_name : gidbuf); + } + return NULL; + } else { + return NULL; + } +} + +static void uxsel_tell(Actual_Socket s) +{ + int rwx = 0; + if (!s->pending_error) { + if (s->listener) { + rwx |= 1; /* read == accept */ + } else { + if (!s->connected) + rwx |= 2; /* write == connect */ + if (s->connected && !s->frozen && !s->incomingeof) + rwx |= 1 | 4; /* read, except */ + if (bufchain_size(&s->output_data)) + rwx |= 2; /* write */ + } + } + uxsel_set(s->s, rwx, net_select_result); +} + +int net_service_lookup(char *service) +{ + struct servent *se; + se = getservbyname(service, NULL); + if (se != NULL) + return ntohs(se->s_port); + else + return 0; +} + +char *get_hostname(void) +{ + int len = 128; + char *hostname = NULL; + do { + len *= 2; + hostname = sresize(hostname, len, char); + if ((gethostname(hostname, len) < 0) && + (errno != ENAMETOOLONG)) { + sfree(hostname); + hostname = NULL; + break; + } + } while (strlen(hostname) >= len-1); + return hostname; +} + +SockAddr platform_get_x11_unix_address(const char *sockpath, int displaynum) +{ + SockAddr ret = snew(struct SockAddr_tag); + int n; + + memset(ret, 0, sizeof *ret); + ret->superfamily = UNIX; + /* + * In special circumstances (notably Mac OS X Leopard), we'll + * have been passed an explicit Unix socket path. + */ + if (sockpath) { + n = snprintf(ret->hostname, sizeof ret->hostname, + "%s", sockpath); + } else { + n = snprintf(ret->hostname, sizeof ret->hostname, + "%s%d", X11_UNIX_PATH, displaynum); + } + + if (n < 0) + ret->error = "snprintf failed"; + else if (n >= sizeof ret->hostname) + ret->error = "X11 UNIX name too long"; + +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; + ret->naddresses = 0; +#endif + ret->refcount = 1; + return ret; +} + +SockAddr unix_sock_addr(const char *path) +{ + SockAddr ret = snew(struct SockAddr_tag); + int n; + + memset(ret, 0, sizeof *ret); + ret->superfamily = UNIX; + n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); + + if (n < 0) + ret->error = "snprintf failed"; + else if (n >= sizeof ret->hostname) + ret->error = "socket pathname too long"; + +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; + ret->naddresses = 0; +#endif + ret->refcount = 1; + return ret; +} + +Socket new_unix_listener(SockAddr listenaddr, Plug plug) +{ + int s; + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + Actual_Socket ret; + int retcode; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->localhost_only = TRUE; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; + ret->listener = 1; + ret->addr = listenaddr; + ret->s = -1; + + assert(listenaddr->superfamily == UNIX); + + /* + * Open socket. + */ + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + cloexec(s); + + ret->oobinline = 0; + + memset(&u, '\0', sizeof(u)); + u.su.sun_family = AF_UNIX; + strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); + addr = &u; + addrlen = sizeof(u.su); + + if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + retcode = bind(s, &addr->sa, addrlen); + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} diff --git a/netbox/libs/Putty/unix/uxnoise.c b/netbox/libs/Putty/unix/uxnoise.c new file mode 100644 index 000000000..c42466f65 --- /dev/null +++ b/netbox/libs/Putty/unix/uxnoise.c @@ -0,0 +1,147 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +static int read_dev_urandom(char *buf, int len) +{ + int fd; + int ngot, ret; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return 0; + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + return 0; + } + ngot += ret; + } + + close(fd); + + return 1; +} + +/* + * This function is called once, at PuTTY startup. It will do some + * slightly silly things such as fetching an entire process listing + * and scanning /tmp, load the saved random seed from disk, and + * also read 32 bytes out of /dev/urandom. + */ + +void noise_get_heavy(void (*func) (void *, int)) +{ + char buf[512]; + FILE *fp; + int ret; + int got_dev_urandom = 0; + + if (read_dev_urandom(buf, 32)) { + got_dev_urandom = 1; + func(buf, 32); + } + + fp = popen("ps -axu 2>/dev/null", "r"); + if (fp) { + while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0) + func(buf, ret); + pclose(fp); + } else if (!got_dev_urandom) { + fprintf(stderr, "popen: %s\n" + "Unable to access fallback entropy source\n", strerror(errno)); + exit(1); + } + + fp = popen("ls -al /tmp 2>/dev/null", "r"); + if (fp) { + while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0) + func(buf, ret); + pclose(fp); + } else if (!got_dev_urandom) { + fprintf(stderr, "popen: %s\n" + "Unable to access fallback entropy source\n", strerror(errno)); + exit(1); + } + + read_random_seed(func); + random_save_seed(); +} + +void random_save_seed(void) +{ + int len; + void *data; + + if (random_active) { + random_get_savedata(&data, &len); + write_random_seed(data, len); + sfree(data); + } +} + +/* + * This function is called every time the random pool needs + * stirring, and will acquire the system time. + */ +void noise_get_light(void (*func) (void *, int)) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + func(&tv, sizeof(tv)); +} + +/* + * This function is called on a timer, and grabs as much changeable + * system data as it can quickly get its hands on. + */ +void noise_regular(void) +{ + int fd; + int ret; + char buf[512]; + struct rusage rusage; + + if ((fd = open("/proc/meminfo", O_RDONLY)) >= 0) { + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + random_add_noise(buf, ret); + close(fd); + } + if ((fd = open("/proc/stat", O_RDONLY)) >= 0) { + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + random_add_noise(buf, ret); + close(fd); + } + getrusage(RUSAGE_SELF, &rusage); + random_add_noise(&rusage, sizeof(rusage)); +} + +/* + * This function is called on every keypress or mouse move, and + * will add the current time to the noise pool. It gets the scan + * code or mouse position passed in, and adds that too. + */ +void noise_ultralight(unsigned long data) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + random_add_noise(&tv, sizeof(tv)); + random_add_noise(&data, sizeof(data)); +} diff --git a/netbox/libs/Putty/unix/uxpeer.c b/netbox/libs/Putty/unix/uxpeer.c new file mode 100644 index 000000000..57bcfb88f --- /dev/null +++ b/netbox/libs/Putty/unix/uxpeer.c @@ -0,0 +1,32 @@ +/* + * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on + * appropriate autoconfery. + */ + +#ifdef HAVE_CONFIG_H +# include "uxconfig.h" /* leading space prevents mkfiles.pl trying to follow */ +#endif + +#ifdef HAVE_SO_PEERCRED +#define _GNU_SOURCE +#include +#endif + +#include + +#include "putty.h" + +int so_peercred(int fd, int *pid, int *uid, int *gid) +{ +#ifdef HAVE_SO_PEERCRED + struct ucred cr; + socklen_t crlen = sizeof(cr); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) { + *pid = cr.pid; + *uid = cr.uid; + *gid = cr.gid; + return TRUE; + } +#endif + return FALSE; +} diff --git a/netbox/libs/Putty/unix/uxplink.c b/netbox/libs/Putty/unix/uxplink.c new file mode 100644 index 000000000..90ad8d527 --- /dev/null +++ b/netbox/libs/Putty/unix/uxplink.c @@ -0,0 +1,1162 @@ +/* + * PLink - a command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NO_SYS_SELECT_H +#include +#endif + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#include "putty.h" +#include "storage.h" +#include "tree234.h" + +#define MAX_STDIN_BACKLOG 4096 + +void *logctx; + +static struct termios orig_termios; + +void fatalbox(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void modalfatalbox(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void nonfatal(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); +} +void connection_fatal(void *frontend, char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void cmdline_error(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "plink: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + exit(1); +} + +static int local_tty = FALSE; /* do we have a local tty? */ + +static Backend *back; +static void *backhandle; +static Conf *conf; + +/* + * Default settings that are specific to Unix plink. + */ +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "TermType")) + return dupstr(getenv("TERM")); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} +int term_ldisc(Terminal *term, int mode) +{ + return FALSE; +} +void ldisc_update(void *frontend, int echo, int edit) +{ + /* Update stdin read mode to reflect changes in line discipline. */ + struct termios mode; + + if (!local_tty) return; + + mode = orig_termios; + + if (echo) + mode.c_lflag |= ECHO; + else + mode.c_lflag &= ~ECHO; + + if (edit) { + mode.c_iflag |= ICRNL; + mode.c_lflag |= ISIG | ICANON; + mode.c_oflag |= OPOST; + } else { + mode.c_iflag &= ~ICRNL; + mode.c_lflag &= ~(ISIG | ICANON); + mode.c_oflag &= ~OPOST; + /* Solaris sets these to unhelpful values */ + mode.c_cc[VMIN] = 1; + mode.c_cc[VTIME] = 0; + /* FIXME: perhaps what we do with IXON/IXOFF should be an + * argument to ldisc_update(), to allow implementation of SSH-2 + * "xon-xoff" and Rlogin's equivalent? */ + mode.c_iflag &= ~IXON; + mode.c_iflag &= ~IXOFF; + } + /* + * Mark parity errors and (more important) BREAK on input. This + * is more complex than it need be because POSIX-2001 suggests + * that escaping of valid 0xff in the input stream is dependent on + * IGNPAR being clear even though marking of BREAK isn't. NetBSD + * 2.0 goes one worse and makes it dependent on INPCK too. We + * deal with this by forcing these flags into a useful state and + * then faking the state in which we found them in from_tty() if + * we get passed a parity or framing error. + */ + mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR; + + tcsetattr(STDIN_FILENO, TCSANOW, &mode); +} + +/* Helper function to extract a special character from a termios. */ +static char *get_ttychar(struct termios *t, int index) +{ + cc_t c = t->c_cc[index]; +#if defined(_POSIX_VDISABLE) + if (c == _POSIX_VDISABLE) + return dupstr(""); +#endif + return dupprintf("^<%d>", c); +} + +char *get_ttymode(void *frontend, const char *mode) +{ + /* + * Propagate appropriate terminal modes from the local terminal, + * if any. + */ + if (!local_tty) return NULL; + +#define GET_CHAR(ourname, uxname) \ + do { \ + if (strcmp(mode, ourname) == 0) \ + return get_ttychar(&orig_termios, uxname); \ + } while(0) +#define GET_BOOL(ourname, uxname, uxmemb, transform) \ + do { \ + if (strcmp(mode, ourname) == 0) { \ + int b = (orig_termios.uxmemb & uxname) != 0; \ + transform; \ + return dupprintf("%d", b); \ + } \ + } while (0) + + /* + * Modes that want to be the same on all terminal devices involved. + */ + /* All the special characters supported by SSH */ +#if defined(VINTR) + GET_CHAR("INTR", VINTR); +#endif +#if defined(VQUIT) + GET_CHAR("QUIT", VQUIT); +#endif +#if defined(VERASE) + GET_CHAR("ERASE", VERASE); +#endif +#if defined(VKILL) + GET_CHAR("KILL", VKILL); +#endif +#if defined(VEOF) + GET_CHAR("EOF", VEOF); +#endif +#if defined(VEOL) + GET_CHAR("EOL", VEOL); +#endif +#if defined(VEOL2) + GET_CHAR("EOL2", VEOL2); +#endif +#if defined(VSTART) + GET_CHAR("START", VSTART); +#endif +#if defined(VSTOP) + GET_CHAR("STOP", VSTOP); +#endif +#if defined(VSUSP) + GET_CHAR("SUSP", VSUSP); +#endif +#if defined(VDSUSP) + GET_CHAR("DSUSP", VDSUSP); +#endif +#if defined(VREPRINT) + GET_CHAR("REPRINT", VREPRINT); +#endif +#if defined(VWERASE) + GET_CHAR("WERASE", VWERASE); +#endif +#if defined(VLNEXT) + GET_CHAR("LNEXT", VLNEXT); +#endif +#if defined(VFLUSH) + GET_CHAR("FLUSH", VFLUSH); +#endif +#if defined(VSWTCH) + GET_CHAR("SWTCH", VSWTCH); +#endif +#if defined(VSTATUS) + GET_CHAR("STATUS", VSTATUS); +#endif +#if defined(VDISCARD) + GET_CHAR("DISCARD", VDISCARD); +#endif + /* Modes that "configure" other major modes. These should probably be + * considered as user preferences. */ + /* Configuration of ICANON */ +#if defined(ECHOK) + GET_BOOL("ECHOK", ECHOK, c_lflag, ); +#endif +#if defined(ECHOKE) + GET_BOOL("ECHOKE", ECHOKE, c_lflag, ); +#endif +#if defined(ECHOE) + GET_BOOL("ECHOE", ECHOE, c_lflag, ); +#endif +#if defined(ECHONL) + GET_BOOL("ECHONL", ECHONL, c_lflag, ); +#endif +#if defined(XCASE) + GET_BOOL("XCASE", XCASE, c_lflag, ); +#endif + /* Configuration of ECHO */ +#if defined(ECHOCTL) + GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, ); +#endif + /* Configuration of IXON/IXOFF */ +#if defined(IXANY) + GET_BOOL("IXANY", IXANY, c_iflag, ); +#endif + /* Configuration of OPOST */ +#if defined(OLCUC) + GET_BOOL("OLCUC", OLCUC, c_oflag, ); +#endif +#if defined(ONLCR) + GET_BOOL("ONLCR", ONLCR, c_oflag, ); +#endif +#if defined(OCRNL) + GET_BOOL("OCRNL", OCRNL, c_oflag, ); +#endif +#if defined(ONOCR) + GET_BOOL("ONOCR", ONOCR, c_oflag, ); +#endif +#if defined(ONLRET) + GET_BOOL("ONLRET", ONLRET, c_oflag, ); +#endif + + /* + * Modes that want to be set in only one place, and that we have + * squashed locally. + */ +#if defined(ISIG) + GET_BOOL("ISIG", ISIG, c_lflag, ); +#endif +#if defined(ICANON) + GET_BOOL("ICANON", ICANON, c_lflag, ); +#endif +#if defined(ECHO) + GET_BOOL("ECHO", ECHO, c_lflag, ); +#endif +#if defined(IXON) + GET_BOOL("IXON", IXON, c_iflag, ); +#endif +#if defined(IXOFF) + GET_BOOL("IXOFF", IXOFF, c_iflag, ); +#endif +#if defined(OPOST) + GET_BOOL("OPOST", OPOST, c_oflag, ); +#endif + + /* + * We do not propagate the following modes: + * - Parity/serial settings, which are a local affair and don't + * make sense propagated over SSH's 8-bit byte-stream. + * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD + * - Things that want to be enabled in one place that we don't + * squash locally. + * IUCLC + * - Status bits. + * PENDIN + * - Things I don't know what to do with. (FIXME) + * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN + * INLCR IGNCR ICRNL + */ + +#undef GET_CHAR +#undef GET_BOOL + + /* Fall through to here for unrecognised names, or ones that are + * unsupported on this platform */ + return NULL; +} + +void cleanup_termios(void) +{ + if (local_tty) + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); +} + +bufchain stdout_data, stderr_data; +enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + +int try_output(int is_stderr) +{ + bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); + int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); + void *senddata; + int sendlen, ret; + + if (bufchain_size(chain) > 0) { + int prev_nonblock = nonblock(fd); + do { + bufchain_prefix(chain, &senddata, &sendlen); + ret = write(fd, senddata, sendlen); + if (ret > 0) + bufchain_consume(chain, ret); + } while (ret == sendlen && bufchain_size(chain) != 0); + if (!prev_nonblock) + no_nonblock(fd); + if (ret < 0 && errno != EAGAIN) { + perror(is_stderr ? "stderr: write" : "stdout: write"); + exit(1); + } + } + if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { + close(STDOUT_FILENO); + outgoingeof = EOF_SENT; + } + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); +} + +int from_backend(void *frontend_handle, int is_stderr, + const char *data, int len) +{ + if (is_stderr) { + bufchain_add(&stderr_data, data, len); + return try_output(TRUE); + } else { + assert(outgoingeof == EOF_NO); + bufchain_add(&stdout_data, data, len); + return try_output(FALSE); + } +} + +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} + +int from_backend_eof(void *frontend_handle) +{ + assert(outgoingeof == EOF_NO); + outgoingeof = EOF_PENDING; + try_output(FALSE); + return FALSE; /* do not respond to incoming EOF with outgoing */ +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +/* + * Handle data from a local tty in PARMRK format. + */ +static void from_tty(void *vbuf, unsigned len) +{ + char *p, *q, *end, *buf = vbuf; + static enum {NORMAL, FF, FF00} state = NORMAL; + + p = buf; end = buf + len; + while (p < end) { + switch (state) { + case NORMAL: + if (*p == '\xff') { + p++; + state = FF; + } else { + q = memchr(p, '\xff', end - p); + if (q == NULL) q = end; + back->send(backhandle, p, q - p); + p = q; + } + break; + case FF: + if (*p == '\xff') { + back->send(backhandle, p, 1); + p++; + state = NORMAL; + } else if (*p == '\0') { + p++; + state = FF00; + } else abort(); + break; + case FF00: + if (*p == '\0') { + back->special(backhandle, TS_BRK); + } else { + /* + * Pretend that PARMRK wasn't set. This involves + * faking what INPCK and IGNPAR would have done if + * we hadn't overridden them. Unfortunately, we + * can't do this entirely correctly because INPCK + * distinguishes between framing and parity + * errors, but PARMRK format represents both in + * the same way. We assume that parity errors are + * more common than framing errors, and hence + * treat all input errors as being subject to + * INPCK. + */ + if (orig_termios.c_iflag & INPCK) { + /* If IGNPAR is set, we throw away the character. */ + if (!(orig_termios.c_iflag & IGNPAR)) { + /* PE/FE get passed on as NUL. */ + *p = 0; + back->send(backhandle, p, 1); + } + } else { + /* INPCK not set. Assume we got a parity error. */ + back->send(backhandle, p, 1); + } + } + p++; + state = NORMAL; + } + } +} + +int signalpipe[2]; + +void sigwinch(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +/* + * In Plink our selects are synchronous, so these functions are + * empty stubs. + */ +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("Plink: command-line connection utility\n"); + printf("%s\n", ver); + printf("Usage: plink [options] [user@]host [command]\n"); + printf(" (\"host\" can also be a PuTTY saved session name)\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -ssh -telnet -rlogin -raw -serial\n"); + printf(" force use of a particular protocol\n"); + printf(" -P port connect to specified port\n"); + printf(" -l user connect with specified username\n"); + printf(" -batch disable all interactive prompts\n"); + printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); + printf(" Specify the serial configuration (serial only)\n"); + printf("The following options only apply to SSH connections:\n"); + printf(" -pw passw login with specified password\n"); + printf(" -D [listen-IP:]listen-port\n"); + printf(" Dynamic SOCKS-based port forwarding\n"); + printf(" -L [listen-IP:]listen-port:host:port\n"); + printf(" Forward local port to remote address\n"); + printf(" -R [listen-IP:]listen-port:host:port\n"); + printf(" Forward remote port to local address\n"); + printf(" -X -x enable / disable X11 forwarding\n"); + printf(" -A -a enable / disable agent forwarding\n"); + printf(" -t -T enable / disable pty allocation\n"); + printf(" -1 -2 force use of particular protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -hostkey aa:bb:cc:...\n"); + printf(" manually specify a host key (may be repeated)\n"); + printf(" -m file read remote command(s) from file\n"); + printf(" -s remote command is an SSH subsystem (SSH-2 only)\n"); + printf(" -N don't start a shell/command (SSH-2 only)\n"); + printf(" -nc host:port\n"); + printf(" open tunnel in place of session (SSH-2 only)\n"); + printf(" -sshlog file\n"); + printf(" -sshrawlog file\n"); + printf(" log protocol details to a file\n"); + exit(1); +} + +static void version(void) +{ + printf("plink: %s\n", ver); + exit(1); +} + +void frontend_net_error_pending(void) {} + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + +int main(int argc, char **argv) +{ + int sending; + int portnumber = -1; + int *fdlist; + int fd; + int i, fdcount, fdsize, fdstate; + int connopen; + int exitcode; + int errors; + int use_subsystem = 0; + int got_host = FALSE; + unsigned long now; + struct winsize size; + + fdlist = NULL; + fdcount = fdsize = 0; + /* + * Initialise port and protocol to sensible defaults. (These + * will be overridden by more or less anything.) + */ + default_protocol = PROT_SSH; + default_port = 22; + + bufchain_init(&stdout_data); + bufchain_init(&stderr_data); + outgoingeof = EOF_NO; + + flags = FLAG_STDERR | FLAG_STDERR_TTY; + + stderr_tty_init(); + /* + * Process the command line. + */ + conf = conf_new(); + do_defaults(NULL, conf); + loaded_session = FALSE; + default_protocol = conf_get_int(conf, CONF_protocol); + default_port = conf_get_int(conf, CONF_port); + errors = 0; + { + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + if (p) { + const Backend *b = backend_from_name(p); + if (b) { + default_protocol = b->protocol; + default_port = b->default_port; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); + } + } + } + while (--argc) { + char *p = *++argv; + if (*p == '-') { + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, conf); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = 1; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = 1; + } else if (!strcmp(p, "-s")) { + /* Save status to write to conf later. */ + use_subsystem = 1; + } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { + version(); + } else if (!strcmp(p, "--help")) { + usage(); + exit(0); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else if (!strcmp(p, "-o")) { + if (argc <= 1) { + fprintf(stderr, + "plink: option \"-o\" requires an argument\n"); + errors = 1; + } else { + --argc; + provide_xrm_string(*++argv); + } + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = 1; + } + } else if (*p) { + if (!conf_launchable(conf) || !(got_host || loaded_session)) { + char *q = p; + + /* + * If the hostname starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + if (!strncmp(q, "telnet:", 7)) { + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + conf_set_int(conf, CONF_protocol, PROT_TELNET); + p = q; + p += host_strcspn(p, ":/"); + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + conf_set_int(conf, CONF_port, atoi(p)); + else + conf_set_int(conf, CONF_port, -1); + conf_set_str(conf, CONF_host, q); + got_host = TRUE; + } else { + char *r, *user, *host; + /* + * Before we process the [user@]host string, we + * first check for the presence of a protocol + * prefix (a protocol name followed by ","). + */ + r = strchr(p, ','); + if (r) { + const Backend *b; + *r = '\0'; + b = backend_from_name(p); + if (b) { + default_protocol = b->protocol; + conf_set_int(conf, CONF_protocol, + default_protocol); + portnumber = b->default_port; + } + p = r + 1; + } + + /* + * A nonzero length string followed by an @ is treated + * as a username. (We discount an _initial_ @.) The + * rest of the string (or the whole string if no @) + * is treated as a session name and/or hostname. + */ + r = strrchr(p, '@'); + if (r == p) + p++, r = NULL; /* discount initial @ */ + if (r) { + *r++ = '\0'; + user = p, host = r; + } else { + user = NULL, host = p; + } + + /* + * Now attempt to load a saved session with the + * same name as the hostname. + */ + { + Conf *conf2 = conf_new(); + do_defaults(host, conf2); + if (loaded_session || !conf_launchable(conf2)) { + /* No settings for this host; use defaults */ + /* (or session was already loaded with -load) */ + conf_set_str(conf, CONF_host, host); + conf_set_int(conf, CONF_port, default_port); + got_host = TRUE; + } else { + conf_copy_into(conf, conf2); + loaded_session = TRUE; + } + conf_free(conf2); + } + + if (user) { + /* Patch in specified username. */ + conf_set_str(conf, CONF_username, user); + } + + } + } else { + char *command; + int cmdlen, cmdsize; + cmdlen = cmdsize = 0; + command = NULL; + + while (argc) { + while (*p) { + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=*p++; + } + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=' '; /* always add trailing space */ + if (--argc) p = *++argv; + } + if (cmdlen) command[--cmdlen]='\0'; + /* change trailing blank to NUL */ + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */ + + break; /* done with cmdline */ + } + } + } + + if (errors) + return 1; + + if (!conf_launchable(conf) || !(got_host || loaded_session)) { + usage(); + } + + /* + * Muck about with the hostname in various ways. + */ + { + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Trim a colon suffix off the hostname if it's there. In + * order to protect unbracketed IPv6 address literals + * against this treatment, we do not do this if there's + * _more_ than one colon. + */ + { + char *c = host_strchr(host, ':'); + + if (c) { + char *d = host_strchr(c+1, ':'); + if (!d) + *c = '\0'; + } + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); + } + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(conf); + + /* + * If we have no better ideas for the remote username, use the local + * one, as 'ssh' does. + */ + if (conf_get_str(conf, CONF_username)[0] == '\0') { + char *user = get_username(); + if (user) { + conf_set_str(conf, CONF_username, user); + sfree(user); + } + } + + /* + * Apply subsystem status. + */ + if (use_subsystem) + conf_set_int(conf, CONF_ssh_subsys, TRUE); + + if (!*conf_get_str(conf, CONF_remote_cmd) && + !*conf_get_str(conf, CONF_remote_cmd2) && + !*conf_get_str(conf, CONF_ssh_nc_host)) + flags |= FLAG_INTERACTIVE; + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + back = backend_from_proto(conf_get_int(conf, CONF_protocol)); + if (back == NULL) { + fprintf(stderr, + "Internal fault: Unsupported protocol found\n"); + return 1; + } + + /* + * Select port. + */ + if (portnumber != -1) + conf_set_int(conf, CONF_port, portnumber); + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); + + /* + * Set up the pipe we'll use to tell us about SIGWINCH. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + putty_signal(SIGWINCH, sigwinch); + + /* + * Now that we've got the SIGWINCH handler installed, try to find + * out the initial terminal size. + */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) { + conf_set_int(conf, CONF_width, size.ws_col); + conf_set_int(conf, CONF_height, size.ws_row); + } + + sk_init(); + uxsel_init(); + + /* + * Unix Plink doesn't provide any way to add forwardings after the + * connection is set up, so if there are none now, we can safely set + * the "simple" flag. + */ + if (conf_get_int(conf, CONF_protocol) == PROT_SSH && + !conf_get_int(conf, CONF_x11_forward) && + !conf_get_int(conf, CONF_agentfwd) && + !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) + conf_set_int(conf, CONF_ssh_simple, TRUE); + + /* + * Start up the connection. + */ + logctx = log_init(NULL, conf); + console_provide_logctx(logctx); + { + const char *error; + char *realhost; + /* nodelay is only useful if stdin is a terminal device */ + int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && isatty(0); + + error = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, nodelay, + conf_get_int(conf, CONF_tcp_keepalives)); + if (error) { + fprintf(stderr, "Unable to open connection:\n%s\n", error); + return 1; + } + back->provide_logctx(backhandle, logctx); + ldisc_create(conf, NULL, back, backhandle, NULL); + sfree(realhost); + } + connopen = 1; + + /* + * Set up the initial console mode. We don't care if this call + * fails, because we know we aren't necessarily running in a + * console. + */ + local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0); + atexit(cleanup_termios); + ldisc_update(NULL, 1, 1); + sending = FALSE; + now = GETTICKCOUNT(); + + while (1) { + fd_set rset, wset, xset; + int maxfd; + int rwx; + int ret; + unsigned long next; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; + + FD_SET_MAX(signalpipe[0], maxfd, rset); + + if (connopen && !sending && + back->connected(backhandle) && + back->sendok(backhandle) && + back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) { + /* If we're OK to send, then try to read from stdin. */ + FD_SET_MAX(STDIN_FILENO, maxfd, rset); + } + + if (bufchain_size(&stdout_data) > 0) { + /* If we have data for stdout, try to write to stdout. */ + FD_SET_MAX(STDOUT_FILENO, maxfd, wset); + } + + if (bufchain_size(&stderr_data) > 0) { + /* If we have data for stderr, try to write to stderr. */ + FD_SET_MAX(STDERR_FILENO, maxfd, wset); + } + + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; + + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } + + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + if (toplevel_callback_pending()) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd, &rset, &wset, &xset, &tv); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + struct timeval tv; + + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = select(maxfd, &rset, &wset, &xset, NULL); + } + + if (ret < 0) { + perror("select"); + exit(1); + } + + for (i = 0; i < fdcount; i++) { + fd = fdlist[i]; + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (FD_ISSET(fd, &xset)) + select_result(fd, 4); + if (FD_ISSET(fd, &rset)) + select_result(fd, 1); + if (FD_ISSET(fd, &wset)) + select_result(fd, 2); + } + + if (FD_ISSET(signalpipe[0], &rset)) { + char c[1]; + struct winsize size; + if (read(signalpipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) + back->size(backhandle, size.ws_col, size.ws_row); + } + + if (FD_ISSET(STDIN_FILENO, &rset)) { + char buf[4096]; + int ret; + + if (connopen && back->connected(backhandle)) { + ret = read(STDIN_FILENO, buf, sizeof(buf)); + if (ret < 0) { + perror("stdin: read"); + exit(1); + } else if (ret == 0) { + back->special(backhandle, TS_EOF); + sending = FALSE; /* send nothing further after this */ + } else { + if (local_tty) + from_tty(buf, ret); + else + back->send(backhandle, buf, ret); + } + } + } + + if (FD_ISSET(STDOUT_FILENO, &wset)) { + back->unthrottle(backhandle, try_output(FALSE)); + } + + if (FD_ISSET(STDERR_FILENO, &wset)) { + back->unthrottle(backhandle, try_output(TRUE)); + } + + run_toplevel_callbacks(); + + if ((!connopen || !back->connected(backhandle)) && + bufchain_size(&stdout_data) == 0 && + bufchain_size(&stderr_data) == 0) + break; /* we closed the connection */ + } + exitcode = back->exitcode(backhandle); + if (exitcode < 0) { + fprintf(stderr, "Remote process exit code unavailable\n"); + exitcode = 1; /* this is an error condition */ + } + cleanup_exit(exitcode); + return exitcode; /* shouldn't happen, but placates gcc */ +} diff --git a/netbox/libs/Putty/unix/uxprint.c b/netbox/libs/Putty/unix/uxprint.c new file mode 100644 index 000000000..372410072 --- /dev/null +++ b/netbox/libs/Putty/unix/uxprint.c @@ -0,0 +1,58 @@ +/* + * Printing interface for PuTTY. + */ + +#include +#include +#include "putty.h" + +struct printer_job_tag { + FILE *fp; +}; + +printer_job *printer_start_job(char *printer) +{ + printer_job *ret = snew(printer_job); + /* + * On Unix, we treat the printer string as the name of a + * command to pipe to - typically lpr, of course. + */ + ret->fp = popen(printer, "w"); + if (!ret->fp) { + sfree(ret); + ret = NULL; + } + return ret; +} + +void printer_job_data(printer_job *pj, void *data, int len) +{ + if (!pj) + return; + + if (fwrite(data, 1, len, pj->fp) < len) + /* ignore */; +} + +void printer_finish_job(printer_job *pj) +{ + if (!pj) + return; + + pclose(pj->fp); + sfree(pj); +} + +/* + * There's no sensible way to enumerate printers under Unix, since + * practically any valid Unix command is a valid printer :-) So + * these are useless stub functions, and uxcfg.c will disable the + * drop-down list in the printer configurer. + */ +printer_enum *printer_start_enum(int *nprinters_ptr) { + *nprinters_ptr = 0; + return NULL; +} +char *printer_get_name(printer_enum *pe, int i) { return NULL; +} +void printer_finish_enum(printer_enum *pe) { } diff --git a/netbox/libs/Putty/unix/uxproxy.c b/netbox/libs/Putty/unix/uxproxy.c new file mode 100644 index 000000000..6f1f793f6 --- /dev/null +++ b/netbox/libs/Putty/unix/uxproxy.c @@ -0,0 +1,325 @@ +/* + * uxproxy.c: Unix implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command. + */ + +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +typedef struct Socket_localproxy_tag * Local_Proxy_Socket; + +struct Socket_localproxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + int to_cmd, from_cmd; /* fds */ + + char *error; + + Plug plug; + + bufchain pending_output_data; + bufchain pending_input_data; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; +}; + +static int localproxy_select_result(int fd, int event); + +/* + * Trees to look up the pipe fds in. + */ +static tree234 *localproxy_by_fromfd, *localproxy_by_tofd; +static int localproxy_fromfd_cmp(void *av, void *bv) +{ + Local_Proxy_Socket a = (Local_Proxy_Socket)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a->from_cmd < b->from_cmd) + return -1; + if (a->from_cmd > b->from_cmd) + return +1; + return 0; +} +static int localproxy_fromfd_find(void *av, void *bv) +{ + int a = *(int *)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a < b->from_cmd) + return -1; + if (a > b->from_cmd) + return +1; + return 0; +} +static int localproxy_tofd_cmp(void *av, void *bv) +{ + Local_Proxy_Socket a = (Local_Proxy_Socket)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a->to_cmd < b->to_cmd) + return -1; + if (a->to_cmd > b->to_cmd) + return +1; + return 0; +} +static int localproxy_tofd_find(void *av, void *bv) +{ + int a = *(int *)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a < b->to_cmd) + return -1; + if (a > b->to_cmd) + return +1; + return 0; +} + +/* basic proxy socket functions */ + +static Plug sk_localproxy_plug (Socket s, Plug p) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_localproxy_close (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + if (ps->to_cmd >= 0) { + del234(localproxy_by_tofd, ps); + uxsel_del(ps->to_cmd); + close(ps->to_cmd); + } + + del234(localproxy_by_fromfd, ps); + uxsel_del(ps->from_cmd); + close(ps->from_cmd); + + bufchain_clear(&ps->pending_input_data); + bufchain_clear(&ps->pending_output_data); + sfree(ps); +} + +static int localproxy_try_send(Local_Proxy_Socket ps) +{ + int sent = 0; + + while (bufchain_size(&ps->pending_output_data) > 0) { + void *data; + int len, ret; + + bufchain_prefix(&ps->pending_output_data, &data, &len); + ret = write(ps->to_cmd, data, len); + if (ret < 0 && errno != EWOULDBLOCK) { + /* We're inside the Unix frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, strerror(errno)); + fatalbox("%s", strerror(errno)); + } else if (ret <= 0) { + break; + } else { + bufchain_consume(&ps->pending_output_data, ret); + sent += ret; + } + } + + if (ps->outgoingeof == EOF_PENDING) { + del234(localproxy_by_tofd, ps); + close(ps->to_cmd); + uxsel_del(ps->to_cmd); + ps->to_cmd = -1; + ps->outgoingeof = EOF_SENT; + } + + if (bufchain_size(&ps->pending_output_data) == 0) + uxsel_del(ps->to_cmd); + else + uxsel_set(ps->to_cmd, 2, localproxy_select_result); + + return sent; +} + +static int sk_localproxy_write (Socket s, const char *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + assert(ps->outgoingeof == EOF_NO); + + bufchain_add(&ps->pending_output_data, data, len); + + localproxy_try_send(ps); + + return bufchain_size(&ps->pending_output_data); +} + +static int sk_localproxy_write_oob (Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_localproxy_write(s, data, len); +} + +static void sk_localproxy_write_eof (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + assert(ps->outgoingeof == EOF_NO); + ps->outgoingeof = EOF_PENDING; + + localproxy_try_send(ps); +} + +static void sk_localproxy_flush (Socket s) +{ + /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ + /* do nothing */ +} + +static void sk_localproxy_set_frozen (Socket s, int is_frozen) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + if (is_frozen) + uxsel_del(ps->from_cmd); + else + uxsel_set(ps->from_cmd, 1, localproxy_select_result); +} + +static const char * sk_localproxy_socket_error (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->error; +} + +static int localproxy_select_result(int fd, int event) +{ + Local_Proxy_Socket s; + char buf[20480]; + int ret; + + if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) && + !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) ) + return 1; /* boggle */ + + if (event == 1) { + assert(fd == s->from_cmd); + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + return plug_closing(s->plug, strerror(errno), errno, 0); + } else if (ret == 0) { + return plug_closing(s->plug, NULL, 0, 0); + } else { + return plug_receive(s->plug, 0, buf, ret); + } + } else if (event == 2) { + assert(fd == s->to_cmd); + if (localproxy_try_send(s)) + plug_sent(s->plug, bufchain_size(&s->pending_output_data)); + return 1; + } + + return 1; +} + +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf) +{ + char *cmd; + + static const struct socket_function_table socket_fn_table = { + sk_localproxy_plug, + sk_localproxy_close, + sk_localproxy_write, + sk_localproxy_write_oob, + sk_localproxy_write_eof, + sk_localproxy_flush, + sk_localproxy_set_frozen, + sk_localproxy_socket_error, + NULL, /* peer_info */ + }; + + Local_Proxy_Socket ret; + int to_cmd_pipe[2], from_cmd_pipe[2], pid; + + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, conf); + + ret = snew(struct Socket_localproxy_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + ret->outgoingeof = EOF_NO; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + if (pipe(to_cmd_pipe) < 0 || + pipe(from_cmd_pipe) < 0) { + ret->error = dupprintf("pipe: %s", strerror(errno)); + sfree(cmd); + return (Socket)ret; + } + cloexec(to_cmd_pipe[1]); + cloexec(from_cmd_pipe[0]); + + pid = fork(); + + if (pid < 0) { + ret->error = dupprintf("fork: %s", strerror(errno)); + sfree(cmd); + return (Socket)ret; + } else if (pid == 0) { + close(0); + close(1); + dup2(to_cmd_pipe[0], 0); + dup2(from_cmd_pipe[1], 1); + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + noncloexec(0); + noncloexec(1); + execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); + _exit(255); + } + + sfree(cmd); + + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + + ret->to_cmd = to_cmd_pipe[1]; + ret->from_cmd = from_cmd_pipe[0]; + + if (!localproxy_by_fromfd) + localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp); + if (!localproxy_by_tofd) + localproxy_by_tofd = newtree234(localproxy_tofd_cmp); + + add234(localproxy_by_fromfd, ret); + add234(localproxy_by_tofd, ret); + + uxsel_set(ret->from_cmd, 1, localproxy_select_result); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + return (Socket) ret; +} diff --git a/netbox/libs/Putty/unix/uxpterm.c b/netbox/libs/Putty/unix/uxpterm.c new file mode 100644 index 000000000..1f4f20c2a --- /dev/null +++ b/netbox/libs/Putty/unix/uxpterm.c @@ -0,0 +1,60 @@ +/* + * pterm main program. + */ + +#include +#include + +#include "putty.h" + +const char *const appname = "pterm"; +const int use_event_log = 0; /* pterm doesn't need it */ +const int new_session = 0, saved_sessions = 0; /* or these */ +const int use_pty_argv = TRUE; + +Backend *select_backend(Conf *conf) +{ + return &pty_backend; +} + +int cfgbox(Conf *conf) +{ + /* + * This is a no-op in pterm, except that we'll ensure the + * protocol is set to -1 to inhibit the useless Connection + * panel in the config box. + */ + conf_set_int(conf, CONF_protocol, -1); + return 1; +} + +void cleanup_exit(int code) +{ + exit(code); +} + +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch) +{ + return 0; /* pterm doesn't have any. */ +} + +char *make_default_wintitle(char *hostname) +{ + return dupstr("pterm"); +} + +int main(int argc, char **argv) +{ + extern int pt_main(int argc, char **argv); + extern void pty_pre_init(void); /* declared in pty.c */ + int ret; + + cmdline_tooltype = TOOLTYPE_NONNETWORK; + default_protocol = -1; + + pty_pre_init(); + + ret = pt_main(argc, argv); + cleanup_exit(ret); + return ret; /* not reached, but placates optimisers */ +} diff --git a/netbox/libs/Putty/unix/uxpty.c b/netbox/libs/Putty/unix/uxpty.c new file mode 100644 index 000000000..e504b7050 --- /dev/null +++ b/netbox/libs/Putty/unix/uxpty.c @@ -0,0 +1,1193 @@ +/* + * Pseudo-tty backend for pterm. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#ifndef OMIT_UTMP +#include +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* updwtmpx() needs the name of the wtmp file. Try to find it. */ +#ifndef WTMPX_FILE +#ifdef _PATH_WTMPX +#define WTMPX_FILE _PATH_WTMPX +#else +#define WTMPX_FILE "/var/log/wtmpx" +#endif +#endif + +#ifndef LASTLOG_FILE +#ifdef _PATH_LASTLOG +#define LASTLOG_FILE _PATH_LASTLOG +#else +#define LASTLOG_FILE "/var/log/lastlog" +#endif +#endif + +/* + * Set up a default for vaguely sane systems. The idea is that if + * OMIT_UTMP is not defined, then at least one of the symbols which + * enable particular forms of utmp processing should be, if only so + * that a link error can warn you that you should have defined + * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is + * the only such symbol. + */ +#ifndef OMIT_UTMP +#if !defined HAVE_PUTUTLINE +#define HAVE_PUTUTLINE +#endif +#endif + +typedef struct pty_tag *Pty; + +/* + * The pty_signal_pipe, along with the SIGCHLD handler, must be + * process-global rather than session-specific. + */ +static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */ + +struct pty_tag { + Conf *conf; + int master_fd, slave_fd; + void *frontend; + char name[FILENAME_MAX]; + pid_t child_pid; + int term_width, term_height; + int child_dead, finished; + int exit_code; + bufchain output_data; +}; + +/* + * We store our pty backends in a tree sorted by master fd, so that + * when we get an uxsel notification we know which backend instance + * is the owner of the pty that caused it. + */ +static int pty_compare_by_fd(void *av, void *bv) +{ + Pty a = (Pty)av; + Pty b = (Pty)bv; + + if (a->master_fd < b->master_fd) + return -1; + else if (a->master_fd > b->master_fd) + return +1; + return 0; +} + +static int pty_find_by_fd(void *av, void *bv) +{ + int a = *(int *)av; + Pty b = (Pty)bv; + + if (a < b->master_fd) + return -1; + else if (a > b->master_fd) + return +1; + return 0; +} + +static tree234 *ptys_by_fd = NULL; + +/* + * We also have a tree sorted by child pid, so that when we wait() + * in response to the signal we know which backend instance is the + * owner of the process that caused the signal. + */ +static int pty_compare_by_pid(void *av, void *bv) +{ + Pty a = (Pty)av; + Pty b = (Pty)bv; + + if (a->child_pid < b->child_pid) + return -1; + else if (a->child_pid > b->child_pid) + return +1; + return 0; +} + +static int pty_find_by_pid(void *av, void *bv) +{ + pid_t a = *(pid_t *)av; + Pty b = (Pty)bv; + + if (a < b->child_pid) + return -1; + else if (a > b->child_pid) + return +1; + return 0; +} + +static tree234 *ptys_by_pid = NULL; + +/* + * If we are using pty_pre_init(), it will need to have already + * allocated a pty structure, which we must then return from + * pty_init() rather than allocating a new one. Here we store that + * structure between allocation and use. + * + * Note that although most of this module is entirely capable of + * handling multiple ptys in a single process, pty_pre_init() is + * fundamentally _dependent_ on there being at most one pty per + * process, so the normal static-data constraints don't apply. + * + * Likewise, since utmp is only used via pty_pre_init, it too must + * be single-instance, so we can declare utmp-related variables + * here. + */ +static Pty single_pty = NULL; + +#ifndef OMIT_UTMP +static pid_t pty_utmp_helper_pid = -1; +static int pty_utmp_helper_pipe = -1; +static int pty_stamped_utmp; +static struct utmpx utmp_entry; +#endif + +/* + * pty_argv is a grievous hack to allow a proper argv to be passed + * through from the Unix command line. Again, it doesn't really + * make sense outside a one-pty-per-process setup. + */ +char **pty_argv; + +static void pty_close(Pty pty); +static void pty_try_write(Pty pty); + +#ifndef OMIT_UTMP +static void setup_utmp(char *ttyname, char *location) +{ +#ifdef HAVE_LASTLOG + struct lastlog lastlog_entry; + FILE *lastlog; +#endif + struct passwd *pw; + struct timeval tv; + + pw = getpwuid(getuid()); + memset(&utmp_entry, 0, sizeof(utmp_entry)); + utmp_entry.ut_type = USER_PROCESS; + utmp_entry.ut_pid = getpid(); + strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line)); + strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id)); + strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user)); + strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host)); + /* + * Apparently there are some architectures where (struct + * utmpx).ut_tv is not essentially struct timeval (e.g. Linux + * amd64). Hence the temporary. + */ + gettimeofday(&tv, NULL); + utmp_entry.ut_tv.tv_sec = tv.tv_sec; + utmp_entry.ut_tv.tv_usec = tv.tv_usec; + + setutxent(); + pututxline(&utmp_entry); + endutxent(); + + updwtmpx(WTMPX_FILE, &utmp_entry); + +#ifdef HAVE_LASTLOG + memset(&lastlog_entry, 0, sizeof(lastlog_entry)); + strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line)); + strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host)); + time(&lastlog_entry.ll_time); + if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) { + fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET); + fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog); + fclose(lastlog); + } +#endif + + pty_stamped_utmp = 1; + +} + +static void cleanup_utmp(void) +{ + struct timeval tv; + + if (!pty_stamped_utmp) + return; + + utmp_entry.ut_type = DEAD_PROCESS; + memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user)); + gettimeofday(&tv, NULL); + utmp_entry.ut_tv.tv_sec = tv.tv_sec; + utmp_entry.ut_tv.tv_usec = tv.tv_usec; + + updwtmpx(WTMPX_FILE, &utmp_entry); + + memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line)); + utmp_entry.ut_tv.tv_sec = 0; + utmp_entry.ut_tv.tv_usec = 0; + + setutxent(); + pututxline(&utmp_entry); + endutxent(); + + pty_stamped_utmp = 0; /* ensure we never double-cleanup */ +} +#endif + +static void sigchld_handler(int signum) +{ + if (write(pty_signal_pipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +#ifndef OMIT_UTMP +static void fatal_sig_handler(int signum) +{ + putty_signal(signum, SIG_DFL); + cleanup_utmp(); + raise(signum); +} +#endif + +static int pty_open_slave(Pty pty) +{ + if (pty->slave_fd < 0) { + pty->slave_fd = open(pty->name, O_RDWR); + cloexec(pty->slave_fd); + } + + return pty->slave_fd; +} + +static void pty_open_master(Pty pty) +{ +#ifdef BSD_PTYS + const char chars1[] = "pqrstuvwxyz"; + const char chars2[] = "0123456789abcdef"; + const char *p1, *p2; + char master_name[20]; + struct group *gp; + + for (p1 = chars1; *p1; p1++) + for (p2 = chars2; *p2; p2++) { + sprintf(master_name, "/dev/pty%c%c", *p1, *p2); + pty->master_fd = open(master_name, O_RDWR); + if (pty->master_fd >= 0) { + if (geteuid() == 0 || + access(master_name, R_OK | W_OK) == 0) { + /* + * We must also check at this point that we are + * able to open the slave side of the pty. We + * wouldn't want to allocate the wrong master, + * get all the way down to forking, and _then_ + * find we're unable to open the slave. + */ + strcpy(pty->name, master_name); + pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */ + + cloexec(pty->master_fd); + + if (pty_open_slave(pty) >= 0 && + access(pty->name, R_OK | W_OK) == 0) + goto got_one; + if (pty->slave_fd > 0) + close(pty->slave_fd); + pty->slave_fd = -1; + } + close(pty->master_fd); + } + } + + /* If we get here, we couldn't get a tty at all. */ + fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n"); + exit(1); + + got_one: + + /* We need to chown/chmod the /dev/ttyXX device. */ + gp = getgrnam("tty"); + chown(pty->name, getuid(), gp ? gp->gr_gid : -1); + chmod(pty->name, 0600); +#else + + const int flags = O_RDWR +#ifdef O_NOCTTY + | O_NOCTTY +#endif + ; + +#ifdef HAVE_POSIX_OPENPT + pty->master_fd = posix_openpt(flags); + + if (pty->master_fd < 0) { + perror("posix_openpt"); + exit(1); + } +#else + pty->master_fd = open("/dev/ptmx", flags); + + if (pty->master_fd < 0) { + perror("/dev/ptmx: open"); + exit(1); + } +#endif + + if (grantpt(pty->master_fd) < 0) { + perror("grantpt"); + exit(1); + } + + if (unlockpt(pty->master_fd) < 0) { + perror("unlockpt"); + exit(1); + } + + cloexec(pty->master_fd); + + pty->name[FILENAME_MAX-1] = '\0'; + strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1); +#endif + + nonblock(pty->master_fd); + + if (!ptys_by_fd) + ptys_by_fd = newtree234(pty_compare_by_fd); + add234(ptys_by_fd, pty); +} + +/* + * Pre-initialisation. This is here to get around the fact that GTK + * doesn't like being run in setuid/setgid programs (probably + * sensibly). So before we initialise GTK - and therefore before we + * even process the command line - we check to see if we're running + * set[ug]id. If so, we open our pty master _now_, chown it as + * necessary, and drop privileges. We can always close it again + * later. If we're potentially going to be doing utmp as well, we + * also fork off a utmp helper process and communicate with it by + * means of a pipe; the utmp helper will keep privileges in order + * to clean up utmp when we exit (i.e. when its end of our pipe + * closes). + */ +void pty_pre_init(void) +{ + Pty pty; + +#ifndef OMIT_UTMP + pid_t pid; + int pipefd[2]; +#endif + + pty = single_pty = snew(struct pty_tag); + pty->conf = NULL; + bufchain_init(&pty->output_data); + + /* set the child signal handler straight away; it needs to be set + * before we ever fork. */ + putty_signal(SIGCHLD, sigchld_handler); + pty->master_fd = pty->slave_fd = -1; +#ifndef OMIT_UTMP + pty_stamped_utmp = FALSE; +#endif + + if (geteuid() != getuid() || getegid() != getgid()) { + pty_open_master(pty); + +#ifndef OMIT_UTMP + /* + * Fork off the utmp helper. + */ + if (pipe(pipefd) < 0) { + perror("pterm: pipe"); + exit(1); + } + cloexec(pipefd[0]); + cloexec(pipefd[1]); + pid = fork(); + if (pid < 0) { + perror("pterm: fork"); + exit(1); + } else if (pid == 0) { + char display[128], buffer[128]; + int dlen, ret; + + close(pipefd[1]); + /* + * Now sit here until we receive a display name from the + * other end of the pipe, and then stamp utmp. Unstamp utmp + * again, and exit, when the pipe closes. + */ + + dlen = 0; + while (1) { + + ret = read(pipefd[0], buffer, lenof(buffer)); + if (ret <= 0) { + cleanup_utmp(); + _exit(0); + } else if (!pty_stamped_utmp) { + if (dlen < lenof(display)) + memcpy(display+dlen, buffer, + min(ret, lenof(display)-dlen)); + if (buffer[ret-1] == '\0') { + /* + * Now we have a display name. NUL-terminate + * it, and stamp utmp. + */ + display[lenof(display)-1] = '\0'; + /* + * Trap as many fatal signals as we can in the + * hope of having the best possible chance to + * clean up utmp before termination. We are + * unfortunately unprotected against SIGKILL, + * but that's life. + */ + putty_signal(SIGHUP, fatal_sig_handler); + putty_signal(SIGINT, fatal_sig_handler); + putty_signal(SIGQUIT, fatal_sig_handler); + putty_signal(SIGILL, fatal_sig_handler); + putty_signal(SIGABRT, fatal_sig_handler); + putty_signal(SIGFPE, fatal_sig_handler); + putty_signal(SIGPIPE, fatal_sig_handler); + putty_signal(SIGALRM, fatal_sig_handler); + putty_signal(SIGTERM, fatal_sig_handler); + putty_signal(SIGSEGV, fatal_sig_handler); + putty_signal(SIGUSR1, fatal_sig_handler); + putty_signal(SIGUSR2, fatal_sig_handler); +#ifdef SIGBUS + putty_signal(SIGBUS, fatal_sig_handler); +#endif +#ifdef SIGPOLL + putty_signal(SIGPOLL, fatal_sig_handler); +#endif +#ifdef SIGPROF + putty_signal(SIGPROF, fatal_sig_handler); +#endif +#ifdef SIGSYS + putty_signal(SIGSYS, fatal_sig_handler); +#endif +#ifdef SIGTRAP + putty_signal(SIGTRAP, fatal_sig_handler); +#endif +#ifdef SIGVTALRM + putty_signal(SIGVTALRM, fatal_sig_handler); +#endif +#ifdef SIGXCPU + putty_signal(SIGXCPU, fatal_sig_handler); +#endif +#ifdef SIGXFSZ + putty_signal(SIGXFSZ, fatal_sig_handler); +#endif +#ifdef SIGIO + putty_signal(SIGIO, fatal_sig_handler); +#endif + setup_utmp(pty->name, display); + } + } + } + } else { + close(pipefd[0]); + pty_utmp_helper_pid = pid; + pty_utmp_helper_pipe = pipefd[1]; + } +#endif + } + + /* Drop privs. */ + { +#ifndef HAVE_NO_SETRESUID + int gid = getgid(), uid = getuid(); + int setresgid(gid_t, gid_t, gid_t); + int setresuid(uid_t, uid_t, uid_t); + if (setresgid(gid, gid, gid) < 0) { + perror("setresgid"); + exit(1); + } + if (setresuid(uid, uid, uid) < 0) { + perror("setresuid"); + exit(1); + } +#else + if (setgid(getgid()) < 0) { + perror("setgid"); + exit(1); + } + if (setuid(getuid()) < 0) { + perror("setuid"); + exit(1); + } +#endif + } +} + +int pty_real_select_result(Pty pty, int event, int status) +{ + char buf[4096]; + int ret; + int finished = FALSE; + + if (event < 0) { + /* + * We've been called because our child process did + * something. `status' tells us what. + */ + if ((WIFEXITED(status) || WIFSIGNALED(status))) { + /* + * The primary child process died. We could keep + * the terminal open for remaining subprocesses to + * output to, but conventional wisdom seems to feel + * that that's the Wrong Thing for an xterm-alike, + * so we bail out now (though we don't necessarily + * _close_ the window, depending on the state of + * Close On Exit). This would be easy enough to + * change or make configurable if necessary. + */ + pty->exit_code = status; + pty->child_dead = TRUE; + del234(ptys_by_pid, pty); + finished = TRUE; + } + } else { + if (event == 1) { + + ret = read(pty->master_fd, buf, sizeof(buf)); + + /* + * Clean termination condition is that either ret == 0, or ret + * < 0 and errno == EIO. Not sure why the latter, but it seems + * to happen. Boo. + */ + if (ret == 0 || (ret < 0 && errno == EIO)) { + /* + * We assume a clean exit if the pty has closed but the + * actual child process hasn't. The only way I can + * imagine this happening is if it detaches itself from + * the pty and goes daemonic - in which case the + * expected usage model would precisely _not_ be for + * the pterm window to hang around! + */ + finished = TRUE; + if (!pty->child_dead) + pty->exit_code = 0; + } else if (ret < 0) { + perror("read pty master"); + exit(1); + } else if (ret > 0) { + from_backend(pty->frontend, 0, buf, ret); + } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + pty_try_write(pty); + } + } + + if (finished && !pty->finished) { + int close_on_exit; + + uxsel_del(pty->master_fd); + pty_close(pty); + pty->master_fd = -1; + + pty->finished = TRUE; + + /* + * This is a slight layering-violation sort of hack: only + * if we're not closing on exit (COE is set to Never, or to + * Only On Clean and it wasn't a clean exit) do we output a + * `terminated' message. + */ + close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_OFF || + (close_on_exit == AUTO && pty->exit_code != 0)) { + char message[512]; + message[0] = '\0'; + if (WIFEXITED(pty->exit_code)) + sprintf(message, "\r\n[pterm: process terminated with exit" + " code %d]\r\n", WEXITSTATUS(pty->exit_code)); + else if (WIFSIGNALED(pty->exit_code)) +#ifdef HAVE_NO_STRSIGNAL + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d]\r\n", WTERMSIG(pty->exit_code)); +#else + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d (%.400s)]\r\n", WTERMSIG(pty->exit_code), + strsignal(WTERMSIG(pty->exit_code))); +#endif + from_backend(pty->frontend, 0, message, strlen(message)); + } + + notify_remote_exit(pty->frontend); + } + + return !finished; +} + +int pty_select_result(int fd, int event) +{ + int ret = TRUE; + Pty pty; + + if (fd == pty_signal_pipe[0]) { + pid_t pid; + int status; + char c[1]; + + if (read(pty_signal_pipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + + do { + pid = waitpid(-1, &status, WNOHANG); + + pty = find234(ptys_by_pid, &pid, pty_find_by_pid); + + if (pty) + ret = ret && pty_real_select_result(pty, -1, status); + } while (pid > 0); + } else { + pty = find234(ptys_by_fd, &fd, pty_find_by_fd); + + if (pty) + ret = ret && pty_real_select_result(pty, event, 0); + } + + return ret; +} + +static void pty_uxsel_setup(Pty pty) +{ + int rwx; + + rwx = 1; /* always want to read from pty */ + if (bufchain_size(&pty->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(pty->master_fd, rwx, pty_select_result); + + /* + * In principle this only needs calling once for all pty + * backend instances, but it's simplest just to call it every + * time; uxsel won't mind. + */ + uxsel_set(pty_signal_pipe[0], 1, pty_select_result); +} + +/* + * Called to set up the pty. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + int slavefd; + pid_t pid, pgrp; +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + long windowid; +#endif + Pty pty; + + if (single_pty) { + pty = single_pty; + assert(pty->conf == NULL); + } else { + pty = snew(struct pty_tag); + pty->master_fd = pty->slave_fd = -1; +#ifndef OMIT_UTMP + pty_stamped_utmp = FALSE; +#endif + } + + pty->frontend = frontend; + *backend_handle = NULL; /* we can't sensibly use this, sadly */ + + pty->conf = conf_copy(conf); + pty->term_width = conf_get_int(conf, CONF_width); + pty->term_height = conf_get_int(conf, CONF_height); + + if (pty->master_fd < 0) + pty_open_master(pty); + + /* + * Set up configuration-dependent termios settings on the new pty. + */ + { + struct termios attrs; + tcgetattr(pty->master_fd, &attrs); + + /* + * Set the backspace character to be whichever of ^H and ^? is + * specified by bksp_is_delete. + */ + attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete) + ? '\177' : '\010'; + + /* + * Set the IUTF8 bit iff the character set is UTF-8. + */ +#ifdef IUTF8 + if (frontend_is_utf8(frontend)) + attrs.c_iflag |= IUTF8; + else + attrs.c_iflag &= ~IUTF8; +#endif + + tcsetattr(pty->master_fd, TCSANOW, &attrs); + } + +#ifndef OMIT_UTMP + /* + * Stamp utmp (that is, tell the utmp helper process to do so), + * or not. + */ + if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */ + if (!conf_get_int(conf, CONF_stamp_utmp)) { + close(pty_utmp_helper_pipe); /* just let the child process die */ + pty_utmp_helper_pipe = -1; + } else { + char *location = get_x_display(pty->frontend); + int len = strlen(location)+1, pos = 0; /* +1 to include NUL */ + while (pos < len) { + int ret = write(pty_utmp_helper_pipe, location+pos, len - pos); + if (ret < 0) { + perror("pterm: writing to utmp helper process"); + close(pty_utmp_helper_pipe); /* arrgh, just give up */ + pty_utmp_helper_pipe = -1; + break; + } + pos += ret; + } + } + } +#endif + +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + windowid = get_windowid(pty->frontend); +#endif + + /* + * Fork and execute the command. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + + if (pid == 0) { + /* + * We are the child. + */ + + slavefd = pty_open_slave(pty); + if (slavefd < 0) { + perror("slave pty: open"); + _exit(1); + } + + close(pty->master_fd); + noncloexec(slavefd); + dup2(slavefd, 0); + dup2(slavefd, 1); + dup2(slavefd, 2); + close(slavefd); + setsid(); +#ifdef TIOCSCTTY + ioctl(0, TIOCSCTTY, 1); +#endif + pgrp = getpid(); + tcsetpgrp(0, pgrp); + setpgid(pgrp, pgrp); + { + int ptyfd = open(pty->name, O_WRONLY, 0); + if (ptyfd >= 0) + close(ptyfd); + } + setpgid(pgrp, pgrp); + { + char *term_env_var = dupprintf("TERM=%s", + conf_get_str(conf, CONF_termtype)); + putenv(term_env_var); + /* We mustn't free term_env_var, as putenv links it into the + * environment in place. + */ + } +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + { + char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid); + putenv(windowid_env_var); + /* We mustn't free windowid_env_var, as putenv links it into the + * environment in place. + */ + } + { + /* + * In case we were invoked with a --display argument that + * doesn't match DISPLAY in our actual environment, we + * should set DISPLAY for processes running inside the + * terminal to match the display the terminal itself is + * on. + */ + const char *x_display = get_x_display(pty->frontend); + char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); + putenv(x_display_env_var); + /* As above, we don't free this. */ + } +#endif + { + char *key, *val; + + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *varval = dupcat(key, "=", val, NULL); + putenv(varval); + /* + * We must not free varval, since putenv links it + * into the environment _in place_. Weird, but + * there we go. Memory usage will be rationalised + * as soon as we exec anyway. + */ + } + } + + /* + * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by + * our parent, particularly by things like sh -c 'pterm &' and + * some window or session managers. SIGCHLD, meanwhile, was + * blocked during pt_main() startup. Reverse all this for our + * child process. + */ + putty_signal(SIGINT, SIG_DFL); + putty_signal(SIGQUIT, SIG_DFL); + putty_signal(SIGPIPE, SIG_DFL); + block_signal(SIGCHLD, 0); + if (pty_argv) { + /* + * Exec the exact argument list we were given. + */ + execvp(pty_argv[0], pty_argv); + /* + * If that fails, and if we had exactly one argument, pass + * that argument to $SHELL -c. + * + * This arranges that we can _either_ follow 'pterm -e' + * with a list of argv elements to be fed directly to + * exec, _or_ with a single argument containing a command + * to be parsed by a shell (but, in cases of doubt, the + * former is more reliable). + * + * A quick survey of other terminal emulators' -e options + * (as of Debian squeeze) suggests that: + * + * - xterm supports both modes, more or less like this + * - gnome-terminal will only accept a one-string shell command + * - Eterm, kterm and rxvt will only accept a list of + * argv elements (as did older versions of pterm). + * + * It therefore seems important to support both usage + * modes in order to be a drop-in replacement for either + * xterm or gnome-terminal, and hence for anyone's + * plausible uses of the Debian-style alias + * 'x-terminal-emulator'... + */ + if (pty_argv[1] == NULL) { + char *shell = getenv("SHELL"); + if (shell) + execl(shell, shell, "-c", pty_argv[0], (void *)NULL); + } + } else { + char *shell = getenv("SHELL"); + char *shellname; + if (conf_get_int(conf, CONF_login_shell)) { + char *p = strrchr(shell, '/'); + shellname = snewn(2+strlen(shell), char); + p = p ? p+1 : shell; + sprintf(shellname, "-%s", p); + } else + shellname = shell; + execl(getenv("SHELL"), shellname, (void *)NULL); + } + + /* + * If we're here, exec has gone badly foom. + */ + perror("exec"); + _exit(127); + } else { + pty->child_pid = pid; + pty->child_dead = FALSE; + pty->finished = FALSE; + if (pty->slave_fd > 0) + close(pty->slave_fd); + if (!ptys_by_pid) + ptys_by_pid = newtree234(pty_compare_by_pid); + add234(ptys_by_pid, pty); + } + + if (pty_signal_pipe[0] < 0) { + if (pipe(pty_signal_pipe) < 0) { + perror("pipe"); + exit(1); + } + cloexec(pty_signal_pipe[0]); + cloexec(pty_signal_pipe[1]); + } + pty_uxsel_setup(pty); + + *backend_handle = pty; + + *realhost = dupstr(""); + + return NULL; +} + +static void pty_reconfig(void *handle, Conf *conf) +{ + Pty pty = (Pty)handle; + /* + * We don't have much need to reconfigure this backend, but + * unfortunately we do need to pick up the setting of Close On + * Exit so we know whether to give a `terminated' message. + */ + conf_copy_into(pty->conf, conf); +} + +/* + * Stub routine (never called in pterm). + */ +static void pty_free(void *handle) +{ + Pty pty = (Pty)handle; + + /* Either of these may fail `not found'. That's fine with us. */ + del234(ptys_by_pid, pty); + del234(ptys_by_fd, pty); + + conf_free(pty->conf); + pty->conf = NULL; + + if (pty == single_pty) { + /* + * Leave this structure around in case we need to Restart + * Session. + */ + } else { + sfree(pty); + } +} + +static void pty_try_write(Pty pty) +{ + void *data; + int len, ret; + + assert(pty->master_fd >= 0); + + while (bufchain_size(&pty->output_data) > 0) { + bufchain_prefix(&pty->output_data, &data, &len); + ret = write(pty->master_fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write pty master"); + exit(1); + } + bufchain_consume(&pty->output_data, ret); + } + + pty_uxsel_setup(pty); +} + +/* + * Called to send data down the pty. + */ +static int pty_send(void *handle, char *buf, int len) +{ + Pty pty = (Pty)handle; + + if (pty->master_fd < 0) + return 0; /* ignore all writes if fd closed */ + + bufchain_add(&pty->output_data, buf, len); + pty_try_write(pty); + + return bufchain_size(&pty->output_data); +} + +static void pty_close(Pty pty) +{ + if (pty->master_fd >= 0) { + close(pty->master_fd); + pty->master_fd = -1; + } +#ifndef OMIT_UTMP + if (pty_utmp_helper_pipe >= 0) { + close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */ + pty_utmp_helper_pipe = -1; + } +#endif +} + +/* + * Called to query the current socket sendability status. + */ +static int pty_sendbuffer(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 0; +} + +/* + * Called to set the size of the window + */ +static void pty_size(void *handle, int width, int height) +{ + Pty pty = (Pty)handle; + struct winsize size; + + pty->term_width = width; + pty->term_height = height; + + size.ws_row = (unsigned short)pty->term_height; + size.ws_col = (unsigned short)pty->term_width; + size.ws_xpixel = (unsigned short) pty->term_width * + font_dimension(pty->frontend, 0); + size.ws_ypixel = (unsigned short) pty->term_height * + font_dimension(pty->frontend, 1); + ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size); + return; +} + +/* + * Send special codes. + */ +static void pty_special(void *handle, Telnet_Special code) +{ + /* Pty pty = (Pty)handle; */ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *pty_get_specials(void *handle) +{ + /* Pty pty = (Pty)handle; */ + /* + * Hmm. When I get round to having this actually usable, it + * might be quite nice to have the ability to deliver a few + * well chosen signals to the child process - SIGINT, SIGTERM, + * SIGKILL at least. + */ + return NULL; +} + +static int pty_connected(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return TRUE; +} + +static int pty_sendok(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 1; +} + +static void pty_unthrottle(void *handle, int backlog) +{ + /* Pty pty = (Pty)handle; */ + /* do nothing */ +} + +static int pty_ldisc(void *handle, int option) +{ + /* Pty pty = (Pty)handle; */ + return 0; /* neither editing nor echoing */ +} + +static void pty_provide_ldisc(void *handle, void *ldisc) +{ + /* Pty pty = (Pty)handle; */ + /* This is a stub. */ +} + +static void pty_provide_logctx(void *handle, void *logctx) +{ + /* Pty pty = (Pty)handle; */ + /* This is a stub. */ +} + +static int pty_exitcode(void *handle) +{ + Pty pty = (Pty)handle; + if (!pty->finished) + return -1; /* not dead yet */ + else + return pty->exit_code; +} + +static int pty_cfg_info(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 0; +} + +Backend pty_backend = { + pty_init, + pty_free, + pty_reconfig, + pty_send, + pty_sendbuffer, + pty_size, + pty_special, + pty_get_specials, + pty_connected, + pty_exitcode, + pty_sendok, + pty_ldisc, + pty_provide_ldisc, + pty_provide_logctx, + pty_unthrottle, + pty_cfg_info, + "pty", + -1, + 0 +}; diff --git a/netbox/libs/Putty/unix/uxputty.c b/netbox/libs/Putty/unix/uxputty.c new file mode 100644 index 000000000..c7b0fcb22 --- /dev/null +++ b/netbox/libs/Putty/unix/uxputty.c @@ -0,0 +1,145 @@ +/* + * Unix PuTTY main program. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" + +/* + * Stubs to avoid uxpty.c needing to be linked in. + */ +const int use_pty_argv = FALSE; +char **pty_argv; /* never used */ + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +Backend *select_backend(Conf *conf) +{ + Backend *back = backend_from_proto(conf_get_int(conf, CONF_protocol)); + assert(back != NULL); + return back; +} + +int cfgbox(Conf *conf) +{ + char *title = dupcat(appname, " Configuration", NULL); + int ret = do_config_box(title, conf, 0, 0); + sfree(title); + return ret; +} + +static int got_host = 0; + +const int use_event_log = 1, new_session = 1, saved_sessions = 1; + +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch) +{ + char *p, *q = arg; + + if (got_host) { + /* + * If we already have a host name, treat this argument as a + * port number. NB we have to treat this as a saved -P + * argument, so that it will be deferred until it's a good + * moment to run it. + */ + int ret = cmdline_process_param("-P", arg, 1, conf); + assert(ret == 2); + } else if (!strncmp(q, "telnet:", 7)) { + /* + * If the hostname starts with "telnet:", + * set the protocol to Telnet and process + * the string as a Telnet URL. + */ + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + conf_set_int(conf, CONF_protocol, PROT_TELNET); + p = q; + p += host_strcspn(p, ":/"); + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + conf_set_int(conf, CONF_port, atoi(p)); + else + conf_set_int(conf, CONF_port, -1); + conf_set_str(conf, CONF_host, q); + got_host = 1; + } else { + /* + * Otherwise, treat this argument as a host name. + */ + p = arg; + while (*p && !isspace((unsigned char)*p)) + p++; + if (*p) + *p++ = '\0'; + conf_set_str(conf, CONF_host, q); + got_host = 1; + } + if (got_host) + *allow_launch = TRUE; + return 1; +} + +char *make_default_wintitle(char *hostname) +{ + return dupcat(hostname, " - ", appname, NULL); +} + +/* + * X11-forwarding-related things suitable for Gtk app. + */ + +char *platform_get_x_display(void) { + const char *display; + /* Try to take account of --display and what have you. */ + if (!(display = gdk_get_display())) + /* fall back to traditional method */ + display = getenv("DISPLAY"); + return dupstr(display); +} + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + +int main(int argc, char **argv) +{ + extern int pt_main(int argc, char **argv); + int ret; + + sk_init(); + flags = FLAG_VERBOSE | FLAG_INTERACTIVE; + default_protocol = be_default_protocol; + /* Find the appropriate default port. */ + { + Backend *b = backend_from_proto(default_protocol); + default_port = 0; /* illegal */ + if (b) + default_port = b->default_port; + } + ret = pt_main(argc, argv); + cleanup_exit(ret); + return ret; /* not reached, but placates optimisers */ +} diff --git a/netbox/libs/Putty/unix/uxsel.c b/netbox/libs/Putty/unix/uxsel.c new file mode 100644 index 000000000..e2979c9a0 --- /dev/null +++ b/netbox/libs/Putty/unix/uxsel.c @@ -0,0 +1,123 @@ +/* + * uxsel.c + * + * This module is a sort of all-purpose interchange for file + * descriptors. At one end it talks to uxnet.c and pty.c and + * anything else which might have one or more fds that need + * select()-type things doing to them during an extended program + * run; at the other end it talks to pterm.c or uxplink.c or + * anything else which might have its own means of actually doing + * those select()-type things. + */ + +#include + +#include "putty.h" +#include "tree234.h" + +struct fd { + int fd; + int rwx; /* 4=except 2=write 1=read */ + uxsel_callback_fn callback; + int id; /* for uxsel_input_remove */ +}; + +static tree234 *fds; + +static int uxsel_fd_cmp(void *av, void *bv) +{ + struct fd *a = (struct fd *)av; + struct fd *b = (struct fd *)bv; + if (a->fd < b->fd) + return -1; + if (a->fd > b->fd) + return +1; + return 0; +} +static int uxsel_fd_findcmp(void *av, void *bv) +{ + int *a = (int *)av; + struct fd *b = (struct fd *)bv; + if (*a < b->fd) + return -1; + if (*a > b->fd) + return +1; + return 0; +} + +void uxsel_init(void) +{ + fds = newtree234(uxsel_fd_cmp); +} + +/* + * Here is the interface to fd-supplying modules. They supply an + * fd, a set of read/write/execute states, and a callback function + * for when the fd satisfies one of those states. Repeated calls to + * uxsel_set on the same fd are perfectly legal and serve to change + * the rwx state (typically you only want to select an fd for + * writing when you actually have pending data you want to write to + * it!). + */ + +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) +{ + struct fd *newfd; + + uxsel_del(fd); + + if (rwx) { + newfd = snew(struct fd); + newfd->fd = fd; + newfd->rwx = rwx; + newfd->callback = callback; + newfd->id = uxsel_input_add(fd, rwx); + add234(fds, newfd); + } +} + +void uxsel_del(int fd) +{ + struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp); + if (oldfd) { + uxsel_input_remove(oldfd->id); + del234(fds, oldfd); + sfree(oldfd); + } +} + +/* + * And here is the interface to select-functionality-supplying + * modules. + */ + +int next_fd(int *state, int *rwx) +{ + struct fd *fd; + fd = index234(fds, (*state)++); + if (fd) { + *rwx = fd->rwx; + return fd->fd; + } else + return -1; +} + +int first_fd(int *state, int *rwx) +{ + *state = 0; + return next_fd(state, rwx); +} + +int select_result(int fd, int event) +{ + struct fd *fdstruct = find234(fds, &fd, uxsel_fd_findcmp); + /* + * Apparently this can sometimes be NULL. Can't see how, but I + * assume it means I need to ignore the event since it's on an + * fd I've stopped being interested in. Sigh. + */ + if (fdstruct) + return fdstruct->callback(fd, event); + else + return 1; +} diff --git a/netbox/libs/Putty/unix/uxser.c b/netbox/libs/Putty/unix/uxser.c new file mode 100644 index 000000000..e45f3ae12 --- /dev/null +++ b/netbox/libs/Putty/unix/uxser.c @@ -0,0 +1,597 @@ +/* + * Serial back end (Unix-specific). + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct serial_backend_data { + void *frontend; + int fd; + int finished; + int inbufsize; + bufchain output_data; +} *Serial; + +/* + * We store our serial backends in a tree sorted by fd, so that + * when we get an uxsel notification we know which backend instance + * is the owner of the serial port that caused it. + */ +static int serial_compare_by_fd(void *av, void *bv) +{ + Serial a = (Serial)av; + Serial b = (Serial)bv; + + if (a->fd < b->fd) + return -1; + else if (a->fd > b->fd) + return +1; + return 0; +} + +static int serial_find_by_fd(void *av, void *bv) +{ + int a = *(int *)av; + Serial b = (Serial)bv; + + if (a < b->fd) + return -1; + else if (a > b->fd) + return +1; + return 0; +} + +static tree234 *serial_by_fd = NULL; + +static int serial_select_result(int fd, int event); +static void serial_uxsel_setup(Serial serial); +static void serial_try_write(Serial serial); + +static const char *serial_configure(Serial serial, Conf *conf) +{ + struct termios options; + int bflag, bval, speed, flow, parity; + const char *str; + char *msg; + + if (serial->fd < 0) + return "Unable to reconfigure already-closed serial connection"; + + tcgetattr(serial->fd, &options); + + /* + * Find the appropriate baud rate flag. + */ + speed = conf_get_int(conf, CONF_serspeed); +#define SETBAUD(x) (bflag = B ## x, bval = x) +#define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0) + SETBAUD(50); +#ifdef B75 + CHECKBAUD(75); +#endif +#ifdef B110 + CHECKBAUD(110); +#endif +#ifdef B134 + CHECKBAUD(134); +#endif +#ifdef B150 + CHECKBAUD(150); +#endif +#ifdef B200 + CHECKBAUD(200); +#endif +#ifdef B300 + CHECKBAUD(300); +#endif +#ifdef B600 + CHECKBAUD(600); +#endif +#ifdef B1200 + CHECKBAUD(1200); +#endif +#ifdef B1800 + CHECKBAUD(1800); +#endif +#ifdef B2400 + CHECKBAUD(2400); +#endif +#ifdef B4800 + CHECKBAUD(4800); +#endif +#ifdef B9600 + CHECKBAUD(9600); +#endif +#ifdef B19200 + CHECKBAUD(19200); +#endif +#ifdef B38400 + CHECKBAUD(38400); +#endif +#ifdef B57600 + CHECKBAUD(57600); +#endif +#ifdef B76800 + CHECKBAUD(76800); +#endif +#ifdef B115200 + CHECKBAUD(115200); +#endif +#ifdef B153600 + CHECKBAUD(153600); +#endif +#ifdef B230400 + CHECKBAUD(230400); +#endif +#ifdef B307200 + CHECKBAUD(307200); +#endif +#ifdef B460800 + CHECKBAUD(460800); +#endif +#ifdef B500000 + CHECKBAUD(500000); +#endif +#ifdef B576000 + CHECKBAUD(576000); +#endif +#ifdef B921600 + CHECKBAUD(921600); +#endif +#ifdef B1000000 + CHECKBAUD(1000000); +#endif +#ifdef B1152000 + CHECKBAUD(1152000); +#endif +#ifdef B1500000 + CHECKBAUD(1500000); +#endif +#ifdef B2000000 + CHECKBAUD(2000000); +#endif +#ifdef B2500000 + CHECKBAUD(2500000); +#endif +#ifdef B3000000 + CHECKBAUD(3000000); +#endif +#ifdef B3500000 + CHECKBAUD(3500000); +#endif +#ifdef B4000000 + CHECKBAUD(4000000); +#endif +#undef CHECKBAUD +#undef SETBAUD + cfsetispeed(&options, bflag); + cfsetospeed(&options, bflag); + msg = dupprintf("Configuring baud rate %d", bval); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag &= ~CSIZE; + switch (conf_get_int(conf, CONF_serdatabits)) { + case 5: options.c_cflag |= CS5; break; + case 6: options.c_cflag |= CS6; break; + case 7: options.c_cflag |= CS7; break; + case 8: options.c_cflag |= CS8; break; + default: return "Invalid number of data bits (need 5, 6, 7 or 8)"; + } + msg = dupprintf("Configuring %d data bits", + conf_get_int(conf, CONF_serdatabits)); + logevent(serial->frontend, msg); + sfree(msg); + + if (conf_get_int(conf, CONF_serstopbits) >= 4) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } + msg = dupprintf("Configuring %d stop bits", + (options.c_cflag & CSTOPB ? 2 : 1)); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_iflag &= ~(IXON|IXOFF); +#ifdef CRTSCTS + options.c_cflag &= ~CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag &= ~CNEW_RTSCTS; +#endif + flow = conf_get_int(conf, CONF_serflow); + if (flow == SER_FLOW_XONXOFF) { + options.c_iflag |= IXON | IXOFF; + str = "XON/XOFF"; + } else if (flow == SER_FLOW_RTSCTS) { +#ifdef CRTSCTS + options.c_cflag |= CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag |= CNEW_RTSCTS; +#endif + str = "RTS/CTS"; + } else + str = "no"; + msg = dupprintf("Configuring %s flow control", str); + logevent(serial->frontend, msg); + sfree(msg); + + /* Parity */ + parity = conf_get_int(conf, CONF_serparity); + if (parity == SER_PAR_ODD) { + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + str = "odd"; + } else if (parity == SER_PAR_EVEN) { + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + str = "even"; + } else { + options.c_cflag &= ~PARENB; + str = "no"; + } + msg = dupprintf("Configuring %s parity", str); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag |= CLOCAL | CREAD; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL +#ifdef IUCLC + | IUCLC +#endif + ); + options.c_oflag &= ~(OPOST +#ifdef ONLCR + | ONLCR +#endif +#ifdef OCRNL + | OCRNL +#endif +#ifdef ONOCR + | ONOCR +#endif +#ifdef ONLRET + | ONLRET +#endif + ); + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 0; + + if (tcsetattr(serial->fd, TCSANOW, &options) < 0) + return "Unable to configure serial port"; + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *serial_init(void *frontend_handle, void **backend_handle, + Conf *conf, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + Serial serial; + const char *err; + char *line; + + serial = snew(struct serial_backend_data); + *backend_handle = serial; + + serial->frontend = frontend_handle; + serial->finished = FALSE; + serial->inbufsize = 0; + bufchain_init(&serial->output_data); + + line = conf_get_str(conf, CONF_serline); + { + char *msg = dupprintf("Opening serial device %s", line); + logevent(serial->frontend, msg); + sfree(msg); + } + + serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if (serial->fd < 0) + return "Unable to open serial port"; + + cloexec(serial->fd); + + err = serial_configure(serial, conf); + if (err) + return err; + + *realhost = dupstr(line); + + if (!serial_by_fd) + serial_by_fd = newtree234(serial_compare_by_fd); + add234(serial_by_fd, serial); + + serial_uxsel_setup(serial); + + /* + * Specials are always available. + */ + update_specials_menu(serial->frontend); + + return NULL; +} + +static void serial_close(Serial serial) +{ + if (serial->fd >= 0) { + close(serial->fd); + serial->fd = -1; + } +} + +static void serial_free(void *handle) +{ + Serial serial = (Serial) handle; + + serial_close(serial); + + bufchain_clear(&serial->output_data); + + sfree(serial); +} + +static void serial_reconfig(void *handle, Conf *conf) +{ + Serial serial = (Serial) handle; + + /* + * FIXME: what should we do if this returns an error? + */ + serial_configure(serial, conf); +} + +static int serial_select_result(int fd, int event) +{ + Serial serial; + char buf[4096]; + int ret; + int finished = FALSE; + + serial = find234(serial_by_fd, &fd, serial_find_by_fd); + + if (!serial) + return 1; /* spurious event; keep going */ + + if (event == 1) { + ret = read(serial->fd, buf, sizeof(buf)); + + if (ret == 0) { + /* + * Shouldn't happen on a real serial port, but I'm open + * to the idea that there might be two-way devices we + * can treat _like_ serial ports which can return EOF. + */ + finished = TRUE; + } else if (ret < 0) { +#ifdef EAGAIN + if (errno == EAGAIN) + return 1; /* spurious */ +#endif +#ifdef EWOULDBLOCK + if (errno == EWOULDBLOCK) + return 1; /* spurious */ +#endif + perror("read serial port"); + exit(1); + } else if (ret > 0) { + serial->inbufsize = from_backend(serial->frontend, 0, buf, ret); + serial_uxsel_setup(serial); /* might acquire backlog and freeze */ + } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + serial_try_write(serial); + } + + if (finished) { + serial_close(serial); + + serial->finished = TRUE; + + notify_remote_exit(serial->frontend); + } + + return !finished; +} + +static void serial_uxsel_setup(Serial serial) +{ + int rwx = 0; + + if (serial->inbufsize <= SERIAL_MAX_BACKLOG) + rwx |= 1; + if (bufchain_size(&serial->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(serial->fd, rwx, serial_select_result); +} + +static void serial_try_write(Serial serial) +{ + void *data; + int len, ret; + + assert(serial->fd >= 0); + + while (bufchain_size(&serial->output_data) > 0) { + bufchain_prefix(&serial->output_data, &data, &len); + ret = write(serial->fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write serial port"); + exit(1); + } + bufchain_consume(&serial->output_data, ret); + } + + serial_uxsel_setup(serial); +} + +/* + * Called to send data down the serial connection. + */ +static int serial_send(void *handle, char *buf, int len) +{ + Serial serial = (Serial) handle; + + if (serial->fd < 0) + return 0; + + bufchain_add(&serial->output_data, buf, len); + serial_try_write(serial); + + return bufchain_size(&serial->output_data); +} + +/* + * Called to query the current sendability status. + */ +static int serial_sendbuffer(void *handle) +{ + Serial serial = (Serial) handle; + return bufchain_size(&serial->output_data); +} + +/* + * Called to set the size of the window + */ +static void serial_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send serial special codes. + */ +static void serial_special(void *handle, Telnet_Special code) +{ + Serial serial = (Serial) handle; + + if (serial->fd >= 0 && code == TS_BRK) { + tcsendbreak(serial->fd, 0); + logevent(serial->frontend, "Sending serial break at user request"); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *serial_get_specials(void *handle) +{ + static const struct telnet_special specials[] = { + {"Break", TS_BRK}, + {NULL, TS_EXITMENU} + }; + return specials; +} + +static int serial_connected(void *handle) +{ + return 1; /* always connected */ +} + +static int serial_sendok(void *handle) +{ + return 1; +} + +static void serial_unthrottle(void *handle, int backlog) +{ + Serial serial = (Serial) handle; + serial->inbufsize = backlog; + serial_uxsel_setup(serial); +} + +static int serial_ldisc(void *handle, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return 0; +} + +static void serial_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void serial_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int serial_exitcode(void *handle) +{ + Serial serial = (Serial) handle; + if (serial->fd >= 0) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept with serial ports */ + return INT_MAX; +} + +/* + * cfg_info for Serial does nothing at all. + */ +static int serial_cfg_info(void *handle) +{ + return 0; +} + +Backend serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_provide_logctx, + serial_unthrottle, + serial_cfg_info, + "serial", + PROT_SERIAL, + 0 +}; diff --git a/netbox/libs/Putty/unix/uxsftp.c b/netbox/libs/Putty/unix/uxsftp.c new file mode 100644 index 000000000..391da0212 --- /dev/null +++ b/netbox/libs/Putty/unix/uxsftp.c @@ -0,0 +1,615 @@ +/* + * uxsftp.c: the Unix-specific parts of PSFTP and PSCP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NO_SYS_SELECT_H +#include +#endif + +#include "putty.h" +#include "ssh.h" +#include "psftp.h" +#include "int64.h" + +/* + * In PSFTP our selects are synchronous, so these functions are + * empty stubs. + */ +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +void platform_get_x11_auth(struct X11Display *display, Conf *conf) +{ + /* Do nothing, therefore no auth. */ +} +const int platform_uses_x11_unix_by_default = TRUE; + +/* + * Default settings that are specific to PSFTP. + */ +char *platform_default_s(const char *name) +{ + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *get_ttymode(void *frontend, const char *mode) { return NULL; } + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +/* + * Set local current directory. Returns NULL on success, or else an + * error message which must be freed after printing. + */ +char *psftp_lcd(char *dir) +{ + if (chdir(dir) < 0) + return dupprintf("%s: chdir: %s", dir, strerror(errno)); + else + return NULL; +} + +/* + * Get local current directory. Returns a string which must be + * freed. + */ +char *psftp_getcwd(void) +{ + char *buffer, *ret; + int size = 256; + + buffer = snewn(size, char); + while (1) { + ret = getcwd(buffer, size); + if (ret != NULL) + return ret; + if (errno != ERANGE) { + sfree(buffer); + return dupprintf("[cwd unavailable: %s]", strerror(errno)); + } + /* + * Otherwise, ERANGE was returned, meaning the buffer + * wasn't big enough. + */ + size = size * 3 / 2; + buffer = sresize(buffer, size, char); + } +} + +struct RFile { + int fd; +}; + +RFile *open_existing_file(char *name, uint64 *size, + unsigned long *mtime, unsigned long *atime, + long *perms) +{ + int fd; + RFile *ret; + + fd = open(name, O_RDONLY); + if (fd < 0) + return NULL; + + ret = snew(RFile); + ret->fd = fd; + + if (size || mtime || atime || perms) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + memset(&statbuf, 0, sizeof(statbuf)); + } + + if (size) + *size = uint64_make((statbuf.st_size >> 16) >> 16, + statbuf.st_size); + + if (mtime) + *mtime = statbuf.st_mtime; + + if (atime) + *atime = statbuf.st_atime; + + if (perms) + *perms = statbuf.st_mode; + } + + return ret; +} + +int read_from_file(RFile *f, void *buffer, int length) +{ + return read(f->fd, buffer, length); +} + +void close_rfile(RFile *f) +{ + close(f->fd); + sfree(f); +} + +struct WFile { + int fd; + char *name; +}; + +WFile *open_new_file(char *name, long perms) +{ + int fd; + WFile *ret; + + fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, + (mode_t)(perms ? perms : 0666)); + if (fd < 0) + return NULL; + + ret = snew(WFile); + ret->fd = fd; + ret->name = dupstr(name); + + return ret; +} + + +WFile *open_existing_wfile(char *name, uint64 *size) +{ + int fd; + WFile *ret; + + fd = open(name, O_APPEND | O_WRONLY); + if (fd < 0) + return NULL; + + ret = snew(WFile); + ret->fd = fd; + ret->name = dupstr(name); + + if (size) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + memset(&statbuf, 0, sizeof(statbuf)); + } + + *size = uint64_make((statbuf.st_size >> 16) >> 16, + statbuf.st_size); + } + + return ret; +} + +int write_to_file(WFile *f, void *buffer, int length) +{ + char *p = (char *)buffer; + int so_far = 0; + + /* Keep trying until we've really written as much as we can. */ + while (length > 0) { + int ret = write(f->fd, p, length); + + if (ret < 0) + return ret; + + if (ret == 0) + break; + + p += ret; + length -= ret; + so_far += ret; + } + + return so_far; +} + +void set_file_times(WFile *f, unsigned long mtime, unsigned long atime) +{ + struct utimbuf ut; + + ut.actime = atime; + ut.modtime = mtime; + + utime(f->name, &ut); +} + +/* Closes and frees the WFile */ +void close_wfile(WFile *f) +{ + close(f->fd); + sfree(f->name); + sfree(f); +} + +/* Seek offset bytes through file, from whence, where whence is + FROM_START, FROM_CURRENT, or FROM_END */ +int seek_file(WFile *f, uint64 offset, int whence) +{ + off_t fileofft; + int lseek_whence; + + fileofft = (((off_t) offset.hi << 16) << 16) + offset.lo; + + switch (whence) { + case FROM_START: + lseek_whence = SEEK_SET; + break; + case FROM_CURRENT: + lseek_whence = SEEK_CUR; + break; + case FROM_END: + lseek_whence = SEEK_END; + break; + default: + return -1; + } + + return lseek(f->fd, fileofft, lseek_whence) >= 0 ? 0 : -1; +} + +uint64 get_file_posn(WFile *f) +{ + off_t fileofft; + uint64 ret; + + fileofft = lseek(f->fd, (off_t) 0, SEEK_CUR); + + ret = uint64_make((fileofft >> 16) >> 16, fileofft); + + return ret; +} + +int file_type(char *name) +{ + struct stat statbuf; + + if (stat(name, &statbuf) < 0) { + if (errno != ENOENT) + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + return FILE_TYPE_NONEXISTENT; + } + + if (S_ISREG(statbuf.st_mode)) + return FILE_TYPE_FILE; + + if (S_ISDIR(statbuf.st_mode)) + return FILE_TYPE_DIRECTORY; + + return FILE_TYPE_WEIRD; +} + +struct DirHandle { + DIR *dir; +}; + +DirHandle *open_directory(char *name) +{ + DIR *dir; + DirHandle *ret; + + dir = opendir(name); + if (!dir) + return NULL; + + ret = snew(DirHandle); + ret->dir = dir; + return ret; +} + +char *read_filename(DirHandle *dir) +{ + struct dirent *de; + + do { + de = readdir(dir->dir); + if (de == NULL) + return NULL; + } while ((de->d_name[0] == '.' && + (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && de->d_name[2] == '\0')))); + + return dupstr(de->d_name); +} + +void close_directory(DirHandle *dir) +{ + closedir(dir->dir); + sfree(dir); +} + +int test_wildcard(char *name, int cmdline) +{ + struct stat statbuf; + + if (stat(name, &statbuf) == 0) { + return WCTYPE_FILENAME; + } else if (cmdline) { + /* + * On Unix, we never need to parse wildcards coming from + * the command line, because the shell will have expanded + * them into a filename list already. + */ + return WCTYPE_NONEXISTENT; + } else { + glob_t globbed; + int ret = WCTYPE_NONEXISTENT; + + if (glob(name, GLOB_ERR, NULL, &globbed) == 0) { + if (globbed.gl_pathc > 0) + ret = WCTYPE_WILDCARD; + globfree(&globbed); + } + + return ret; + } +} + +/* + * Actually return matching file names for a local wildcard. + */ +struct WildcardMatcher { + glob_t globbed; + int i; +}; +WildcardMatcher *begin_wildcard_matching(char *name) { + WildcardMatcher *ret = snew(WildcardMatcher); + + if (glob(name, 0, NULL, &ret->globbed) < 0) { + sfree(ret); + return NULL; + } + + ret->i = 0; + + return ret; +} +char *wildcard_get_filename(WildcardMatcher *dir) { + if (dir->i < dir->globbed.gl_pathc) { + return dupstr(dir->globbed.gl_pathv[dir->i++]); + } else + return NULL; +} +void finish_wildcard_matching(WildcardMatcher *dir) { + globfree(&dir->globbed); + sfree(dir); +} + +int vet_filename(char *name) +{ + if (strchr(name, '/')) + return FALSE; + + if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) + return FALSE; + + return TRUE; +} + +int create_directory(char *name) +{ + return mkdir(name, 0777) == 0; +} + +char *dir_file_cat(char *dir, char *file) +{ + return dupcat(dir, "/", file, NULL); +} + +/* + * Do a select() between all currently active network fds and + * optionally stdin. + */ +static int ssh_sftp_do_select(int include_stdin, int no_fds_ok) +{ + fd_set rset, wset, xset; + int i, fdcount, fdsize, *fdlist; + int fd, fdstate, rwx, ret, maxfd; + unsigned long now = GETTICKCOUNT(); + unsigned long next; + + fdlist = NULL; + fdcount = fdsize = 0; + + do { + + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; + + if (i < 1 && !no_fds_ok) + return -1; /* doom */ + + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; + + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + if (include_stdin) + FD_SET_MAX(0, maxfd, rset); + + if (toplevel_callback_pending()) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + run_toplevel_callbacks(); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + struct timeval tv; + + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = select(maxfd, &rset, &wset, &xset, NULL); + } + } while (ret == 0); + + if (ret < 0) { + perror("select"); + exit(1); + } + + for (i = 0; i < fdcount; i++) { + fd = fdlist[i]; + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (FD_ISSET(fd, &xset)) + select_result(fd, 4); + if (FD_ISSET(fd, &rset)) + select_result(fd, 1); + if (FD_ISSET(fd, &wset)) + select_result(fd, 2); + } + + sfree(fdlist); + + run_toplevel_callbacks(); + + return FD_ISSET(0, &rset) ? 1 : 0; +} + +/* + * Wait for some network data and process it. + */ +int ssh_sftp_loop_iteration(void) +{ + return ssh_sftp_do_select(FALSE, FALSE); +} + +/* + * Read a PSFTP command line from stdin. + */ +char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok) +{ + char *buf; + int buflen, bufsize, ret; + + fputs(prompt, stdout); + fflush(stdout); + + buf = NULL; + buflen = bufsize = 0; + + while (1) { + ret = ssh_sftp_do_select(TRUE, no_fds_ok); + if (ret < 0) { + printf("connection died\n"); + sfree(buf); + return NULL; /* woop woop */ + } + if (ret > 0) { + if (buflen >= bufsize) { + bufsize = buflen + 512; + buf = sresize(buf, bufsize, char); + } + ret = read(0, buf+buflen, 1); + if (ret < 0) { + perror("read"); + sfree(buf); + return NULL; + } + if (ret == 0) { + /* eof on stdin; no error, but no answer either */ + sfree(buf); + return NULL; + } + + if (buf[buflen++] == '\n') { + /* we have a full line */ + return buf; + } + } + } +} + +void frontend_net_error_pending(void) {} + +/* + * Main program: do platform-specific initialisation and then call + * psftp_main(). + */ +int main(int argc, char *argv[]) +{ + uxsel_init(); + return psftp_main(argc, argv); +} diff --git a/netbox/libs/Putty/unix/uxshare.c b/netbox/libs/Putty/unix/uxshare.c new file mode 100644 index 000000000..3da52defb --- /dev/null +++ b/netbox/libs/Putty/unix/uxshare.c @@ -0,0 +1,414 @@ +/* + * Unix implementation of SSH connection-sharing IPC setup. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" +#define SALT_FILENAME "salt" +#define SALT_SIZE 64 + +/* + * Functions provided by uxnet.c to help connection sharing. + */ +SockAddr unix_sock_addr(const char *path); +Socket new_unix_listener(SockAddr listenaddr, Plug plug); + +static char *make_parentdir_name(void) +{ + char *username, *parent; + + username = get_username(); + parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username); + sfree(username); + assert(*parent == '/'); + + return parent; +} + +static char *make_dir_and_check_ours(const char *dirname) +{ + struct stat st; + + /* + * Create the directory. We might have created it before, so + * EEXIST is an OK error; but anything else is doom. + */ + if (mkdir(dirname, 0700) < 0 && errno != EEXIST) + return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); + + /* + * Now check that that directory is _owned by us_ and not writable + * by anybody else. This protects us against somebody else + * previously having created the directory in a way that's + * writable to us, and thus manipulating us into creating the + * actual socket in a directory they can see so that they can + * connect to it and use our authenticated SSH sessions. + */ + if (stat(dirname, &st) < 0) + return dupprintf("%s: stat: %s", dirname, strerror(errno)); + if (st.st_uid != getuid()) + return dupprintf("%s: directory owned by uid %d, not by us", + dirname, st.st_uid); + if ((st.st_mode & 077) != 0) + return dupprintf("%s: directory has overgenerous permissions %03o" + " (expected 700)", dirname, st.st_mode & 0777); + + return NULL; +} + +static char *make_dirname(const char *pi_name, char **logtext) +{ + char *name, *parentdirname, *dirname, *err; + + /* + * First, create the top-level directory for all shared PuTTY + * connections owned by this user. + */ + parentdirname = make_parentdir_name(); + if ((err = make_dir_and_check_ours(parentdirname)) != NULL) { + *logtext = err; + sfree(parentdirname); + return NULL; + } + + /* + * Transform the platform-independent version of the connection + * identifier into the name we'll actually use for the directory + * containing the Unix socket. + * + * We do this by hashing the identifier with some user-specific + * secret information, to avoid the privacy leak of having + * "user@host" strings show up in 'netstat -x'. (Irritatingly, the + * full pathname of a Unix-domain socket _does_ show up in the + * 'netstat -x' output, at least on Linux, even if that socket is + * in a directory not readable to the user running netstat. You'd + * think putting things inside an 0700 directory would hide their + * names from other users, but no.) + * + * The secret information we use to salt the hash lives in a file + * inside the top-level directory we just created, so we must + * first create that file (with some fresh random data in it) if + * it's not already been done by a previous PuTTY. + */ + { + unsigned char saltbuf[SALT_SIZE]; + char *saltname; + int saltfd, i, ret; + + saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME); + saltfd = open(saltname, O_RDONLY); + if (saltfd < 0) { + char *tmpname; + int pid; + + if (errno != ENOENT) { + *logtext = dupprintf("%s: open: %s", saltname, + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * The salt file doesn't already exist, so try to create + * it. Another process may be attempting the same thing + * simultaneously, so we must do this carefully: we write + * a salt file under a different name, then hard-link it + * into place, which guarantees that we won't change the + * contents of an existing salt file. + */ + pid = getpid(); + for (i = 0;; i++) { + tmpname = dupprintf("%s/%s.tmp.%d.%d", + parentdirname, SALT_FILENAME, pid, i); + saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400); + if (saltfd >= 0) + break; + if (errno != EEXIST) { + *logtext = dupprintf("%s: open: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + sfree(tmpname); /* go round and try again with i+1 */ + } + /* + * Invent some random data. + */ + for (i = 0; i < SALT_SIZE; i++) { + saltbuf[i] = random_byte(); + } + ret = write(saltfd, saltbuf, SALT_SIZE); + /* POSIX atomicity guarantee: because we wrote less than + * PIPE_BUF bytes, the write either completed in full or + * failed. */ + assert(SALT_SIZE < PIPE_BUF); + assert(ret < 0 || ret == SALT_SIZE); + if (ret < 0) { + close(saltfd); + *logtext = dupprintf("%s: write: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + if (close(saltfd) < 0) { + *logtext = dupprintf("%s: close: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * Now attempt to hard-link our temp file into place. We + * tolerate EEXIST as an outcome, because that just means + * another PuTTY got their attempt in before we did (and + * we only care that there is a valid salt file we can + * agree on, no matter who created it). + */ + if (link(tmpname, saltname) < 0 && errno != EEXIST) { + *logtext = dupprintf("%s: link: %s", saltname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * Whether that succeeded or not, get rid of our temp file. + */ + if (unlink(tmpname) < 0) { + *logtext = dupprintf("%s: unlink: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * And now we've arranged for there to be a salt file, so + * we can try to open it for reading again and this time + * expect it to work. + */ + sfree(tmpname); + + saltfd = open(saltname, O_RDONLY); + if (saltfd < 0) { + *logtext = dupprintf("%s: open: %s", saltname, + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + } + + for (i = 0; i < SALT_SIZE; i++) { + ret = read(saltfd, saltbuf, SALT_SIZE); + if (ret <= 0) { + close(saltfd); + *logtext = dupprintf("%s: read: %s", saltname, + ret == 0 ? "unexpected EOF" : + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + assert(0 < ret && ret <= SALT_SIZE - i); + i += ret; + } + + close(saltfd); + sfree(saltname); + + /* + * Now we've got our salt, hash it with the connection + * identifier to produce our actual socket name. + */ + { + SHA256_State sha; + unsigned len; + unsigned char lenbuf[4]; + unsigned char digest[32]; + char retbuf[65]; + + SHA256_Init(&sha); + PUT_32BIT(lenbuf, SALT_SIZE); + SHA256_Bytes(&sha, lenbuf, 4); + SHA256_Bytes(&sha, saltbuf, SALT_SIZE); + len = strlen(pi_name); + PUT_32BIT(lenbuf, len); + SHA256_Bytes(&sha, lenbuf, 4); + SHA256_Bytes(&sha, pi_name, len); + SHA256_Final(&sha, digest); + + /* + * And make it printable. + */ + for (i = 0; i < 32; i++) { + sprintf(retbuf + 2*i, "%02x", digest[i]); + /* the last of those will also write the trailing NUL */ + } + + name = dupstr(retbuf); + } + + smemclr(saltbuf, sizeof(saltbuf)); + } + + dirname = dupprintf("%s/%s", parentdirname, name); + sfree(parentdirname); + sfree(name); + + return dirname; +} + +int platform_ssh_share(const char *pi_name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream) +{ + char *dirname, *lockname, *sockname, *err; + int lockfd; + Socket retsock; + + /* + * Sort out what we're going to call the directory in which we + * keep the socket. This has the side effect of potentially + * creating its top-level containing dir and/or the salt file + * within that, if they don't already exist. + */ + dirname = make_dirname(pi_name, logtext); + if (!dirname) { + return SHARE_NONE; + } + + /* + * Now make sure the subdirectory exists. + */ + if ((err = make_dir_and_check_ours(dirname)) != NULL) { + *logtext = err; + sfree(dirname); + return SHARE_NONE; + } + + /* + * Acquire a lock on a file in that directory. + */ + lockname = dupcat(dirname, "/lock", (char *)NULL); + lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600); + if (lockfd < 0) { + *logtext = dupprintf("%s: open: %s", lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + return SHARE_NONE; + } + if (flock(lockfd, LOCK_EX) < 0) { + *logtext = dupprintf("%s: flock(LOCK_EX): %s", + lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_NONE; + } + + sockname = dupprintf("%s/socket", dirname); + + *logtext = NULL; + + if (can_downstream) { + retsock = new_connection(unix_sock_addr(sockname), + "", 0, 0, 1, 0, 0, downplug, conf); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_DOWNSTREAM; + } + sfree(*ds_err); + *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + if (can_upstream) { + retsock = new_unix_listener(unix_sock_addr(sockname), upplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_UPSTREAM; + } + sfree(*us_err); + *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + /* One of the above clauses ought to have happened. */ + assert(*logtext || *ds_err || *us_err); + + sfree(dirname); + sfree(lockname); + sfree(sockname); + close(lockfd); + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ + char *dirname, *filename, *logtext; + + dirname = make_dirname(name, &logtext); + if (!dirname) { + sfree(logtext); /* we can't do much with this */ + return; + } + + filename = dupcat(dirname, "/socket", (char *)NULL); + remove(filename); + sfree(filename); + + filename = dupcat(dirname, "/lock", (char *)NULL); + remove(filename); + sfree(filename); + + rmdir(dirname); + + /* + * We deliberately _don't_ clean up the parent directory + * /tmp/putty-connshare., because if we leave it around + * then it reduces the ability for other users to be a nuisance by + * putting their own directory in the way of it. Also, the salt + * file in it can be reused. + */ + + sfree(dirname); +} diff --git a/netbox/libs/Putty/unix/uxsignal.c b/netbox/libs/Putty/unix/uxsignal.c new file mode 100644 index 000000000..e21e0e805 --- /dev/null +++ b/netbox/libs/Putty/unix/uxsignal.c @@ -0,0 +1,45 @@ +#include +#include +#include + +/* + * Calling signal() is non-portable, as it varies in meaning + * between platforms and depending on feature macros, and has + * stupid semantics at least some of the time. + * + * This function provides the same interface as the libc function, + * but provides consistent semantics. It assumes POSIX semantics + * for sigaction() (so you might need to do some more work if you + * port to something ancient like SunOS 4) + */ +void (*putty_signal(int sig, void (*func)(int)))(int) { + struct sigaction sa; + struct sigaction old; + + sa.sa_handler = func; + if(sigemptyset(&sa.sa_mask) < 0) + return SIG_ERR; + sa.sa_flags = SA_RESTART; + if(sigaction(sig, &sa, &old) < 0) + return SIG_ERR; + return old.sa_handler; +} + +void block_signal(int sig, int block_it) +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, sig); + if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { + perror("sigprocmask"); + exit(1); + } +} + +/* +Local Variables: +c-basic-offset:4 +comment-column:40 +End: +*/ diff --git a/netbox/libs/Putty/unix/uxstore.c b/netbox/libs/Putty/unix/uxstore.c new file mode 100644 index 000000000..ad12f167d --- /dev/null +++ b/netbox/libs/Putty/unix/uxstore.c @@ -0,0 +1,755 @@ +/* + * uxstore.c: Unix-specific implementation of the interface defined + * in storage.h. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "putty.h" +#include "storage.h" +#include "tree234.h" + +#ifdef PATH_MAX +#define FNLEN PATH_MAX +#else +#define FNLEN 1024 /* XXX */ +#endif + +enum { + INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED, + INDEX_SESSIONDIR, INDEX_SESSION, +}; + +static const char hex[16] = "0123456789ABCDEF"; + +static char *mungestr(const char *in) +{ + char *out, *ret; + + if (!in || !*in) + in = "Default Settings"; + + ret = out = snewn(3*strlen(in)+1, char); + + while (*in) { + /* + * There are remarkably few punctuation characters that + * aren't shell-special in some way or likely to be used as + * separators in some file format or another! Hence we use + * opt-in for safe characters rather than opt-out for + * specific unsafe ones... + */ + if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' && + !(*in >= '0' && *in <= '9') && + !(*in >= 'A' && *in <= 'Z') && + !(*in >= 'a' && *in <= 'z')) { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + } + *out = '\0'; + return ret; +} + +static char *unmungestr(const char *in) +{ + char *out, *ret; + out = ret = snewn(strlen(in)+1, char); + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + in += 3; + } else { + *out++ = *in++; + } + } + *out = '\0'; + return ret; +} + +static char *make_filename(int index, const char *subname) +{ + char *env, *tmp, *ret; + + /* + * Allow override of the PuTTY configuration location, and of + * specific subparts of it, by means of environment variables. + */ + if (index == INDEX_DIR) { + struct passwd *pwd; + + env = getenv("PUTTYDIR"); + if (env) + return dupstr(env); + env = getenv("HOME"); + if (env) + return dupprintf("%s/.putty", env); + pwd = getpwuid(getuid()); + if (pwd && pwd->pw_dir) + return dupprintf("%s/.putty", pwd->pw_dir); + return dupstr("/.putty"); + } + if (index == INDEX_SESSIONDIR) { + env = getenv("PUTTYSESSIONS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sessions", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_SESSION) { + char *munged = mungestr(subname); + tmp = make_filename(INDEX_SESSIONDIR, NULL); + ret = dupprintf("%s/%s", tmp, munged); + sfree(tmp); + sfree(munged); + return ret; + } + if (index == INDEX_HOSTKEYS) { + env = getenv("PUTTYSSHHOSTKEYS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sshhostkeys", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_HOSTKEYS_TMP) { + tmp = make_filename(INDEX_HOSTKEYS, NULL); + ret = dupprintf("%s.tmp", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_RANDSEED) { + env = getenv("PUTTYRANDOMSEED"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/randomseed", tmp); + sfree(tmp); + return ret; + } + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/ERROR", tmp); + sfree(tmp); + return ret; +} + +void *open_settings_w(const char *sessionname, char **errmsg) +{ + char *filename; + FILE *fp; + + *errmsg = NULL; + + /* + * Start by making sure the .putty directory and its sessions + * subdir actually exist. + */ + filename = make_filename(INDEX_DIR, NULL); + if (mkdir(filename, 0700) < 0 && errno != EEXIST) { + *errmsg = dupprintf("Unable to save session: mkdir(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; + } + sfree(filename); + + filename = make_filename(INDEX_SESSIONDIR, NULL); + if (mkdir(filename, 0700) < 0 && errno != EEXIST) { + *errmsg = dupprintf("Unable to save session: mkdir(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; + } + sfree(filename); + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "w"); + if (!fp) { + *errmsg = dupprintf("Unable to save session: open(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; /* can't open */ + } + sfree(filename); + return fp; +} + +void write_setting_s(void *handle, const char *key, const char *value) +{ + FILE *fp = (FILE *)handle; + fprintf(fp, "%s=%s\n", key, value); +} + +void write_setting_i(void *handle, const char *key, int value) +{ + FILE *fp = (FILE *)handle; + fprintf(fp, "%s=%d\n", key, value); +} + +void close_settings_w(void *handle) +{ + FILE *fp = (FILE *)handle; + fclose(fp); +} + +/* + * Reading settings, for the moment, is done by retrieving X + * resources from the X display. When we introduce disk files, I + * think what will happen is that the X resources will override + * PuTTY's inbuilt defaults, but that the disk files will then + * override those. This isn't optimal, but it's the best I can + * immediately work out. + * FIXME: the above comment is a bit out of date. Did it happen? + */ + +struct skeyval { + const char *key; + const char *value; +}; + +static tree234 *xrmtree = NULL; + +int keycmp(void *av, void *bv) +{ + struct skeyval *a = (struct skeyval *)av; + struct skeyval *b = (struct skeyval *)bv; + return strcmp(a->key, b->key); +} + +void provide_xrm_string(char *string) +{ + char *p, *q, *key; + struct skeyval *xrms, *ret; + + p = q = strchr(string, ':'); + if (!q) { + fprintf(stderr, "pterm: expected a colon in resource string" + " \"%s\"\n", string); + return; + } + q++; + while (p > string && p[-1] != '.' && p[-1] != '*') + p--; + xrms = snew(struct skeyval); + key = snewn(q-p, char); + memcpy(key, p, q-p); + key[q-p-1] = '\0'; + xrms->key = key; + while (*q && isspace((unsigned char)*q)) + q++; + xrms->value = dupstr(q); + + if (!xrmtree) + xrmtree = newtree234(keycmp); + + ret = add234(xrmtree, xrms); + if (ret) { + /* Override an existing string. */ + del234(xrmtree, ret); + add234(xrmtree, xrms); + } +} + +const char *get_setting(const char *key) +{ + struct skeyval tmp, *ret; + tmp.key = key; + if (xrmtree) { + ret = find234(xrmtree, &tmp, NULL); + if (ret) + return ret->value; + } + return x_get_default(key); +} + +void *open_settings_r(const char *sessionname) +{ + char *filename; + FILE *fp; + char *line; + tree234 *ret; + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return NULL; /* can't open */ + + ret = newtree234(keycmp); + + while ( (line = fgetline(fp)) ) { + char *value = strchr(line, '='); + struct skeyval *kv; + + if (!value) { + sfree(line); + continue; + } + *value++ = '\0'; + value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ + + kv = snew(struct skeyval); + kv->key = dupstr(line); + kv->value = dupstr(value); + add234(ret, kv); + + sfree(line); + } + + fclose(fp); + + return ret; +} + +char *read_setting_s(void *handle, const char *key) +{ + tree234 *tree = (tree234 *)handle; + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (tree != NULL && + (kv = find234(tree, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return NULL; + else + return dupstr(val); +} + +int read_setting_i(void *handle, const char *key, int defvalue) +{ + tree234 *tree = (tree234 *)handle; + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (tree != NULL && + (kv = find234(tree, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return defvalue; + else + return atoi(val); +} + +FontSpec *read_setting_fontspec(void *handle, const char *name) +{ + /* + * In GTK1-only PuTTY, we used to store font names simply as a + * valid X font description string (logical or alias), under a + * bare key such as "Font". + * + * In GTK2 PuTTY, we have a prefix system where "client:" + * indicates a Pango font and "server:" an X one; existing + * configuration needs to be reinterpreted as having the + * "server:" prefix, so we change the storage key from the + * provided name string (e.g. "Font") to a suffixed one + * ("FontName"). + */ + char *suffname = dupcat(name, "Name", NULL); + char *tmp; + + if ((tmp = read_setting_s(handle, suffname)) != NULL) { + FontSpec *fs = fontspec_new(tmp); + sfree(suffname); + sfree(tmp); + return fs; /* got new-style name */ + } + sfree(suffname); + + /* Fall back to old-style name. */ + tmp = read_setting_s(handle, name); + if (tmp && *tmp) { + char *tmp2 = dupcat("server:", tmp, NULL); + FontSpec *fs = fontspec_new(tmp2); + sfree(tmp2); + sfree(tmp); + return fs; + } else { + sfree(tmp); + return NULL; + } +} +Filename *read_setting_filename(void *handle, const char *name) +{ + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; +} + +void write_setting_fontspec(void *handle, const char *name, FontSpec *fs) +{ + /* + * read_setting_fontspec had to handle two cases, but when + * writing our settings back out we simply always generate the + * new-style name. + */ + char *suffname = dupcat(name, "Name", NULL); + write_setting_s(handle, suffname, fs->name); + sfree(suffname); +} +void write_setting_filename(void *handle, const char *name, Filename *result) +{ + write_setting_s(handle, name, result->path); +} + +void close_settings_r(void *handle) +{ + tree234 *tree = (tree234 *)handle; + struct skeyval *kv; + + if (!tree) + return; + + while ( (kv = index234(tree, 0)) != NULL) { + del234(tree, kv); + sfree((char *)kv->key); + sfree((char *)kv->value); + sfree(kv); + } + + freetree234(tree); +} + +void del_settings(const char *sessionname) +{ + char *filename; + filename = make_filename(INDEX_SESSION, sessionname); + unlink(filename); + sfree(filename); +} + +void *enum_settings_start(void) +{ + DIR *dp; + char *filename; + + filename = make_filename(INDEX_SESSIONDIR, NULL); + dp = opendir(filename); + sfree(filename); + + return dp; +} + +char *enum_settings_next(void *handle, char *buffer, int buflen) +{ + DIR *dp = (DIR *)handle; + struct dirent *de; + struct stat st; + char *fullpath; + int maxlen, thislen, len; + char *unmunged; + + fullpath = make_filename(INDEX_SESSIONDIR, NULL); + maxlen = len = strlen(fullpath); + + while ( (de = readdir(dp)) != NULL ) { + thislen = len + 1 + strlen(de->d_name); + if (maxlen < thislen) { + maxlen = thislen; + fullpath = sresize(fullpath, maxlen+1, char); + } + fullpath[len] = '/'; + strncpy(fullpath+len+1, de->d_name, thislen - (len+1)); + fullpath[thislen] = '\0'; + + if (stat(fullpath, &st) < 0 || !S_ISREG(st.st_mode)) + continue; /* try another one */ + + unmunged = unmungestr(de->d_name); + strncpy(buffer, unmunged, buflen); + buffer[buflen-1] = '\0'; + sfree(unmunged); + sfree(fullpath); + return buffer; + } + + sfree(fullpath); + return NULL; +} + +void enum_settings_finish(void *handle) +{ + DIR *dp = (DIR *)handle; + closedir(dp); +} + +/* + * Lines in the host keys file are of the form + * + * type@port:hostname keydata + * + * e.g. + * + * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343 + */ +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + FILE *fp; + char *filename; + char *line; + int ret; + + filename = make_filename(INDEX_HOSTKEYS, NULL); + fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return 1; /* key does not exist */ + + ret = 1; + while ( (line = fgetline(fp)) ) { + int i; + char *p = line; + char porttext[20]; + + line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */ + + i = strlen(keytype); + if (strncmp(p, keytype, i)) + goto done; + p += i; + + if (*p != '@') + goto done; + p++; + + sprintf(porttext, "%d", port); + i = strlen(porttext); + if (strncmp(p, porttext, i)) + goto done; + p += i; + + if (*p != ':') + goto done; + p++; + + i = strlen(hostname); + if (strncmp(p, hostname, i)) + goto done; + p += i; + + if (*p != ' ') + goto done; + p++; + + /* + * Found the key. Now just work out whether it's the right + * one or not. + */ + if (!strcmp(p, key)) + ret = 0; /* key matched OK */ + else + ret = 2; /* key mismatch */ + + done: + sfree(line); + if (ret != 1) + break; + } + + fclose(fp); + return ret; +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + FILE *rfp, *wfp; + char *newtext, *line; + int headerlen; + char *filename, *tmpfilename; + + /* + * Open both the old file and a new file. + */ + tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL); + wfp = fopen(tmpfilename, "w"); + if (!wfp && errno == ENOENT) { + char *dir; + + dir = make_filename(INDEX_DIR, NULL); + if (mkdir(dir, 0700) < 0) { + nonfatal("Unable to store host key: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); + sfree(dir); + sfree(tmpfilename); + return; + } + sfree(dir); + + wfp = fopen(tmpfilename, "w"); + } + if (!wfp) { + nonfatal("Unable to store host key: open(\"%s\") " + "returned '%s'", tmpfilename, strerror(errno)); + sfree(tmpfilename); + return; + } + filename = make_filename(INDEX_HOSTKEYS, NULL); + rfp = fopen(filename, "r"); + + newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); + headerlen = 1 + strcspn(newtext, " "); /* count the space too */ + + /* + * Copy all lines from the old file to the new one that _don't_ + * involve the same host key identifier as the one we're adding. + */ + if (rfp) { + while ( (line = fgetline(rfp)) ) { + if (strncmp(line, newtext, headerlen)) + fputs(line, wfp); + sfree(line); + } + fclose(rfp); + } + + /* + * Now add the new line at the end. + */ + fputs(newtext, wfp); + + fclose(wfp); + + if (rename(tmpfilename, filename) < 0) { + nonfatal("Unable to store host key: rename(\"%s\",\"%s\")" + " returned '%s'", tmpfilename, filename, + strerror(errno)); + } + + sfree(tmpfilename); + sfree(filename); + sfree(newtext); +} + +void read_random_seed(noise_consumer_t consumer) +{ + int fd; + char *fname; + + fname = make_filename(INDEX_RANDSEED, NULL); + fd = open(fname, O_RDONLY); + sfree(fname); + if (fd >= 0) { + char buf[512]; + int ret; + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + consumer(buf, ret); + close(fd); + } +} + +void write_random_seed(void *data, int len) +{ + int fd; + char *fname; + + fname = make_filename(INDEX_RANDSEED, NULL); + /* + * Don't truncate the random seed file if it already exists; if + * something goes wrong half way through writing it, it would + * be better to leave the old data there than to leave it empty. + */ + fd = open(fname, O_CREAT | O_WRONLY, 0600); + if (fd < 0) { + if (errno != ENOENT) { + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + sfree(fname); + return; + } + char *dir; + + dir = make_filename(INDEX_DIR, NULL); + if (mkdir(dir, 0700) < 0) { + nonfatal("Unable to write random seed: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); + sfree(fname); + sfree(dir); + return; + } + sfree(dir); + + fd = open(fname, O_CREAT | O_WRONLY, 0600); + if (fd < 0) { + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + sfree(fname); + return; + } + } + + while (len > 0) { + int ret = write(fd, data, len); + if (ret < 0) { + nonfatal("Unable to write random seed: write " + "returned '%s'", strerror(errno)); + break; + } + len -= ret; + data = (char *)data + len; + } + + close(fd); + sfree(fname); +} + +void cleanup_all(void) +{ +} + +#ifdef MPEXT + +void putty_mungestr(const char *in, char *out) +{ + char *o = mungestr(in); + strcpy(out, o); + free(o); +} + +void putty_unmungestr(const char *in, char *out, int outlen) +{ + char *o = unmungestr(in); + strncpy(out, o, outlen); + free(o); +} + +#endif diff --git a/netbox/libs/Putty/unix/uxucs.c b/netbox/libs/Putty/unix/uxucs.c new file mode 100644 index 000000000..5d3d5af09 --- /dev/null +++ b/netbox/libs/Putty/unix/uxucs.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "putty.h" +#include "charset.h" +#include "terminal.h" +#include "misc.h" + +/* + * Unix Unicode-handling routines. + */ + +int is_dbcs_leadbyte(int codepage, char byte) +{ + return 0; /* we don't do DBCS */ +} + +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + if (codepage == DEFAULT_CODEPAGE) { + int n = 0; + mbstate_t state; + + memset(&state, 0, sizeof state); + + while (mblen > 0) { + size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state); + if (i == (size_t)-1 || i == (size_t)-2) + break; + n++; + mbstr += i; + mblen -= i; + } + + return n; + } else if (codepage == CS_NONE) { + int n = 0; + + while (mblen > 0) { + wcstr[n] = 0xD800 | (mbstr[0] & 0xFF); + n++; + mbstr++; + mblen--; + } + + return n; + } else + return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage, + NULL, NULL, 0); +} + +int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, + char *mbstr, int mblen, char *defchr, int *defused, + struct unicode_data *ucsdata) +{ + /* FIXME: we should remove the defused param completely... */ + if (defused) + *defused = 0; + + if (codepage == DEFAULT_CODEPAGE) { + char output[MB_LEN_MAX]; + mbstate_t state; + int n = 0; + + memset(&state, 0, sizeof state); + + while (wclen > 0) { + int i = wcrtomb(output, wcstr[0], &state); + if (i == (size_t)-1 || i > n - mblen) + break; + memcpy(mbstr+n, output, i); + n += i; + wcstr++; + wclen--; + } + + return n; + } else if (codepage == CS_NONE) { + int n = 0; + while (wclen > 0 && n < mblen) { + if (*wcstr >= 0xD800 && *wcstr < 0xD900) + mbstr[n++] = (*wcstr & 0xFF); + else if (defchr) + mbstr[n++] = *defchr; + wcstr++; + wclen--; + } + return n; + } else { + return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage, + NULL, defchr?defchr:NULL, defchr?1:0); + } +} + +/* + * Return value is TRUE if pterm is to run in direct-to-font mode. + */ +int init_ucs(struct unicode_data *ucsdata, char *linecharset, + int utf8_override, int font_charset, int vtmode) +{ + int i, ret = 0; + + /* + * In the platform-independent parts of the code, font_codepage + * is used only for system DBCS support - which we don't + * support at all. So we set this to something which will never + * be used. + */ + ucsdata->font_codepage = -1; + + /* + * If utf8_override is set and the POSIX locale settings + * dictate a UTF-8 character set, then just go straight for + * UTF-8. + */ + ucsdata->line_codepage = CS_NONE; + if (utf8_override) { + const char *s; + if (((s = getenv("LC_ALL")) && *s) || + ((s = getenv("LC_CTYPE")) && *s) || + ((s = getenv("LANG")) && *s)) { + if (strstr(s, "UTF-8")) + ucsdata->line_codepage = CS_UTF8; + } + } + + /* + * Failing that, line_codepage should be decoded from the + * specification in conf. + */ + if (ucsdata->line_codepage == CS_NONE) + ucsdata->line_codepage = decode_codepage(linecharset); + + /* + * If line_codepage is _still_ CS_NONE, we assume we're using + * the font's own encoding. This has been passed in to us, so + * we use that. If it's still CS_NONE after _that_ - i.e. the + * font we were given had an incomprehensible charset - then we + * fall back to using the D800 page. + */ + if (ucsdata->line_codepage == CS_NONE) + ucsdata->line_codepage = font_charset; + + if (ucsdata->line_codepage == CS_NONE) + ret = 1; + + /* + * Set up unitab_line, by translating each individual character + * in the line codepage into Unicode. + */ + for (i = 0; i < 256; i++) { + char c[1]; + const char *p; + wchar_t wc[1]; + int len; + c[0] = i; + p = c; + len = 1; + if (ucsdata->line_codepage == CS_NONE) + ucsdata->unitab_line[i] = 0xD800 | i; + else if (1 == charset_to_unicode(&p, &len, wc, 1, + ucsdata->line_codepage, + NULL, L"", 0)) + ucsdata->unitab_line[i] = wc[0]; + else + ucsdata->unitab_line[i] = 0xFFFD; + } + + /* + * Set up unitab_xterm. This is the same as unitab_line except + * in the line-drawing regions, where it follows the Unicode + * encoding. + * + * (Note that the strange X encoding of line-drawing characters + * in the bottom 32 glyphs of ISO8859-1 fonts is taken care of + * by the font encoding, which will spot such a font and act as + * if it were in a variant encoding of ISO8859-1.) + */ + for (i = 0; i < 256; i++) { + static const wchar_t unitab_xterm_std[32] = { + 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, + 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020 + }; + static const wchar_t unitab_xterm_poorman[32] = + L"*#****o~**+++++-----++++|****L. "; + + const wchar_t *ptr; + + if (vtmode == VT_POORMAN) + ptr = unitab_xterm_poorman; + else + ptr = unitab_xterm_std; + + if (i >= 0x5F && i < 0x7F) + ucsdata->unitab_xterm[i] = ptr[i & 0x1F]; + else + ucsdata->unitab_xterm[i] = ucsdata->unitab_line[i]; + } + + /* + * Set up unitab_scoacs. The SCO Alternate Character Set is + * simply CP437. + */ + for (i = 0; i < 256; i++) { + char c[1]; + const char *p; + wchar_t wc[1]; + int len; + c[0] = i; + p = c; + len = 1; + if (1 == charset_to_unicode(&p, &len, wc, 1, CS_CP437, NULL, L"", 0)) + ucsdata->unitab_scoacs[i] = wc[0]; + else + ucsdata->unitab_scoacs[i] = 0xFFFD; + } + + /* + * Find the control characters in the line codepage. For + * direct-to-font mode using the D800 hack, we assume 00-1F and + * 7F are controls, but allow 80-9F through. (It's as good a + * guess as anything; and my bet is that half the weird fonts + * used in this way will be IBM or MS code pages anyway.) + */ + for (i = 0; i < 256; i++) { + int lineval = ucsdata->unitab_line[i]; + if (lineval < ' ' || (lineval >= 0x7F && lineval < 0xA0) || + (lineval >= 0xD800 && lineval < 0xD820) || (lineval == 0xD87F)) + ucsdata->unitab_ctrl[i] = i; + else + ucsdata->unitab_ctrl[i] = 0xFF; + } + + return ret; +} + +const char *cp_name(int codepage) +{ + if (codepage == CS_NONE) + return "Use font encoding"; + return charset_to_localenc(codepage); +} + +const char *cp_enumerate(int index) +{ + int charset; + charset = charset_localenc_nth(index); + if (charset == CS_NONE) { + /* "Use font encoding" comes after all the named charsets */ + if (charset_localenc_nth(index-1) != CS_NONE) + return "Use font encoding"; + return NULL; + } + return charset_to_localenc(charset); +} + +int decode_codepage(char *cp_name) +{ + if (!cp_name || !*cp_name) + return CS_UTF8; + return charset_from_localenc(cp_name); +} diff --git a/netbox/libs/Putty/unix/xkeysym.c b/netbox/libs/Putty/unix/xkeysym.c new file mode 100644 index 000000000..45f6a0cb6 --- /dev/null +++ b/netbox/libs/Putty/unix/xkeysym.c @@ -0,0 +1,1011 @@ +/* + * xkeysym.c: mapping from X keysyms to Unicode values + * + * The basic idea of this is shamelessly cribbed from xterm. The + * actual character data is generated from Markus Kuhn's proposed + * redraft of the X11 keysym mapping table, using the following + * piece of Perl/sh code: + +wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \ +perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \ + -e ' do { $a{$1 * 256+ $2} = hex $3; };' \ + -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \ + -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \ + -e 'BEGIN { $a{0x13a4} = 0x20ac }' + + * (The BEGIN clause inserts a mapping for the Euro sign which for + * some reason isn't in the list but xterm supports. *shrug*.) + */ + +#include "misc.h" + +struct keysym { + /* + * Currently nothing in here is above 0xFFFF, so I'll use + * `unsigned short' to save space. + */ + unsigned short keysym; + unsigned short unicode; +}; + +static struct keysym keysyms[] = { + {0x20, 0x20}, + {0x21, 0x21}, + {0x22, 0x22}, + {0x23, 0x23}, + {0x24, 0x24}, + {0x25, 0x25}, + {0x26, 0x26}, + {0x27, 0x27}, + {0x28, 0x28}, + {0x29, 0x29}, + {0x2a, 0x2a}, + {0x2b, 0x2b}, + {0x2c, 0x2c}, + {0x2d, 0x2d}, + {0x2e, 0x2e}, + {0x2f, 0x2f}, + {0x30, 0x30}, + {0x31, 0x31}, + {0x32, 0x32}, + {0x33, 0x33}, + {0x34, 0x34}, + {0x35, 0x35}, + {0x36, 0x36}, + {0x37, 0x37}, + {0x38, 0x38}, + {0x39, 0x39}, + {0x3a, 0x3a}, + {0x3b, 0x3b}, + {0x3c, 0x3c}, + {0x3d, 0x3d}, + {0x3e, 0x3e}, + {0x3f, 0x3f}, + {0x40, 0x40}, + {0x41, 0x41}, + {0x42, 0x42}, + {0x43, 0x43}, + {0x44, 0x44}, + {0x45, 0x45}, + {0x46, 0x46}, + {0x47, 0x47}, + {0x48, 0x48}, + {0x49, 0x49}, + {0x4a, 0x4a}, + {0x4b, 0x4b}, + {0x4c, 0x4c}, + {0x4d, 0x4d}, + {0x4e, 0x4e}, + {0x4f, 0x4f}, + {0x50, 0x50}, + {0x51, 0x51}, + {0x52, 0x52}, + {0x53, 0x53}, + {0x54, 0x54}, + {0x55, 0x55}, + {0x56, 0x56}, + {0x57, 0x57}, + {0x58, 0x58}, + {0x59, 0x59}, + {0x5a, 0x5a}, + {0x5b, 0x5b}, + {0x5c, 0x5c}, + {0x5d, 0x5d}, + {0x5e, 0x5e}, + {0x5f, 0x5f}, + {0x60, 0x60}, + {0x61, 0x61}, + {0x62, 0x62}, + {0x63, 0x63}, + {0x64, 0x64}, + {0x65, 0x65}, + {0x66, 0x66}, + {0x67, 0x67}, + {0x68, 0x68}, + {0x69, 0x69}, + {0x6a, 0x6a}, + {0x6b, 0x6b}, + {0x6c, 0x6c}, + {0x6d, 0x6d}, + {0x6e, 0x6e}, + {0x6f, 0x6f}, + {0x70, 0x70}, + {0x71, 0x71}, + {0x72, 0x72}, + {0x73, 0x73}, + {0x74, 0x74}, + {0x75, 0x75}, + {0x76, 0x76}, + {0x77, 0x77}, + {0x78, 0x78}, + {0x79, 0x79}, + {0x7a, 0x7a}, + {0x7b, 0x7b}, + {0x7c, 0x7c}, + {0x7d, 0x7d}, + {0x7e, 0x7e}, + {0xa0, 0xa0}, + {0xa1, 0xa1}, + {0xa2, 0xa2}, + {0xa3, 0xa3}, + {0xa4, 0xa4}, + {0xa5, 0xa5}, + {0xa6, 0xa6}, + {0xa7, 0xa7}, + {0xa8, 0xa8}, + {0xa9, 0xa9}, + {0xaa, 0xaa}, + {0xab, 0xab}, + {0xac, 0xac}, + {0xad, 0xad}, + {0xae, 0xae}, + {0xaf, 0xaf}, + {0xb0, 0xb0}, + {0xb1, 0xb1}, + {0xb2, 0xb2}, + {0xb3, 0xb3}, + {0xb4, 0xb4}, + {0xb5, 0xb5}, + {0xb6, 0xb6}, + {0xb7, 0xb7}, + {0xb8, 0xb8}, + {0xb9, 0xb9}, + {0xba, 0xba}, + {0xbb, 0xbb}, + {0xbc, 0xbc}, + {0xbd, 0xbd}, + {0xbe, 0xbe}, + {0xbf, 0xbf}, + {0xc0, 0xc0}, + {0xc1, 0xc1}, + {0xc2, 0xc2}, + {0xc3, 0xc3}, + {0xc4, 0xc4}, + {0xc5, 0xc5}, + {0xc6, 0xc6}, + {0xc7, 0xc7}, + {0xc8, 0xc8}, + {0xc9, 0xc9}, + {0xca, 0xca}, + {0xcb, 0xcb}, + {0xcc, 0xcc}, + {0xcd, 0xcd}, + {0xce, 0xce}, + {0xcf, 0xcf}, + {0xd0, 0xd0}, + {0xd1, 0xd1}, + {0xd2, 0xd2}, + {0xd3, 0xd3}, + {0xd4, 0xd4}, + {0xd5, 0xd5}, + {0xd6, 0xd6}, + {0xd7, 0xd7}, + {0xd8, 0xd8}, + {0xd9, 0xd9}, + {0xda, 0xda}, + {0xdb, 0xdb}, + {0xdc, 0xdc}, + {0xdd, 0xdd}, + {0xde, 0xde}, + {0xdf, 0xdf}, + {0xe0, 0xe0}, + {0xe1, 0xe1}, + {0xe2, 0xe2}, + {0xe3, 0xe3}, + {0xe4, 0xe4}, + {0xe5, 0xe5}, + {0xe6, 0xe6}, + {0xe7, 0xe7}, + {0xe8, 0xe8}, + {0xe9, 0xe9}, + {0xea, 0xea}, + {0xeb, 0xeb}, + {0xec, 0xec}, + {0xed, 0xed}, + {0xee, 0xee}, + {0xef, 0xef}, + {0xf0, 0xf0}, + {0xf1, 0xf1}, + {0xf2, 0xf2}, + {0xf3, 0xf3}, + {0xf4, 0xf4}, + {0xf5, 0xf5}, + {0xf6, 0xf6}, + {0xf7, 0xf7}, + {0xf8, 0xf8}, + {0xf9, 0xf9}, + {0xfa, 0xfa}, + {0xfb, 0xfb}, + {0xfc, 0xfc}, + {0xfd, 0xfd}, + {0xfe, 0xfe}, + {0xff, 0xff}, + {0x1a1, 0x104}, + {0x1a2, 0x2d8}, + {0x1a3, 0x141}, + {0x1a5, 0x13d}, + {0x1a6, 0x15a}, + {0x1a9, 0x160}, + {0x1aa, 0x15e}, + {0x1ab, 0x164}, + {0x1ac, 0x179}, + {0x1ae, 0x17d}, + {0x1af, 0x17b}, + {0x1b1, 0x105}, + {0x1b2, 0x2db}, + {0x1b3, 0x142}, + {0x1b5, 0x13e}, + {0x1b6, 0x15b}, + {0x1b7, 0x2c7}, + {0x1b9, 0x161}, + {0x1ba, 0x15f}, + {0x1bb, 0x165}, + {0x1bc, 0x17a}, + {0x1bd, 0x2dd}, + {0x1be, 0x17e}, + {0x1bf, 0x17c}, + {0x1c0, 0x154}, + {0x1c3, 0x102}, + {0x1c5, 0x139}, + {0x1c6, 0x106}, + {0x1c8, 0x10c}, + {0x1ca, 0x118}, + {0x1cc, 0x11a}, + {0x1cf, 0x10e}, + {0x1d0, 0x110}, + {0x1d1, 0x143}, + {0x1d2, 0x147}, + {0x1d5, 0x150}, + {0x1d8, 0x158}, + {0x1d9, 0x16e}, + {0x1db, 0x170}, + {0x1de, 0x162}, + {0x1e0, 0x155}, + {0x1e3, 0x103}, + {0x1e5, 0x13a}, + {0x1e6, 0x107}, + {0x1e8, 0x10d}, + {0x1ea, 0x119}, + {0x1ec, 0x11b}, + {0x1ef, 0x10f}, + {0x1f0, 0x111}, + {0x1f1, 0x144}, + {0x1f2, 0x148}, + {0x1f5, 0x151}, + {0x1f8, 0x159}, + {0x1f9, 0x16f}, + {0x1fb, 0x171}, + {0x1fe, 0x163}, + {0x1ff, 0x2d9}, + {0x2a1, 0x126}, + {0x2a6, 0x124}, + {0x2a9, 0x130}, + {0x2ab, 0x11e}, + {0x2ac, 0x134}, + {0x2b1, 0x127}, + {0x2b6, 0x125}, + {0x2b9, 0x131}, + {0x2bb, 0x11f}, + {0x2bc, 0x135}, + {0x2c5, 0x10a}, + {0x2c6, 0x108}, + {0x2d5, 0x120}, + {0x2d8, 0x11c}, + {0x2dd, 0x16c}, + {0x2de, 0x15c}, + {0x2e5, 0x10b}, + {0x2e6, 0x109}, + {0x2f5, 0x121}, + {0x2f8, 0x11d}, + {0x2fd, 0x16d}, + {0x2fe, 0x15d}, + {0x3a2, 0x138}, + {0x3a3, 0x156}, + {0x3a5, 0x128}, + {0x3a6, 0x13b}, + {0x3aa, 0x112}, + {0x3ab, 0x122}, + {0x3ac, 0x166}, + {0x3b3, 0x157}, + {0x3b5, 0x129}, + {0x3b6, 0x13c}, + {0x3ba, 0x113}, + {0x3bb, 0x123}, + {0x3bc, 0x167}, + {0x3bd, 0x14a}, + {0x3bf, 0x14b}, + {0x3c0, 0x100}, + {0x3c7, 0x12e}, + {0x3cc, 0x116}, + {0x3cf, 0x12a}, + {0x3d1, 0x145}, + {0x3d2, 0x14c}, + {0x3d3, 0x136}, + {0x3d9, 0x172}, + {0x3dd, 0x168}, + {0x3de, 0x16a}, + {0x3e0, 0x101}, + {0x3e7, 0x12f}, + {0x3ec, 0x117}, + {0x3ef, 0x12b}, + {0x3f1, 0x146}, + {0x3f2, 0x14d}, + {0x3f3, 0x137}, + {0x3f9, 0x173}, + {0x3fd, 0x169}, + {0x3fe, 0x16b}, + {0x47e, 0x203e}, + {0x4a1, 0x3002}, + {0x4a2, 0x300c}, + {0x4a3, 0x300d}, + {0x4a4, 0x3001}, + {0x4a5, 0x30fb}, + {0x4a6, 0x30f2}, + {0x4a7, 0x30a1}, + {0x4a8, 0x30a3}, + {0x4a9, 0x30a5}, + {0x4aa, 0x30a7}, + {0x4ab, 0x30a9}, + {0x4ac, 0x30e3}, + {0x4ad, 0x30e5}, + {0x4ae, 0x30e7}, + {0x4af, 0x30c3}, + {0x4b0, 0x30fc}, + {0x4b1, 0x30a2}, + {0x4b2, 0x30a4}, + {0x4b3, 0x30a6}, + {0x4b4, 0x30a8}, + {0x4b5, 0x30aa}, + {0x4b6, 0x30ab}, + {0x4b7, 0x30ad}, + {0x4b8, 0x30af}, + {0x4b9, 0x30b1}, + {0x4ba, 0x30b3}, + {0x4bb, 0x30b5}, + {0x4bc, 0x30b7}, + {0x4bd, 0x30b9}, + {0x4be, 0x30bb}, + {0x4bf, 0x30bd}, + {0x4c0, 0x30bf}, + {0x4c1, 0x30c1}, + {0x4c2, 0x30c4}, + {0x4c3, 0x30c6}, + {0x4c4, 0x30c8}, + {0x4c5, 0x30ca}, + {0x4c6, 0x30cb}, + {0x4c7, 0x30cc}, + {0x4c8, 0x30cd}, + {0x4c9, 0x30ce}, + {0x4ca, 0x30cf}, + {0x4cb, 0x30d2}, + {0x4cc, 0x30d5}, + {0x4cd, 0x30d8}, + {0x4ce, 0x30db}, + {0x4cf, 0x30de}, + {0x4d0, 0x30df}, + {0x4d1, 0x30e0}, + {0x4d2, 0x30e1}, + {0x4d3, 0x30e2}, + {0x4d4, 0x30e4}, + {0x4d5, 0x30e6}, + {0x4d6, 0x30e8}, + {0x4d7, 0x30e9}, + {0x4d8, 0x30ea}, + {0x4d9, 0x30eb}, + {0x4da, 0x30ec}, + {0x4db, 0x30ed}, + {0x4dc, 0x30ef}, + {0x4dd, 0x30f3}, + {0x4de, 0x309b}, + {0x4df, 0x309c}, + {0x5ac, 0x60c}, + {0x5bb, 0x61b}, + {0x5bf, 0x61f}, + {0x5c1, 0x621}, + {0x5c2, 0x622}, + {0x5c3, 0x623}, + {0x5c4, 0x624}, + {0x5c5, 0x625}, + {0x5c6, 0x626}, + {0x5c7, 0x627}, + {0x5c8, 0x628}, + {0x5c9, 0x629}, + {0x5ca, 0x62a}, + {0x5cb, 0x62b}, + {0x5cc, 0x62c}, + {0x5cd, 0x62d}, + {0x5ce, 0x62e}, + {0x5cf, 0x62f}, + {0x5d0, 0x630}, + {0x5d1, 0x631}, + {0x5d2, 0x632}, + {0x5d3, 0x633}, + {0x5d4, 0x634}, + {0x5d5, 0x635}, + {0x5d6, 0x636}, + {0x5d7, 0x637}, + {0x5d8, 0x638}, + {0x5d9, 0x639}, + {0x5da, 0x63a}, + {0x5e0, 0x640}, + {0x5e1, 0x641}, + {0x5e2, 0x642}, + {0x5e3, 0x643}, + {0x5e4, 0x644}, + {0x5e5, 0x645}, + {0x5e6, 0x646}, + {0x5e7, 0x647}, + {0x5e8, 0x648}, + {0x5e9, 0x649}, + {0x5ea, 0x64a}, + {0x5eb, 0x64b}, + {0x5ec, 0x64c}, + {0x5ed, 0x64d}, + {0x5ee, 0x64e}, + {0x5ef, 0x64f}, + {0x5f0, 0x650}, + {0x5f1, 0x651}, + {0x5f2, 0x652}, + {0x6a1, 0x452}, + {0x6a2, 0x453}, + {0x6a3, 0x451}, + {0x6a4, 0x454}, + {0x6a5, 0x455}, + {0x6a6, 0x456}, + {0x6a7, 0x457}, + {0x6a8, 0x458}, + {0x6a9, 0x459}, + {0x6aa, 0x45a}, + {0x6ab, 0x45b}, + {0x6ac, 0x45c}, + {0x6ae, 0x45e}, + {0x6af, 0x45f}, + {0x6b0, 0x2116}, + {0x6b1, 0x402}, + {0x6b2, 0x403}, + {0x6b3, 0x401}, + {0x6b4, 0x404}, + {0x6b5, 0x405}, + {0x6b6, 0x406}, + {0x6b7, 0x407}, + {0x6b8, 0x408}, + {0x6b9, 0x409}, + {0x6ba, 0x40a}, + {0x6bb, 0x40b}, + {0x6bc, 0x40c}, + {0x6be, 0x40e}, + {0x6bf, 0x40f}, + {0x6c0, 0x44e}, + {0x6c1, 0x430}, + {0x6c2, 0x431}, + {0x6c3, 0x446}, + {0x6c4, 0x434}, + {0x6c5, 0x435}, + {0x6c6, 0x444}, + {0x6c7, 0x433}, + {0x6c8, 0x445}, + {0x6c9, 0x438}, + {0x6ca, 0x439}, + {0x6cb, 0x43a}, + {0x6cc, 0x43b}, + {0x6cd, 0x43c}, + {0x6ce, 0x43d}, + {0x6cf, 0x43e}, + {0x6d0, 0x43f}, + {0x6d1, 0x44f}, + {0x6d2, 0x440}, + {0x6d3, 0x441}, + {0x6d4, 0x442}, + {0x6d5, 0x443}, + {0x6d6, 0x436}, + {0x6d7, 0x432}, + {0x6d8, 0x44c}, + {0x6d9, 0x44b}, + {0x6da, 0x437}, + {0x6db, 0x448}, + {0x6dc, 0x44d}, + {0x6dd, 0x449}, + {0x6de, 0x447}, + {0x6df, 0x44a}, + {0x6e0, 0x42e}, + {0x6e1, 0x410}, + {0x6e2, 0x411}, + {0x6e3, 0x426}, + {0x6e4, 0x414}, + {0x6e5, 0x415}, + {0x6e6, 0x424}, + {0x6e7, 0x413}, + {0x6e8, 0x425}, + {0x6e9, 0x418}, + {0x6ea, 0x419}, + {0x6eb, 0x41a}, + {0x6ec, 0x41b}, + {0x6ed, 0x41c}, + {0x6ee, 0x41d}, + {0x6ef, 0x41e}, + {0x6f0, 0x41f}, + {0x6f1, 0x42f}, + {0x6f2, 0x420}, + {0x6f3, 0x421}, + {0x6f4, 0x422}, + {0x6f5, 0x423}, + {0x6f6, 0x416}, + {0x6f7, 0x412}, + {0x6f8, 0x42c}, + {0x6f9, 0x42b}, + {0x6fa, 0x417}, + {0x6fb, 0x428}, + {0x6fc, 0x42d}, + {0x6fd, 0x429}, + {0x6fe, 0x427}, + {0x6ff, 0x42a}, + {0x7a1, 0x386}, + {0x7a2, 0x388}, + {0x7a3, 0x389}, + {0x7a4, 0x38a}, + {0x7a5, 0x3aa}, + {0x7a7, 0x38c}, + {0x7a8, 0x38e}, + {0x7a9, 0x3ab}, + {0x7ab, 0x38f}, + {0x7ae, 0x385}, + {0x7af, 0x2015}, + {0x7b1, 0x3ac}, + {0x7b2, 0x3ad}, + {0x7b3, 0x3ae}, + {0x7b4, 0x3af}, + {0x7b5, 0x3ca}, + {0x7b6, 0x390}, + {0x7b7, 0x3cc}, + {0x7b8, 0x3cd}, + {0x7b9, 0x3cb}, + {0x7ba, 0x3b0}, + {0x7bb, 0x3ce}, + {0x7c1, 0x391}, + {0x7c2, 0x392}, + {0x7c3, 0x393}, + {0x7c4, 0x394}, + {0x7c5, 0x395}, + {0x7c6, 0x396}, + {0x7c7, 0x397}, + {0x7c8, 0x398}, + {0x7c9, 0x399}, + {0x7ca, 0x39a}, + {0x7cb, 0x39b}, + {0x7cc, 0x39c}, + {0x7cd, 0x39d}, + {0x7ce, 0x39e}, + {0x7cf, 0x39f}, + {0x7d0, 0x3a0}, + {0x7d1, 0x3a1}, + {0x7d2, 0x3a3}, + {0x7d4, 0x3a4}, + {0x7d5, 0x3a5}, + {0x7d6, 0x3a6}, + {0x7d7, 0x3a7}, + {0x7d8, 0x3a8}, + {0x7d9, 0x3a9}, + {0x7e1, 0x3b1}, + {0x7e2, 0x3b2}, + {0x7e3, 0x3b3}, + {0x7e4, 0x3b4}, + {0x7e5, 0x3b5}, + {0x7e6, 0x3b6}, + {0x7e7, 0x3b7}, + {0x7e8, 0x3b8}, + {0x7e9, 0x3b9}, + {0x7ea, 0x3ba}, + {0x7eb, 0x3bb}, + {0x7ec, 0x3bc}, + {0x7ed, 0x3bd}, + {0x7ee, 0x3be}, + {0x7ef, 0x3bf}, + {0x7f0, 0x3c0}, + {0x7f1, 0x3c1}, + {0x7f2, 0x3c3}, + {0x7f3, 0x3c2}, + {0x7f4, 0x3c4}, + {0x7f5, 0x3c5}, + {0x7f6, 0x3c6}, + {0x7f7, 0x3c7}, + {0x7f8, 0x3c8}, + {0x7f9, 0x3c9}, + {0x8a1, 0x23b7}, + {0x8a2, 0x250c}, + {0x8a3, 0x2500}, + {0x8a4, 0x2320}, + {0x8a5, 0x2321}, + {0x8a6, 0x2502}, + {0x8a7, 0x23a1}, + {0x8a8, 0x23a3}, + {0x8a9, 0x23a4}, + {0x8aa, 0x23a6}, + {0x8ab, 0x239b}, + {0x8ac, 0x239d}, + {0x8ad, 0x239e}, + {0x8ae, 0x23a0}, + {0x8af, 0x23a8}, + {0x8b0, 0x23ac}, + {0x8bc, 0x2264}, + {0x8bd, 0x2260}, + {0x8be, 0x2265}, + {0x8bf, 0x222b}, + {0x8c0, 0x2234}, + {0x8c1, 0x221d}, + {0x8c2, 0x221e}, + {0x8c5, 0x2207}, + {0x8c8, 0x223c}, + {0x8c9, 0x2243}, + {0x8cd, 0x21d4}, + {0x8ce, 0x21d2}, + {0x8cf, 0x2261}, + {0x8d6, 0x221a}, + {0x8da, 0x2282}, + {0x8db, 0x2283}, + {0x8dc, 0x2229}, + {0x8dd, 0x222a}, + {0x8de, 0x2227}, + {0x8df, 0x2228}, + {0x8ef, 0x2202}, + {0x8f6, 0x192}, + {0x8fb, 0x2190}, + {0x8fc, 0x2191}, + {0x8fd, 0x2192}, + {0x8fe, 0x2193}, + {0x9e0, 0x25c6}, + {0x9e1, 0x2592}, + {0x9e2, 0x2409}, + {0x9e3, 0x240c}, + {0x9e4, 0x240d}, + {0x9e5, 0x240a}, + {0x9e8, 0x2424}, + {0x9e9, 0x240b}, + {0x9ea, 0x2518}, + {0x9eb, 0x2510}, + {0x9ec, 0x250c}, + {0x9ed, 0x2514}, + {0x9ee, 0x253c}, + {0x9ef, 0x23ba}, + {0x9f0, 0x23bb}, + {0x9f1, 0x2500}, + {0x9f2, 0x23bc}, + {0x9f3, 0x23bd}, + {0x9f4, 0x251c}, + {0x9f5, 0x2524}, + {0x9f6, 0x2534}, + {0x9f7, 0x252c}, + {0x9f8, 0x2502}, + {0xaa1, 0x2003}, + {0xaa2, 0x2002}, + {0xaa3, 0x2004}, + {0xaa4, 0x2005}, + {0xaa5, 0x2007}, + {0xaa6, 0x2008}, + {0xaa7, 0x2009}, + {0xaa8, 0x200a}, + {0xaa9, 0x2014}, + {0xaaa, 0x2013}, + {0xaae, 0x2026}, + {0xaaf, 0x2025}, + {0xab0, 0x2153}, + {0xab1, 0x2154}, + {0xab2, 0x2155}, + {0xab3, 0x2156}, + {0xab4, 0x2157}, + {0xab5, 0x2158}, + {0xab6, 0x2159}, + {0xab7, 0x215a}, + {0xab8, 0x2105}, + {0xabb, 0x2012}, + {0xabc, 0x2329}, + {0xabe, 0x232a}, + {0xac3, 0x215b}, + {0xac4, 0x215c}, + {0xac5, 0x215d}, + {0xac6, 0x215e}, + {0xac9, 0x2122}, + {0xaca, 0x2613}, + {0xacc, 0x25c1}, + {0xacd, 0x25b7}, + {0xace, 0x25cb}, + {0xacf, 0x25af}, + {0xad0, 0x2018}, + {0xad1, 0x2019}, + {0xad2, 0x201c}, + {0xad3, 0x201d}, + {0xad4, 0x211e}, + {0xad6, 0x2032}, + {0xad7, 0x2033}, + {0xad9, 0x271d}, + {0xadb, 0x25ac}, + {0xadc, 0x25c0}, + {0xadd, 0x25b6}, + {0xade, 0x25cf}, + {0xadf, 0x25ae}, + {0xae0, 0x25e6}, + {0xae1, 0x25ab}, + {0xae2, 0x25ad}, + {0xae3, 0x25b3}, + {0xae4, 0x25bd}, + {0xae5, 0x2606}, + {0xae6, 0x2022}, + {0xae7, 0x25aa}, + {0xae8, 0x25b2}, + {0xae9, 0x25bc}, + {0xaea, 0x261c}, + {0xaeb, 0x261e}, + {0xaec, 0x2663}, + {0xaed, 0x2666}, + {0xaee, 0x2665}, + {0xaf0, 0x2720}, + {0xaf1, 0x2020}, + {0xaf2, 0x2021}, + {0xaf3, 0x2713}, + {0xaf4, 0x2717}, + {0xaf5, 0x266f}, + {0xaf6, 0x266d}, + {0xaf7, 0x2642}, + {0xaf8, 0x2640}, + {0xaf9, 0x260e}, + {0xafa, 0x2315}, + {0xafb, 0x2117}, + {0xafc, 0x2038}, + {0xafd, 0x201a}, + {0xafe, 0x201e}, + {0xba3, 0x3c}, + {0xba6, 0x3e}, + {0xba8, 0x2228}, + {0xba9, 0x2227}, + {0xbc0, 0xaf}, + {0xbc2, 0x22a5}, + {0xbc3, 0x2229}, + {0xbc4, 0x230a}, + {0xbc6, 0x5f}, + {0xbca, 0x2218}, + {0xbcc, 0x2395}, + {0xbce, 0x22a4}, + {0xbcf, 0x25cb}, + {0xbd3, 0x2308}, + {0xbd6, 0x222a}, + {0xbd8, 0x2283}, + {0xbda, 0x2282}, + {0xbdc, 0x22a2}, + {0xbfc, 0x22a3}, + {0xcdf, 0x2017}, + {0xce0, 0x5d0}, + {0xce1, 0x5d1}, + {0xce2, 0x5d2}, + {0xce3, 0x5d3}, + {0xce4, 0x5d4}, + {0xce5, 0x5d5}, + {0xce6, 0x5d6}, + {0xce7, 0x5d7}, + {0xce8, 0x5d8}, + {0xce9, 0x5d9}, + {0xcea, 0x5da}, + {0xceb, 0x5db}, + {0xcec, 0x5dc}, + {0xced, 0x5dd}, + {0xcee, 0x5de}, + {0xcef, 0x5df}, + {0xcf0, 0x5e0}, + {0xcf1, 0x5e1}, + {0xcf2, 0x5e2}, + {0xcf3, 0x5e3}, + {0xcf4, 0x5e4}, + {0xcf5, 0x5e5}, + {0xcf6, 0x5e6}, + {0xcf7, 0x5e7}, + {0xcf8, 0x5e8}, + {0xcf9, 0x5e9}, + {0xcfa, 0x5ea}, + {0xda1, 0xe01}, + {0xda2, 0xe02}, + {0xda3, 0xe03}, + {0xda4, 0xe04}, + {0xda5, 0xe05}, + {0xda6, 0xe06}, + {0xda7, 0xe07}, + {0xda8, 0xe08}, + {0xda9, 0xe09}, + {0xdaa, 0xe0a}, + {0xdab, 0xe0b}, + {0xdac, 0xe0c}, + {0xdad, 0xe0d}, + {0xdae, 0xe0e}, + {0xdaf, 0xe0f}, + {0xdb0, 0xe10}, + {0xdb1, 0xe11}, + {0xdb2, 0xe12}, + {0xdb3, 0xe13}, + {0xdb4, 0xe14}, + {0xdb5, 0xe15}, + {0xdb6, 0xe16}, + {0xdb7, 0xe17}, + {0xdb8, 0xe18}, + {0xdb9, 0xe19}, + {0xdba, 0xe1a}, + {0xdbb, 0xe1b}, + {0xdbc, 0xe1c}, + {0xdbd, 0xe1d}, + {0xdbe, 0xe1e}, + {0xdbf, 0xe1f}, + {0xdc0, 0xe20}, + {0xdc1, 0xe21}, + {0xdc2, 0xe22}, + {0xdc3, 0xe23}, + {0xdc4, 0xe24}, + {0xdc5, 0xe25}, + {0xdc6, 0xe26}, + {0xdc7, 0xe27}, + {0xdc8, 0xe28}, + {0xdc9, 0xe29}, + {0xdca, 0xe2a}, + {0xdcb, 0xe2b}, + {0xdcc, 0xe2c}, + {0xdcd, 0xe2d}, + {0xdce, 0xe2e}, + {0xdcf, 0xe2f}, + {0xdd0, 0xe30}, + {0xdd1, 0xe31}, + {0xdd2, 0xe32}, + {0xdd3, 0xe33}, + {0xdd4, 0xe34}, + {0xdd5, 0xe35}, + {0xdd6, 0xe36}, + {0xdd7, 0xe37}, + {0xdd8, 0xe38}, + {0xdd9, 0xe39}, + {0xdda, 0xe3a}, + {0xddf, 0xe3f}, + {0xde0, 0xe40}, + {0xde1, 0xe41}, + {0xde2, 0xe42}, + {0xde3, 0xe43}, + {0xde4, 0xe44}, + {0xde5, 0xe45}, + {0xde6, 0xe46}, + {0xde7, 0xe47}, + {0xde8, 0xe48}, + {0xde9, 0xe49}, + {0xdea, 0xe4a}, + {0xdeb, 0xe4b}, + {0xdec, 0xe4c}, + {0xded, 0xe4d}, + {0xdf0, 0xe50}, + {0xdf1, 0xe51}, + {0xdf2, 0xe52}, + {0xdf3, 0xe53}, + {0xdf4, 0xe54}, + {0xdf5, 0xe55}, + {0xdf6, 0xe56}, + {0xdf7, 0xe57}, + {0xdf8, 0xe58}, + {0xdf9, 0xe59}, + {0xea1, 0x3131}, + {0xea2, 0x3132}, + {0xea3, 0x3133}, + {0xea4, 0x3134}, + {0xea5, 0x3135}, + {0xea6, 0x3136}, + {0xea7, 0x3137}, + {0xea8, 0x3138}, + {0xea9, 0x3139}, + {0xeaa, 0x313a}, + {0xeab, 0x313b}, + {0xeac, 0x313c}, + {0xead, 0x313d}, + {0xeae, 0x313e}, + {0xeaf, 0x313f}, + {0xeb0, 0x3140}, + {0xeb1, 0x3141}, + {0xeb2, 0x3142}, + {0xeb3, 0x3143}, + {0xeb4, 0x3144}, + {0xeb5, 0x3145}, + {0xeb6, 0x3146}, + {0xeb7, 0x3147}, + {0xeb8, 0x3148}, + {0xeb9, 0x3149}, + {0xeba, 0x314a}, + {0xebb, 0x314b}, + {0xebc, 0x314c}, + {0xebd, 0x314d}, + {0xebe, 0x314e}, + {0xebf, 0x314f}, + {0xec0, 0x3150}, + {0xec1, 0x3151}, + {0xec2, 0x3152}, + {0xec3, 0x3153}, + {0xec4, 0x3154}, + {0xec5, 0x3155}, + {0xec6, 0x3156}, + {0xec7, 0x3157}, + {0xec8, 0x3158}, + {0xec9, 0x3159}, + {0xeca, 0x315a}, + {0xecb, 0x315b}, + {0xecc, 0x315c}, + {0xecd, 0x315d}, + {0xece, 0x315e}, + {0xecf, 0x315f}, + {0xed0, 0x3160}, + {0xed1, 0x3161}, + {0xed2, 0x3162}, + {0xed3, 0x3163}, + {0xed4, 0x11a8}, + {0xed5, 0x11a9}, + {0xed6, 0x11aa}, + {0xed7, 0x11ab}, + {0xed8, 0x11ac}, + {0xed9, 0x11ad}, + {0xeda, 0x11ae}, + {0xedb, 0x11af}, + {0xedc, 0x11b0}, + {0xedd, 0x11b1}, + {0xede, 0x11b2}, + {0xedf, 0x11b3}, + {0xee0, 0x11b4}, + {0xee1, 0x11b5}, + {0xee2, 0x11b6}, + {0xee3, 0x11b7}, + {0xee4, 0x11b8}, + {0xee5, 0x11b9}, + {0xee6, 0x11ba}, + {0xee7, 0x11bb}, + {0xee8, 0x11bc}, + {0xee9, 0x11bd}, + {0xeea, 0x11be}, + {0xeeb, 0x11bf}, + {0xeec, 0x11c0}, + {0xeed, 0x11c1}, + {0xeee, 0x11c2}, + {0xeef, 0x316d}, + {0xef0, 0x3171}, + {0xef1, 0x3178}, + {0xef2, 0x317f}, + {0xef3, 0x3181}, + {0xef4, 0x3184}, + {0xef5, 0x3186}, + {0xef6, 0x318d}, + {0xef7, 0x318e}, + {0xef8, 0x11eb}, + {0xef9, 0x11f0}, + {0xefa, 0x11f9}, + {0xeff, 0x20a9}, + {0x13a4, 0x20ac}, + {0x13bc, 0x152}, + {0x13bd, 0x153}, + {0x13be, 0x178}, + {0x20a0, 0x20a0}, + {0x20a1, 0x20a1}, + {0x20a2, 0x20a2}, + {0x20a3, 0x20a3}, + {0x20a4, 0x20a4}, + {0x20a5, 0x20a5}, + {0x20a6, 0x20a6}, + {0x20a7, 0x20a7}, + {0x20a8, 0x20a8}, + {0x20aa, 0x20aa}, + {0x20ab, 0x20ab}, + {0x20ac, 0x20ac}, +}; + +int keysym_to_unicode(int keysym) +{ + int i, j, k; + + i = -1; + j = lenof(keysyms); + + while (j - i >= 2) { + k = (j + i) / 2; + if (keysyms[k].keysym == keysym) + return keysyms[k].unicode; + else if (keysyms[k].keysym < keysym) + i = k; + else + j = k; + } + return -1; +} diff --git a/netbox/libs/Putty/unix/xpmptcfg.c b/netbox/libs/Putty/unix/xpmptcfg.c new file mode 100644 index 000000000..92835c158 --- /dev/null +++ b/netbox/libs/Putty/unix/xpmptcfg.c @@ -0,0 +1,150 @@ +/* XPM */ +static const char *const cfg_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$ $$$$$$$$$$$", +"$$ OO $$$$", +"$ +oO+###@+ $$$", +" o #.oO.XX@+ $$$", +" oO+.OO.XX@+ $$$", +"$ oOOOO.XX@+ $$$", +"$$ oooOO.X@+ $$$", +"$$ +..oOO.@+ $$$", +"$$ @@@+oOO++ $$", +"$ +++++ oOO #+ $", +" #######+oOO++ $", +" #@@@@@++ oOO $", +" @++++++++ oOO $", +"$ oOO ", +"$$$$$$$$$$$$ oO ", +"$$$$$$$$$$$$$ $" +}; + +/* XPM */ +static const char *const cfg_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$", +"$$$$$ ooOO $$$$$$$$$$$$$$$$$$$$$", +"$$$$$$ ooOO $$$$$$", +"$$ $$$ oOO @@@@@@@@@@@@@+ $$$$$", +"$ oO $$ oOOO @@@@@@@@@@@++ $$$$$", +"$ oOO oOOOO #########@+++ $$$$$", +"$$ oOOOOOOO ..........@+++ $$$$$", +"$$ ooOOOOOOO XXXXXXXXX@+++ $$$$$", +"$$$ ooooooOOO XXXXXXXX@+++ $$$$$", +"$$$$ oo ooOOO XXXXXXX@+++ $$$$$", +"$$$$$$ . ooOOO XXXXXX@+++ $$$$$", +"$$$$$$ #.X ooOOO XXXXX@+++ $$$$$", +"$$$$$$ #.XX ooOOO XXXX@+++ $$$$$", +"$$$$$$ #.XXX ooOOO XXX@+++ $$$$$", +"$$$$$$ #.XXXX ooOOO XX@+++ $$$$$", +"$$$$$$ ####### ooOOO #@+++ $$$", +"$$$$$ #@@@@@@@ ooOOO +++ @#+ $$", +"$$$$ @ @++++++++ ooOOO + @#++ $$", +"$$$ @@ ooOOO @#+++ $$", +"$$ ############### ooOOO @+++ $$", +"$$ #@@@@@@@@@@@@@@@ ooOOO +++ $$", +"$$ #@@@@@@@@@@@@@@@@ ooOOO + $$$", +"$$ #@@@@@@@@@@@@+ ooOOO $$$$", +"$$ @++++++++++++++++++ ooOOO $$$", +"$$$ ooOOO $$", +"$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" +}; + +/* XPM */ +static const char *const cfg_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$ oOOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$ oOOO $$$$$$$$$$", +"$$$ $$$$$$ oOOO @@@@@@@@@@@@@@@@@@@@+ $$$$$$$$$", +"$$ oO $$$$$ oOOOO @@@@@@@@@@@@@@@@@@++ $$$$$$$$$", +"$$ ooO $$$ oOOOO @@@@@@@@@@@@@@@@@+++ $$$$$$$$$", +"$$$ oOO OOOOO ################@++++ $$$$$$$$$", +"$$$ ooOOOOOOOOOOO ++++++++++++++@+++++ $$$$$$$$$", +"$$$ ooOOOOOOOOOOOO .............#+++++ $$$$$$$$$", +"$$$$ oooOOOOoOOOOOO XXXXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$ oooooooOOOOOOO XXXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$ oo ooOOOOOOO XXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ + ooOOOOOOO XXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+. ooOOOOOOO XXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.X ooOOOOOOO XXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XX ooOOOOOOO XXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXX ooOOOOOOO XXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXX ooOOOOOOO XXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXX ooOOOOOOO XXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXX ooOOOOOOO XX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXXX ooOOOOOOO X#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXXXX ooOOOOOOO #+++++ $$$$$$$$$", +"$$$$$$$$ #@########## ooOOOOOOO +++++ $$$$$", +"$$$$$$$ @ #@@@@@@@@@@@@ ooOOOOOOO +++ @@##+ $$$$", +"$$$$$$ @@ #@@@@@@@@@@@@@ ooOOOOOOO + @@##++ $$$$", +"$$$$$ @@@ @++++++++++++++ ooOOOOOOO @@##+++ $$$$", +"$$$$ @@@@ ooOOOOOOO ##++++ $$$$", +"$$$ ####################### ooOOOOOOO @++++ $$$$", +"$$$ ######################## ooOOOOOOO ++++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO +++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO ++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$", +"$$$ @@+++++++++++++++++++++++++++ ooOOOOOOO $$$$", +"$$$ @@++++++++++++++++++++++++++++ ooOOOOOOO $$$", +"$$$$ ooOOOOO $$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooOOO $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" +}; + +const char *const *const cfg_icon[] = { + cfg_icon_0, + cfg_icon_1, + cfg_icon_2, +}; +const int n_cfg_icon = 3; diff --git a/netbox/libs/Putty/unix/xpmpterm.c b/netbox/libs/Putty/unix/xpmpterm.c new file mode 100644 index 000000000..aea5e4e20 --- /dev/null +++ b/netbox/libs/Putty/unix/xpmpterm.c @@ -0,0 +1,143 @@ +/* XPM */ +static const char *const main_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 6 1", +" c black", +". c blue", +"X c #808080", +"o c #C0C0C0", +"O c gray100", +"+ c None", +/* pixels */ +"++++++++++++++++", +"+++ ++++", +"++ OOOOOOOoX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ ooooooooX ++", +"+ XXXXXXXXXXOX +", +" OOOOOOOOOOOoX +", +" OoooooXXXXoXX +", +" oXXXXXXXXXXX ++", +"+ +++", +"++++++++++++++++", +"++++++++++++++++" +}; + +/* XPM */ +static const char *const main_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 7 1", +" c black", +". c navy", +"X c blue", +"o c #808080", +"O c #C0C0C0", +"+ c gray100", +"@ c None", +/* pixels */ +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@ @@@@@@", +"@@@@@@@@ OOOOOOOOOOOOOOOOo @@@@@", +"@@@@@@@ OOOOOOOOOOOOOOOOoo @@@@@", +"@@@@@@ +++++++++++++++Oooo @@@@@", +"@@@@@@ +..............Oooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +++++++++++++++Oooo @@@", +"@@@@@ +OOOOOOOOOOOOOOooo O+o @@", +"@@@@ O Ooooooooooooooooo O+oo @@", +"@@@ OO O+ooo @@", +"@@ ++++++++++++++++++++++Oooo @@", +"@@ +OOOOOOOOOOOOOOOOOOOOOoooo @@", +"@@ +OOOOOOOOOOOOOOOOOOOOOooo @@@", +"@@ +OOOOOOOOOOOOo oOoo @@@@", +"@@ Ooooooooooooooooooooooo @@@@@", +"@@@ @@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" +}; + +/* XPM */ +static const char *const main_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 7 1", +" c black", +". c navy", +"X c blue", +"o c #808080", +"O c #C0C0C0", +"+ c gray100", +"@ c None", +/* pixels */ +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@ @@@@@@@@@@", +"@@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOo @@@@@@@@@", +"@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOoo @@@@@@@@@", +"@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOooo @@@@@@@@@", +"@@@@@@@@@ +++++++++++++++++++++++Ooooo @@@@@@@@@", +"@@@@@@@@@ +oooooooooooooooooooooOooooo @@@@@@@@@", +"@@@@@@@@@ +o....................+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@ +O+++++++++++++++++++++ooooo @@@@@", +"@@@@@@@ O +OOOOOOOOOOOOOOOOOOOOOOoooo OO++o @@@@", +"@@@@@@ OO +OOOOOOOOOOOOOOOOOOOOOOooo OO++oo @@@@", +"@@@@@ OOO Ooooooooooooooooooooooooo OO++ooo @@@@", +"@@@@ OOOO OO++oooo @@@@", +"@@@ ++++++++++++++++++++++++++++++++++Ooooo @@@@", +"@@@ +++++++++++++++++++++++++++++++++Oooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooooo @@@@@", +"@@@ ++OOOOOOOOOOOOOOOOOO oOOoooo @@@@@@", +"@@@ OOoooooooooooooooooooooooooooooooooo @@@@@@@", +"@@@ OOooooooooooooooooooooooooooooooooo @@@@@@@@", +"@@@@ @@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" +}; + +const char *const *const main_icon[] = { + main_icon_0, + main_icon_1, + main_icon_2, +}; +const int n_main_icon = 3; diff --git a/netbox/libs/Putty/unix/xpmpucfg.c b/netbox/libs/Putty/unix/xpmpucfg.c new file mode 100644 index 000000000..34c5d4910 --- /dev/null +++ b/netbox/libs/Putty/unix/xpmpucfg.c @@ -0,0 +1,150 @@ +/* XPM */ +static const char *const cfg_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$ $$ $$", +"$$ OO #####@+ $", +"$ $ oO #XX..@+ $", +" o $ oO+X.O.@+ $", +" oO OO .O.X@+ $", +"$ oOOOOoO++@@+ $", +"$$ oooOOoOO +++ ", +"$ # oooOO +++++ ", +"$ #X..ooOO +++ $", +"$ #X.O. oOO $$", +"$ #.O.X@ oOO $$$", +"$ @++@@@+ oOO $$", +"$ ++++++++ oOO $", +" #####++++ oOO ", +" @+++++++ $$ oO ", +"$ $$$$ $" +}; + +/* XPM */ +static const char *const cfg_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$ $$$$", +"$$$$$$ $$$$$$$ @@@@@@@@@@@+ $$$", +"$$$$$ OO $$$$ ##########@++ $$$", +"$$$$$ ooOO $$$ #.........@++ $$$", +"$$$$$$ ooOO $$ #.XXXXXXXX@++ $$$", +"$$ $$$ oOO $$ #.XXXX XX@++ $$$", +"$ oO $$ oOOO $ #.XXX O XX@++ $$$", +"$ oOO oOOOO $ #.X O XXX@++ $$$", +"$$ oOOOOOOO $$ #. OO XXXX@++ $$$", +"$$ ooOOOOOOO $ # OO XXXXX@++ $$$", +"$$$ ooooooOOO OO ######@++ $", +"$$$$ oo ooOOO OO +++++++++ @#+ ", +"$$$$$$ $ ooOOO @#++ ", +"$$$$$$$$$$ ooOOO OOOO ######@++ ", +"$$$$$ O ooOOO O @@@@@@@+++ ", +"$$$$ @@@@@ ooOOO @@+ +@++ $", +"$$$ ######### ooOOO +++++++++ $$", +"$$$ #....... O ooOOO $$$", +"$$$ #.XXXXX OO ooOOO $$$$$$$$$$", +"$$$ #.XXXX OO @+ ooOOO $$$$$$$$$", +"$$$ #.XXX O X@++ ooOOO $$$$$$$$", +"$$$ #.XX O XXX@++ ooOOO $$$$$$$", +"$$$ #.XX XXXX@++ $ ooOOO $$$$$$", +"$$$ #.XXXXXXXX@++ $$ ooOOO $$$$$", +"$$$ ##########@++ $ ooOOO $$$$", +"$$ @+++++++++++ @#+ $ ooOOO $$$", +"$ @ @#++ $$ ooOOO $$", +" ################@++ $$$ ooO $$$", +" #@@@@@@@@@@@@@@@+++ $$$$ o $$$$", +" #@@@@@@@@+ +@++ $$$$$$ $$$$$", +" @++++++++++++++++ $$$$$$$$$$$$$", +"$ $$$$$$$$$$$$$$" +}; + +/* XPM */ +static const char *const cfg_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$ @@@@@@@@@@@@@@@@@+ $$$$", +"$$$$$$$$$ $$$$$$$$$$$$ @@@@@@@@@@@@@@@@@++ $$$$", +"$$$$$$$$ OO $$$$$$$$ ################@+++ $$$$", +"$$$$$$$$ oOOOO $$$$$$$ #++++++++++++++@++++ $$$$", +"$$$$$$$$$ ooOOO $$$$$$ #+.............#++++ $$$$", +"$$$$$$$$$$ ooOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$", +"$$$$$$$$$$$ oOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$", +"$$$ $$$$$$ oOOO $$$$$ #+.XXXXXXX XXX#++++ $$$$", +"$$ oO $$$$$ oOOOO $$$$ #+.XXXXXX O XXX#++++ $$$$", +"$$ ooO $$$$ oOOOO $$$$ #+.XXXXX O XXXX#++++ $$$$", +"$$$ oOO OOOOO $$$$$ #+.XXX O XXXXX#++++ $$$$", +"$$$ ooOOOOOOOOOOO $$$$ #+.XX OO XXXXXX#++++ $$$$", +"$$$ ooOOOOOOOOOOOO $$$ #+.X OO XXXXXXX#++++ $$$$", +"$$$$ oooOOOOoOOOOOO $$ #@ OO #########++++ $", +"$$$$$ oooooooOOOOOOO # OOO @@@@@@@@@@+++ @##+ ", +"$$$$$$ oo ooOOOOOOO OO +++++++++++++ @##++ ", +"$$$$$$$$$ $ ooOOOOOOO OO @##+++ ", +"$$$$$$$$$$$$$ ooOOOOOOO ############@+++ ", +"$$$$$$$$$$$$$$ ooOOOOOOO OOOOOO ##########@++++ ", +"$$$$$$$$$$$$$$$ ooOOOOOOO OOO @@+ @++++ $", +"$$$$$$$$$$$$$$$$ ooOOOOOOO O ++++++++++++++++ $$", +"$$$$$$$$$$$$$$$ O ooOOOOOOO ++++++++++++++++ $$$", +"$$$$$$$$$$$$$$$$ ooOOOOOOO $$$$", +"$$$$$$$ ooOOOOOOO $$$$$$$$$$$$$$$$$$", +"$$$$$$ @@@@@@@@@@@@ ooOOOOOOO $$$$$$$$$$$$$$$$$", +"$$$$$ @@@@@@@@@@@@ OO ooOOOOOOO $$$$$$$$$$$$$$$$", +"$$$$ ############ OO ooOOOOOOO $$$$$$$$$$$$$$$", +"$$$$ #++++++++++ OO @++ ooOOOOOOO $$$$$$$$$$$$$$", +"$$$$ #+........ OO .#+++ ooOOOOOOO $$$$$$$$$$$$$", +"$$$$ #+.XXXXXX O XX#++++ ooOOOOOOO $$$$$$$$$$$$", +"$$$$ #+.XXXXX O XXXX#++++ ooOOOOOOO $$$$$$$$$$$", +"$$$$ #+.XXXX O XXXXX#++++ $ ooOOOOOOO $$$$$$$$$$", +"$$$$ #+.XXXX XXXXXX#++++ $$ ooOOOOOOO $$$$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$ ooOOOOOOO $$$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$ ooOOOOOOO $$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$ ooOOOOOOO $$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$$ ooOOOOOOO $$$$$", +"$$$$ #@##############++++ $$$$ ooOOOOOOO $$$$", +"$$$ #@@@@@@@@@@@@@@@+++ @##+ $$$$ ooOOOOOOO $$$", +"$$ @ @+++++++++++++++++ @##++ $$$$$ ooOOOOO $$$$", +"$ @@ @##+++ $$$$$$ ooOOO $$$$$", +" ########################@+++ $$$$$$$ ooO $$$$$$", +" #######################@++++ $$$$$$$$ o $$$$$$$", +" ##@@@@@@@@@@@@+ @++++ $$$$$$$$$$ $$$$$$$$", +" @@++++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$", +" @@+++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$$", +"$ $$$$$$$$$$$$$$$$$$$$$$" +}; + +const char *const *const cfg_icon[] = { + cfg_icon_0, + cfg_icon_1, + cfg_icon_2, +}; +const int n_cfg_icon = 3; diff --git a/netbox/libs/Putty/unix/xpmputty.c b/netbox/libs/Putty/unix/xpmputty.c new file mode 100644 index 000000000..56d16bee1 --- /dev/null +++ b/netbox/libs/Putty/unix/xpmputty.c @@ -0,0 +1,147 @@ +/* XPM */ +static const char *const main_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"####### ##", +"###### @@@@@+O #", +"###### @XX..+O #", +"###### @X.o.+O #", +"###### O.o.X+O #", +"###### ooOO++O #", +"## ooooo OOO ", +"# @Oooooo OOOOO ", +"# @X..oo OOOO #", +"# @X.o.OO ##", +"# @.o.X+O ######", +"# +OO+++O ######", +"# OOOOOOOO #####", +" @@@@@OOOO #####", +" +OOOOOOO ######", +"# #######" +}; + +/* XPM */ +static const char *const main_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"################ ####", +"############### +++++++++++O ###", +"############## @@@@@@@@@@+OO ###", +"############## @.........+OO ###", +"############## @.XXXXXXXX+OO ###", +"############## @.XXXX XX+OO ###", +"############## @.XXX o XX+OO ###", +"############## @.X o XXX+OO ###", +"############## @. oo XXXX+OO ###", +"############## @ oo XXXXX+OO ###", +"############## oo @@@@@@+OO #", +"############# ooo OOOOOOOOO +@O ", +"############ ooo +@OO ", +"########## ooooooooo @@@@@@+OO ", +"##### ooooooooo +++++++OOO ", +"#### +++++ ooo ++O O+OO #", +"### @@@@@@@@@ ooo OOOOOOOOOOO ##", +"### @....... oo ###", +"### @.XXXXX oo OO ##############", +"### @.XXXX oo +OO ##############", +"### @.XXX o X+OO ##############", +"### @.XX o XXX+OO ##############", +"### @.XX XXXX+OO ##############", +"### @.XXXXXXXX+OO ##############", +"### @@@@@@@@@@+OO ############", +"## +OOOOOOOOOOO +@O ###########", +"# + +@OO ###########", +" @@@@@@@@@@@@@@@@+OO ###########", +" @+++++++++++++++OOO ###########", +" @++++++++O O+OO ############", +" +OOOOOOOOOOOOOOOO #############", +"# ##############" +}; + +/* XPM */ +static const char *const main_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"######################### #####", +"######################## +++++++++++++++++O ####", +"####################### +++++++++++++++++OO ####", +"###################### @@@@@@@@@@@@@@@@+OOO ####", +"###################### @OOOOOOOOOOOOOO+OOOO ####", +"###################### @O.............@OOOO ####", +"###################### @O.XXXXXXXXXXXX@OOOO ####", +"###################### @O.XXXXXXXXXXXX@OOOO ####", +"###################### @O.XXXXXXX XXX@OOOO ####", +"###################### @O.XXXXXX o XXX@OOOO ####", +"###################### @O.XXXXX o XXXX@OOOO ####", +"###################### @O.XXX o XXXXX@OOOO ####", +"###################### @O.XX oo XXXXXX@OOOO ####", +"###################### @O.X oo XXXXXXX@OOOO ####", +"###################### @+ oo @@@@@@@@@OOOO #", +"##################### @ ooo ++++++++++OOO +@@O ", +"#################### + oo OOOOOOOOOOOOO +@@OO ", +"################### + oo +@@OOO ", +"################## @ ooo @@@@@@@@@@@@+OOO ", +"################## ooooooooooo @@@@@@@@@@+OOOO ", +"################## oooooooooo ++O +OOOO #", +"################ oooooooooo OOOOOOOOOOOOOOOO ##", +"############### ooooooooooo OOOOOOOOOOOOOOOO ###", +"################ ooo ####", +"####### oo ######################", +"###### ++++++++++++ oo O ######################", +"##### ++++++++++++ ooo OO ######################", +"#### @@@@@@@@@@@@ oo OOO ######################", +"#### @OOOOOOOOOO oo +OOOO ######################", +"#### @O........ oo .@OOOO ######################", +"#### @O.XXXXXX o XX@OOOO ######################", +"#### @O.XXXXX o XXXX@OOOO ######################", +"#### @O.XXXX o XXXXX@OOOO ######################", +"#### @O.XXXX XXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @+@@@@@@@@@@@@@@OOOO ###################", +"### @+++++++++++++++OOO +@@O ##################", +"## + +OOOOOOOOOOOOOOOOO +@@OO ##################", +"# ++ +@@OOO ##################", +" @@@@@@@@@@@@@@@@@@@@@@@@+OOO ##################", +" @@@@@@@@@@@@@@@@@@@@@@@+OOOO ##################", +" @@++++++++++++O +OOOO ###################", +" ++OOOOOOOOOOOOOOOOOOOOOOOO ####################", +" ++OOOOOOOOOOOOOOOOOOOOOOO #####################", +"# ######################" +}; + +const char *const *const main_icon[] = { + main_icon_0, + main_icon_1, + main_icon_2, +}; +const int n_main_icon = 3; diff --git a/netbox/libs/Putty/uxconfig.h b/netbox/libs/Putty/uxconfig.h new file mode 100644 index 000000000..b44a3ef79 --- /dev/null +++ b/netbox/libs/Putty/uxconfig.h @@ -0,0 +1,148 @@ +/* uxconfig.h. Generated from uxconfig.in by configure. */ +/* uxconfig.in. Generated from configure.ac by autoheader. */ + +/* Define if clock_gettime() is available */ +#define HAVE_CLOCK_GETTIME /**/ + +/* Define to 1 if you have the declaration of `CLOCK_MONOTONIC', and to 0 if + you don't. */ +#define HAVE_DECL_CLOCK_MONOTONIC 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if libX11.a is available */ +#define HAVE_LIBX11 /**/ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `pango_font_family_is_monospace' function. */ +#define HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE 1 + +/* Define to 1 if you have the `pango_font_map_list_families' function. */ +#define HAVE_PANGO_FONT_MAP_LIST_FAMILIES 1 + +/* Define to 1 if you have the `posix_openpt' function. */ +#define HAVE_POSIX_OPENPT 1 + +/* Define to 1 if you have the `ptsname' function. */ +#define HAVE_PTSNAME 1 + +/* Define to 1 if you have the `setresuid' function. */ +#define HAVE_SETRESUID 1 + +/* Define if SO_PEERCRED works in the Linux fashion. */ +#define HAVE_SO_PEERCRED 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strsignal' function. */ +#define HAVE_STRSIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `updwtmpx' function. */ +#define HAVE_UPDWTMPX 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTMPX_H 1 + +/* Define if we could not find a gssapi library */ +/* #undef NO_GSSAPI_LIB */ + +/* Define if we could not find libdl. */ +/* #undef NO_LIBDL */ + +/* Name of package */ +#define PACKAGE "putty" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "putty" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "putty 0.67" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "putty" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.67" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "0.67" + +/* Define if building with GSSAPI support. */ +#define WITH_GSSAPI 1 + + +/* Convert autoconf definitions to ones that PuTTY wants. */ + +#ifndef HAVE_GETADDRINFO +# define NO_IPV6 +#endif +#ifndef HAVE_SETRESUID +# define HAVE_NO_SETRESUID +#endif +#ifndef HAVE_STRSIGNAL +# define HAVE_NO_STRSIGNAL +#endif +#if !defined(HAVE_UTMPX_H) || !defined(HAVE_UPDWTMPX) +# define OMIT_UTMP +#endif +#ifndef HAVE_PTSNAME +# define BSD_PTYS +#endif +#ifndef HAVE_SYS_SELECT_H +# define HAVE_NO_SYS_SELECT_H +#endif +#ifndef HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE +# define PANGO_PRE_1POINT4 +#endif +#ifndef HAVE_PANGO_FONT_MAP_LIST_FAMILIES +# define PANGO_PRE_1POINT6 +#endif +#if !defined(WITH_GSSAPI) +# define NO_GSSAPI +#endif +#if !defined(NO_GSSAPI) && defined(NO_LIBDL) +# if !defined(HAVE_GSSAPI_GSSAPI_H) || defined(NO_GSSAPI_LIB) +# define NO_GSSAPI +# endif +#endif + diff --git a/netbox/libs/Putty/version.c b/netbox/libs/Putty/version.c new file mode 100644 index 000000000..f6086127e --- /dev/null +++ b/netbox/libs/Putty/version.c @@ -0,0 +1,20 @@ +/* + * PuTTY version numbering + */ + +/* + * The difficult part of deciding what goes in these version strings + * is done in Buildscr, and then written into version.h. All we have + * to do here is to drop it into variables of the right names. + */ + +#include "version.h" + +char ver[] = TEXTVER; +char sshver[] = SSHVER; + +/* + * SSH local version string MUST be under 40 characters. Here's a + * compile time assertion to verify this. + */ +enum { vorpal_sword = 1 / (sizeof(sshver) <= 40) }; diff --git a/netbox/libs/Putty/version.h b/netbox/libs/Putty/version.h new file mode 100644 index 000000000..8dbb8a0c0 --- /dev/null +++ b/netbox/libs/Putty/version.h @@ -0,0 +1,5 @@ +/* Generated by automated build script */ +#define RELEASE 0.67 +#define TEXTVER "Release 0.67" +#define SSHVER "PuTTY-Release-0.67" +#define BINARY_VERSION 0,67,0,0 diff --git a/netbox/libs/Putty/wcwidth.c b/netbox/libs/Putty/wcwidth.c new file mode 100644 index 000000000..ec0fb2ba0 --- /dev/null +++ b/netbox/libs/Putty/wcwidth.c @@ -0,0 +1,311 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +#include "putty.h" /* for prototypes */ + +struct interval { + unsigned int first; + unsigned int last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(unsigned int ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +int mk_wcwidth(unsigned int ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +int mk_wcswidth(const unsigned int *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +int mk_wcwidth_cjk(unsigned int ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} diff --git a/netbox/libs/Putty/wildcard.c b/netbox/libs/Putty/wildcard.c new file mode 100644 index 000000000..c1cb0b49e --- /dev/null +++ b/netbox/libs/Putty/wildcard.c @@ -0,0 +1,473 @@ +/* + * Wildcard matching engine for use with SFTP-based file transfer + * programs (PSFTP, new-look PSCP): since SFTP has no notion of + * getting the remote side to do globbing (and rightly so) we have + * to do it locally, by retrieving all the filenames in a directory + * and checking each against the wildcard pattern. + */ + +#include +#include +#include + +#include "putty.h" + +/* + * Definition of wildcard syntax: + * + * - * matches any sequence of characters, including zero. + * - ? matches exactly one character which can be anything. + * - [abc] matches exactly one character which is a, b or c. + * - [a-f] matches anything from a through f. + * - [^a-f] matches anything _except_ a through f. + * - [-_] matches - or _; [^-_] matches anything else. (The - is + * non-special if it occurs immediately after the opening + * bracket or ^.) + * - [a^] matches an a or a ^. (The ^ is non-special if it does + * _not_ occur immediately after the opening bracket.) + * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \. + * - All other characters are non-special and match themselves. + */ + +/* + * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.): + * - backslashes act as escapes even within [] bracket expressions + * - does not support [!...] for non-matching list (POSIX are weird); + * NB POSIX allows [^...] as well via "A bracket expression starting + * with an unquoted circumflex character produces unspecified + * results". If we wanted to allow [!...] we might want to define + * [^!] as having its literal meaning (match '^' or '!'). + * - none of the scary [[:class:]] stuff, etc + */ + +/* + * The wildcard matching technique we use is very simple and + * potentially O(N^2) in running time, but I don't anticipate it + * being that bad in reality (particularly since N will be the size + * of a filename, which isn't all that much). Perhaps one day, once + * PuTTY has grown a regexp matcher for some other reason, I might + * come back and reimplement wildcards by translating them into + * regexps or directly into NFAs; but for the moment, in the + * absence of any other need for the NFA->DFA translation engine, + * anything more than the simplest possible wildcard matcher is + * vast code-size overkill. + * + * Essentially, these wildcards are much simpler than regexps in + * that they consist of a sequence of rigid fragments (? and [...] + * can never match more or less than one character) separated by + * asterisks. It is therefore extremely simple to look at a rigid + * fragment and determine whether or not it begins at a particular + * point in the test string; so we can search along the string + * until we find each fragment, then search for the next. As long + * as we find each fragment in the _first_ place it occurs, there + * will never be a danger of having to backpedal and try to find it + * again somewhere else. + */ + +enum { + WC_TRAILINGBACKSLASH = 1, + WC_UNCLOSEDCLASS, + WC_INVALIDRANGE +}; + +/* + * Error reporting is done by returning various negative values + * from the wildcard routines. Passing any such value to wc_error + * will give a human-readable message. + */ +const char *wc_error(int value) +{ + value = abs(value); + switch (value) { + case WC_TRAILINGBACKSLASH: + return "'\' occurred at end of string (expected another character)"; + case WC_UNCLOSEDCLASS: + return "expected ']' to close character class"; + case WC_INVALIDRANGE: + return "character range was not terminated (']' just after '-')"; + } + return "INTERNAL ERROR: unrecognised wildcard error number"; +} + +/* + * This is the routine that tests a target string to see if an + * initial substring of it matches a fragment. If successful, it + * returns 1, and advances both `fragment' and `target' past the + * fragment and matching substring respectively. If unsuccessful it + * returns zero. If the wildcard fragment suffers a syntax error, + * it returns <0 and the precise value indexes into wc_error. + */ +static int wc_match_fragment(const char **fragment, const char **target) +{ + const char *f, *t; + + f = *fragment; + t = *target; + /* + * The fragment terminates at either the end of the string, or + * the first (unescaped) *. + */ + while (*f && *f != '*' && *t) { + /* + * Extract one character from t, and one character's worth + * of pattern from f, and step along both. Return 0 if they + * fail to match. + */ + if (*f == '\\') { + /* + * Backslash, which means f[1] is to be treated as a + * literal character no matter what it is. It may not + * be the end of the string. + */ + if (!f[1]) + return -WC_TRAILINGBACKSLASH; /* error */ + if (f[1] != *t) + return 0; /* failed to match */ + f += 2; + } else if (*f == '?') { + /* + * Question mark matches anything. + */ + f++; + } else if (*f == '[') { + int invert = 0; + int matched = 0; + /* + * Open bracket introduces a character class. + */ + f++; + if (*f == '^') { + invert = 1; + f++; + } + while (*f != ']') { + if (*f == '\\') + f++; /* backslashes still work */ + if (!*f) + return -WC_UNCLOSEDCLASS; /* error again */ + if (f[1] == '-') { + int lower, upper, ourchr; + lower = (unsigned char) *f++; + f++; /* eat the minus */ + if (*f == ']') + return -WC_INVALIDRANGE; /* different error! */ + if (*f == '\\') + f++; /* backslashes _still_ work */ + if (!*f) + return -WC_UNCLOSEDCLASS; /* error again */ + upper = (unsigned char) *f++; + ourchr = (unsigned char) *t; + if (lower > upper) { + int t = lower; lower = upper; upper = t; + } + if (ourchr >= lower && ourchr <= upper) + matched = 1; + } else { + matched |= (*t == *f++); + } + } + if (invert == matched) + return 0; /* failed to match character class */ + f++; /* eat the ] */ + } else { + /* + * Non-special character matches itself. + */ + if (*f != *t) + return 0; + f++; + } + /* + * Now we've done that, increment t past the character we + * matched. + */ + t++; + } + if (!*f || *f == '*') { + /* + * We have reached the end of f without finding a mismatch; + * so we're done. Update the caller pointers and return 1. + */ + *fragment = f; + *target = t; + return 1; + } + /* + * Otherwise, we must have reached the end of t before we + * reached the end of f; so we've failed. Return 0. + */ + return 0; +} + +/* + * This is the real wildcard matching routine. It returns 1 for a + * successful match, 0 for an unsuccessful match, and <0 for a + * syntax error in the wildcard. + */ +int wc_match(const char *wildcard, const char *target) +{ + int ret; + + /* + * Every time we see a '*' _followed_ by a fragment, we just + * search along the string for a location at which the fragment + * matches. The only special case is when we see a fragment + * right at the start, in which case we just call the matching + * routine once and give up if it fails. + */ + if (*wildcard != '*') { + ret = wc_match_fragment(&wildcard, &target); + if (ret <= 0) + return ret; /* pass back failure or error alike */ + } + + while (*wildcard) { + assert(*wildcard == '*'); + while (*wildcard == '*') + wildcard++; + + /* + * It's possible we've just hit the end of the wildcard + * after seeing a *, in which case there's no need to + * bother searching any more because we've won. + */ + if (!*wildcard) + return 1; + + /* + * Now `wildcard' points at the next fragment. So we + * attempt to match it against `target', and if that fails + * we increment `target' and try again, and so on. When we + * find we're about to try matching against the empty + * string, we give up and return 0. + */ + ret = 0; + while (*target) { + const char *save_w = wildcard, *save_t = target; + + ret = wc_match_fragment(&wildcard, &target); + + if (ret < 0) + return ret; /* syntax error */ + + if (ret > 0 && !*wildcard && *target) { + /* + * Final special case - literally. + * + * This situation arises when we are matching a + * _terminal_ fragment of the wildcard (that is, + * there is nothing after it, e.g. "*a"), and it + * has matched _too early_. For example, matching + * "*a" against "parka" will match the "a" fragment + * against the _first_ a, and then (if it weren't + * for this special case) matching would fail + * because we're at the end of the wildcard but not + * at the end of the target string. + * + * In this case what we must do is measure the + * length of the fragment in the target (which is + * why we saved `target'), jump straight to that + * distance from the end of the string using + * strlen, and match the same fragment again there + * (which is why we saved `wildcard'). Then we + * return whatever that operation returns. + */ + target = save_t + strlen(save_t) - (target - save_t); + wildcard = save_w; + return wc_match_fragment(&wildcard, &target); + } + + if (ret > 0) + break; + target++; + } + if (ret > 0) + continue; + return 0; + } + + /* + * If we reach here, it must be because we successfully matched + * a fragment and then found ourselves right at the end of the + * wildcard. Hence, we return 1 if and only if we are also + * right at the end of the target. + */ + return (*target ? 0 : 1); +} + +/* + * Another utility routine that translates a non-wildcard string + * into its raw equivalent by removing any escaping backslashes. + * Expects a target string buffer of anything up to the length of + * the original wildcard. You can also pass NULL as the output + * buffer if you're only interested in the return value. + * + * Returns 1 on success, or 0 if a wildcard character was + * encountered. In the latter case the output string MAY not be + * zero-terminated and you should not use it for anything! + */ +int wc_unescape(char *output, const char *wildcard) +{ + while (*wildcard) { + if (*wildcard == '\\') { + wildcard++; + /* We are lenient about trailing backslashes in non-wildcards. */ + if (*wildcard) { + if (output) + *output++ = *wildcard; + wildcard++; + } + } else if (*wildcard == '*' || *wildcard == '?' || + *wildcard == '[' || *wildcard == ']') { + return 0; /* it's a wildcard! */ + } else { + if (output) + *output++ = *wildcard; + wildcard++; + } + } + if (output) + *output = '\0'; + return 1; /* it's clean */ +} + +#ifdef TESTMODE + +struct test { + const char *wildcard; + const char *target; + int expected_result; +}; + +const struct test fragment_tests[] = { + /* + * We exhaustively unit-test the fragment matching routine + * itself, which should save us the need to test all its + * intricacies during the full wildcard tests. + */ + {"abc", "abc", 1}, + {"abc", "abd", 0}, + {"abc", "abcd", 1}, + {"abcd", "abc", 0}, + {"ab[cd]", "abc", 1}, + {"ab[cd]", "abd", 1}, + {"ab[cd]", "abe", 0}, + {"ab[^cd]", "abc", 0}, + {"ab[^cd]", "abd", 0}, + {"ab[^cd]", "abe", 1}, + {"ab\\", "abc", -WC_TRAILINGBACKSLASH}, + {"ab\\*", "ab*", 1}, + {"ab\\?", "ab*", 0}, + {"ab?", "abc", 1}, + {"ab?", "ab", 0}, + {"ab[", "abc", -WC_UNCLOSEDCLASS}, + {"ab[c-", "abb", -WC_UNCLOSEDCLASS}, + {"ab[c-]", "abb", -WC_INVALIDRANGE}, + {"ab[c-e]", "abb", 0}, + {"ab[c-e]", "abc", 1}, + {"ab[c-e]", "abd", 1}, + {"ab[c-e]", "abe", 1}, + {"ab[c-e]", "abf", 0}, + {"ab[e-c]", "abb", 0}, + {"ab[e-c]", "abc", 1}, + {"ab[e-c]", "abd", 1}, + {"ab[e-c]", "abe", 1}, + {"ab[e-c]", "abf", 0}, + {"ab[^c-e]", "abb", 1}, + {"ab[^c-e]", "abc", 0}, + {"ab[^c-e]", "abd", 0}, + {"ab[^c-e]", "abe", 0}, + {"ab[^c-e]", "abf", 1}, + {"ab[^e-c]", "abb", 1}, + {"ab[^e-c]", "abc", 0}, + {"ab[^e-c]", "abd", 0}, + {"ab[^e-c]", "abe", 0}, + {"ab[^e-c]", "abf", 1}, + {"ab[a^]", "aba", 1}, + {"ab[a^]", "ab^", 1}, + {"ab[a^]", "abb", 0}, + {"ab[^a^]", "aba", 0}, + {"ab[^a^]", "ab^", 0}, + {"ab[^a^]", "abb", 1}, + {"ab[-c]", "ab-", 1}, + {"ab[-c]", "abc", 1}, + {"ab[-c]", "abd", 0}, + {"ab[^-c]", "ab-", 0}, + {"ab[^-c]", "abc", 0}, + {"ab[^-c]", "abd", 1}, + {"ab[\\[-\\]]", "abZ", 0}, + {"ab[\\[-\\]]", "ab[", 1}, + {"ab[\\[-\\]]", "ab\\", 1}, + {"ab[\\[-\\]]", "ab]", 1}, + {"ab[\\[-\\]]", "ab^", 0}, + {"ab[^\\[-\\]]", "abZ", 1}, + {"ab[^\\[-\\]]", "ab[", 0}, + {"ab[^\\[-\\]]", "ab\\", 0}, + {"ab[^\\[-\\]]", "ab]", 0}, + {"ab[^\\[-\\]]", "ab^", 1}, + {"ab[a-fA-F]", "aba", 1}, + {"ab[a-fA-F]", "abF", 1}, + {"ab[a-fA-F]", "abZ", 0}, +}; + +const struct test full_tests[] = { + {"a", "argh", 0}, + {"a", "ba", 0}, + {"a", "a", 1}, + {"a*", "aardvark", 1}, + {"a*", "badger", 0}, + {"*a", "park", 0}, + {"*a", "pArka", 1}, + {"*a", "parka", 1}, + {"*a*", "park", 1}, + {"*a*", "perk", 0}, + {"?b*r?", "abracadabra", 1}, + {"?b*r?", "abracadabr", 0}, + {"?b*r?", "abracadabzr", 0}, +}; + +int main(void) +{ + int i; + int fails, passes; + + fails = passes = 0; + + for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) { + const char *f, *t; + int eret, aret; + f = fragment_tests[i].wildcard; + t = fragment_tests[i].target; + eret = fragment_tests[i].expected_result; + aret = wc_match_fragment(&f, &t); + if (aret != eret) { + printf("failed test: /%s/ against /%s/ returned %d not %d\n", + fragment_tests[i].wildcard, fragment_tests[i].target, + aret, eret); + fails++; + } else + passes++; + } + + for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) { + const char *f, *t; + int eret, aret; + f = full_tests[i].wildcard; + t = full_tests[i].target; + eret = full_tests[i].expected_result; + aret = wc_match(f, t); + if (aret != eret) { + printf("failed test: /%s/ against /%s/ returned %d not %d\n", + full_tests[i].wildcard, full_tests[i].target, + aret, eret); + fails++; + } else + passes++; + } + + printf("passed %d, failed %d\n", passes, fails); + + return 0; +} + +#endif diff --git a/netbox/libs/Putty/windows/wingss.c b/netbox/libs/Putty/windows/wingss.c new file mode 100644 index 000000000..f35415a24 --- /dev/null +++ b/netbox/libs/Putty/windows/wingss.c @@ -0,0 +1,518 @@ +#ifndef NO_GSSAPI + +#include "putty.h" + +#define SECURITY_WIN32 +#include + +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.h" + +#include "misc.h" + +/* Windows code to set up the GSSAPI library list. */ + +const int ngsslibs = 3; +const char *const gsslibnames[3] = { + "MIT Kerberos GSSAPI32.DLL", + "Microsoft SSPI SECUR32.DLL", + "User-specified GSSAPI DLL", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "gssapi32", 0, -1, -1 }, + { "sspi", 1, -1, -1 }, + { "custom", 2, -1, -1 }, +}; + +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + AcquireCredentialsHandleA, + (SEC_CHAR *, SEC_CHAR *, ULONG, PLUID, + PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + InitializeSecurityContextA, + (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG, + ULONG, PSecBufferDesc, ULONG, PCtxtHandle, + PSecBufferDesc, PULONG, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeContextBuffer, + (PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeCredentialsHandle, + (PCredHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + DeleteSecurityContext, + (PCtxtHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + QueryContextAttributesA, + (PCtxtHandle, ULONG, PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + MakeSignature, + (PCtxtHandle, ULONG, PSecBufferDesc, ULONG)); + +typedef struct winSsh_gss_ctx { + unsigned long maj_stat; + unsigned long min_stat; + CredHandle cred_handle; + CtxtHandle context; + PCtxtHandle context_handle; + TimeStamp expiry; +} winSsh_gss_ctx; + + +#ifdef MPEXT +static +#endif +const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; + +const char *gsslogmsg = NULL; + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + HMODULE module; + HKEY regkey; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + char *path; + + list->libraries = snewn(3, struct ssh_gss_library); + list->nlibraries = 0; + + /* MIT Kerberos GSSAPI implementation */ + /* TODO: For 64-bit builds, check for gssapi64.dll */ + module = NULL; + if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) + == ERROR_SUCCESS) { + DWORD type, size; + LONG ret; + char *buffer; + + /* Find out the string length */ + ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + buffer = snewn(size + 20, char); + ret = RegQueryValueEx(regkey, "InstallDir", NULL, + &type, buffer, &size); + if (ret == ERROR_SUCCESS && type == REG_SZ) { + strcat(buffer, "\\bin\\gssapi32.dll"); + module = LoadLibrary(buffer); + } + sfree(buffer); + } + RegCloseKey(regkey); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 0; + lib->gsslogmsg = "Using GSSAPI from GSSAPI32.DLL"; + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + /* Microsoft SSPI Implementation */ + module = load_system32_dll("secur32.dll"); + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 1; + lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; + lib->handle = (void *)module; + + GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); + GET_WINDOWS_FUNCTION(module, FreeContextBuffer); + GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); + GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); + GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); + GET_WINDOWS_FUNCTION(module, MakeSignature); + + ssh_sspi_bind_fns(lib); + } + + /* + * Custom GSSAPI DLL. + */ + module = NULL; + path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*path) { + module = LoadLibrary(path); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 2; + lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" + " library '%s'", path); + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + int i; + + /* + * LoadLibrary and FreeLibrary are defined to employ reference + * counting in the case where the same library is repeatedly + * loaded, so even in a multiple-sessions-per-process context + * (not that we currently expect ever to have such a thing on + * Windows) it's safe to naively FreeLibrary everything here + * without worrying about destroying it under the feet of + * another SSH instance still using it. + */ + for (i = 0; i < list->nlibraries; i++) { + FreeLibrary((HMODULE)list->libraries[i].handle); + if (list->libraries[i].id == 2) { + /* The 'custom' id involves a dynamically allocated message. + * Note that we must cast away the 'const' to free it. */ + sfree((char *)list->libraries[i].gsslogmsg); + } + } + sfree(list->libraries); + sfree(list); +} + +static Ssh_gss_stat ssh_sspi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + *mech = gss_mech_krb5; + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, + char *host, Ssh_gss_name *srv_name) +{ + char *pStr; + + /* Check hostname */ + if (host == NULL) return SSH_GSS_FAILURE; + + /* copy it into form host/FQDN */ + pStr = dupcat("host/", host, NULL); + + *srv_name = (Ssh_gss_name) pStr; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); + memset(winctx, 0, sizeof(winSsh_gss_ctx)); + + /* prepare our "wrapper" structure */ + winctx->maj_stat = winctx->min_stat = SEC_E_OK; + winctx->context_handle = NULL; + + /* Specifying no principal name here means use the credentials of + the current logged-in user */ + + winctx->maj_stat = p_AcquireCredentialsHandleA(NULL, + "Kerberos", + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &winctx->cred_handle, + &winctx->expiry); + + if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE; + + *ctx = (Ssh_gss_ctx) winctx; + return SSH_GSS_OK; +} + +#ifdef MPEXT +static SecBuffer ssh_gss_init_sec_buffer(unsigned long buffersize, + unsigned long buffertype, void * buffer) +{ + SecBuffer result; + result.cbBuffer = buffersize; + result.BufferType = buffertype; + result.pvBuffer = buffer; + return result; +} + +static SecBufferDesc ssh_gss_init_sec_buffer_desc(unsigned long version, + unsigned long bufferscount, PSecBuffer buffers) +{ + SecBufferDesc result; + result.ulVersion = version; + result.cBuffers = bufferscount; + result.pBuffers = buffers; + return result; +} + +#define MPEXT_INIT_SEC_BUFFER(BUFFERSIZE, BUFFERTYPE, BUFFER) \ + ssh_gss_init_sec_buffer(BUFFERSIZE, BUFFERTYPE, BUFFER) +#define MPEXT_INIT_SEC_BUFFERDESC(VERSION, BUFFERSCOUNT, BUFFERS) \ + ssh_gss_init_sec_buffer_desc(VERSION, BUFFERSCOUNT, BUFFERS) + +#else +#define MPEXT_INIT_SEC_BUFFER(BUFFERSIZE, BUFFERTYPE, BUFFER) \ + {BUFFERSIZE, BUFFERTYPE, BUFFER} +#define MPEXT_INIT_SEC_BUFFERDESC(VERSION, BUFFERSCOUNT, BUFFERS) \ + {VERSION, BUFFERSCOUNT, BUFFERS} +#endif +static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok) +{ + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; + SecBuffer wsend_tok = MPEXT_INIT_SEC_BUFFER(send_tok->length,SECBUFFER_TOKEN,send_tok->value); + SecBuffer wrecv_tok = MPEXT_INIT_SEC_BUFFER(recv_tok->length,SECBUFFER_TOKEN,recv_tok->value); + SecBufferDesc output_desc= MPEXT_INIT_SEC_BUFFERDESC(SECBUFFER_VERSION,1,&wsend_tok); + SecBufferDesc input_desc = MPEXT_INIT_SEC_BUFFERDESC(SECBUFFER_VERSION,1,&wrecv_tok); + unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| + ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; + unsigned long ret_flags=0; + + /* check if we have to delegate ... */ + if (to_deleg) flags |= ISC_REQ_DELEGATE; + winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle, + winctx->context_handle, + (char*) srv_name, + flags, + 0, /* reserved */ + SECURITY_NATIVE_DREP, + &input_desc, + 0, /* reserved */ + &winctx->context, + &output_desc, + &ret_flags, + &winctx->expiry); + + /* prepare for the next round */ + winctx->context_handle = &winctx->context; + send_tok->value = wsend_tok.pvBuffer; + send_tok->length = wsend_tok.cbBuffer; + + /* check & return our status */ + if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE; + if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + /* check input */ + if (send_tok == NULL) return SSH_GSS_FAILURE; + + /* free Windows buffer */ + p_FreeContextBuffer(send_tok->value); + SSH_GSS_CLEAR_BUF(send_tok); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx; + + /* check input */ + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* free Windows data */ + p_FreeCredentialsHandle(&winctx->cred_handle); + #ifdef MPEXT + if (winctx->context_handle != NULL) + #endif + p_DeleteSecurityContext(&winctx->context); + + /* delete our "wrapper" structure */ + sfree(winctx); + *ctx = (Ssh_gss_ctx) NULL; + + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib, + Ssh_gss_name *srv_name) +{ + char *pStr= (char *) *srv_name; + + if (pStr == NULL) return SSH_GSS_FAILURE; + sfree(pStr); + *srv_name = (Ssh_gss_name) NULL; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf) +{ + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; + const char *msg; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* decode the error code */ + switch (winctx->maj_stat) { + case SEC_E_OK: msg="SSPI status OK"; break; + case SEC_E_INVALID_HANDLE: msg="The handle passed to the function" + " is invalid."; + break; + case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break; + case SEC_E_LOGON_DENIED: msg="The logon failed."; break; + case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot" + " be contacted."; + break; + case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the" + " security package."; + break; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + msg="No authority could be contacted for authentication." + "The domain name of the authenticating party could be wrong," + " the domain could be unreachable, or there might have been" + " a trust relationship failure."; + break; + case SEC_E_INSUFFICIENT_MEMORY: + msg="One or more of the SecBufferDesc structures passed as" + " an OUT parameter has a buffer that is too small."; + break; + case SEC_E_INVALID_TOKEN: + msg="The error is due to a malformed input token, such as a" + " token corrupted in transit, a token" + " of incorrect size, or a token passed into the wrong" + " security package. Passing a token to" + " the wrong package can happen if client and server did not" + " negotiate the proper security package."; + break; + default: + msg = "Internal SSPI error"; + break; + } + + buf->value = dupstr(msg); + buf->length = strlen(buf->value); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecPkgContext_Sizes ContextSizes; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + memset(&ContextSizes, 0, sizeof(ContextSizes)); + + winctx->maj_stat = p_QueryContextAttributesA(&winctx->context, + SECPKG_ATTR_SIZES, + &ContextSizes); + + if (winctx->maj_stat != SEC_E_OK || + ContextSizes.cbMaxSignature == 0) + return winctx->maj_stat; + + InputBufferDescriptor.cBuffers = 2; + InputBufferDescriptor.pBuffers = InputSecurityToken; + InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + InputSecurityToken[0].BufferType = SECBUFFER_DATA; + InputSecurityToken[0].cbBuffer = buf->length; + InputSecurityToken[0].pvBuffer = buf->value; + InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; + InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature; + InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char); + + winctx->maj_stat = p_MakeSignature(&winctx->context, + 0, + &InputBufferDescriptor, + 0); + + if (winctx->maj_stat == SEC_E_OK) { + hash->length = InputSecurityToken[1].cbBuffer; + hash->value = InputSecurityToken[1].pvBuffer; + } + + return winctx->maj_stat; +} + +static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + sfree(hash->value); + return SSH_GSS_OK; +} + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_sspi_indicate_mech; + lib->import_name = ssh_sspi_import_name; + lib->release_name = ssh_sspi_release_name; + lib->init_sec_context = ssh_sspi_init_sec_context; + lib->free_tok = ssh_sspi_free_tok; + lib->acquire_cred = ssh_sspi_acquire_cred; + lib->release_cred = ssh_sspi_release_cred; + lib->get_mic = ssh_sspi_get_mic; + lib->free_mic = ssh_sspi_free_mic; + lib->display_status = ssh_sspi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +void ssh_gss_init(void) +{ +} + +#endif diff --git a/netbox/libs/Putty/windows/winhandl.c b/netbox/libs/Putty/windows/winhandl.c new file mode 100644 index 000000000..f75ce6492 --- /dev/null +++ b/netbox/libs/Putty/windows/winhandl.c @@ -0,0 +1,745 @@ +/* + * winhandl.c: Module to give Windows front ends the general + * ability to deal with consoles, pipes, serial ports, or any other + * type of data stream accessed through a Windows API HANDLE rather + * than a WinSock SOCKET. + * + * We do this by spawning a subthread to continuously try to read + * from the handle. Every time a read successfully returns some + * data, the subthread sets an event object which is picked up by + * the main thread, and the main thread then sets an event in + * return to instruct the subthread to resume reading. + * + * Output works precisely the other way round, in a second + * subthread. The output subthread should not be attempting to + * write all the time, because it hasn't always got data _to_ + * write; so the output thread waits for an event object notifying + * it to _attempt_ a write, and then it sets an event in return + * when one completes. + * + * (It's terribly annoying having to spawn a subthread for each + * direction of each handle. Technically it isn't necessary for + * serial ports, since we could use overlapped I/O within the main + * thread and wait directly on the event objects in the OVERLAPPED + * structures. However, we can't use this trick for some types of + * file handle at all - for some reason Windows restricts use of + * OVERLAPPED to files which were opened with the overlapped flag - + * and so we must use threads for those. This being the case, it's + * simplest just to use threads for everything rather than trying + * to keep track of multiple completely separate mechanisms.) + */ + +#include + +#include "putty.h" + +/* ---------------------------------------------------------------------- + * Generic definitions. + */ + +/* + * Maximum amount of backlog we will allow to build up on an input + * handle before we stop reading from it. + */ +#define MAX_BACKLOG 32768 + +struct handle_generic { + /* + * Initial fields common to both handle_input and handle_output + * structures. + * + * The three HANDLEs are set up at initialisation time and are + * thereafter read-only to both main thread and subthread. + * `moribund' is only used by the main thread; `done' is + * written by the main thread before signalling to the + * subthread. `defunct' and `busy' are used only by the main + * thread. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ +}; + +typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType; + +/* ---------------------------------------------------------------------- + * Input threads. + */ + +/* + * Data required by an input thread. + */ +struct handle_input { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Data set at initialisation and then read-only. + */ + int flags; + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + char buffer[4096]; /* the data read from the handle */ + DWORD len; /* how much data that was */ + int readerr; /* lets us know about read errors */ + + /* + * Callback function called by this module when data arrives on + * an input handle. + */ + handle_inputfn_t gotdata; +}; + +/* + * The actual thread procedure for an input thread. + */ +static DWORD WINAPI handle_input_threadfunc(void *param) +{ + struct handle_input *ctx = (struct handle_input *) param; + OVERLAPPED ovl, *povl; + HANDLE oev = 0; + int readret, readlen, finished; + + if (ctx->flags & HANDLE_FLAG_OVERLAPPED) { + povl = &ovl; + oev = CreateEvent(NULL, TRUE, FALSE, NULL); + } else { + povl = NULL; + } + + if (ctx->flags & HANDLE_FLAG_UNITBUFFER) + readlen = 1; + else + readlen = sizeof(ctx->buffer); + + while (1) { + if (povl) { + memset(povl, 0, sizeof(OVERLAPPED)); + povl->hEvent = oev; + } + readret = ReadFile(ctx->h, ctx->buffer,readlen, &ctx->len, povl); + if (!readret) + ctx->readerr = GetLastError(); + else + ctx->readerr = 0; + if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) { + WaitForSingleObject(povl->hEvent, INFINITE); + readret = GetOverlappedResult(ctx->h, povl, &ctx->len, FALSE); + if (!readret) + ctx->readerr = GetLastError(); + else + ctx->readerr = 0; + } + + if (!readret) { + /* + * Windows apparently sends ERROR_BROKEN_PIPE when a + * pipe we're reading from is closed normally from the + * writing end. This is ludicrous; if that situation + * isn't a natural EOF, _nothing_ is. So if we get that + * particular error, we pretend it's EOF. + */ + if (ctx->readerr == ERROR_BROKEN_PIPE) + ctx->readerr = 0; + ctx->len = 0; + } + + if (readret && ctx->len == 0 && + (ctx->flags & HANDLE_FLAG_IGNOREEOF)) + continue; + + /* + * If we just set ctx->len to 0, that means the read operation + * has returned end-of-file. Telling that to the main thread + * will cause it to set its 'defunct' flag and dispose of the + * handle structure at the next opportunity, in which case we + * mustn't touch ctx at all after the SetEvent. (Hence we do + * even _this_ check before the SetEvent.) + */ + finished = (ctx->len == 0); + + SetEvent(ctx->ev_to_main); + + if (finished) + break; + + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + /* + * The main thread has asked us to shut down. Send back an + * event indicating that we've done so. Hereafter we must + * not touch ctx at all, because the main thread might + * have freed it. + */ + SetEvent(ctx->ev_to_main); + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +/* + * This is called after a succcessful read, or from the + * `unthrottle' function. It decides whether or not to begin a new + * read operation. + */ +static void handle_throttle(struct handle_input *ctx, int backlog) +{ + if (ctx->defunct) + return; + + /* + * If there's a read operation already in progress, do nothing: + * when that completes, we'll come back here and be in a + * position to make a better decision. + */ + if (ctx->busy) + return; + + /* + * Otherwise, we must decide whether to start a new read based + * on the size of the backlog. + */ + if (backlog < MAX_BACKLOG) { + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Output threads. + */ + +/* + * Data required by an output thread. + */ +struct handle_output { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Data set at initialisation and then read-only. + */ + int flags; + + /* + * Data set by the main thread before signalling ev_from_main, + * and read by the input thread after receiving that signal. + */ + char *buffer; /* the data to write */ + DWORD len; /* how much data there is */ + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + DWORD lenwritten; /* how much data we actually wrote */ + int writeerr; /* return value from WriteFile */ + + /* + * Data only ever read or written by the main thread. + */ + bufchain queued_data; /* data still waiting to be written */ + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + + /* + * Callback function called when the backlog in the bufchain + * drops. + */ + handle_outputfn_t sentdata; +}; + +static DWORD WINAPI handle_output_threadfunc(void *param) +{ + struct handle_output *ctx = (struct handle_output *) param; + OVERLAPPED ovl, *povl; + HANDLE oev; + int writeret; + + if (ctx->flags & HANDLE_FLAG_OVERLAPPED) { + povl = &ovl; + oev = CreateEvent(NULL, TRUE, FALSE, NULL); + } else { + povl = NULL; + } + + while (1) { + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + /* + * The main thread has asked us to shut down. Send back an + * event indicating that we've done so. Hereafter we must + * not touch ctx at all, because the main thread might + * have freed it. + */ + SetEvent(ctx->ev_to_main); + break; + } + if (povl) { + memset(povl, 0, sizeof(OVERLAPPED)); + povl->hEvent = oev; + } + + writeret = WriteFile(ctx->h, ctx->buffer, ctx->len, + &ctx->lenwritten, povl); + if (!writeret) + ctx->writeerr = GetLastError(); + else + ctx->writeerr = 0; + if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) { + writeret = GetOverlappedResult(ctx->h, povl, + &ctx->lenwritten, TRUE); + if (!writeret) + ctx->writeerr = GetLastError(); + else + ctx->writeerr = 0; + } + + SetEvent(ctx->ev_to_main); + if (!writeret) { + /* + * The write operation has suffered an error. Telling that + * to the main thread will cause it to set its 'defunct' + * flag and dispose of the handle structure at the next + * opportunity, so we must not touch ctx at all after + * this. + */ + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +static void handle_try_output(struct handle_output *ctx) +{ + void *senddata; + int sendlen; + + if (!ctx->busy && bufchain_size(&ctx->queued_data)) { + bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); + ctx->buffer = senddata; + ctx->len = sendlen; + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && + ctx->outgoingeof == EOF_PENDING) { + CloseHandle(ctx->h); + ctx->h = INVALID_HANDLE_VALUE; + ctx->outgoingeof = EOF_SENT; + } +} + +/* ---------------------------------------------------------------------- + * 'Foreign events'. These are handle structures which just contain a + * single event object passed to us by another module such as + * winnps.c, so that they can make use of our handle_get_events / + * handle_got_event mechanism for communicating with application main + * loops. + */ +struct handle_foreign { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Our own data, just consisting of knowledge of who to call back. + */ + void (*callback)(void *); + void *ctx; +}; + +/* ---------------------------------------------------------------------- + * Unified code handling both input and output threads. + */ + +struct handle { + HandleType type; + union { + struct handle_generic g; + struct handle_input i; + struct handle_output o; + struct handle_foreign f; + } u; +}; + +static tree234 *handles_by_evtomain; + +static int handle_cmp_evtomain(void *av, void *bv) +{ + struct handle *a = (struct handle *)av; + struct handle *b = (struct handle *)bv; + + if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +static int handle_find_evtomain(void *av, void *bv) +{ + HANDLE *a = (HANDLE *)av; + struct handle *b = (struct handle *)bv; + + if ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)*a > (uintptr_t)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, + void *privdata, int flags) +{ + struct handle *h = snew(struct handle); + DWORD in_threadid; /* required for Win9x */ + + h->type = HT_INPUT; + h->u.i.h = handle; + h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.gotdata = gotdata; + h->u.i.defunct = FALSE; + h->u.i.moribund = FALSE; + h->u.i.done = FALSE; + h->u.i.privdata = privdata; + h->u.i.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_input_threadfunc, + &h->u.i, 0, &in_threadid); + h->u.i.busy = TRUE; + + return h; +} + +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, + void *privdata, int flags) +{ + struct handle *h = snew(struct handle); + DWORD out_threadid; /* required for Win9x */ + + h->type = HT_OUTPUT; + h->u.o.h = handle; + h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.o.busy = FALSE; + h->u.o.defunct = FALSE; + h->u.o.moribund = FALSE; + h->u.o.done = FALSE; + h->u.o.privdata = privdata; + bufchain_init(&h->u.o.queued_data); + h->u.o.outgoingeof = EOF_NO; + h->u.o.sentdata = sentdata; + h->u.o.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.o, 0, &out_threadid); + + return h; +} + +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx) +{ + struct handle *h = snew(struct handle); + + h->type = HT_FOREIGN; + h->u.f.h = INVALID_HANDLE_VALUE; + h->u.f.ev_to_main = event; + h->u.f.ev_from_main = INVALID_HANDLE_VALUE; + h->u.f.defunct = TRUE; /* we have no thread in the first place */ + h->u.f.moribund = FALSE; + h->u.f.done = FALSE; + h->u.f.privdata = NULL; + h->u.f.callback = callback; + h->u.f.ctx = ctx; + h->u.f.busy = TRUE; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + return h; +} + +int handle_write(struct handle *h, const void *data, int len) +{ + assert(h->type == HT_OUTPUT); + assert(h->u.o.outgoingeof == EOF_NO); + bufchain_add(&h->u.o.queued_data, data, len); + handle_try_output(&h->u.o); + return bufchain_size(&h->u.o.queued_data); +} + +void handle_write_eof(struct handle *h) +{ + /* + * This function is called when we want to proactively send an + * end-of-file notification on the handle. We can only do this by + * actually closing the handle - so never call this on a + * bidirectional handle if we're still interested in its incoming + * direction! + */ + assert(h->type == HT_OUTPUT); + if (!h->u.o.outgoingeof == EOF_NO) { + h->u.o.outgoingeof = EOF_PENDING; + handle_try_output(&h->u.o); + } +} + +HANDLE *handle_get_events(int *nevents) +{ + HANDLE *ret; + struct handle *h; + int i, n, size; + + /* + * Go through our tree counting the handle objects currently + * engaged in useful activity. + */ + ret = NULL; + n = size = 0; + if (handles_by_evtomain) { + for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) { + if (h->u.g.busy) { + if (n >= size) { + size += 32; + ret = sresize(ret, size, HANDLE); + } + ret[n++] = h->u.g.ev_to_main; + } + } + } + + *nevents = n; + return ret; +} + +static void handle_destroy(struct handle *h) +{ + if (h->type == HT_OUTPUT) + bufchain_clear(&h->u.o.queued_data); + CloseHandle(h->u.g.ev_from_main); + CloseHandle(h->u.g.ev_to_main); + del234(handles_by_evtomain, h); + sfree(h); +} + +void handle_free(struct handle *h) +{ + assert(h && !h->u.g.moribund); + if (h->u.g.busy && h->type != HT_FOREIGN) { + /* + * If the handle is currently busy, we cannot immediately free + * it, because its subthread is in the middle of something. + * (Exception: foreign handles don't have a subthread.) + * + * Instead we must wait until it's finished its current + * operation, because otherwise the subthread will write to + * invalid memory after we free its context from under it. So + * we set the moribund flag, which will be noticed next time + * an operation completes. + */ + h->u.g.moribund = TRUE; + } else if (h->u.g.defunct) { + /* + * There isn't even a subthread; we can go straight to + * handle_destroy. + */ + handle_destroy(h); + } else { + /* + * The subthread is alive but not busy, so we now signal it + * to die. Set the moribund flag to indicate that it will + * want destroying after that. + */ + h->u.g.moribund = TRUE; + h->u.g.done = TRUE; + h->u.g.busy = TRUE; + SetEvent(h->u.g.ev_from_main); + } +} + +#ifdef MPEXT +int handle_got_event(HANDLE event) +#else +void handle_got_event(HANDLE event) +#endif +{ + struct handle *h; + + assert(handles_by_evtomain); + h = find234(handles_by_evtomain, &event, handle_find_evtomain); + if (!h) { + /* + * This isn't an error condition. If two or more event + * objects were signalled during the same select operation, + * and processing of the first caused the second handle to + * be closed, then it will sometimes happen that we receive + * an event notification here for a handle which is already + * deceased. In that situation we simply do nothing. + */ + #ifdef MPEXT + return 0; + #else + return; + #endif + } + + if (h->u.g.moribund) { + /* + * A moribund handle is one which we have either already + * signalled to die, or are waiting until its current I/O op + * completes to do so. Either way, it's treated as already + * dead from the external user's point of view, so we ignore + * the actual I/O result. We just signal the thread to die if + * we haven't yet done so, or destroy the handle if not. + */ + if (h->u.g.done) { + handle_destroy(h); + } else { + h->u.g.done = TRUE; + h->u.g.busy = TRUE; + SetEvent(h->u.g.ev_from_main); + } + #ifdef MPEXT + return 0; + #else + return; + #endif + } + + switch (h->type) { + int backlog; + + case HT_INPUT: + h->u.i.busy = FALSE; + + /* + * A signal on an input handle means data has arrived. + */ + if (h->u.i.len == 0) { + /* + * EOF, or (nearly equivalently) read error. + */ + h->u.i.defunct = TRUE; + h->u.i.gotdata(h, NULL, -h->u.i.readerr); + } else { + backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); + handle_throttle(&h->u.i, backlog); + } + #ifdef MPEXT + return 1; + #else + break; + #endif + + case HT_OUTPUT: + h->u.o.busy = FALSE; + + /* + * A signal on an output handle means we have completed a + * write. Call the callback to indicate that the output + * buffer size has decreased, or to indicate an error. + */ + if (h->u.o.writeerr) { + /* + * Write error. Send a negative value to the callback, + * and mark the thread as defunct (because the output + * thread is terminating by now). + */ + h->u.o.defunct = TRUE; + h->u.o.sentdata(h, -h->u.o.writeerr); + } else { + bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); + handle_try_output(&h->u.o); + } + #ifdef MPEXT + return 0; + #else + break; + #endif + + case HT_FOREIGN: + /* Just call the callback. */ + h->u.f.callback(h->u.f.ctx); + #ifdef MPEXT + return 0; + #else + break; + #endif + } +#ifdef MPEXT + return 0; +#endif +} + +void handle_unthrottle(struct handle *h, int backlog) +{ + assert(h->type == HT_INPUT); + handle_throttle(&h->u.i, backlog); +} + +int handle_backlog(struct handle *h) +{ + assert(h->type == HT_OUTPUT); + return bufchain_size(&h->u.o.queued_data); +} + +void *handle_get_privdata(struct handle *h) +{ + return h->u.g.privdata; +} diff --git a/netbox/libs/Putty/windows/winhsock.c b/netbox/libs/Putty/windows/winhsock.c new file mode 100644 index 000000000..b0171978c --- /dev/null +++ b/netbox/libs/Putty/windows/winhsock.c @@ -0,0 +1,326 @@ +/* + * General mechanism for wrapping up reading/writing of Windows + * HANDLEs into a PuTTY Socket abstraction. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct Socket_handle_tag *Handle_Socket; + +#ifdef MPEXT +extern char *do_select(Plug plug, SOCKET skt, int startup); +#endif + +struct Socket_handle_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; + + /* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress + * it can't sensibly be interrupted. Hence, after the user tries + * to freeze one of these sockets, it's unavoidable that we may + * receive one more load of data before we manage to get + * winhandl.c to stop reading. + */ + enum { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ + } frozen; + /* We buffer data here if we receive it from winhandl while frozen. */ + bufchain inputdata; + + /* Data received from stderr_H, if we have one. */ + bufchain stderrdata; + + char *error; + + Plug plug; +}; + +static int handle_gotdata(struct handle *h, void *data, int len) +{ + Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + + if (len < 0) { + return plug_closing(ps->plug, "Read error from handle", + 0, 0); + } else if (len == 0) { + return plug_closing(ps->plug, NULL, 0, 0); + } else { + assert(ps->frozen != FREEZING && ps->frozen != THAWING); + if (ps->frozen == FREEZING) { + /* + * If we've received data while this socket is supposed to + * be frozen (because the read winhandl.c started before + * sk_set_frozen was called has now returned) then buffer + * the data for when we unfreeze. + */ + bufchain_add(&ps->inputdata, data, len); + + /* + * And return a very large backlog, to prevent further + * data arriving from winhandl until we unfreeze. + */ + return INT_MAX; + } else { + return plug_receive(ps->plug, 0, data, len); + } + } +} + +static int handle_stderr(struct handle *h, void *data, int len) +{ + Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + + if (len > 0) + log_proxy_stderr(ps->plug, &ps->stderrdata, data, len); + + return 0; +} + +static void handle_sentdata(struct handle *h, int new_backlog) +{ + Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + + plug_sent(ps->plug, new_backlog); +} + +static Plug sk_handle_plug(Socket s, Plug p) +{ + Handle_Socket ps = (Handle_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_handle_close(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + + #ifdef MPEXT + // WinSCP core uses do_select as signalization of connection up/down + do_select(ps->plug, INVALID_SOCKET, 0); + #endif + + handle_free(ps->send_h); + handle_free(ps->recv_h); + CloseHandle(ps->send_H); + if (ps->recv_H != ps->send_H) + CloseHandle(ps->recv_H); + bufchain_clear(&ps->inputdata); + bufchain_clear(&ps->stderrdata); + + sfree(ps); +} + +static int sk_handle_write(Socket s, const char *data, int len) +{ + Handle_Socket ps = (Handle_Socket) s; + + return handle_write(ps->send_h, data, len); +} + +static int sk_handle_write_oob(Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_handle_write(s, data, len); +} + +static void sk_handle_write_eof(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + + handle_write_eof(ps->send_h); +} + +static void sk_handle_flush(Socket s) +{ + /* Handle_Socket ps = (Handle_Socket) s; */ + /* do nothing */ +} + +static void handle_socket_unfreeze(void *psv) +{ + Handle_Socket ps = (Handle_Socket) psv; + void *data; + int len, new_backlog; + + /* + * If we've been put into a state other than THAWING since the + * last callback, then we're done. + */ + if (ps->frozen != THAWING) + return; + + /* + * Get some of the data we've buffered. + */ + bufchain_prefix(&ps->inputdata, &data, &len); + assert(len > 0); + + /* + * Hand it off to the plug. + */ + new_backlog = plug_receive(ps->plug, 0, data, len); + + if (bufchain_size(&ps->inputdata) > 0) { + /* + * If there's still data in our buffer, stay in THAWING state, + * and reschedule ourself. + */ + queue_toplevel_callback(handle_socket_unfreeze, ps); + } else { + /* + * Otherwise, we've successfully thawed! + */ + ps->frozen = UNFROZEN; + handle_unthrottle(ps->recv_h, new_backlog); + } +} + +static void sk_handle_set_frozen(Socket s, int is_frozen) +{ + Handle_Socket ps = (Handle_Socket) s; + + if (is_frozen) { + switch (ps->frozen) { + case FREEZING: + case FROZEN: + return; /* nothing to do */ + + case THAWING: + /* + * We were in the middle of emptying our bufchain, and got + * frozen again. In that case, winhandl.c is already + * throttled, so just return to FROZEN state. The toplevel + * callback will notice and disable itself. + */ + ps->frozen = FROZEN; + break; + + case UNFROZEN: + /* + * The normal case. Go to FREEZING, and expect one more + * load of data from winhandl if we're unlucky. + */ + ps->frozen = FREEZING; + break; + } + } else { + switch (ps->frozen) { + case UNFROZEN: + case THAWING: + return; /* nothing to do */ + + case FREEZING: + /* + * If winhandl didn't send us any data throughout the time + * we were frozen, then we'll still be in this state and + * can just unfreeze in the trivial way. + */ + assert(bufchain_size(&ps->inputdata) == 0); + ps->frozen = UNFROZEN; + break; + + case FROZEN: + /* + * If we have buffered data, go to THAWING and start + * releasing it in top-level callbacks. + */ + ps->frozen = THAWING; + queue_toplevel_callback(handle_socket_unfreeze, ps); + } + } +} + +static const char *sk_handle_socket_error(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + return ps->error; +} + +static char *sk_handle_peer_info(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + ULONG pid; + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, + (HANDLE, PULONG)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + GET_WINDOWS_FUNCTION(kernel32_module, GetNamedPipeClientProcessId); + } + + /* + * Of course, not all handles managed by this module will be + * server ends of named pipes, but if they are, then it's useful + * to log what we can find out about the client end. + */ + if (p_GetNamedPipeClientProcessId && + p_GetNamedPipeClientProcessId(ps->send_H, &pid)) + return dupprintf("process id %lu", (unsigned long)pid); + + return NULL; +} + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug plug, int overlapped) +{ + static const struct socket_function_table socket_fn_table = { + sk_handle_plug, + sk_handle_close, + sk_handle_write, + sk_handle_write_oob, + sk_handle_write_eof, + sk_handle_flush, + sk_handle_set_frozen, + sk_handle_socket_error, + sk_handle_peer_info, + }; + + Handle_Socket ret; + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + ret = snew(struct Socket_handle_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + ret->frozen = UNFROZEN; + bufchain_init(&ret->inputdata); + bufchain_init(&ret->stderrdata); + + ret->recv_H = recv_H; + ret->recv_h = handle_input_new(ret->recv_H, handle_gotdata, ret, flags); + ret->send_H = send_H; + ret->send_h = handle_output_new(ret->send_H, handle_sentdata, ret, flags); + ret->stderr_H = stderr_H; + if (ret->stderr_H) + ret->stderr_h = handle_input_new(ret->stderr_H, handle_stderr, + ret, flags); + + #ifdef MPEXT + // WinSCP core uses do_select as signalization of connection up/down + do_select(plug, INVALID_SOCKET, 1); + #endif + + return (Socket) ret; +} diff --git a/netbox/libs/Putty/windows/winmisc.c b/netbox/libs/Putty/windows/winmisc.c new file mode 100644 index 000000000..e33bfd0f8 --- /dev/null +++ b/netbox/libs/Putty/windows/winmisc.c @@ -0,0 +1,591 @@ +/* + * winmisc.c: miscellaneous Windows-specific things + */ + +#include +#include +#include "putty.h" +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 +#endif +#include +#ifdef MPEXT +#include +#endif + +OSVERSIONINFO osVersion; + +char *platform_get_x_display(void) { + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +int filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +int filename_is_null(const Filename *fn) +{ + return !*fn->path; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +int filename_serialise(const Filename *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->path) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->path); + } + return len; +} +Filename *filename_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end; + end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + end++; + *used = end - data; + return filename_from_str(data); +} + +char filename_char_sanitise(char c) +{ + if (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} + +#ifdef MPEXT + +FILE * mp_wfopen(const char *filename, const char *mode) +{ + size_t len = strlen(filename); + wchar_t * wfilename = snewn(len * 10, wchar_t); + size_t wlen = MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, len * 10); + FILE * file; + if (wlen <= 0) + { + file = NULL; + } + else + { + wchar_t wmode[3]; + memset(wmode, 0, sizeof(wmode)); + wmode[0] = (wchar_t)mode[0]; + if (mode[0] != '\0') + { + wmode[1] = (wchar_t)mode[1]; + if (mode[1] != '\0') + { + assert(mode[2] == '\0'); + } + } + + file = _wfopen(wfilename, wmode); + } + sfree(wfilename); + return file; +} + +#endif + +#ifndef NO_SECUREZEROMEMORY +/* + * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. + */ +void smemclr(void *b, size_t n) { + if (b && n > 0) + SecureZeroMemory(b, n); +} +#endif + +char *get_username(void) +{ + DWORD namelen; + char *user; + int got_username = FALSE; + DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, + (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); + + { + static int tried_usernameex = FALSE; + if (!tried_usernameex) { + /* Not available on Win9x, so load dynamically */ + HMODULE secur32 = load_system32_dll("secur32.dll"); + GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); + tried_usernameex = TRUE; + } + } + + if (p_GetUserNameExA) { + /* + * If available, use the principal -- this avoids the problem + * that the local username is case-insensitive but Kerberos + * usernames are case-sensitive. + */ + + /* Get the length */ + namelen = 0; + (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); + + user = snewn(namelen, char); + got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); + if (got_username) { + char *p = strchr(user, '@'); + if (p) *p = 0; + } else { + sfree(user); + } + } + + if (!got_username) { + /* Fall back to local user name */ + namelen = 0; + if (GetUserName(NULL, &namelen) == FALSE) { + /* + * Apparently this doesn't work at least on Windows XP SP2. + * Thus assume a maximum of 256. It will fail again if it + * doesn't fit. + */ + namelen = 256; + } + + user = snewn(namelen, char); + got_username = GetUserName(user, &namelen); + if (!got_username) { + sfree(user); + } + } + + return got_username ? user : NULL; +} + +BOOL init_winver(void) +{ + ZeroMemory(&osVersion, sizeof(osVersion)); + osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + return GetVersionEx ( (OSVERSIONINFO *) &osVersion); +} + +#ifdef MPEXT +static char *sysdir = NULL; + +void win_misc_cleanup() +{ + sfree(sysdir); +} +#endif + +HMODULE load_system32_dll(const char *libname) +{ + /* + * Wrapper function to load a DLL out of c:\windows\system32 + * without going through the full DLL search path. (Hence no + * attack is possible by placing a substitute DLL earlier on that + * path.) + */ +#ifndef MPEXT + static char *sysdir = NULL; +#endif + char *fullpath; + HMODULE ret; + + if (!sysdir) { + int size = 0, len; + do { + size = 3*size/2 + 512; + sysdir = sresize(sysdir, size, char); + len = GetSystemDirectory(sysdir, size); + } while (len >= size); + } + + fullpath = dupcat(sysdir, "\\", libname, NULL); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} + +/* + * A tree234 containing mappings from system error codes to strings. + */ + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + int bufsize; + + es = snew(struct errstring); + es->error = error; + /* maximum size for FormatMessage is 64K */ + bufsize = 65535; + es->text = snewn(bufsize+1, char); + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + es->text, bufsize, NULL)) { + sprintf(es->text, + "Windows error code %d (and FormatMessage returned %d)", + error, GetLastError()); + } else { + int len = strlen(es->text); + if (len > 0 && es->text[len-1] == '\n') + es->text[len-1] = '\0'; + } + es->text = sresize(es->text, strlen(es->text) + 1, char); + add234(errstrings, es); + } + + return es->text; +} + +#ifdef DEBUG +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(const char *buf) +{ + DWORD dw; + + if (!debug_got_console) { + if (AllocConsole()) { + debug_got_console = 1; + debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); + } + } + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (debug_hdl != INVALID_HANDLE_VALUE) { + WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); + } + fputs(buf, debug_fp); + fflush(debug_fp); +} +#endif + +#ifdef MINEFIELD +/* + * Minefield - a Windows equivalent for Electric Fence + */ + +#define PAGESIZE 4096 + +/* + * Design: + * + * We start by reserving as much virtual address space as Windows + * will sensibly (or not sensibly) let us have. We flag it all as + * invalid memory. + * + * Any allocation attempt is satisfied by committing one or more + * pages, with an uncommitted page on either side. The returned + * memory region is jammed up against the _end_ of the pages. + * + * Freeing anything causes instantaneous decommitment of the pages + * involved, so stale pointers are caught as soon as possible. + */ + +static int minefield_initialised = 0; +static void *minefield_region = NULL; +static long minefield_size = 0; +static long minefield_npages = 0; +static long minefield_curpos = 0; +static unsigned short *minefield_admin = NULL; +static void *minefield_pages = NULL; + +static void minefield_admin_hide(int hide) +{ + int access = hide ? PAGE_NOACCESS : PAGE_READWRITE; + VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL); +} + +static void minefield_init(void) +{ + int size; + int admin_size; + int i; + + for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) { + minefield_region = VirtualAlloc(NULL, size, + MEM_RESERVE, PAGE_NOACCESS); + if (minefield_region) + break; + } + minefield_size = size; + + /* + * Firstly, allocate a section of that to be the admin block. + * We'll need a two-byte field for each page. + */ + minefield_admin = minefield_region; + minefield_npages = minefield_size / PAGESIZE; + admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1); + minefield_npages = (minefield_size - admin_size) / PAGESIZE; + minefield_pages = (char *) minefield_region + admin_size; + + /* + * Commit the admin region. + */ + VirtualAlloc(minefield_admin, minefield_npages * 2, + MEM_COMMIT, PAGE_READWRITE); + + /* + * Mark all pages as unused (0xFFFF). + */ + for (i = 0; i < minefield_npages; i++) + minefield_admin[i] = 0xFFFF; + + /* + * Hide the admin region. + */ + minefield_admin_hide(1); + + minefield_initialised = 1; +} + +static void minefield_bomb(void) +{ + div(1, *(int *) minefield_pages); +} + +static void *minefield_alloc(int size) +{ + int npages; + int pos, lim, region_end, region_start; + int start; + int i; + + npages = (size + PAGESIZE - 1) / PAGESIZE; + + minefield_admin_hide(0); + + /* + * Search from current position until we find a contiguous + * bunch of npages+2 unused pages. + */ + pos = minefield_curpos; + lim = minefield_npages; + while (1) { + /* Skip over used pages. */ + while (pos < lim && minefield_admin[pos] != 0xFFFF) + pos++; + /* Count unused pages. */ + start = pos; + while (pos < lim && pos - start < npages + 2 && + minefield_admin[pos] == 0xFFFF) + pos++; + if (pos - start == npages + 2) + break; + /* If we've reached the limit, reset the limit or stop. */ + if (pos >= lim) { + if (lim == minefield_npages) { + /* go round and start again at zero */ + lim = minefield_curpos; + pos = 0; + } else { + minefield_admin_hide(1); + return NULL; + } + } + } + + minefield_curpos = pos - 1; + + /* + * We have npages+2 unused pages starting at start. We leave + * the first and last of these alone and use the rest. + */ + region_end = (start + npages + 1) * PAGESIZE; + region_start = region_end - size; + /* FIXME: could align here if we wanted */ + + /* + * Update the admin region. + */ + for (i = start + 2; i < start + npages + 1; i++) + minefield_admin[i] = 0xFFFE; /* used but no region starts here */ + minefield_admin[start + 1] = region_start % PAGESIZE; + + minefield_admin_hide(1); + + VirtualAlloc((char *) minefield_pages + region_start, size, + MEM_COMMIT, PAGE_READWRITE); + return (char *) minefield_pages + region_start; +} + +static void minefield_free(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) { + minefield_admin[j] = 0xFFFF; + } + + VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT); + + minefield_admin_hide(1); +} + +static int minefield_get_size(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++); + + minefield_admin_hide(1); + + return j * PAGESIZE - region_start; +} + +void *minefield_c_malloc(size_t size) +{ + if (!minefield_initialised) + minefield_init(); + return minefield_alloc(size); +} + +void minefield_c_free(void *p) +{ + if (!minefield_initialised) + minefield_init(); + minefield_free(p); +} + +/* + * realloc _always_ moves the chunk, for rapid detection of code + * that assumes it won't. + */ +void *minefield_c_realloc(void *p, size_t size) +{ + size_t oldsize; + void *q; + if (!minefield_initialised) + minefield_init(); + q = minefield_alloc(size); + oldsize = minefield_get_size(p); + memcpy(q, p, (oldsize < size ? oldsize : size)); + minefield_free(p); + return q; +} + +#endif /* MINEFIELD */ + +FontSpec *fontspec_new(const char *name, + int bold, int height, int charset) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + f->isbold = bold; + f->height = height; + f->charset = charset; + return f; +} +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name, f->isbold, f->height, f->charset); +} +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} +int fontspec_serialise(FontSpec *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->name) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->name); + PUT_32BIT_MSB_FIRST(data + len, f->isbold); + PUT_32BIT_MSB_FIRST(data + len + 4, f->height); + PUT_32BIT_MSB_FIRST(data + len + 8, f->charset); + } + return len + 12; /* also include three 4-byte ints */ +} +FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end; + if (maxsize < 13) + return NULL; + end = memchr(data, '\0', maxsize-12); + if (!end) + return NULL; + end++; + *used = end - data + 12; + return fontspec_new(data, + GET_32BIT_MSB_FIRST(end), + GET_32BIT_MSB_FIRST(end + 4), + GET_32BIT_MSB_FIRST(end + 8)); +} diff --git a/netbox/libs/Putty/windows/winnet.c b/netbox/libs/Putty/windows/winnet.c new file mode 100644 index 000000000..f4ffc8a31 --- /dev/null +++ b/netbox/libs/Putty/windows/winnet.c @@ -0,0 +1,2004 @@ +/* + * Windows networking abstraction. + * + * For the IPv6 code in here I am indebted to Jeroen Massar and + * unfix.org. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "tree234.h" + +#if defined(MPEXT) && !defined(_MSC_VER) +// ws2tcpip.h does not compile without _MSC_VER defined +#define _MSC_VER 1000 +#endif +#include + +#ifndef NO_IPV6 +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; +#endif + +#define ipv4_is_loopback(addr) \ + ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) + +/* + * We used to typedef struct Socket_tag *Socket. + * + * Since we have made the networking abstraction slightly more + * abstract, Socket no longer means a tcp socket (it could mean + * an ssl socket). So now we must use Actual_Socket when we know + * we are talking about a tcp socket. + */ +typedef struct Socket_tag *Actual_Socket; + +/* + * Mutable state that goes with a SockAddr: stores information + * about where in the list of candidate IP(v*) addresses we've + * currently got to. + */ +typedef struct SockAddrStep_tag SockAddrStep; +struct SockAddrStep_tag { +#ifndef NO_IPV6 + struct addrinfo *ai; /* steps along addr->ais */ +#endif + int curraddr; +}; + +struct Socket_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + const char *error; + SOCKET s; + Plug plug; + bufchain output_data; + int connected; + int writable; + int frozen; /* this causes readability notifications to be ignored */ + int frozen_readable; /* this means we missed at least one readability + * notification while we were frozen */ + int localhost_only; /* for listening sockets */ + char oobdata[1]; + int sending_oob; + int oobinline, nodelay, keepalive, privport; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + SockAddr addr; + SockAddrStep step; + int port; + int pending_error; /* in case send() returns error */ + /* + * We sometimes need pairs of Socket structures to be linked: + * if we are listening on the same IPv6 and v4 port, for + * example. So here we define `parent' and `child' pointers to + * track this link. + */ + Actual_Socket parent, child; +}; + +struct SockAddr_tag { + int refcount; + char *error; + int resolved; + int namedpipe; /* indicates that this SockAddr is phony, holding a Windows + * named pipe pathname instead of a network address */ +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#endif + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; + char hostname[512]; /* Store an unresolved host name. */ +}; + +/* + * Which address family this address belongs to. AF_INET for IPv4; + * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has + * not been done and a simple host name is held in this SockAddr + * structure. + */ +#ifndef NO_IPV6 +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : \ + (step).ai ? (step).ai->ai_family : AF_INET) +#else +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : AF_INET) +#endif + +/* + * Start a SockAddrStep structure to step through multiple + * addresses. + */ +#ifndef NO_IPV6 +#define START_STEP(addr, step) \ + ((step).ai = (addr)->ais, (step).curraddr = 0) +#else +#define START_STEP(addr, step) \ + ((step).curraddr = 0) +#endif + +static tree234 *sktree; + +static int cmpfortree(void *av, void *bv) +{ + Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv; + unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + if (a < b) + return -1; + if (a > b) + return +1; + return 0; +} + +static int cmpforsearch(void *av, void *bv) +{ + Actual_Socket b = (Actual_Socket) bv; + uintptr_t as = (uintptr_t) av, bs = (uintptr_t) b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + return 0; +} + +DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA)); +DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void)); +DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET)); +DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long)); +DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long)); +DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short)); +DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short)); +DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int)); +DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname, + (const char FAR *)); +DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname, + (const char FAR *, const char FAR *)); +DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *)); +DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr)); +DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop, + (int, void FAR *, char *, size_t)); +DECL_WINDOWS_FUNCTION(static, int, connect, + (SOCKET, const struct sockaddr FAR *, int)); +DECL_WINDOWS_FUNCTION(static, int, bind, + (SOCKET, const struct sockaddr FAR *, int)); +#ifdef MPEXT +DECL_WINDOWS_FUNCTION(static, int, getsockopt, + (SOCKET, int, int, char FAR *, int *)); +#endif +DECL_WINDOWS_FUNCTION(static, int, setsockopt, + (SOCKET, int, int, const char FAR *, int)); +DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int)); +DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int)); +DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int)); +DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, + (SOCKET, long, u_long FAR *)); +DECL_WINDOWS_FUNCTION(static, SOCKET, accept, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, getpeername, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, WSAIoctl, + (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD, + LPDWORD, LPWSAOVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE)); +#ifndef NO_IPV6 +DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, + (const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res)); +DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); +DECL_WINDOWS_FUNCTION(static, int, getnameinfo, + (const struct sockaddr FAR * sa, socklen_t salen, + char FAR * host, size_t hostlen, char FAR * serv, + size_t servlen, int flags)); +DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode)); +DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, + (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, + LPSTR, LPDWORD)); +#endif + +static HMODULE winsock_module = NULL; +static WSADATA wsadata; +#ifndef NO_IPV6 +static HMODULE winsock2_module = NULL; +static HMODULE wship6_module = NULL; +#endif + +int sk_startup(int hi, int lo) +{ + WORD winsock_ver; + + winsock_ver = MAKEWORD(hi, lo); + + if (p_WSAStartup(winsock_ver, &wsadata)) { + return FALSE; + } + + if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) { + return FALSE; + } + +#ifdef NET_SETUP_DIAGNOSTICS + { + char buf[80]; + sprintf(buf, "Using WinSock %d.%d", hi, lo); + logevent(NULL, buf); + } +#endif + return TRUE; +} + +void sk_init(void) +{ +#ifndef NO_IPV6 + winsock2_module = +#endif + winsock_module = load_system32_dll("ws2_32.dll"); + if (!winsock_module) { + winsock_module = load_system32_dll("wsock32.dll"); + } + if (!winsock_module) + { + fatalbox("Unable to load any WinSock library"); + } + +#ifndef NO_IPV6 + /* Check if we have getaddrinfo in Winsock */ + if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Native WinSock IPv6 support detected"); +#endif + GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, getnameinfo); + GET_WINDOWS_FUNCTION(winsock_module, gai_strerror); + } else { + /* Fall back to wship6.dll for Windows 2000 */ + wship6_module = load_system32_dll("wship6.dll"); + if (wship6_module) { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "WSH IPv6 support detected"); +#endif + GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, getnameinfo); + GET_WINDOWS_FUNCTION(wship6_module, gai_strerror); + } else { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "No IPv6 support detected"); +#endif + } + } + GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); +#else +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "PuTTY was built without IPv6 support"); +#endif +#endif + + GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); + GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); + GET_WINDOWS_FUNCTION(winsock_module, select); + GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError); + GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents); + GET_WINDOWS_FUNCTION(winsock_module, WSAStartup); + GET_WINDOWS_FUNCTION(winsock_module, WSACleanup); + GET_WINDOWS_FUNCTION(winsock_module, closesocket); + GET_WINDOWS_FUNCTION(winsock_module, ntohl); + GET_WINDOWS_FUNCTION(winsock_module, htonl); + GET_WINDOWS_FUNCTION(winsock_module, htons); + GET_WINDOWS_FUNCTION(winsock_module, ntohs); + GET_WINDOWS_FUNCTION(winsock_module, gethostname); + GET_WINDOWS_FUNCTION(winsock_module, gethostbyname); + GET_WINDOWS_FUNCTION(winsock_module, getservbyname); + GET_WINDOWS_FUNCTION(winsock_module, inet_addr); + GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa); + GET_WINDOWS_FUNCTION(winsock_module, inet_ntop); + GET_WINDOWS_FUNCTION(winsock_module, connect); + GET_WINDOWS_FUNCTION(winsock_module, bind); + #ifdef MPEXT + GET_WINDOWS_FUNCTION(winsock_module, getsockopt); + #endif + GET_WINDOWS_FUNCTION(winsock_module, setsockopt); + GET_WINDOWS_FUNCTION(winsock_module, socket); + GET_WINDOWS_FUNCTION(winsock_module, listen); + GET_WINDOWS_FUNCTION(winsock_module, send); + GET_WINDOWS_FUNCTION(winsock_module, shutdown); + GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket); + GET_WINDOWS_FUNCTION(winsock_module, accept); + GET_WINDOWS_FUNCTION(winsock_module, getpeername); + GET_WINDOWS_FUNCTION(winsock_module, recv); + GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl); + + /* Try to get the best WinSock version we can get */ + if (!sk_startup(2,2) && + !sk_startup(2,0) && + !sk_startup(1,1)) { + fatalbox("Unable to initialise WinSock"); + } + + sktree = newtree234(cmpfortree); +} + +void sk_cleanup(void) +{ + Actual_Socket s; + int i; + + if (sktree) { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + p_closesocket(s->s); + } + freetree234(sktree); + sktree = NULL; + } + + if (p_WSACleanup) + { + p_WSACleanup(); + } + if (winsock_module) + FreeLibrary(winsock_module); +#ifndef NO_IPV6 + if (wship6_module) + FreeLibrary(wship6_module); +#endif +} + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *winsock_error_string(int error) +{ + const char prefix[] = "Network error: "; + struct errstring *es; + + /* + * Error codes we know about and have historically had reasonably + * sensible error messages for. + */ + switch (error) { + case WSAEACCES: + return "Network error: Permission denied"; + case WSAEADDRINUSE: + return "Network error: Address already in use"; + case WSAEADDRNOTAVAIL: + return "Network error: Cannot assign requested address"; + case WSAEAFNOSUPPORT: + return + "Network error: Address family not supported by protocol family"; + case WSAEALREADY: + return "Network error: Operation already in progress"; + case WSAECONNABORTED: + return "Network error: Software caused connection abort"; + case WSAECONNREFUSED: + return "Network error: Connection refused"; + case WSAECONNRESET: + return "Network error: Connection reset by peer"; + case WSAEDESTADDRREQ: + return "Network error: Destination address required"; + case WSAEFAULT: + return "Network error: Bad address"; + case WSAEHOSTDOWN: + return "Network error: Host is down"; + case WSAEHOSTUNREACH: + return "Network error: No route to host"; + case WSAEINPROGRESS: + return "Network error: Operation now in progress"; + case WSAEINTR: + return "Network error: Interrupted function call"; + case WSAEINVAL: + return "Network error: Invalid argument"; + case WSAEISCONN: + return "Network error: Socket is already connected"; + case WSAEMFILE: + return "Network error: Too many open files"; + case WSAEMSGSIZE: + return "Network error: Message too long"; + case WSAENETDOWN: + return "Network error: Network is down"; + case WSAENETRESET: + return "Network error: Network dropped connection on reset"; + case WSAENETUNREACH: + return "Network error: Network is unreachable"; + case WSAENOBUFS: + return "Network error: No buffer space available"; + case WSAENOPROTOOPT: + return "Network error: Bad protocol option"; + case WSAENOTCONN: + return "Network error: Socket is not connected"; + case WSAENOTSOCK: + return "Network error: Socket operation on non-socket"; + case WSAEOPNOTSUPP: + return "Network error: Operation not supported"; + case WSAEPFNOSUPPORT: + return "Network error: Protocol family not supported"; + case WSAEPROCLIM: + return "Network error: Too many processes"; + case WSAEPROTONOSUPPORT: + return "Network error: Protocol not supported"; + case WSAEPROTOTYPE: + return "Network error: Protocol wrong type for socket"; + case WSAESHUTDOWN: + return "Network error: Cannot send after socket shutdown"; + case WSAESOCKTNOSUPPORT: + return "Network error: Socket type not supported"; + case WSAETIMEDOUT: + return "Network error: Connection timed out"; + case WSAEWOULDBLOCK: + return "Network error: Resource temporarily unavailable"; + case WSAEDISCON: + return "Network error: Graceful shutdown in progress"; + } + + /* + * Generic code to handle any other error. + * + * Slightly nasty hack here: we want to return a static string + * which the caller will never have to worry about freeing, but on + * the other hand if we call FormatMessage to get it then it will + * want to either allocate a buffer or write into one we own. + * + * So what we do is to maintain a tree234 of error strings we've + * already used. New ones are allocated from the heap, but then + * put in this tree and kept forever. + */ + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + int bufsize, bufused; + + es = snew(struct errstring); + es->error = error; + /* maximum size for FormatMessage is 64K */ + bufsize = 65535 + sizeof(prefix); + es->text = snewn(bufsize, char); + strcpy(es->text, prefix); + bufused = strlen(es->text); + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + es->text + bufused, bufsize - bufused, NULL)) { + sprintf(es->text + bufused, + "Windows error code %d (and FormatMessage returned %u)", + error, (unsigned int)GetLastError()); + } else { + int len = strlen(es->text); + if (len > 0 && es->text[len-1] == '\n') + es->text[len-1] = '\0'; + } + es->text = sresize(es->text, strlen(es->text) + 1, char); + add234(errstrings, es); + } + + return es->text; +} + +SockAddr sk_namelookup(const char *host, char **canonicalname, + int address_family) +{ + SockAddr ret = snew(struct SockAddr_tag); + unsigned long a; + char realhost[8192]; + int hint_family; + + /* Default to IPv4. */ + hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(struct SockAddr_tag)); +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = FALSE; + ret->addresses = NULL; + ret->resolved = FALSE; + ret->refcount = 1; + *realhost = '\0'; + + if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { + struct hostent *h = NULL; + int err; +#ifndef NO_IPV6 + /* + * Use getaddrinfo when it's available + */ + if (p_getaddrinfo) { + struct addrinfo hints; +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Using getaddrinfo() for resolving"); +#endif + memset(&hints, 0, sizeof(hints)); + hints.ai_family = hint_family; + hints.ai_flags = AI_CANONNAME; + { + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + sfree(trimmed_host); + } + if (err == 0) + { + ret->resolved = TRUE; + } + } else +#endif + { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Using gethostbyname() for resolving"); +#endif + /* + * Otherwise use the IPv4-only gethostbyname... + * (NOTE: we don't use gethostbyname as a fallback!) + */ + if ( (h = p_gethostbyname(host)) ) + ret->resolved = TRUE; + else + err = p_WSAGetLastError(); + } + + if (!ret->resolved) { + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : +#ifndef NO_IPV6 + p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) : +#endif + "gethostbyname: unknown error"); + } else { + ret->error = NULL; + +#ifndef NO_IPV6 + /* If we got an address info use that... */ + if (ret->ais) { + /* Are we in IPv4 fallback mode? */ + /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */ + if (ret->ais->ai_family == AF_INET) + { + memcpy(&a, + (char *) &((SOCKADDR_IN *) ret->ais-> + ai_addr)->sin_addr, sizeof(a)); + } + + if (ret->ais->ai_canonname) + strncpy(realhost, ret->ais->ai_canonname, lenof(realhost)); + else + strncpy(realhost, host, lenof(realhost)); + } + /* We used the IPv4-only gethostbyname()... */ + else +#endif + { + int n; + for (n = 0; h->h_addr_list[n]; n++); + ret->addresses = snewn(n, unsigned long); + ret->naddresses = n; + for (n = 0; n < ret->naddresses; n++) { + memcpy(&a, h->h_addr_list[n], sizeof(a)); + ret->addresses[n] = p_ntohl(a); + } + memcpy(&a, h->h_addr, sizeof(a)); + /* This way we are always sure the h->h_name is valid :) */ + strncpy(realhost, h->h_name, sizeof(realhost)); + } + } + } else { + /* + * This must be a numeric IPv4 address because it caused a + * success return from inet_addr. + */ + ret->addresses = snewn(1, unsigned long); + ret->naddresses = 1; + ret->addresses[0] = p_ntohl(a); + ret->resolved = TRUE; + strncpy(realhost, host, sizeof(realhost)); + } + realhost[lenof(realhost)-1] = '\0'; + *canonicalname = snewn(1+strlen(realhost), char); + strcpy(*canonicalname, realhost); + return ret; +} + +SockAddr sk_nonamelookup(const char *host) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->resolved = FALSE; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = FALSE; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +SockAddr sk_namedpipe_addr(const char *pipename) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->resolved = FALSE; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = TRUE; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, pipename, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +int sk_nextaddr(SockAddr addr, SockAddrStep *step) +{ +#ifndef NO_IPV6 + if (step->ai) { + if (step->ai->ai_next) { + step->ai = step->ai->ai_next; + return TRUE; + } else + return FALSE; + } +#endif + if (step->curraddr+1 < addr->naddresses) { + step->curraddr++; + return TRUE; + } else { + return FALSE; + } +} + +void sk_getaddr(SockAddr addr, char *buf, int buflen) +{ + SockAddrStep step; + START_STEP(addr, step); + +#ifndef NO_IPV6 + if (step.ai) { + int err = 0; + if (p_WSAAddressToStringA) { + DWORD dwbuflen = buflen; + err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen, + NULL, buf, &dwbuflen); + } else + err = -1; + if (err) { + strncpy(buf, addr->hostname, buflen); + if (!buf[0]) + strncpy(buf, "", buflen); + buf[buflen-1] = '\0'; + } + } else +#endif + if (SOCKADDR_FAMILY(addr, step) == AF_INET) { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + strncpy(buf, p_inet_ntoa(a), buflen); + buf[buflen-1] = '\0'; + } else { + strncpy(buf, addr->hostname, buflen); + buf[buflen-1] = '\0'; + } +} + +int sk_addr_needs_port(SockAddr addr) +{ + return addr->namedpipe ? FALSE : TRUE; +} + +int sk_hostname_is_local(const char *name) +{ + return !strcmp(name, "localhost") || + !strcmp(name, "::1") || + !strncmp(name, "127.", 4); +} + +static INTERFACE_INFO local_interfaces[16]; +static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */ + +static int ipv4_is_local_addr(struct in_addr addr) +{ + if (ipv4_is_loopback(addr)) + return 1; /* loopback addresses are local */ + if (!n_local_interfaces) { + SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); + DWORD retbytes; + + if (p_WSAIoctl && + p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, + local_interfaces, sizeof(local_interfaces), + &retbytes, NULL, NULL) == 0) + n_local_interfaces = retbytes / sizeof(INTERFACE_INFO); + else + logevent(NULL, "Unable to get list of local IP addresses"); + } + if (n_local_interfaces > 0) { + int i; + for (i = 0; i < n_local_interfaces; i++) { + SOCKADDR_IN *address = + (SOCKADDR_IN *)&local_interfaces[i].iiAddress; + if (address->sin_addr.s_addr == addr.s_addr) + return 1; /* this address is local */ + } + } + return 0; /* this address is not local */ +} + +int sk_address_is_local(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + +#ifndef NO_IPV6 + if (family == AF_INET6) { + return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr); + } else +#endif + if (family == AF_INET) { +#ifndef NO_IPV6 + if (step.ai) { + return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr) + ->sin_addr); + } else +#endif + { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + return ipv4_is_local_addr(a); + } + } else { + assert(family == AF_UNSPEC); + return 0; /* we don't know; assume not */ + } +} + +int sk_address_is_special_local(SockAddr addr) +{ + return 0; /* no Unix-domain socket analogue here */ +} + +int sk_addrtype(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + return (family == AF_INET ? ADDRTYPE_IPV4 : +#ifndef NO_IPV6 + family == AF_INET6 ? ADDRTYPE_IPV6 : +#endif + ADDRTYPE_NAME); +} + +void sk_addrcopy(SockAddr addr, char *buf) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + assert(family != AF_UNSPEC); +#ifndef NO_IPV6 + if (step.ai) { + if (family == AF_INET) + memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr, + sizeof(struct in_addr)); + else if (family == AF_INET6) + memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + else + assert(FALSE); + } else +#endif + if (family == AF_INET) { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + memcpy(buf, (char*) &a.s_addr, 4); + } +} + +void sk_addr_free(SockAddr addr) +{ + if (--addr->refcount > 0) + return; +#ifndef NO_IPV6 + if (addr->ais && p_freeaddrinfo) + p_freeaddrinfo(addr->ais); +#endif + if (addr->addresses) + sfree(addr->addresses); + sfree(addr); +} + +SockAddr sk_addr_dup(SockAddr addr) +{ + addr->refcount++; + return addr; +} + +static Plug sk_tcp_plug(Socket sock, Plug p) +{ + Actual_Socket s = (Actual_Socket) sock; + Plug ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_tcp_flush(Socket s) +{ + /* + * We send data to the socket as soon as we can anyway, + * so we don't need to do anything here. :-) + */ +} + +static void sk_tcp_close(Socket s); +static int sk_tcp_write(Socket s, const char *data, int len); +static int sk_tcp_write_oob(Socket s, const char *data, int len); +static void sk_tcp_write_eof(Socket s); +static void sk_tcp_set_frozen(Socket s, int is_frozen); +static const char *sk_tcp_socket_error(Socket s); +static char *sk_tcp_peer_info(Socket s); + +#ifdef MPEXT +extern char *do_select(Plug plug, SOCKET skt, int startup); +#else +extern char *do_select(SOCKET skt, int startup); +#endif + +static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_write_eof, + sk_tcp_flush, + sk_tcp_set_frozen, + sk_tcp_socket_error, + sk_tcp_peer_info, + }; + + DWORD err; + char *errstr; + Actual_Socket ret; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 1; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = 1; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + ret->s = (SOCKET)ctx.p; + + if (ret->s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + ret->oobinline = 0; + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ +#ifdef MPEXT + errstr = do_select(plug, ret->s, 1); +#else + errstr = do_select(ret->s, 1); +#endif + if (errstr) { + ret->error = errstr; + return (Socket) ret; + } + + add234(sktree, ret); + + return (Socket) ret; +} + +static DWORD try_connect(Actual_Socket sock, +#ifdef MPEXT + int timeout, + int sndbuf +#endif +) +{ + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + DWORD err; + char *errstr; + short localport; + int family; +#ifdef MPEXT + struct timeval rcvtimeo; +#endif + + if (sock->s != INVALID_SOCKET) { +#ifdef MPEXT + do_select(sock->plug, sock->s, 0); +#else + do_select(sock->s, 0); +#endif + p_closesocket(sock->s); + } + + plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0); + + /* + * Open socket. + */ + family = SOCKADDR_FAMILY(sock->addr, sock->step); + + /* + * Remove the socket from the tree before we overwrite its + * internal socket id, because that forms part of the tree's + * sorting criterion. We'll add it back before exiting this + * function, whether we changed anything or not. + */ + del234(sktree, sock); + + s = p_socket(family, SOCK_STREAM, 0); + sock->s = s; + + if (s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + sock->error = winsock_error_string(err); + goto ret; + } + + if (sock->oobinline) { + BOOL b = TRUE; + p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)); + } + + if (sock->nodelay) { + BOOL b = TRUE; + p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); + } + + if (sock->keepalive) { + BOOL b = TRUE; + p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); + } + + if (sndbuf > 0) + { + int rcvbuf = 256 * 1024; // 4 * 1024 * 1024; + p_setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf, sizeof(sndbuf)); + + // For now we increase receive buffer, whenever send buffer is set. + // The size is not configurable. The constant taken from FZ. + p_setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*) &rcvbuf, sizeof(rcvbuf)); + } + + /* + * Bind to local address. + */ + if (sock->privport) + localport = 1023; /* count from 1023 downwards */ + else + localport = 0; /* just use port 0 (ie winsock picks) */ + + /* Loop round trying to bind */ + while (1) { + int sockcode; + +#ifndef NO_IPV6 + if (family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */ + a6.sin6_port = p_htons(localport); + } else +#endif + { + a.sin_family = AF_INET; + a.sin_addr.s_addr = p_htonl(INADDR_ANY); + a.sin_port = p_htons(localport); + } +#ifndef NO_IPV6 + sockcode = p_bind(s, (family == AF_INET6 ? + (struct sockaddr *) &a6 : + (struct sockaddr *) &a), + (family == AF_INET6 ? sizeof(a6) : sizeof(a))); +#else + sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); +#endif + if (sockcode != SOCKET_ERROR) { + err = 0; + break; /* done */ + } else { + err = p_WSAGetLastError(); + if (err != WSAEADDRINUSE) /* failed, for a bad reason */ + break; + } + + if (localport == 0) + break; /* we're only looping once */ + localport--; + if (localport == 0) + break; /* we might have got to the end */ + } + + if (err) { + sock->error = winsock_error_string(err); + goto ret; + } + + /* + * Connect to remote address. + */ +#ifndef NO_IPV6 + if (sock->step.ai) { + if (family == AF_INET6) { + a6.sin6_family = AF_INET6; + a6.sin6_port = p_htons((short) sock->port); + a6.sin6_addr = + ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr; + a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo; + a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id; + } else { + a.sin_family = AF_INET; + a.sin_addr = + ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr; + a.sin_port = p_htons((short) sock->port); + } + } else +#endif + { + assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses); + a.sin_family = AF_INET; + a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]); + a.sin_port = p_htons((short) sock->port); + } + +#ifndef MPEXT + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(s, 1); + if (errstr) { + sock->error = errstr; + err = 1; + goto ret; + } +#endif + +#ifdef MPEXT + if (timeout > 0) + { + if (p_getsockopt (s, SOL_SOCKET, SO_RCVTIMEO, (char *)&rcvtimeo, &rcvtimeo) < 0) + { + rcvtimeo.tv_sec = -1; + } + else + { + struct timeval timeoutval; + timeoutval.tv_sec = timeout / 1000; + timeoutval.tv_usec = (timeout % 1000) * 1000; + p_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (void *) &timeoutval, sizeof(timeoutval)); + } + } +#endif + + if (( +#ifndef NO_IPV6 + p_connect(s, + ((family == AF_INET6) ? (struct sockaddr *) &a6 : + (struct sockaddr *) &a), + (family == AF_INET6) ? sizeof(a6) : sizeof(a)) +#else + p_connect(s, (struct sockaddr *) &a, sizeof(a)) +#endif + ) == SOCKET_ERROR) { + err = p_WSAGetLastError(); + /* + * We expect a potential EWOULDBLOCK here, because the + * chances are the front end has done a select for + * FD_CONNECT, so that connect() will complete + * asynchronously. + */ + if ( err != WSAEWOULDBLOCK ) { +#ifdef MPEXT + // unselect on error + do_select(sock->plug, s, 0); +#endif + sock->error = winsock_error_string(err); + goto ret; + } + } else { + /* + * If we _don't_ get EWOULDBLOCK, the connect has completed + * and we should set the socket as writable. + */ + sock->writable = 1; + } + +#ifdef MPEXT + if ((timeout > 0) && (rcvtimeo.tv_sec >= 0)) + { + p_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (void *) &rcvtimeo, sizeof(rcvtimeo)); + } + + // MP: Calling EventSelect only after connect makes sure we receive FD_CLOSE. + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(sock->plug, s, 1); + if (errstr) { + sock->error = errstr; + err = 1; + goto ret; + } +#endif + + err = 0; + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) + { + plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err); + } + return err; +} + +Socket putty_sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug plug, +#ifdef MPEXT + int timeout, + int sndbuf +#endif + ) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_write_eof, + sk_tcp_flush, + sk_tcp_set_frozen, + sk_tcp_socket_error, + sk_tcp_peer_info, + }; + + Actual_Socket ret; + DWORD err; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = 0; /* to start with */ + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobinline = oobinline; + ret->nodelay = nodelay; + ret->keepalive = keepalive; + ret->privport = privport; + ret->port = port; + ret->addr = addr; + START_STEP(ret->addr, ret->step); + ret->s = INVALID_SOCKET; + + err = 0; + do { +#ifdef MPEXT + ret->error = NULL; +#endif + err = try_connect(ret +#ifdef MPEXT + , timeout, sndbuf +#endif + ); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + return (Socket) ret; +} + +Socket sk_newlistener(const char *srcaddr, int port, Plug plug, + int local_host_only, int orig_address_family) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_write_eof, + sk_tcp_flush, + sk_tcp_set_frozen, + sk_tcp_socket_error, + sk_tcp_peer_info, + }; + + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + + DWORD err; + char *errstr; + Actual_Socket ret; + int retcode; + int on = 1; + + int address_family; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* + * Our default, if passed the `don't care' value + * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported, + * we will also set up a second socket listening on IPv6, but + * the v4 one is primary since that ought to work even on + * non-v6-supporting systems. + */ + if (address_family == AF_UNSPEC) address_family = AF_INET; + + /* + * Open socket. + */ + s = p_socket(address_family, SOCK_STREAM, 0); + ret->s = s; + + if (s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + ret->oobinline = 0; + + p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); + +#ifndef NO_IPV6 + if (address_family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + if (srcaddr != NULL && p_getaddrinfo) { + struct addrinfo hints; + struct addrinfo *ai; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = 0; + { + /* strip [] on IPv6 address literals */ + char *trimmed_addr = host_strduptrim(srcaddr); + err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); + sfree(trimmed_addr); + } + if (err == 0 && ai->ai_family == AF_INET6) { + a6.sin6_addr = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; + } + } + a6.sin6_port = p_htons(port); + } else +#endif + { + int got_addr = 0; + a.sin_family = AF_INET; + + /* + * Bind to source address. First try an explicitly + * specified one... + */ + if (srcaddr) { + a.sin_addr.s_addr = p_inet_addr(srcaddr); + if (a.sin_addr.s_addr != INADDR_NONE) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(a.sin_addr); + got_addr = 1; + } + } + + /* + * ... and failing that, go with one of the standard ones. + */ + if (!got_addr) { + if (local_host_only) + a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK); + else + a.sin_addr.s_addr = p_htonl(INADDR_ANY); + } + + a.sin_port = p_htons((short)port); + } +#ifndef NO_IPV6 + retcode = p_bind(s, (address_family == AF_INET6 ? + (struct sockaddr *) &a6 : + (struct sockaddr *) &a), + (address_family == + AF_INET6 ? sizeof(a6) : sizeof(a))); +#else + retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); +#endif + if (retcode != SOCKET_ERROR) { + err = 0; + } else { + err = p_WSAGetLastError(); + } + + if (err) { + p_closesocket(s); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + + if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { + p_closesocket(s); + ret->error = winsock_error_string(p_WSAGetLastError()); + return (Socket) ret; + } + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ +#ifdef MPEXT + errstr = do_select(plug, s, 1); +#else + errstr = do_select(s, 1); +#endif + if (errstr) { + p_closesocket(s); + ret->error = errstr; + return (Socket) ret; + } + + add234(sktree, ret); + +#ifndef NO_IPV6 + /* + * If we were given ADDRTYPE_UNSPEC, we must also create an + * IPv6 listening socket and link it to this one. + */ + if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) { + Actual_Socket other; + + other = (Actual_Socket) sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV6); + + if (other) { + if (!other->error) { + other->parent = ret; + ret->child = other; + } else { + sfree(other); + } + } + } +#endif + + return (Socket) ret; +} + +static void sk_tcp_close(Socket sock) +{ +#ifdef MPEXT + extern char *do_select(Plug plug, SOCKET skt, int startup); +#else + extern char *do_select(SOCKET skt, int startup); +#endif + Actual_Socket s = (Actual_Socket) sock; + + if (s->child) + sk_tcp_close((Socket)s->child); + + del234(sktree, s); +#ifdef MPEXT + do_select(s->plug, s->s, 0); +#else + do_select(s->s, 0); +#endif + p_closesocket(s->s); + if (s->addr) + sk_addr_free(s->addr); + sfree(s); +} + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + Actual_Socket s = (Actual_Socket)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, winsock_error_string(s->pending_error), + s->pending_error, 0); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(Actual_Socket s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + DWORD err; + void *data; + int len, urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + bufchain_prefix(&s->output_data, &data, &len); + } + nsent = p_send(s->s, data, len, urgentflag); + noise_ultralight(nsent); + if (nsent <= 0) { + err = (nsent < 0 ? p_WSAGetLastError() : 0); + if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) { + /* + * Perfectly normal: we've sent all we can for the moment. + * + * (Some WinSock send() implementations can return + * <0 but leave no sensible error indication - + * WSAGetLastError() is called but returns zero or + * a small number - so we check that case and treat + * it just like WSAEWOULDBLOCK.) + */ + s->writable = FALSE; + return; + } else if (nsent == 0 || + err == WSAECONNABORTED || err == WSAECONNRESET) { + /* + * If send() returns CONNABORTED or CONNRESET, we + * unfortunately can't just call plug_closing(), + * because it's quite likely that we're currently + * _in_ a call from the code we'd be calling back + * to, so we'd have to make half the SSH code + * reentrant. Instead we flag a pending error on + * the socket, to be dealt with (by calling + * plug_closing()) at some suitable future moment. + */ + s->pending_error = err; + queue_toplevel_callback(socket_error_callback, s); + return; + } else { + /* We're inside the Windows frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, winsock_error_string(err)); + fatalbox("%s", winsock_error_string(err)); + } + } else { + if (s->sending_oob) { + if (nsent < len) { + memmove(s->oobdata, s->oobdata+nsent, len-nsent); + s->sending_oob = len - nsent; + } else { + s->sending_oob = 0; + } + } else { + bufchain_consume(&s->output_data, nsent); + } + } + } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + p_shutdown(s->s, SD_SEND); + s->outgoingeof = EOF_SENT; + } +} + +static int sk_tcp_write(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Add the data to the buffer list on the socket. + */ + bufchain_add(&s->output_data, buf, len); + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + return bufchain_size(&s->output_data); +} + +static int sk_tcp_write_oob(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Replace the buffer list on the socket with the data. + */ + bufchain_clear(&s->output_data); + assert(len <= sizeof(s->oobdata)); + memcpy(s->oobdata, buf, len); + s->sending_oob = len; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + return s->sending_oob; +} + +static void sk_tcp_write_eof(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); +} + +int select_result(WPARAM wParam, LPARAM lParam) +{ + int ret, open; + DWORD err; + char buf[20480]; /* nice big buffer for plenty of speed */ + Actual_Socket s; + u_long atmark; + + /* wParam is the socket itself */ + + if (wParam == 0) + return 1; /* boggle */ + + s = find234(sktree, (void *) wParam, cmpforsearch); + if (!s) + return 1; /* boggle */ + + if ((err = WSAGETSELECTERROR(lParam)) != 0) { + /* + * An error has occurred on this socket. Pass it to the + * plug. + */ + if (s->addr) { + plug_log(s->plug, 1, s->addr, s->port, + winsock_error_string(err), err); + while (s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s +#ifdef MPEXT + , 0, 0 +#endif + ); + } + } + if (err != 0) + return plug_closing(s->plug, winsock_error_string(err), err, 0); + else + return 1; + } + + noise_ultralight(lParam); + + switch (WSAGETSELECTEVENT(lParam)) { + case FD_CONNECT: + s->connected = s->writable = 1; + /* + * Once a socket is connected, we can stop falling + * back through the candidate addresses to connect + * to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + break; + case FD_READ: + /* In the case the socket is still frozen, we don't even bother */ + if (s->frozen) { + s->frozen_readable = 1; + break; + } + + /* + * We have received data on the socket. For an oobinline + * socket, this might be data _before_ an urgent pointer, + * in which case we send it to the back end with type==1 + * (data prior to urgent). + */ + if (s->oobinline) { + atmark = 1; + p_ioctlsocket(s->s, SIOCATMARK, &atmark); + /* + * Avoid checking the return value from ioctlsocket(), + * on the grounds that some WinSock wrappers don't + * support it. If it does nothing, we get atmark==1, + * which is equivalent to `no OOB pending', so the + * effect will be to non-OOB-ify any OOB data. + */ + } else + atmark = 1; + + ret = p_recv(s->s, buf, sizeof(buf), 0); + noise_ultralight(ret); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + break; + } + } + if (ret < 0) { + return plug_closing(s->plug, winsock_error_string(err), err, + 0); + } else if (0 == ret) { + return plug_closing(s->plug, NULL, 0, 0); + } else { + return plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + break; + case FD_OOB: + /* + * This will only happen on a non-oobinline socket. It + * indicates that we can immediately perform an OOB read + * and get back OOB data, which we will send to the back + * end with type==2 (urgent data). + */ + ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB); + noise_ultralight(ret); + if (ret <= 0) { + const char *str = (ret == 0 ? "Internal networking trouble" : + winsock_error_string(p_WSAGetLastError())); + /* We're inside the Windows frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, str); + fatalbox("%s", str); + } else { + return plug_receive(s->plug, 2, buf, ret); + } + break; + case FD_WRITE: + { + int bufsize_before, bufsize_after; + s->writable = 1; + bufsize_before = s->sending_oob + bufchain_size(&s->output_data); + try_send(s); + bufsize_after = s->sending_oob + bufchain_size(&s->output_data); + if (bufsize_after < bufsize_before) + plug_sent(s->plug, bufsize_after); + } + break; + case FD_CLOSE: + /* Signal a close on the socket. First read any outstanding data. */ + open = 1; + do { + ret = p_recv(s->s, buf, sizeof(buf), 0); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) + break; + return plug_closing(s->plug, winsock_error_string(err), + err, 0); + } else { + if (ret) + open &= plug_receive(s->plug, 0, buf, ret); + else + open &= plug_closing(s->plug, NULL, 0, 0); + } + } while (ret > 0); + return open; + case FD_ACCEPT: + { +#ifdef NO_IPV6 + struct sockaddr_in isa; +#else + struct sockaddr_storage isa; +#endif + int addrlen = sizeof(isa); + SOCKET t; /* socket of connection */ + accept_ctx_t actx; + + memset(&isa, 0, sizeof(isa)); + err = 0; + t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); + if (t == INVALID_SOCKET) + { + err = p_WSAGetLastError(); + if (err == WSATRY_AGAIN) + break; + } + + actx.p = (void *)t; + +#ifndef NO_IPV6 + if (isa.ss_family == AF_INET && + s->localhost_only && + !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) +#else + if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) +#endif + { + p_closesocket(t); /* dodgy WinSock let nonlocal through */ + } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) { + p_closesocket(t); /* denied or error */ + } + } + } + + return 1; +} + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr) +{ + return addr->error; +} +static const char *sk_tcp_socket_error(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->error; +} + +static char *sk_tcp_peer_info(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; +#ifdef NO_IPV6 + struct sockaddr_in addr; +#else + struct sockaddr_storage addr; +#endif + int addrlen = sizeof(addr); + char buf[INET6_ADDRSTRLEN]; + + if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0) + return NULL; + + if (((struct sockaddr *)&addr)->sa_family == AF_INET) { + return dupprintf + ("%s:%d", + p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr), + (int)p_ntohs(((struct sockaddr_in *)&addr)->sin_port)); +#ifndef NO_IPV6 + } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { + return dupprintf + ("[%s]:%d", + p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, + buf, sizeof(buf)), + (int)p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port)); +#endif + } else { + return NULL; + } +} + +static void sk_tcp_set_frozen(Socket sock, int is_frozen) +{ + Actual_Socket s = (Actual_Socket) sock; + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + if (!is_frozen) { +#ifdef MPEXT + do_select(s->plug, s->s, 1); +#else + do_select(s->s, 1); +#endif + if (s->frozen_readable) { + char c; + p_recv(s->s, &c, 1, MSG_PEEK); + } + } + s->frozen_readable = 0; +} + +void socket_reselect_all(void) +{ + Actual_Socket s; + int i; + + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + if (!s->frozen) +#ifdef MPEXT + do_select(s->plug, s->s, 1); +#else + do_select(s->s, 1); +#endif + } +} + +/* + * For Plink: enumerate all sockets currently active. + */ +SOCKET first_socket(int *state) +{ + Actual_Socket s; + *state = 0; + s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +SOCKET next_socket(int *state) +{ + Actual_Socket s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +extern int socket_writable(SOCKET skt) +{ + Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch); + + if (s) + return bufchain_size(&s->output_data) > 0; + else + return 0; +} + +int net_service_lookup(char *service) +{ + struct servent *se; + se = p_getservbyname(service, NULL); + if (se != NULL) + return p_ntohs(se->s_port); + else + return 0; +} + +char *get_hostname(void) +{ + int len = 128; + char *hostname = NULL; + do { + len *= 2; + hostname = sresize(hostname, len, char); + if (p_gethostname(hostname, len) < 0) { + sfree(hostname); + hostname = NULL; + break; + } + } while (strlen(hostname) >= (size_t)(len-1)); + return hostname; +} + +SockAddr platform_get_x11_unix_address(const char *display, int displaynum, + char **canonicalname) +{ + SockAddr ret = snew(struct SockAddr_tag); + memset(ret, 0, sizeof(struct SockAddr_tag)); + ret->error = "unix sockets not supported on this platform"; + ret->refcount = 1; + return ret; +} diff --git a/netbox/libs/Putty/windows/winnoise.c b/netbox/libs/Putty/windows/winnoise.c new file mode 100644 index 000000000..6220383fd --- /dev/null +++ b/netbox/libs/Putty/windows/winnoise.c @@ -0,0 +1,158 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +#include +DECL_WINDOWS_FUNCTION(static, BOOL, CryptAcquireContextA, + (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptGenRandom, + (HCRYPTPROV, DWORD, BYTE *)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext, + (HCRYPTPROV, DWORD)); +static HMODULE wincrypt_module = NULL; + +/* + * This function is called once, at PuTTY startup. + */ + +void noise_get_heavy(void (*func) (void *, int)) +{ + HANDLE srch; + WIN32_FIND_DATA finddata; + DWORD pid; + HCRYPTPROV crypt_provider; + char winpath[MAX_PATH + 3]; + + GetWindowsDirectory(winpath, sizeof(winpath)); + strcat(winpath, "\\*"); + srch = FindFirstFile(winpath, &finddata); + if (srch != INVALID_HANDLE_VALUE) { + do { + func(&finddata, sizeof(finddata)); + } while (FindNextFile(srch, &finddata)); + FindClose(srch); + } + + pid = GetCurrentProcessId(); + func(&pid, sizeof(pid)); + + if (!wincrypt_module) { + wincrypt_module = load_system32_dll("advapi32.dll"); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext); + } + + if (wincrypt_module && p_CryptAcquireContextA && + p_CryptGenRandom && p_CryptReleaseContext && + p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + BYTE buf[32]; + if (p_CryptGenRandom(crypt_provider, 32, buf)) { + func(buf, sizeof(buf)); + } + p_CryptReleaseContext(crypt_provider, 0); + } + + read_random_seed(func); + /* Update the seed immediately, in case another instance uses it. */ + random_save_seed(); +} + +void random_save_seed(void) +{ + int len; + void *data; + + if (random_active) { + random_get_savedata(&data, &len); + write_random_seed(data, len); + sfree(data); + } +} + +/* + * This function is called every time the random pool needs + * stirring, and will acquire the system time in all available + * forms. + */ +void noise_get_light(void (*func) (void *, int)) +{ + SYSTEMTIME systime; + DWORD adjust[2]; + BOOL rubbish; + + GetSystemTime(&systime); + func(&systime, sizeof(systime)); + + GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish); + func(&adjust, sizeof(adjust)); +} + +/* + * This function is called on a timer, and it will monitor + * frequently changing quantities such as the state of physical and + * virtual memory, the state of the process's message queue, which + * window is in the foreground, which owns the clipboard, etc. + */ +void noise_regular(void) +{ + HWND w; + DWORD z; + POINT pt; + MEMORYSTATUS memstat; + FILETIME times[4]; + + MPEXT_PUTTY_SECTION_ENTER; + w = GetForegroundWindow(); + random_add_noise(&w, sizeof(w)); + w = GetCapture(); + random_add_noise(&w, sizeof(w)); + w = GetClipboardOwner(); + random_add_noise(&w, sizeof(w)); + z = GetQueueStatus(QS_ALLEVENTS); + random_add_noise(&z, sizeof(z)); + + GetCursorPos(&pt); + random_add_noise(&pt, sizeof(pt)); + + GlobalMemoryStatus(&memstat); + random_add_noise(&memstat, sizeof(memstat)); + + GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, + times + 3); + random_add_noise(×, sizeof(times)); + GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, + times + 3); + random_add_noise(×, sizeof(times)); + MPEXT_PUTTY_SECTION_LEAVE; +} + +/* + * This function is called on every keypress or mouse move, and + * will add the current Windows time and performance monitor + * counter to the noise pool. It gets the scan code or mouse + * position passed in. + */ +void noise_ultralight(unsigned long data) +{ + DWORD wintime; + LARGE_INTEGER perftime; + + MPEXT_PUTTY_SECTION_ENTER; + random_add_noise(&data, sizeof(DWORD)); + + wintime = GetTickCount(); + random_add_noise(&wintime, sizeof(DWORD)); + + if (QueryPerformanceCounter(&perftime)) + random_add_noise(&perftime, sizeof(perftime)); + MPEXT_PUTTY_SECTION_LEAVE; +} diff --git a/netbox/libs/Putty/windows/winnojmp.c b/netbox/libs/Putty/windows/winnojmp.c new file mode 100644 index 000000000..dd61dc692 --- /dev/null +++ b/netbox/libs/Putty/windows/winnojmp.c @@ -0,0 +1,8 @@ +/* + * winnojmp.c: stub jump list functions for Windows executables that + * don't update the jump list. + */ + +void add_session_to_jumplist(const char * const sessionname) {} +void remove_session_from_jumplist(const char * const sessionname) {} +void clear_jumplist(void) {} diff --git a/netbox/libs/Putty/windows/winpgntc.c b/netbox/libs/Putty/windows/winpgntc.c new file mode 100644 index 000000000..06649abc1 --- /dev/null +++ b/netbox/libs/Putty/windows/winpgntc.c @@ -0,0 +1,186 @@ +/* + * Pageant client code. + */ + +#include +#include + +#include "putty.h" + +#ifndef NO_SECURITY +#include "winsecur.h" +#endif + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ +#define AGENT_MAX_MSGLEN 8192 + +int agent_exists(void) +{ + HWND hwnd; + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return FALSE; + else + return TRUE; +} + +/* + * Unfortunately, this asynchronous agent request mechanism doesn't + * appear to work terribly well. I'm going to comment it out for + * the moment, and see if I can come up with a better one :-/ + */ +#ifdef WINDOWS_ASYNC_AGENT + +struct agent_query_data { + COPYDATASTRUCT cds; + unsigned char *mapping; + HANDLE handle; + char *mapname; + HWND hwnd; + void (*callback)(void *, void *, int); + void *callback_ctx; +}; + +DWORD WINAPI agent_query_thread(LPVOID param) +{ + struct agent_query_data *data = (struct agent_query_data *)param; + unsigned char *ret; + int id, retlen; + + id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL, + (LPARAM) &data->cds); + ret = NULL; + if (id > 0) { + retlen = 4 + GET_32BIT(data->mapping); + ret = snewn(retlen, unsigned char); + if (ret) { + memcpy(ret, data->mapping, retlen); + } + } + if (!ret) + retlen = 0; + UnmapViewOfFile(data->mapping); + CloseHandle(data->handle); + sfree(data->mapname); + + agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen); + + return 0; +} + +#endif + +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + HWND hwnd; + char *mapname; + HANDLE filemap; + unsigned char *p, *ret; + int id, retlen; + COPYDATASTRUCT cds; + SECURITY_ATTRIBUTES sa, *psa; + PSECURITY_DESCRIPTOR psd = NULL; + PSID usersid = NULL; + + *out = NULL; + *outlen = 0; + + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return 1; /* *out == NULL, so failure */ + mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); + + psa = NULL; +#ifndef NO_SECURITY + if (got_advapi()) { + /* + * Make the file mapping we create for communication with + * Pageant owned by the user SID rather than the default. This + * should make communication between processes with slightly + * different contexts more reliable: in particular, command + * prompts launched as administrator should still be able to + * run PSFTPs which refer back to the owning user's + * unprivileged Pageant. + */ + usersid = get_user_sid(); + + if (usersid) { + psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psd) { + if (p_InitializeSecurityDescriptor + (psd, SECURITY_DESCRIPTOR_REVISION) && + p_SetSecurityDescriptorOwner(psd, usersid, FALSE)) { + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = psd; + psa = &sa; + } else { + LocalFree(psd); + psd = NULL; + } + } + } + } +#endif /* NO_SECURITY */ + + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, + 0, AGENT_MAX_MSGLEN, mapname); + if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { + sfree(mapname); + return 1; /* *out == NULL, so failure */ + } + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); + memcpy(p, in, inlen); + cds.dwData = AGENT_COPYDATA_ID; + cds.cbData = 1 + strlen(mapname); + cds.lpData = mapname; +#ifdef WINDOWS_ASYNC_AGENT + if (callback != NULL && !(flags & FLAG_SYNCAGENT)) { + /* + * We need an asynchronous Pageant request. Since I know of + * no way to stop SendMessage from blocking the thread it's + * called in, I see no option but to start a fresh thread. + * When we're done we'll PostMessage the result back to our + * main window, so that the callback is done in the primary + * thread to avoid concurrency. + */ + struct agent_query_data *data = snew(struct agent_query_data); + DWORD threadid; + data->mapping = p; + data->handle = filemap; + data->mapname = mapname; + data->callback = callback; + data->callback_ctx = callback_ctx; + data->cds = cds; /* structure copy */ + data->hwnd = hwnd; + if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid)) + return 0; + sfree(mapname); + sfree(data); + } +#endif + + /* + * The user either passed a null callback (indicating that the + * query is required to be synchronous) or CreateThread failed. + * Either way, we need a synchronous request. + */ + id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds); + if (id > 0) { + retlen = 4 + GET_32BIT(p); + ret = snewn(retlen, unsigned char); + if (ret) { + memcpy(ret, p, retlen); + *out = ret; + *outlen = retlen; + } + } + UnmapViewOfFile(p); + CloseHandle(filemap); + sfree(mapname); + if (psd) + LocalFree(psd); + return 1; +} diff --git a/netbox/libs/Putty/windows/winproxy.c b/netbox/libs/Putty/windows/winproxy.c new file mode 100644 index 000000000..bd64d39e5 --- /dev/null +++ b/netbox/libs/Putty/windows/winproxy.c @@ -0,0 +1,238 @@ +/* + * winproxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the winhandl.c + * mechanism. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug plug, int overlapped); + +typedef struct Socket_localproxy_tag *Local_Proxy_Socket; + +#ifdef MPEXT +extern char *do_select(Plug plug, SOCKET skt, int startup); +#endif + +struct Socket_localproxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + HANDLE to_cmd_H, from_cmd_H; + struct handle *to_cmd_h, *from_cmd_h; + + char *error; + + Plug plug; + + void *privptr; +}; + +int localproxy_gotdata(struct handle *h, void *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); + + if (len < 0) { + return plug_closing(ps->plug, "Read error from local proxy command", + 0, 0); + } else if (len == 0) { + return plug_closing(ps->plug, NULL, 0, 0); + } else { + return plug_receive(ps->plug, 0, data, len); + } +} + +void localproxy_sentdata(struct handle *h, int new_backlog) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); + + plug_sent(ps->plug, new_backlog); +} + +static Plug sk_localproxy_plug (Socket s, Plug p) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_localproxy_close (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + #ifdef MPEXT + // WinSCP core uses do_select as signalization of connection up/down + do_select(ps->plug, INVALID_SOCKET, 0); + #endif + + handle_free(ps->to_cmd_h); + handle_free(ps->from_cmd_h); + CloseHandle(ps->to_cmd_H); + CloseHandle(ps->from_cmd_H); + + sfree(ps); +} + +static int sk_localproxy_write (Socket s, const char *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + return handle_write(ps->to_cmd_h, data, len); +} + +static int sk_localproxy_write_oob(Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_localproxy_write(s, data, len); +} + +static void sk_localproxy_write_eof(Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + handle_write_eof(ps->to_cmd_h); +} + +static void sk_localproxy_flush(Socket s) +{ + /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ + /* do nothing */ +} + +static void sk_localproxy_set_private_ptr(Socket s, void *ptr) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + ps->privptr = ptr; +} + +static void *sk_localproxy_get_private_ptr(Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->privptr; +} + +static void sk_localproxy_set_frozen(Socket s, int is_frozen) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + /* + * FIXME + */ +} + +static const char *sk_localproxy_socket_error(Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->error; +} + +Socket platform_new_connection(SockAddr addr, const char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, Conf *conf) +{ + char *cmd; + HANDLE us_to_cmd, cmd_from_us; + HANDLE us_from_cmd, cmd_to_us; + HANDLE us_from_cmd_err, cmd_err_to_us; + SECURITY_ATTRIBUTES sa; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, conf); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + { + char *msg = dupprintf("Starting local proxy command: %s", cmd); + plug_log(plug, 2, NULL, 0, msg, 0); + sfree(msg); + } + + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; /* default */ + sa.bInheritHandle = TRUE; + if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { + Socket ret = + new_error_socket("Unable to create pipes for proxy command", plug); + sfree(cmd); + return ret; + } + + if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { + Socket ret = + new_error_socket("Unable to create pipes for proxy command", plug); + sfree(cmd); + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + return ret; + } + + if (flags & FLAG_STDERR) { + /* If we have a sensible stderr, the proxy command can send + * its own standard error there, so we won't interfere. */ + us_from_cmd_err = cmd_err_to_us = NULL; + } else { + /* If we don't have a sensible stderr, we should catch the + * proxy command's standard error to put in our event log. */ + if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { + Socket ret = new_error_socket + ("Unable to create pipes for proxy command", plug); + sfree(cmd); + return ret; + } + } + + SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0); + if (us_from_cmd_err != NULL) + SetHandleInformation(us_from_cmd_err, HANDLE_FLAG_INHERIT, 0); + + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = STARTF_USESTDHANDLES; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + si.hStdInput = cmd_from_us; + si.hStdOutput = cmd_to_us; + si.hStdError = cmd_err_to_us; + CreateProcess(NULL, cmd, NULL, NULL, TRUE, + CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, + NULL, NULL, &si, &pi); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + sfree(cmd); + + CloseHandle(cmd_from_us); + CloseHandle(cmd_to_us); + + if (cmd_err_to_us != NULL) + CloseHandle(cmd_err_to_us); + + return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, + plug, FALSE); +} diff --git a/netbox/libs/Putty/windows/winsecur.c b/netbox/libs/Putty/windows/winsecur.c new file mode 100644 index 000000000..3cc2329d9 --- /dev/null +++ b/netbox/libs/Putty/windows/winsecur.c @@ -0,0 +1,299 @@ +/* + * winsecur.c: implementation of winsecur.h. + */ + +#include +#include + +#include "putty.h" + +#if !defined NO_SECURITY + +#define WINSECUR_GLOBAL +#include "winsecur.h" + +/* Initialised once, then kept around to reuse forever */ +static PSID worldsid, networksid, usersid; + +#ifdef MPEXT +void win_secur_cleanup(void) +{ + if (usersid) + { + sfree(usersid); + usersid = NULL; + } +} +#endif + +int got_advapi(void) +{ + static int attempted = FALSE; + static int successful; + static HMODULE advapi; + + if (!attempted) { + attempted = TRUE; + advapi = load_system32_dll("advapi32.dll"); + successful = advapi && + GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && + GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && + GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) && + GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA); + } + return successful; +} + +PSID get_user_sid(void) +{ + HANDLE proc = NULL, tok = NULL; + TOKEN_USER *user = NULL; + DWORD toklen, sidlen; + PSID sid = NULL, ret = NULL; + + if (usersid) + return usersid; + + if (!got_advapi()) + goto cleanup; + + if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE, + GetCurrentProcessId())) == NULL) + goto cleanup; + + if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto cleanup; + + if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen)) + goto cleanup; + + sidlen = GetLengthSid(user->User.Sid); + + sid = (PSID)smalloc(sidlen); + + if (!CopySid(sidlen, sid, user->User.Sid)) + goto cleanup; + + /* Success. Move sid into the return value slot, and null it out + * to stop the cleanup code freeing it. */ + ret = usersid = sid; + sid = NULL; + + cleanup: + if (proc != NULL) + CloseHandle(proc); + if (tok != NULL) + CloseHandle(tok); + if (user != NULL) + LocalFree(user); + if (sid != NULL) + sfree(sid); + + return ret; +} + +int getsids(char *error) +{ + SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY; + SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; + int ret; + + error=NULL; + + if (!usersid) { + if ((usersid = get_user_sid()) == NULL) { + error = dupprintf("unable to construct SID for current user: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + if (!worldsid) { + if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, &worldsid)) { + error = dupprintf("unable to construct SID for world: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + if (!networksid) { + if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID, + 0, 0, 0, 0, 0, 0, 0, &networksid)) { + error = dupprintf("unable to construct SID for " + "local same-user access only: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + ret=TRUE; + + cleanup: + if (ret) { + sfree(error); + error = NULL; + } + return ret; +} + + +int make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PACL *acl, + char **error) +{ + EXPLICIT_ACCESS ea[3]; + int acl_err; + int ret = FALSE; + + + *psd = NULL; + *acl = NULL; + *error = NULL; + + if (!getsids(*error)) + goto cleanup; + + memset(ea, 0, sizeof(ea)); + ea[0].grfAccessPermissions = permissions; + ea[0].grfAccessMode = REVOKE_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.ptstrName = (LPTSTR)worldsid; + ea[1].grfAccessPermissions = permissions; + ea[1].grfAccessMode = GRANT_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPTSTR)usersid; + ea[2].grfAccessPermissions = permissions; + ea[2].grfAccessMode = REVOKE_ACCESS; + ea[2].grfInheritance = NO_INHERITANCE; + ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[2].Trustee.ptstrName = (LPTSTR)networksid; + + acl_err = p_SetEntriesInAclA(3, ea, NULL, acl); + if (acl_err != ERROR_SUCCESS || *acl == NULL) { + *error = dupprintf("unable to construct ACL: %s", + win_strerror(acl_err)); + goto cleanup; + } + + *psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!*psd) { + *error = dupprintf("unable to allocate security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) { + *error = dupprintf("unable to initialise security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorOwner(*psd, usersid, FALSE)) { + *error = dupprintf("unable to set owner in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorDacl(*psd, TRUE, *acl, FALSE)) { + *error = dupprintf("unable to set DACL in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + ret = TRUE; + + cleanup: + if (!ret) { + if (*psd) { + LocalFree(*psd); + *psd = NULL; + } + if (*acl) { + LocalFree(*acl); + *acl = NULL; + } + } else { + sfree(*error); + *error = NULL; + } + return ret; +} + +int setprocessacl(char *error) +{ + EXPLICIT_ACCESS ea[2]; + int acl_err; + int ret=FALSE; + PACL acl = NULL; + + static const DWORD nastyace=WRITE_DAC | WRITE_OWNER | + PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | + PROCESS_DUP_HANDLE | + PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | + PROCESS_SUSPEND_RESUME; + + if (!getsids(error)) + goto cleanup; + + memset(ea, 0, sizeof(ea)); + + /* Everyone: deny */ + ea[0].grfAccessPermissions = nastyace; + ea[0].grfAccessMode = DENY_ACCESS; + ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.ptstrName = (LPTSTR)worldsid; + + /* User: user ace */ + ea[1].grfAccessPermissions = ~nastyace & 0x1fff; + ea[1].grfAccessMode = GRANT_ACCESS; + ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPTSTR)usersid; + + acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl); + + if (acl_err != ERROR_SUCCESS || acl == NULL) { + error = dupprintf("unable to construct ACL: %s", + win_strerror(acl_err)); + goto cleanup; + } + + if (ERROR_SUCCESS != p_SetSecurityInfo + (GetCurrentProcess(), SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + usersid, NULL, acl, NULL)) { + error=dupprintf("Unable to set process ACL: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + + ret=TRUE; + + cleanup: + if (!ret) { + if (acl) { + LocalFree(acl); + acl = NULL; + } + } + return ret; +} +#endif /* !defined NO_SECURITY */ diff --git a/netbox/libs/Putty/windows/winsecur.h b/netbox/libs/Putty/windows/winsecur.h new file mode 100644 index 000000000..ed3151c51 --- /dev/null +++ b/netbox/libs/Putty/windows/winsecur.h @@ -0,0 +1,61 @@ +/* + * winsecur.h: some miscellaneous security-related helper functions, + * defined in winsecur.c, that use the advapi32 library. Also + * centralises the machinery for dynamically loading that library. + */ + +#if !defined NO_SECURITY + +#include + +#ifndef WINSECUR_GLOBAL +#define WINSECUR_GLOBAL extern +#endif + +/* + * Functions loaded from advapi32.dll. + */ +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, OpenProcessToken, + (HANDLE, DWORD, PHANDLE)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, GetTokenInformation, + (HANDLE, TOKEN_INFORMATION_CLASS, + LPVOID, DWORD, PDWORD)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, InitializeSecurityDescriptor, + (PSECURITY_DESCRIPTOR, DWORD)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, SetSecurityDescriptorOwner, + (PSECURITY_DESCRIPTOR, PSID, BOOL)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, GetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID *, PSID *, PACL *, PACL *, + PSECURITY_DESCRIPTOR *)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID, PSID, PACL, PACL)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA, + (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); +int got_advapi(void); + +/* + * Find the SID describing the current user. The return value (if not + * NULL for some error-related reason) is smalloced. + */ +PSID get_user_sid(void); + +/* + * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe + * servers, i.e. allowing access only to the current user id and also + * only local (i.e. not over SMB) connections. + * + * If this function returns TRUE, then 'psd' and 'acl' will have been + * filled in with memory allocated using LocalAlloc (and hence must be + * freed later using LocalFree). If it returns FALSE, then instead + * 'error' has been filled with a dynamically allocated error message. + */ +int make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PACL *acl, + char **error); + +int setprocessacl(char *error); + +#endif diff --git a/netbox/libs/Putty/windows/winstore.c b/netbox/libs/Putty/windows/winstore.c new file mode 100644 index 000000000..7dc9e7b8f --- /dev/null +++ b/netbox/libs/Putty/windows/winstore.c @@ -0,0 +1,942 @@ +/* + * winstore.c: Windows-specific implementation of the interface + * defined in storage.h. + */ + +#ifdef MPEXT + +#include "puttyexp.h" + +#undef RegOpenKey +#undef RegCreateKey +#undef RegCreateKey +#undef RegQueryValueEx +#undef RegSetValueEx +#undef RegCloseKey + +#define RegOpenKey reg_open_winscp_key +#define RegCreateKey reg_create_winscp_key +#define RegCreateKey reg_create_winscp_key +#define RegQueryValueEx reg_query_winscp_value_ex +#define RegSetValueEx reg_set_winscp_value_ex +#define RegCloseKey reg_close_winscp_key + +#endif + +#include +#include +#include +#include +#include "putty.h" +#include "storage.h" + +#include +#ifndef CSIDL_APPDATA +#define CSIDL_APPDATA 0x001a +#endif +#ifndef CSIDL_LOCAL_APPDATA +#define CSIDL_LOCAL_APPDATA 0x001c +#endif + +static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist"; +static const char *const reg_jumplist_value = "Recent sessions"; +static const char *const puttystr = PUTTY_REG_POS "\\Sessions"; + +static const char hex[16] = "0123456789ABCDEF"; + +static int tried_shgetfolderpath = FALSE; +static HMODULE shell32_module = NULL; +DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, + (HWND, int, HANDLE, DWORD, LPSTR)); + +static void mungestr(const char *in, char *out) +{ + int candot = 0; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + candot = 1; + } + *out = '\0'; + return; +} + +static void unmungestr(const char *in, char *out, int outlen) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + if (!--outlen) + return; + in += 3; + } else { + *out++ = *in++; + if (!--outlen) + return; + } + } + *out = '\0'; + return; +} + +void *open_settings_w(const char *sessionname, char **errmsg) +{ + HKEY subkey1, sesskey; + int ret; + char *p; + + *errmsg = NULL; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); + if (ret != ERROR_SUCCESS) { + sfree(p); + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s", puttystr); + return NULL; + } + ret = RegCreateKey(subkey1, p, &sesskey); + RegCloseKey(subkey1); + if (ret != ERROR_SUCCESS) { + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", puttystr, p); + sfree(p); + return NULL; + } + sfree(p); + return (void *) sesskey; +} + +void write_setting_s(void *handle, const char *key, const char *value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value, + 1 + strlen(value)); +} + +void write_setting_i(void *handle, const char *key, int value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_DWORD, + (CONST BYTE *) &value, sizeof(value)); +} + +void close_settings_w(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +void *open_settings_r(const char *sessionname) +{ + HKEY subkey1, sesskey; + char *p; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { + sesskey = NULL; + } else { + if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) { + sesskey = NULL; + } + RegCloseKey(subkey1); + } + + sfree(p); + + return (void *) sesskey; +} + +char *read_setting_s(void *handle, const char *key) +{ + DWORD type, allocsize, size; + char *ret; + + if (!handle) + return NULL; + + /* Find out the type and size of the data. */ + if (RegQueryValueEx((HKEY) handle, key, 0, + &type, NULL, &size) != ERROR_SUCCESS || + type != REG_SZ) + return NULL; + + allocsize = size+1; /* allow for an extra NUL if needed */ + ret = snewn(allocsize, char); + if (RegQueryValueEx((HKEY) handle, key, 0, + &type, ret, &size) != ERROR_SUCCESS || + type != REG_SZ) { + sfree(ret); + return NULL; + } + assert(size < allocsize); + ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx + * didn't supply one */ + + return ret; +} + +int read_setting_i(void *handle, const char *key, int defvalue) +{ + DWORD type, val, size; + size = sizeof(val); + + if (!handle || + RegQueryValueEx((HKEY) handle, key, 0, &type, + (BYTE *) &val, &size) != ERROR_SUCCESS || + size != sizeof(val) || type != REG_DWORD) + return defvalue; + else + return val; +} + +FontSpec *read_setting_fontspec(void *handle, const char *name) +{ + char *settingname; + char *fontname; + FontSpec *ret; + int isbold, height, charset; + + fontname = read_setting_s(handle, name); + if (!fontname) + return NULL; + + settingname = dupcat(name, "IsBold", NULL); + isbold = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (isbold == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "CharSet", NULL); + charset = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (charset == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "Height", NULL); + height = read_setting_i(handle, settingname, INT_MIN); + sfree(settingname); + if (height == INT_MIN) { + sfree(fontname); + return NULL; + } + + ret = fontspec_new(fontname, isbold, height, charset); + sfree(fontname); + return ret; +} + +void write_setting_fontspec(void *handle, const char *name, FontSpec *font) +{ + char *settingname; + + write_setting_s(handle, name, font->name); + settingname = dupcat(name, "IsBold", NULL); + write_setting_i(handle, settingname, font->isbold); + sfree(settingname); + settingname = dupcat(name, "CharSet", NULL); + write_setting_i(handle, settingname, font->charset); + sfree(settingname); + settingname = dupcat(name, "Height", NULL); + write_setting_i(handle, settingname, font->height); + sfree(settingname); +} + +Filename *read_setting_filename(void *handle, const char *name) +{ + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; +} + +void write_setting_filename(void *handle, const char *name, Filename *result) +{ + write_setting_s(handle, name, result->path); +} + +void close_settings_r(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +void del_settings(const char *sessionname) +{ + HKEY subkey1; + char *p; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + return; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + RegDeleteKey(subkey1, p); + sfree(p); + + RegCloseKey(subkey1); + + remove_session_from_jumplist(sessionname); +} + +struct enumsettings { + HKEY key; + int i; +}; + +void *enum_settings_start(void) +{ + struct enumsettings *ret; + HKEY key; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(struct enumsettings); + if (ret) { + ret->key = key; + ret->i = 0; + } + + return ret; +} + +char *enum_settings_next(void *handle, char *buffer, int buflen) +{ + struct enumsettings *e = (struct enumsettings *) handle; + char *otherbuf; + otherbuf = snewn(3 * buflen, char); + if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) { + unmungestr(otherbuf, buffer, buflen); + sfree(otherbuf); + return buffer; + } else { + sfree(otherbuf); + return NULL; + } +} + +void enum_settings_finish(void *handle) +{ + struct enumsettings *e = (struct enumsettings *) handle; + RegCloseKey(e->key); + sfree(e); +} + +static void hostkey_regname(char *buffer, const char *hostname, + int port, const char *keytype) +{ + int len; + strcpy(buffer, keytype); + strcat(buffer, "@"); + len = strlen(buffer); + len += sprintf(buffer + len, "%d:", port); + mungestr(hostname, buffer + strlen(buffer)); +} + +#ifdef MPEXT +int retrieve_host_key(const char *hostname, int port, + const char *keytype, char *key, int maxlen) +#else +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +#endif +{ + char *otherstr, *regname; + int len; + HKEY rkey; + DWORD readlen; + DWORD type; + int ret, compare; + +#ifdef MPEXT + len = maxlen; +#else + len = 1 + strlen(key); +#endif + + /* + * Now read a saved key in from the registry and see what it + * says. + */ + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) != ERROR_SUCCESS) { + sfree(regname); + return 1; /* key does not exist in registry */ + } + + readlen = len; + otherstr = snewn(len, char); + ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen); + + if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && + !strcmp(keytype, "rsa")) { + /* + * Key didn't exist. If the key type is RSA, we'll try + * another trick, which is to look up the _old_ key format + * under just the hostname and translate that. + */ + char *justhost = regname + 1 + strcspn(regname, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueEx(rkey, justhost, NULL, &type, + oldstyle, &readlen); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + /* + * The old format is two old-style bignums separated by + * a slash. An old-style bignum is made of groups of + * four hex digits: digits are ordered in sensible + * (most to least significant) order within each group, + * but groups are ordered in silly (least to most) + * order within the bignum. The new format is two + * ordinary C-format hex numbers (0xABCDEFG...XYZ, with + * A nonzero except in the special case 0x0, which + * doesn't appear anyway in RSA keys) separated by a + * comma. All hex digits are lowercase in both formats. + */ + char *p = otherstr; + char *q = oldstyle; + int i, j; + + for (i = 0; i < 2; i++) { + int ndigits, nwords; + *p++ = '0'; + *p++ = 'x'; + ndigits = strcspn(q, "/"); /* find / or end of string */ + nwords = ndigits / 4; + /* now trim ndigits to remove leading zeros */ + while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) + ndigits--; + /* now move digits over to new string */ + for (j = 0; j < ndigits; j++) + p[ndigits - 1 - j] = q[j ^ 3]; + p += ndigits; + q += nwords * 4; + if (*q) { + q++; /* eat the slash */ + *p++ = ','; /* add a comma */ + } + *p = '\0'; /* terminate the string */ + } + + /* + * Now _if_ this key matches, we'll enter it in the new + * format. If not, we'll assume something odd went + * wrong, and hyper-cautiously do nothing. + */ + if (!strcmp(otherstr, key)) + RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr, + strlen(otherstr) + 1); + } + + sfree(oldstyle); + } + + RegCloseKey(rkey); + +#ifdef MPEXT + // make sure it is zero terminated, what it is not, particularly when + // RegQueryValueEx fails (the key is unknown) + otherstr[len - 1] = '\0'; +#endif +#ifdef MPEXT + strncpy(key, otherstr, maxlen); + key[maxlen - 1] = '\0'; +#else + compare = strcmp(otherstr, key); +#endif + + sfree(otherstr); + sfree(regname); + +#ifndef MPEXT + if (ret == ERROR_MORE_DATA || + (ret == ERROR_SUCCESS && type == REG_SZ && compare)) + return 2; /* key is different in registry */ + else +#endif + if (ret != ERROR_SUCCESS || type != REG_SZ) + return 1; /* key does not exist in registry */ + else + return 0; /* key matched OK in registry */ +} + +#ifndef MPEXT +int have_ssh_host_key(const char *hostname, int port, + const char *keytype) +{ + /* + * If we have a host key, verify_host_key will return 0 or 2. + * If we don't have one, it'll return 1. + */ + char key[10]; + return retrieve_host_key(hostname, port, keytype, key, 1) != 1; +} +#endif + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *regname; + HKEY rkey; + + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) == ERROR_SUCCESS) { + RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1); + RegCloseKey(rkey); + } /* else key does not exist in registry */ + + sfree(regname); +} + +/* + * Open (or delete) the random seed file. + */ +enum { DEL, OPEN_R, OPEN_W }; +static int try_random_seed(char const *path, int action, HANDLE *ret) +{ + if (action == DEL) { + if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) { + nonfatal("Unable to delete '%s': %s", path, + win_strerror(GetLastError())); + } + *ret = INVALID_HANDLE_VALUE; + return FALSE; /* so we'll do the next ones too */ + } + + *ret = CreateFile(path, + action == OPEN_W ? GENERIC_WRITE : GENERIC_READ, + action == OPEN_W ? 0 : (FILE_SHARE_READ | + FILE_SHARE_WRITE), + NULL, + action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING, + action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0, + NULL); + + return (*ret != INVALID_HANDLE_VALUE); +} + +static HANDLE access_random_seed(int action) +{ + HKEY rkey; + DWORD type, size; + HANDLE rethandle; + char seedpath[2 * MAX_PATH + 10] = "\0"; + + /* + * Iterate over a selection of possible random seed paths until + * we find one that works. + * + * We do this iteration separately for reading and writing, + * meaning that we will automatically migrate random seed files + * if a better location becomes available (by reading from the + * best location in which we actually find one, and then + * writing to the best location in which we can _create_ one). + */ + + /* + * First, try the location specified by the user in the + * Registry, if any. + */ + size = sizeof(seedpath); + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == + ERROR_SUCCESS) { + int ret = RegQueryValueEx(rkey, "RandSeedFile", + 0, &type, seedpath, &size); + if (ret != ERROR_SUCCESS || type != REG_SZ) + seedpath[0] = '\0'; + RegCloseKey(rkey); + + if (*seedpath && try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + /* + * Next, try the user's local Application Data directory, + * followed by their non-local one. This is found using the + * SHGetFolderPath function, which won't be present on all + * versions of Windows. + */ + if (!tried_shgetfolderpath) { + /* This is likely only to bear fruit on systems with IE5+ + * installed, or WinMe/2K+. There is some faffing with + * SHFOLDER.DLL we could do to try to find an equivalent + * on older versions of Windows if we cared enough. + * However, the invocation below requires IE5+ anyway, + * so stuff that. */ + shell32_module = load_system32_dll("shell32.dll"); + GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); + tried_shgetfolderpath = TRUE; + } + if (p_SHGetFolderPathA) { + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the + * user's home directory. + */ + { + int len, ret; + + len = + GetEnvironmentVariable("HOMEDRIVE", seedpath, + sizeof(seedpath)); + ret = + GetEnvironmentVariable("HOMEPATH", seedpath + len, + sizeof(seedpath) - len); + if (ret != 0) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * And finally, fall back to C:\WINDOWS. + */ + GetWindowsDirectory(seedpath, sizeof(seedpath)); + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + + /* + * If even that failed, give up. + */ + return INVALID_HANDLE_VALUE; +} + +void read_random_seed(noise_consumer_t consumer) +{ + HANDLE seedf = access_random_seed(OPEN_R); + + if (seedf != INVALID_HANDLE_VALUE) { + while (1) { + char buf[1024]; + DWORD len; + + if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len) + consumer(buf, len); + else + break; + } + CloseHandle(seedf); + } +} + +void write_random_seed(void *data, int len) +{ + HANDLE seedf = access_random_seed(OPEN_W); + + if (seedf != INVALID_HANDLE_VALUE) { + DWORD lenwritten; + + WriteFile(seedf, data, len, &lenwritten, NULL); + CloseHandle(seedf); + } +} + +/* + * Internal function supporting the jump list registry code. All the + * functions to add, remove and read the list have substantially + * similar content, so this is a generalisation of all of them which + * transforms the list in the registry by prepending 'add' (if + * non-null), removing 'rem' from what's left (if non-null), and + * returning the resulting concatenated list of strings in 'out' (if + * non-null). + */ +static int transform_jumplist_registry + (const char *add, const char *rem, char **out) +{ + int ret; + HKEY pjumplist_key, psettings_tmp; + DWORD type; + DWORD value_length; + char *old_value, *new_value; + char *piterator_old, *piterator_new, *piterator_tmp; + + ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL, + REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL, + &pjumplist_key, NULL); + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + + /* Get current list of saved sessions in the registry. */ + value_length = 200; + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + /* When the passed buffer is too small, ERROR_MORE_DATA is + * returned and the required size is returned in the length + * argument. */ + if (ret == ERROR_MORE_DATA) { + sfree(old_value); + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + } + + if (ret == ERROR_FILE_NOT_FOUND) { + /* Value doesn't exist yet. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } else if (ret != ERROR_SUCCESS) { + /* Some non-recoverable error occurred. */ + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } else if (type != REG_MULTI_SZ) { + /* The value present in the registry has the wrong type: we + * try to delete it and start from an empty value. */ + ret = RegDeleteValue(pjumplist_key, reg_jumplist_value); + if (ret != ERROR_SUCCESS) { + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } + + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* Check validity of registry data: REG_MULTI_SZ value must end + * with \0\0. */ + piterator_tmp = old_value; + while (((piterator_tmp - old_value) < (value_length - 1)) && + !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) { + ++piterator_tmp; + } + + if ((piterator_tmp - old_value) >= (value_length-1)) { + /* Invalid value. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* + * Modify the list, if we're modifying. + */ + if (add || rem) { + /* Walk through the existing list and construct the new list of + * saved sessions. */ + new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char); + piterator_new = new_value; + piterator_old = old_value; + + /* First add the new item to the beginning of the list. */ + if (add) { + strcpy(piterator_new, add); + piterator_new += strlen(piterator_new) + 1; + } + /* Now add the existing list, taking care to leave out the removed + * item, if it was already in the existing list. */ + while (*piterator_old != '\0') { + if (!rem || strcmp(piterator_old, rem) != 0) { + /* Check if this is a valid session, otherwise don't add. */ + psettings_tmp = open_settings_r(piterator_old); + if (psettings_tmp != NULL) { + close_settings_r(psettings_tmp); + strcpy(piterator_new, piterator_old); + piterator_new += strlen(piterator_new) + 1; + } + } + piterator_old += strlen(piterator_old) + 1; + } + *piterator_new = '\0'; + ++piterator_new; + + /* Save the new list to the registry. */ + ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ, + new_value, piterator_new - new_value); + + sfree(old_value); + old_value = new_value; + } else + ret = ERROR_SUCCESS; + + /* + * Either return or free the result. + */ + if (out && ret == ERROR_SUCCESS) + *out = old_value; + else + sfree(old_value); + + /* Clean up and return. */ + RegCloseKey(pjumplist_key); + + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; + } else { + return JUMPLISTREG_OK; + } +} + +/* Adds a new entry to the jumplist entries in the registry. */ +int add_to_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(item, item, NULL); +} + +/* Removes an item from the jumplist entries in the registry. */ +int remove_from_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(NULL, item, NULL); +} + +/* Returns the jumplist entries from the registry. Caller must free + * the returned pointer. */ +char *get_jumplist_registry_entries (void) +{ + char *list_value; + + if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) { + list_value = snewn(2, char); + *list_value = '\0'; + *(list_value + 1) = '\0'; + } + return list_value; +} + +/* + * Recursively delete a registry key and everything under it. + */ +static void registry_recursive_remove(HKEY key) +{ + DWORD i; + char name[MAX_PATH + 1]; + HKEY subkey; + + i = 0; + while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) { + if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) { + registry_recursive_remove(subkey); + RegCloseKey(subkey); + } + RegDeleteKey(key, name); + } +} + +void cleanup_all(void) +{ + HKEY key; + int ret; + char name[MAX_PATH + 1]; + + /* ------------------------------------------------------------ + * Wipe out the random seed file, in all of its possible + * locations. + */ + access_random_seed(DEL); + + /* ------------------------------------------------------------ + * Ask Windows to delete any jump list information associated + * with this installation of PuTTY. + */ + clear_jumplist(); + + /* ------------------------------------------------------------ + * Destroy all registry information associated with PuTTY. + */ + + /* + * Open the main PuTTY registry key and remove everything in it. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == + ERROR_SUCCESS) { + registry_recursive_remove(key); + RegCloseKey(key); + } + /* + * Now open the parent key and remove the PuTTY main key. Once + * we've done that, see if the parent key has any other + * children. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_PARENT_CHILD); + ret = RegEnumKey(key, 0, name, sizeof(name)); + RegCloseKey(key); + /* + * If the parent key had no other children, we must delete + * it in its turn. That means opening the _grandparent_ + * key. + */ + if (ret != ERROR_SUCCESS) { + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD); + RegCloseKey(key); + } + } + } + /* + * Now we're done. + */ +} + +#ifdef MPEXT + +void putty_mungestr(const char *in, char *out) +{ + mungestr(in, out); +} + +void putty_unmungestr(const char *in, char *out, int outlen) +{ + unmungestr(in, out, outlen); +} + +#endif diff --git a/netbox/libs/Putty/windows/winstuff.h b/netbox/libs/Putty/windows/winstuff.h new file mode 100644 index 000000000..2b5e4ce0b --- /dev/null +++ b/netbox/libs/Putty/windows/winstuff.h @@ -0,0 +1,590 @@ +/* + * winstuff.h: Windows-specific inter-module stuff. + */ + +#ifndef PUTTY_WINSTUFF_H +#define PUTTY_WINSTUFF_H + +#ifndef AUTO_WINSOCK +#include +#endif +#include +#include /* for FILENAME_MAX */ + +/* We use uintptr_t for Win32/Win64 portability, so we should in + * principle include stdint.h, which defines it according to the C + * standard. But older versions of Visual Studio - including the one + * used for official PuTTY builds as of 2015-09-28 - don't provide + * stdint.h at all, but do (non-standardly) define uintptr_t in + * stddef.h. So here we try to make sure _some_ standard header is + * included which defines uintptr_t. */ +#include +#if !defined _MSC_VER || _MSC_VER >= 1600 +#include +#endif + +#include "tree234.h" + +#ifndef MPEXT +#include "winhelp.h" +#endif + +struct Filename { + char *path; +}; +#ifdef MPEXT +FILE * mp_wfopen(const char *filename, const char *mode); +#define f_open(filename, mode, isprivate) ( mp_wfopen((filename)->path, (mode)) ) +#else +#define f_open(filename, mode, isprivate) ( fopen((filename)->path, (mode)) ) +#endif + +struct FontSpec { + char *name; + int isbold; + int height; + int charset; +}; +struct FontSpec *fontspec_new(const char *name, + int bold, int height, int charset); + +#ifndef CLEARTYPE_QUALITY +#define CLEARTYPE_QUALITY 5 +#endif +#define FONT_QUALITY(fq) ( \ + (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \ + (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \ + (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \ + CLEARTYPE_QUALITY) + +#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging + * wchar_t strings with environment */ + +/* + * Where we can, we use GetWindowLongPtr and friends because they're + * more useful on 64-bit platforms, but they're a relatively recent + * innovation, missing from VC++ 6 and older MinGW. Degrade nicely. + * (NB that on some systems, some of these things are available but + * not others...) + */ + +#ifndef GCLP_HCURSOR +/* GetClassLongPtr and friends */ +#undef GetClassLongPtr +#define GetClassLongPtr GetClassLong +#undef SetClassLongPtr +#define SetClassLongPtr SetClassLong +#define GCLP_HCURSOR GCL_HCURSOR +/* GetWindowLongPtr and friends */ +#undef GetWindowLongPtr +#define GetWindowLongPtr GetWindowLong +#undef SetWindowLongPtr +#define SetWindowLongPtr SetWindowLong +#undef GWLP_USERDATA +#define GWLP_USERDATA GWL_USERDATA +#undef DWLP_MSGRESULT +#define DWLP_MSGRESULT DWL_MSGRESULT +/* Since we've clobbered the above functions, we should clobber the + * associated type regardless of whether it's defined. */ +#undef LONG_PTR +#define LONG_PTR LONG +#endif + +#define BOXFLAGS DLGWINDOWEXTRA +#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) +#define DF_END 0x0001 + +#ifndef NO_SECUREZEROMEMORY +#define PLATFORM_HAS_SMEMCLR /* inhibit cross-platform one in misc.c */ +#endif + +#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ + +/* + * Dynamically linked functions. These come in two flavours: + * + * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor, + * so will always dynamically link against exactly what is specified + * in "name". If you're not sure, use this one. + * + * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via + * preprocessor definitions like "#define foo bar"; this is principally + * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW. + * If your function has an argument of type "LPTSTR" or similar, this + * is the variant to use. + * (However, it can't always be used, as it trips over more complicated + * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.) + * + * (DECL_WINDOWS_FUNCTION works with both these variants.) + */ +#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ + typedef rettype (WINAPI *t_##name) params; \ + linkage t_##name p_##name +#define STR1(x) #x +#define STR(x) STR1(x) +#define GET_WINDOWS_FUNCTION_PP(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL) +#define GET_WINDOWS_FUNCTION(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL) + +/* + * Global variables. Most modules declare these `extern', but + * window.c will do `#define PUTTY_DO_GLOBALS' before including this + * module, and so will get them properly defined. +*/ +#ifndef GLOBAL +#ifdef PUTTY_DO_GLOBALS +#define GLOBAL +#else +#define GLOBAL extern +#endif +#endif + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct conf_tag Conf; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY" +#define PUTTY_REG_PARENT "Software\\SimonTatham" +#define PUTTY_REG_PARENT_CHILD "PuTTY" +#define PUTTY_REG_GPARENT "Software" +#define PUTTY_REG_GPARENT_CHILD "SimonTatham" + +/* Result values for the jumplist registry functions. */ +#define JUMPLISTREG_OK 0 +#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1 +#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2 +#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3 +#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4 +#define JUMPLISTREG_ERROR_INVALID_VALUE 5 + +#define PUTTY_HELP_FILE "putty.hlp" +#define PUTTY_CHM_FILE "putty.chm" +#define PUTTY_HELP_CONTENTS "putty.cnt" + +#define GETTICKCOUNT GetTickCount +#define CURSORBLINK GetCaretBlinkTime() +#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ + +#define DEFAULT_CODEPAGE CP_ACP +#define USES_VTLINE_HACK + +typedef HDC Context; + +typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */ +#define PUTTY_UINT32_DEFINED + +#ifndef NO_GSSAPI +/* + * GSS-API stuff + */ +#define GSS_CC CALLBACK +/* +typedef struct Ssh_gss_buf { + size_t length; + char *value; +} Ssh_gss_buf; + +#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL} +typedef void *Ssh_gss_name; +*/ +#endif + +/* + * Window handles for the windows that can be running during a + * PuTTY session. + */ +GLOBAL HWND hwnd; /* the main terminal window */ +GLOBAL HWND logbox; + +/* + * The all-important instance handle. + */ +GLOBAL HINSTANCE hinst; + +/* + * Help file stuff in winhelp.c. + */ +void init_help(void); +void shutdown_help(void); +int has_help(void); +void launch_help(HWND hwnd, const char *topic); +void quit_help(HWND hwnd); + +/* + * The terminal and logging context are notionally local to the + * Windows front end, but they must be shared between window.c and + * windlg.c. Likewise the saved-sessions list. + */ +GLOBAL Terminal *term; +GLOBAL void *logctx; + +#define WM_NETEVENT (WM_APP + 5) + +/* + * On Windows, we send MA_2CLK as the only event marking the second + * press of a mouse button. Compare unix.h. + */ +#define MULTICLICK_ONLY_EVENT 1 + +/* + * On Windows, data written to the clipboard must be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 1 + +/* + * On Windows, copying to the clipboard terminates lines with CRLF. + */ +#define SEL_NL { 13, 10 } + +/* + * sk_getxdmdata() does not exist under Windows (not that I + * couldn't write it if I wanted to, but I haven't bothered), so + * it's a macro which always returns NULL. With any luck this will + * cause the compiler to notice it can optimise away the + * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-) + */ +#define sk_getxdmdata(socket, lenp) (NULL) + +/* + * File-selector filter strings used in the config box. On Windows, + * these strings are of exactly the type needed to go in + * `lpstrFilter' in an OPENFILENAME structure. + */ +#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \ + "All Files (*.*)\0*\0\0\0") + +/* + * Exports from winnet.c. + */ +extern int select_result(WPARAM, LPARAM); + +/* + * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on + * what it can get, which means any WinSock routines used outside + * that module must be exported from it as function pointers. So + * here they are. + */ +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect, + (SOCKET, HWND, u_int, long)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect, + (SOCKET, WSAEVENT, long)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, select, + (int, fd_set FAR *, fd_set FAR *, + fd_set FAR *, const struct timeval FAR *)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents, + (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); + +extern int socket_writable(SOCKET skt); + +extern void socket_reselect_all(void); + +/* + * Exports from winctrls.c. + */ + +struct ctlpos { + HWND hwnd; + WPARAM font; + int dlu4inpix; + int ypos, width; + int xoff; + int boxystart, boxid; + char *boxtext; +}; + +/* + * Exports from winutils.c. + */ +typedef struct filereq_tag filereq; /* cwd for file requester */ +#ifndef MPEXT +BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save); +#endif +filereq *filereq_new(void); +void filereq_free(filereq *state); +int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid); +char *GetDlgItemText_alloc(HWND hwnd, int id); +void split_into_argv(char *, int *, char ***, char ***); + +/* + * Private structure for prefslist state. Only in the header file + * so that we can delegate allocation to callers. + */ +struct prefslist { + int listid, upbid, dnbid; + int srcitem; + int dummyitem; + int dragging; +}; + +/* + * This structure is passed to event handler functions as the `dlg' + * parameter, and hence is passed back to winctrls access functions. + */ +struct dlgparam { + HWND hwnd; /* the hwnd of the dialog box */ + struct winctrls *controltrees[8]; /* can have several of these */ + int nctrltrees; + char *wintitle; /* title of actual window */ + char *errtitle; /* title of error sub-messageboxes */ + void *data; /* data to pass in refresh events */ + union control *focused, *lastfocused; /* which ctrl has focus now/before */ + char shortcuts[128]; /* track which shortcuts in use */ + int coloursel_wanted; /* has an event handler asked for + * a colour selector? */ + struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */ + tree234 *privdata; /* stores per-control private data */ + int ended, endresult; /* has the dialog been ended? */ + int fixed_pitch_fonts; /* are we constrained to fixed fonts? */ +}; + +/* + * Exports from winctrls.c. + */ +void ctlposinit(struct ctlpos *cp, HWND hwnd, + int leftborder, int rightborder, int topborder); +HWND doctl(struct ctlpos *cp, RECT r, + char *wclass, int wstyle, int exstyle, char *wtext, int wid); +void bartitle(struct ctlpos *cp, char *name, int id); +void beginbox(struct ctlpos *cp, char *name, int idbox); +void endbox(struct ctlpos *cp); +void editboxfw(struct ctlpos *cp, int password, char *text, + int staticid, int editid); +void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); +void bareradioline(struct ctlpos *cp, int nacross, ...); +void radiobig(struct ctlpos *cp, char *text, int id, ...); +void checkbox(struct ctlpos *cp, char *text, int id); +void statictext(struct ctlpos *cp, char *text, int lines, int id); +void staticbtn(struct ctlpos *cp, char *stext, int sid, + char *btext, int bid); +void static2btn(struct ctlpos *cp, char *stext, int sid, + char *btext1, int bid1, char *btext2, int bid2); +void staticedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void staticddl(struct ctlpos *cp, char *stext, + int sid, int lid, int percentlist); +void combobox(struct ctlpos *cp, char *text, int staticid, int listid); +void staticpassedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void bigeditctrl(struct ctlpos *cp, char *stext, + int sid, int eid, int lines); +void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id); +void editbutton(struct ctlpos *cp, char *stext, int sid, + int eid, char *btext, int bid); +void sesssaver(struct ctlpos *cp, char *text, + int staticid, int editid, int listid, ...); +void envsetter(struct ctlpos *cp, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + int listid, char *b1text, int b1id, char *b2text, int b2id); +void charclass(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, int eid, char *s2text, int s2id); +void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, ...); +void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, + char *stext, int sid, int listid, int upbid, int dnbid); +int handle_prefslist(struct prefslist *hdl, + int *array, int maxmemb, + int is_dlmsg, HWND hwnd, + WPARAM wParam, LPARAM lParam); +void progressbar(struct ctlpos *cp, int id); +void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + char *btext, int bid, + char *r1text, int r1id, char *r2text, int r2id); + +void dlg_auto_set_fixed_pitch_flag(void *dlg); +int dlg_get_fixed_pitch_flag(void *dlg); +void dlg_set_fixed_pitch_flag(void *dlg, int flag); + +#define MAX_SHORTCUTS_PER_CTRL 16 + +/* + * This structure is what's stored for each `union control' in the + * portable-dialog interface. + */ +struct winctrl { + union control *ctrl; + /* + * The control may have several components at the Windows + * level, with different dialog IDs. To avoid needing N + * separate platformsidectrl structures (which could be stored + * separately in a tree234 so that lookup by ID worked), we + * impose the constraint that those IDs must be in a contiguous + * block. + */ + int base_id; + int num_ids; + /* + * Remember what keyboard shortcuts were used by this control, + * so that when we remove it again we can take them out of the + * list in the dlgparam. + */ + char shortcuts[MAX_SHORTCUTS_PER_CTRL]; + /* + * Some controls need a piece of allocated memory in which to + * store temporary data about the control. + */ + void *data; +}; +/* + * And this structure holds a set of the above, in two separate + * tree234s so that it can find an item by `union control' or by + * dialog ID. + */ +struct winctrls { + tree234 *byctrl, *byid; +}; +struct controlset; +struct controlbox; + +void winctrl_init(struct winctrls *); +void winctrl_cleanup(struct winctrls *); +void winctrl_add(struct winctrls *, struct winctrl *); +void winctrl_remove(struct winctrls *, struct winctrl *); +struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *); +struct winctrl *winctrl_findbyid(struct winctrls *, int); +struct winctrl *winctrl_findbyindex(struct winctrls *, int); +void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, + struct ctlpos *cp, struct controlset *s, int *id); +int winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam); +void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); +int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id); + +void dp_init(struct dlgparam *dp); +void dp_add_tree(struct dlgparam *dp, struct winctrls *tree); +void dp_cleanup(struct dlgparam *dp); + +/* + * Exports from wincfg.c. + */ +void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, + int midsession, int protocol); + +/* + * Exports from windlg.c. + */ +void defuse_showwindow(void); +int do_config(void); +int do_reconfig(HWND, int); +void showeventlog(HWND); +void showabout(HWND); +void force_normal(HWND hwnd); +void modal_about_box(HWND hwnd); +void show_help(HWND hwnd); + +/* + * Exports from winmisc.c. + */ +extern OSVERSIONINFO osVersion; +BOOL init_winver(void); +HMODULE load_system32_dll(const char *libname); +const char *win_strerror(int error); + +/* + * Exports from sizetip.c. + */ +void UpdateSizeTip(HWND src, int cx, int cy); +void EnableSizeTip(int bEnable); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +void init_ucs(Conf *, struct unicode_data *); + +/* + * Exports from winhandl.c. + */ +#define HANDLE_FLAG_OVERLAPPED 1 +#define HANDLE_FLAG_IGNOREEOF 2 +#define HANDLE_FLAG_UNITBUFFER 4 +struct handle; +typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len); +typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog); +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, + void *privdata, int flags); +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, + void *privdata, int flags); +int handle_write(struct handle *h, const void *data, int len); +void handle_write_eof(struct handle *h); +HANDLE *handle_get_events(int *nevents); +void handle_free(struct handle *h); +#ifdef MPEXT +int handle_got_event(HANDLE event); +#else +void handle_got_event(HANDLE event); +#endif +void handle_unthrottle(struct handle *h, int backlog); +int handle_backlog(struct handle *h); +void *handle_get_privdata(struct handle *h); +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx); + +/* + * winpgntc.c needs to schedule callbacks for asynchronous agent + * requests. This has to be done differently in GUI and console, so + * there's an exported function used for the purpose. + * + * Also, we supply FLAG_SYNCAGENT to force agent requests to be + * synchronous in pscp and psftp. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len); +#define FLAG_SYNCAGENT 0x1000 + +/* + * winpgntc.c also exports these two functions which are used by the + * server side of Pageant as well, to get the user SID for comparing + * with clients'. + */ +int init_advapi(void); /* initialises everything needed by get_user_sid */ +PSID get_user_sid(void); + +/* + * Exports from winser.c. + */ +extern Backend serial_backend; + +/* + * Exports from winjump.c. + */ +#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */ +void add_session_to_jumplist(const char * const sessionname); +void remove_session_from_jumplist(const char * const sessionname); +void clear_jumplist(void); + +/* + * Extra functions in winstore.c over and above the interface in + * storage.h. + * + * These functions manipulate the Registry section which mirrors the + * current Windows 7 jump list. (Because the real jump list storage is + * write-only, we need to keep another copy of whatever we put in it, + * so that we can put in a slightly modified version the next time.) + */ + +/* Adds a saved session to the registry jump list mirror. 'item' is a + * string naming a saved session. */ +int add_to_jumplist_registry(const char *item); + +/* Removes an item from the registry jump list mirror. */ +int remove_from_jumplist_registry(const char *item); + +/* Returns the current jump list entries from the registry. Caller + * must free the returned pointer, which points to a contiguous + * sequence of NUL-terminated strings in memory, terminated with an + * empty one. */ +char *get_jumplist_registry_entries(void); + +#endif diff --git a/netbox/libs/Putty/windows/wintime.c b/netbox/libs/Putty/windows/wintime.c new file mode 100644 index 000000000..eebed9e5a --- /dev/null +++ b/netbox/libs/Putty/windows/wintime.c @@ -0,0 +1,24 @@ +/* + * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. + */ + +#include "putty.h" +#include + +struct tm ltime(void) +{ + SYSTEMTIME st; + struct tm tm; + + GetLocalTime(&st); + tm.tm_sec=st.wSecond; + tm.tm_min=st.wMinute; + tm.tm_hour=st.wHour; + tm.tm_mday=st.wDay; + tm.tm_mon=st.wMonth-1; + tm.tm_year=(st.wYear>=1900?st.wYear-1900:0); + tm.tm_wday=st.wDayOfWeek; + tm.tm_yday=-1; /* GetLocalTime doesn't tell us */ + tm.tm_isdst=0; /* GetLocalTime doesn't tell us */ + return tm; +} diff --git a/netbox/libs/Putty/x11fwd.c b/netbox/libs/Putty/x11fwd.c new file mode 100644 index 000000000..64eba596c --- /dev/null +++ b/netbox/libs/Putty/x11fwd.c @@ -0,0 +1,1088 @@ +/* + * Platform-independent bits of X11 forwarding. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "tree234.h" + +#define GET_16BIT(endian, cp) \ + (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp)) + +#define PUT_16BIT(endian, cp, val) \ + (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val)) + +const char *const x11_authnames[] = { + "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" +}; + +struct XDMSeen { + unsigned int time; + unsigned char clientid[6]; +}; + +struct X11Connection { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + unsigned char firstpkt[12]; /* first X data packet */ + tree234 *authtree; + struct X11Display *disp; + char *auth_protocol; + unsigned char *auth_data; + int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; + int verified; + int throttled, throttle_override; + int no_data_sent_to_x_client; + char *peer_addr; + int peer_port; + struct ssh_channel *c; /* channel structure held by ssh.c */ + Socket s; +}; + +static int xdmseen_cmp(void *a, void *b) +{ + struct XDMSeen *sa = a, *sb = b; + return sa->time > sb->time ? 1 : + sa->time < sb->time ? -1 : + memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid)); +} + +/* Do-nothing "plug" implementation, used by x11_setup_display() when it + * creates a trial connection (and then immediately closes it). + * XXX: bit out of place here, could in principle live in a platform- + * independent network.c or something */ +static void dummy_plug_log(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code) { } +static int dummy_plug_closing + (Plug p, const char *error_msg, int error_code, int calling_back) +{ return 1; } +static int dummy_plug_receive(Plug p, int urgent, char *data, int len) +{ return 1; } +static void dummy_plug_sent(Plug p, int bufsize) { } +static int dummy_plug_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) { return 1; } +static const struct plug_function_table dummy_plug = { + dummy_plug_log, dummy_plug_closing, dummy_plug_receive, + dummy_plug_sent, dummy_plug_accepting +}; + +struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) +{ + struct X11FakeAuth *auth = snew(struct X11FakeAuth); + int i; + + /* + * This function has the job of inventing a set of X11 fake auth + * data, and adding it to 'authtree'. We must preserve the + * property that for any given actual authorisation attempt, _at + * most one_ thing in the tree can possibly match it. + * + * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match + * criterion is simply that the entire cookie is correct, so we + * just have to make sure we don't make up two cookies the same. + * (Vanishingly unlikely, but we check anyway to be sure, and go + * round again inventing a new cookie if add234 tells us the one + * we thought of is already in use.) + * + * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup + * with XA1 is that half the cookie is used as a DES key with + * which to CBC-encrypt an assortment of stuff. Happily, the stuff + * encrypted _begins_ with the other half of the cookie, and the + * IV is always zero, which means that any valid XA1 authorisation + * attempt for a given cookie must begin with the same cipher + * block, consisting of the DES ECB encryption of the first half + * of the cookie using the second half as a key. So we compute + * that cipher block here and now, and use it as the sorting key + * for distinguishing XA1 entries in the tree. + */ + + if (authtype == X11_MIT) { + auth->proto = X11_MIT; + + /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = NULL; + + while (1) { + for (i = 0; i < auth->datalen; i++) + auth->data[i] = random_byte(); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = NULL; + } else { + assert(authtype == X11_XDM); + auth->proto = X11_XDM; + + /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = snewn(8, unsigned char); + memset(auth->xa1_firstblock, 0, 8); + + while (1) { + for (i = 0; i < auth->datalen; i++) + auth->data[i] = (i == 8 ? 0 : random_byte()); + memcpy(auth->xa1_firstblock, auth->data, 8); + des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = newtree234(xdmseen_cmp); + } + auth->protoname = dupstr(x11_authnames[auth->proto]); + auth->datastring = snewn(auth->datalen * 2 + 1, char); + for (i = 0; i < auth->datalen; i++) + sprintf(auth->datastring + i*2, "%02x", + auth->data[i]); + + auth->disp = NULL; + auth->share_cs = auth->share_chan = NULL; + + return auth; +} + +void x11_free_fake_auth(struct X11FakeAuth *auth) +{ + if (auth->data) + smemclr(auth->data, auth->datalen); + sfree(auth->data); + sfree(auth->protoname); + sfree(auth->datastring); + sfree(auth->xa1_firstblock); + if (auth->xdmseen != NULL) { + struct XDMSeen *seen; + while ((seen = delpos234(auth->xdmseen, 0)) != NULL) + sfree(seen); + freetree234(auth->xdmseen); + } + sfree(auth); +} + +int x11_authcmp(void *av, void *bv) +{ + struct X11FakeAuth *a = (struct X11FakeAuth *)av; + struct X11FakeAuth *b = (struct X11FakeAuth *)bv; + + if (a->proto < b->proto) + return -1; + else if (a->proto > b->proto) + return +1; + + if (a->proto == X11_MIT) { + if (a->datalen < b->datalen) + return -1; + else if (a->datalen > b->datalen) + return +1; + + return memcmp(a->data, b->data, a->datalen); + } else { + assert(a->proto == X11_XDM); + + return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8); + } +} + +struct X11Display *x11_setup_display(char *display, Conf *conf) +{ + struct X11Display *disp = snew(struct X11Display); + char *localcopy; + + if (!display || !*display) { + localcopy = platform_get_x_display(); + if (!localcopy || !*localcopy) { + sfree(localcopy); + localcopy = dupstr(":0"); /* plausible default for any platform */ + } + } else + localcopy = dupstr(display); + + /* + * Parse the display name. + * + * We expect this to have one of the following forms: + * + * - the standard X format which looks like + * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ] + * (X11 also permits a double colon to indicate DECnet, but + * that's not our problem, thankfully!) + * + * - only seen in the wild on MacOS (so far): a pathname to a + * Unix-domain socket, which will typically and confusingly + * end in ":0", and which I'm currently distinguishing from + * the standard scheme by noting that it starts with '/'. + */ + if (localcopy[0] == '/') { + disp->unixsocketpath = localcopy; + disp->unixdomain = TRUE; + disp->hostname = NULL; + disp->displaynum = -1; + disp->screennum = 0; + disp->addr = NULL; + } else { + char *colon, *dot, *slash; + char *protocol, *hostname; + + colon = host_strrchr(localcopy, ':'); + if (!colon) { + sfree(disp); + sfree(localcopy); + return NULL; /* FIXME: report a specific error? */ + } + + *colon++ = '\0'; + dot = strchr(colon, '.'); + if (dot) + *dot++ = '\0'; + + disp->displaynum = atoi(colon); + if (dot) + disp->screennum = atoi(dot); + else + disp->screennum = 0; + + protocol = NULL; + hostname = localcopy; + if (colon > localcopy) { + slash = strchr(localcopy, '/'); + if (slash) { + *slash++ = '\0'; + protocol = localcopy; + hostname = slash; + } + } + + disp->hostname = *hostname ? dupstr(hostname) : NULL; + + if (protocol) + disp->unixdomain = (!strcmp(protocol, "local") || + !strcmp(protocol, "unix")); + else if (!*hostname || !strcmp(hostname, "unix")) + disp->unixdomain = platform_uses_x11_unix_by_default; + else + disp->unixdomain = FALSE; + + if (!disp->hostname && !disp->unixdomain) + disp->hostname = dupstr("localhost"); + + disp->unixsocketpath = NULL; + disp->addr = NULL; + + sfree(localcopy); + } + + /* + * Look up the display hostname, if we need to. + */ + if (!disp->unixdomain) { + const char *err; + + disp->port = 6000 + disp->displaynum; + disp->addr = name_lookup(disp->hostname, disp->port, + &disp->realhost, conf, ADDRTYPE_UNSPEC); + + if ((err = sk_addr_error(disp->addr)) != NULL) { + sk_addr_free(disp->addr); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + sfree(disp); + return NULL; /* FIXME: report an error */ + } + } + + /* + * Try upgrading an IP-style localhost display to a Unix-socket + * display (as the standard X connection libraries do). + */ + if (!disp->unixdomain && sk_address_is_local(disp->addr)) { + SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum); + const char *err = sk_addr_error(ux); + if (!err) { + /* Create trial connection to see if there is a useful Unix-domain + * socket */ + const struct plug_function_table *dummy = &dummy_plug; + Socket s = putty_sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy); + err = sk_socket_error(s); + sk_close(s); + } + if (err) { + sk_addr_free(ux); + } else { + sk_addr_free(disp->addr); + disp->unixdomain = TRUE; + disp->addr = ux; + /* Fill in the rest in a moment */ + } + } + + if (disp->unixdomain) { + if (!disp->addr) + disp->addr = platform_get_x11_unix_address(disp->unixsocketpath, + disp->displaynum); + if (disp->unixsocketpath) + disp->realhost = dupstr(disp->unixsocketpath); + else + disp->realhost = dupprintf("unix:%d", disp->displaynum); + disp->port = 0; + } + + /* + * Fetch the local authorisation details. + */ + disp->localauthproto = X11_NO_AUTH; + disp->localauthdata = NULL; + disp->localauthdatalen = 0; + platform_get_x11_auth(disp, conf); + + return disp; +} + +void x11_free_display(struct X11Display *disp) +{ + sfree(disp->hostname); + sfree(disp->unixsocketpath); + if (disp->localauthdata) + smemclr(disp->localauthdata, disp->localauthdatalen); + sfree(disp->localauthdata); + sk_addr_free(disp->addr); + sfree(disp); +} + +#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ + +static char *x11_verify(unsigned long peer_ip, int peer_port, + tree234 *authtree, char *proto, + unsigned char *data, int dlen, + struct X11FakeAuth **auth_ret) +{ + struct X11FakeAuth match_dummy; /* for passing to find234 */ + struct X11FakeAuth *auth; + + /* + * First, do a lookup in our tree to find the only authorisation + * record that _might_ match. + */ + if (!strcmp(proto, x11_authnames[X11_MIT])) { + /* + * Just look up the whole cookie that was presented to us, + * which x11_authcmp will compare against the cookies we + * currently believe in. + */ + match_dummy.proto = X11_MIT; + match_dummy.datalen = dlen; + match_dummy.data = data; + } else if (!strcmp(proto, x11_authnames[X11_XDM])) { + /* + * Look up the first cipher block, against the stored first + * cipher blocks for the XDM-AUTHORIZATION-1 cookies we + * currently know. (See comment in x11_invent_fake_auth.) + */ + match_dummy.proto = X11_XDM; + match_dummy.xa1_firstblock = data; + } else { + return "Unsupported authorisation protocol"; + } + + if ((auth = find234(authtree, &match_dummy, 0)) == NULL) + return "Authorisation not recognised"; + + /* + * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If + * we're doing XDM-AUTHORIZATION-1, though, we have to check the + * rest of the auth data. + */ + if (auth->proto == X11_XDM) { + unsigned long t; + time_t tim; + int i; + struct XDMSeen *seen, *ret; + + if (dlen != 24) + return "XDM-AUTHORIZATION-1 data was wrong length"; + if (peer_port == -1) + return "cannot do XDM-AUTHORIZATION-1 without remote address data"; + des_decrypt_xdmauth(auth->data+9, data, 24); + if (memcmp(auth->data, data, 8) != 0) + return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ + if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) + return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ + if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) + return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ + t = GET_32BIT_MSB_FIRST(data+14); + for (i = 18; i < 24; i++) + if (data[i] != 0) /* zero padding wrong */ + return "XDM-AUTHORIZATION-1 data failed check"; + tim = time(NULL); + if (abs(t - tim) > XDM_MAXSKEW) + return "XDM-AUTHORIZATION-1 time stamp was too far out"; + seen = snew(struct XDMSeen); + seen->time = t; + memcpy(seen->clientid, data+8, 6); + assert(auth->xdmseen != NULL); + ret = add234(auth->xdmseen, seen); + if (ret != seen) { + sfree(seen); + return "XDM-AUTHORIZATION-1 data replayed"; + } + /* While we're here, purge entries too old to be replayed. */ + for (;;) { + seen = index234(auth->xdmseen, 0); + assert(seen != NULL); + if (t - seen->time <= XDM_MAXSKEW) + break; + sfree(delpos234(auth->xdmseen, 0)); + } + } + /* implement other protocols here if ever required */ + + *auth_ret = auth; + return NULL; +} + +void x11_get_auth_from_authfile(struct X11Display *disp, + const char *authfilename) +{ + FILE *authfp; + char *buf, *ptr, *str[4]; + int len[4]; + int family, protocol; + int ideal_match = FALSE; + char *ourhostname; + + /* + * Normally we should look for precisely the details specified in + * `disp'. However, there's an oddity when the display is local: + * displays like "localhost:0" usually have their details stored + * in a Unix-domain-socket record (even if there isn't actually a + * real Unix-domain socket available, as with OpenSSH's proxy X11 + * server). + * + * This is apparently a fudge to get round the meaninglessness of + * "localhost" in a shared-home-directory context -- xauth entries + * for Unix-domain sockets already disambiguate this by storing + * the *local* hostname in the conveniently-blank hostname field, + * but IP "localhost" records couldn't do this. So, typically, an + * IP "localhost" entry in the auth database isn't present and if + * it were it would be ignored. + * + * However, we don't entirely trust that (say) Windows X servers + * won't rely on a straight "localhost" entry, bad idea though + * that is; so if we can't find a Unix-domain-socket entry we'll + * fall back to an IP-based entry if we can find one. + */ + int localhost = !disp->unixdomain && sk_address_is_local(disp->addr); + + authfp = fopen(authfilename, "rb"); + if (!authfp) + return; + + ourhostname = get_hostname(); + + /* Records in .Xauthority contain four strings of up to 64K each */ + buf = snewn(65537 * 4, char); + + while (!ideal_match) { + int c, i, j, match = FALSE; + +#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0) + /* Expect a big-endian 2-byte number giving address family */ + GET; family = c; + GET; family = (family << 8) | c; + /* Then expect four strings, each composed of a big-endian 2-byte + * length field followed by that many bytes of data */ + ptr = buf; + for (i = 0; i < 4; i++) { + GET; len[i] = c; + GET; len[i] = (len[i] << 8) | c; + str[i] = ptr; + for (j = 0; j < len[i]; j++) { + GET; *ptr++ = c; + } + *ptr++ = '\0'; + } +#undef GET + + /* + * Now we have a full X authority record in memory. See + * whether it matches the display we're trying to + * authenticate to. + * + * The details we've just read should be interpreted as + * follows: + * + * - 'family' is the network address family used to + * connect to the display. 0 means IPv4; 6 means IPv6; + * 256 means Unix-domain sockets. + * + * - str[0] is the network address itself. For IPv4 and + * IPv6, this is a string of binary data of the + * appropriate length (respectively 4 and 16 bytes) + * representing the address in big-endian format, e.g. + * 7F 00 00 01 means IPv4 localhost. For Unix-domain + * sockets, this is the host name of the machine on + * which the Unix-domain display resides (so that an + * .Xauthority file on a shared file system can contain + * authority entries for Unix-domain displays on + * several machines without them clashing). + * + * - str[1] is the display number. I've no idea why + * .Xauthority stores this as a string when it has a + * perfectly good integer format, but there we go. + * + * - str[2] is the authorisation method, encoded as its + * canonical string name (i.e. "MIT-MAGIC-COOKIE-1", + * "XDM-AUTHORIZATION-1" or something we don't + * recognise). + * + * - str[3] is the actual authorisation data, stored in + * binary form. + */ + + if (disp->displaynum < 0 || disp->displaynum != atoi(str[1])) + continue; /* not the one */ + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (!strcmp(str[2], x11_authnames[protocol])) + break; + if (protocol == lenof(x11_authnames)) + continue; /* don't recognise this protocol, look for another */ + + switch (family) { + case 0: /* IPv4 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { + char buf[4]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 4 && !memcmp(str[0], buf, 4)) { + match = TRUE; + /* If this is a "localhost" entry, note it down + * but carry on looking for a Unix-domain entry. */ + ideal_match = !localhost; + } + } + break; + case 6: /* IPv6 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { + char buf[16]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 16 && !memcmp(str[0], buf, 16)) { + match = TRUE; + ideal_match = !localhost; + } + } + break; + case 256: /* Unix-domain / localhost */ + if ((disp->unixdomain || localhost) + && ourhostname && !strcmp(ourhostname, str[0])) + /* A matching Unix-domain socket is always the best + * match. */ + match = ideal_match = TRUE; + break; + } + + if (match) { + /* Current best guess -- may be overridden if !ideal_match */ + disp->localauthproto = protocol; + sfree(disp->localauthdata); /* free previous guess, if any */ + disp->localauthdata = snewn(len[3], unsigned char); + memcpy(disp->localauthdata, str[3], len[3]); + disp->localauthdatalen = len[3]; + } + } + + done: + fclose(authfp); + smemclr(buf, 65537 * 4); + sfree(buf); + sfree(ourhostname); +} + +static void x11_log(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + /* We have no interface to the logging module here, so we drop these. */ +} + +static void x11_send_init_error(struct X11Connection *conn, + const char *err_message); + +static int x11_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct X11Connection *xconn = (struct X11Connection *) plug; + + if (error_msg) { + /* + * Socket error. If we're still at the connection setup stage, + * construct an X11 error packet passing on the problem. + */ + if (xconn->no_data_sent_to_x_client) { + char *err_message = dupprintf("unable to connect to forwarded " + "X server: %s", error_msg); + x11_send_init_error(xconn, err_message); + sfree(err_message); + } + + /* + * Whether we did that or not, now we slam the connection + * shut. + */ + sshfwd_unclean_close(xconn->c, error_msg); + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } + + return 1; +} + +static int x11_receive(Plug plug, int urgent, char *data, int len) +{ + struct X11Connection *xconn = (struct X11Connection *) plug; + + if (sshfwd_write(xconn->c, data, len) > 0) { + xconn->throttled = 1; + xconn->no_data_sent_to_x_client = FALSE; + sk_set_frozen(xconn->s, 1); + } + + return 1; +} + +static void x11_sent(Plug plug, int bufsize) +{ + struct X11Connection *xconn = (struct X11Connection *) plug; + + sshfwd_unthrottle(xconn->c, bufsize); +} + +/* + * When setting up X forwarding, we should send the screen number + * from the specified local display. This function extracts it from + * the display string. + */ +int x11_get_screen_number(char *display) +{ + int n; + + n = host_strcspn(display, ":"); + if (!display[n]) + return 0; + n = strcspn(display, "."); + if (!display[n]) + return 0; + return atoi(display + n + 1); +} + +/* + * Called to set up the X11Connection structure, though this does not + * yet connect to an actual server. + */ +struct X11Connection *x11_init(tree234 *authtree, void *c, + const char *peeraddr, int peerport) +{ + static const struct plug_function_table fn_table = { + x11_log, + x11_closing, + x11_receive, + x11_sent, + NULL + }; + + struct X11Connection *xconn; + + /* + * Open socket. + */ + xconn = snew(struct X11Connection); + xconn->fn = &fn_table; + xconn->auth_protocol = NULL; + xconn->authtree = authtree; + xconn->verified = 0; + xconn->data_read = 0; + xconn->throttled = xconn->throttle_override = 0; + xconn->no_data_sent_to_x_client = TRUE; + xconn->c = c; + + /* + * We don't actually open a local socket to the X server just yet, + * because we don't know which one it is. Instead, we'll wait + * until we see the incoming authentication data, which may tell + * us what display to connect to, or whether we have to divert + * this X forwarding channel to a connection-sharing downstream + * rather than handling it ourself. + */ + xconn->disp = NULL; + xconn->s = NULL; + + /* + * Stash the peer address we were given in its original text form. + */ + xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; + xconn->peer_port = peerport; + + return xconn; +} + +void x11_close(struct X11Connection *xconn) +{ + if (!xconn) + return; + + if (xconn->auth_protocol) { + sfree(xconn->auth_protocol); + sfree(xconn->auth_data); + } + + if (xconn->s) + sk_close(xconn->s); + + sfree(xconn->peer_addr); + sfree(xconn); +} + +void x11_unthrottle(struct X11Connection *xconn) +{ + if (!xconn) + return; + + xconn->throttled = 0; + if (xconn->s) + sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); +} + +void x11_override_throttle(struct X11Connection *xconn, int enable) +{ + if (!xconn) + return; + + xconn->throttle_override = enable; + if (xconn->s) + sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); +} + +static void x11_send_init_error(struct X11Connection *xconn, + const char *err_message) +{ + char *full_message; + int msglen, msgsize; + unsigned char *reply; + + full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message); + + msglen = strlen(full_message); + reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ + msgsize = (msglen + 3) & ~3; + reply[0] = 0; /* failure */ + reply[1] = msglen; /* length of reason string */ + memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */ + PUT_16BIT(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ + memset(reply + 8, 0, msgsize); + memcpy(reply + 8, full_message, msglen); + sshfwd_write(xconn->c, (char *)reply, 8 + msgsize); + sshfwd_write_eof(xconn->c); + xconn->no_data_sent_to_x_client = FALSE; + sfree(reply); + sfree(full_message); +} + +static int x11_parse_ip(const char *addr_string, unsigned long *ip) +{ + + /* + * See if we can make sense of this string as an IPv4 address, for + * XDM-AUTHORIZATION-1 purposes. + */ + int i[4]; + if (addr_string && + 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + return TRUE; + } else { + return FALSE; + } +} + +/* + * Called to send data down the raw connection. + */ +int x11_send(struct X11Connection *xconn, char *data, int len) +{ + if (!xconn) + return 0; + + /* + * Read the first packet. + */ + while (len > 0 && xconn->data_read < 12) + xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12) + return 0; + + /* + * If we have not allocated the auth_protocol and auth_data + * strings, do so now. + */ + if (!xconn->auth_protocol) { + xconn->auth_plen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 6); + xconn->auth_dlen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 8); + xconn->auth_psize = (xconn->auth_plen + 3) & ~3; + xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3; + /* Leave room for a terminating zero, to make our lives easier. */ + xconn->auth_protocol = snewn(xconn->auth_psize + 1, char); + xconn->auth_data = snewn(xconn->auth_dsize, unsigned char); + } + + /* + * Read the auth_protocol and auth_data strings. + */ + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize) + xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++); + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) + xconn->auth_data[xconn->data_read++ - 12 - + xconn->auth_psize] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) + return 0; + + /* + * If we haven't verified the authorisation, do so now. + */ + if (!xconn->verified) { + const char *err; + struct X11FakeAuth *auth_matched = NULL; + unsigned long peer_ip; + int peer_port; + int protomajor, protominor; + void *greeting; + int greeting_len; + unsigned char *socketdata; + int socketdatalen; + char new_peer_addr[32]; + int new_peer_port; + + protomajor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 2); + protominor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 4); + + assert(!xconn->s); + + xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */ + + peer_ip = 0; /* placate optimiser */ + if (x11_parse_ip(xconn->peer_addr, &peer_ip)) + peer_port = xconn->peer_port; + else + peer_port = -1; /* signal no peer address data available */ + + err = x11_verify(peer_ip, peer_port, + xconn->authtree, xconn->auth_protocol, + xconn->auth_data, xconn->auth_dlen, &auth_matched); + if (err) { + x11_send_init_error(xconn, err); + return 0; + } + assert(auth_matched); + + /* + * If this auth points to a connection-sharing downstream + * rather than an X display we know how to connect to + * directly, pass it off to the sharing module now. + */ + if (auth_matched->share_cs) { + sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs, + auth_matched->share_chan, + xconn->peer_addr, xconn->peer_port, + xconn->firstpkt[0], + protomajor, protominor, data, len); + return 0; + } + + /* + * Now we know we're going to accept the connection, and what + * X display to connect to. Actually connect to it. + */ + sshfwd_x11_is_local(xconn->c); + xconn->disp = auth_matched->disp; + xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), + xconn->disp->realhost, xconn->disp->port, + 0, 1, 0, 0, (Plug) xconn, + sshfwd_get_conf(xconn->c)); + if ((err = sk_socket_error(xconn->s)) != NULL) { + char *err_message = dupprintf("unable to connect to" + " forwarded X server: %s", err); + x11_send_init_error(xconn, err_message); + sfree(err_message); + return 0; + } + + /* + * Write a new connection header containing our replacement + * auth data. + */ + + socketdata = sk_getxdmdata(xconn->s, &socketdatalen); + if (socketdata && socketdatalen==6) { + sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0], + socketdata[1], socketdata[2], socketdata[3]); + new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4); + } else { + strcpy(new_peer_addr, "0.0.0.0"); + new_peer_port = 0; + } + + greeting = x11_make_greeting(xconn->firstpkt[0], + protomajor, protominor, + xconn->disp->localauthproto, + xconn->disp->localauthdata, + xconn->disp->localauthdatalen, + new_peer_addr, new_peer_port, + &greeting_len); + + sk_write(xconn->s, greeting, greeting_len); + + smemclr(greeting, greeting_len); + sfree(greeting); + + /* + * Now we're done. + */ + xconn->verified = 1; + } + + /* + * After initialisation, just copy data simply. + */ + + return sk_write(xconn->s, data, len); +} + +void x11_send_eof(struct X11Connection *xconn) +{ + if (xconn->s) { + sk_write_eof(xconn->s); + } else { + /* + * If EOF is received from the X client before we've got to + * the point of actually connecting to an X server, then we + * should send an EOF back to the client so that the + * forwarded channel will be terminated. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } +} + +/* + * Utility functions used by connection sharing to convert textual + * representations of an X11 auth protocol name + hex cookie into our + * usual integer protocol id and binary auth data. + */ +int x11_identify_auth_proto(const char *protoname) +{ + int protocol; + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (!strcmp(protoname, x11_authnames[protocol])) + return protocol; + return -1; +} + +void *x11_dehexify(const char *hex, int *outlen) +{ + int len, i; + unsigned char *ret; + + len = strlen(hex) / 2; + ret = snewn(len, unsigned char); + + for (i = 0; i < len; i++) { + char bytestr[3]; + unsigned val = 0; + bytestr[0] = hex[2*i]; + bytestr[1] = hex[2*i+1]; + bytestr[2] = '\0'; + sscanf(bytestr, "%x", &val); + ret[i] = val; + } + + *outlen = len; + return ret; +} + +/* + * Construct an X11 greeting packet, including making up the right + * authorisation data. + */ +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_addr, int peer_port, + int *outlen) +{ + unsigned char *greeting; + unsigned char realauthdata[64]; + const char *authname; + const unsigned char *authdata; + int authnamelen, authnamelen_pad; + int authdatalen, authdatalen_pad; + int greeting_len; + + authname = x11_authnames[auth_proto]; + authnamelen = strlen(authname); + authnamelen_pad = (authnamelen + 3) & ~3; + + if (auth_proto == X11_MIT) { + authdata = auth_data; + authdatalen = auth_len; + } else if (auth_proto == X11_XDM && auth_len == 16) { + time_t t; + unsigned long peer_ip = 0; + + x11_parse_ip(peer_addr, &peer_ip); + + authdata = realauthdata; + authdatalen = 24; + memset(realauthdata, 0, authdatalen); + memcpy(realauthdata, auth_data, 8); + PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); + PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+14, t); + + des_encrypt_xdmauth((const unsigned char *)auth_data + 9, + realauthdata, authdatalen); + } else { + authdata = realauthdata; + authdatalen = 0; + } + + authdatalen_pad = (authdatalen + 3) & ~3; + greeting_len = 12 + authnamelen_pad + authdatalen_pad; + + greeting = snewn(greeting_len, unsigned char); + memset(greeting, 0, greeting_len); + greeting[0] = endian; + PUT_16BIT(endian, greeting+2, protomajor); + PUT_16BIT(endian, greeting+4, protominor); + PUT_16BIT(endian, greeting+6, authnamelen); + PUT_16BIT(endian, greeting+8, authdatalen); + memcpy(greeting+12, authname, authnamelen); + memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); + + smemclr(realauthdata, sizeof(realauthdata)); + + *outlen = greeting_len; + return greeting; +} diff --git a/netbox/netbox.mk b/netbox/netbox.mk new file mode 100644 index 000000000..0d115aedf --- /dev/null +++ b/netbox/netbox.mk @@ -0,0 +1,1174 @@ +## +## Auto Generated makefile by CodeLite IDE +## any manual changes will be erased +## +## Debug +ProjectName :=netbox +ConfigurationName :=Debug +WorkspacePath := "/home/lion/github/far2l" +ProjectPath := "/home/lion/github/far2l/netbox" +IntermediateDirectory :=./Debug +OutDir := $(IntermediateDirectory) +CurrentFileName := +CurrentFilePath := +CurrentFileFullPath := +User :=lion +Date :=25/08/16 +CodeLitePath :="/home/lion/.codelite" +LinkerName :=/usr/bin/g++ +SharedObjectLinkerName :=/usr/bin/g++ -shared -fPIC +ObjectSuffix :=.o +DependSuffix :=.o.d +PreprocessSuffix :=.i +DebugSwitch :=-g +IncludeSwitch :=-I +LibrarySwitch :=-l +OutputSwitch :=-o +LibraryPathSwitch :=-L +PreprocessorSwitch :=-D +SourceSwitch :=-c +OutputFile :=../Build/Plugins/netbox/bin/$(ProjectName).far-plug-utf8 +Preprocessors :=$(PreprocessorSwitch)NO_FILEZILLA $(PreprocessorSwitch)MPEXT +ObjectSwitch :=-o +ArchiveOutputSwitch := +PreprocessOnlySwitch :=-E +ObjectsFileList :="netbox.txt" +PCHCompileFlags := +MakeDirCommand :=mkdir -p +LinkOptions := -Wl,--no-undefined $(shell wx-config --debug=yes --libs --unicode=yes) -export-dynamic +IncludePath := $(IncludeSwitch). $(IncludeSwitch). $(IncludeSwitch)../WinPort $(IncludeSwitch)src/base $(IncludeSwitch)src/resource $(IncludeSwitch)src/core $(IncludeSwitch)src/PluginSDK/Far2 $(IncludeSwitch)src/windows $(IncludeSwitch)src/NetBox $(IncludeSwitch)libs/Putty $(IncludeSwitch)libs/Putty/unix $(IncludeSwitch)libs/Putty/charset +IncludePCH := +RcIncludePath := +Libs := $(LibrarySwitch)neon $(LibrarySwitch)tinyxml2 $(LibrarySwitch)ssl $(LibrarySwitch)crypto $(LibrarySwitch)dl $(LibrarySwitch)WinPort +ArLibs := "neon" "tinyxml2" "ssl" "crypto" "dl" "WinPort" +LibPath := $(LibraryPathSwitch). $(LibraryPathSwitch)../WinPort/Debug + +## +## Common variables +## AR, CXX, CC, AS, CXXFLAGS and CFLAGS can be overriden using an environment variables +## +AR := /usr/bin/ar rcu +CXX := /usr/bin/g++ +CC := /usr/bin/gcc +CXXFLAGS := -g -std=c++14 -fpic $(Preprocessors) +CFLAGS := -g -fpic $(Preprocessors) +ASFLAGS := +AS := /usr/bin/as + + +## +## User defined environment variables +## +CodeLiteDir:=/usr/share/codelite +Objects0=$(IntermediateDirectory)/base_Classes.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_Common.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_FileBuffer.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_Global.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_LibraryLoader.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_local.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_Masks.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_rtti.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_StrUtils.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_Sysutils.cpp$(ObjectSuffix) \ + $(IntermediateDirectory)/base_UnicodeString.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_WideStrUtils.cpp$(ObjectSuffix) $(IntermediateDirectory)/base_Exceptions.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarDialog.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarInterface.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarPlugin.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_FarUtil.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_NetBox.cpp$(ObjectSuffix) \ + $(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(ObjectSuffix) $(IntermediateDirectory)/NetBox_XmlStorage.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Bookmarks.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Configuration.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_CopyParam.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_CoreMain.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Cryptography.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_FileInfo.cpp$(ObjectSuffix) \ + $(IntermediateDirectory)/core_FileMasks.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_FileOperationProgress.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_FileSystems.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_FtpFileSystem.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_HierarchicalStorage.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Http.cpp$(ObjectSuffix) + +Objects1=$(IntermediateDirectory)/core_NamedObjs.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_NeonIntf.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Option.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_PuttyIntf.cpp$(ObjectSuffix) \ + $(IntermediateDirectory)/core_Queue.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_RemoteFiles.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_ScpFileSystem.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_SecureShell.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_SessionData.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_SessionInfo.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_SftpFileSystem.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_Terminal.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(ObjectSuffix) $(IntermediateDirectory)/core_WinSCPSecurity.cpp$(ObjectSuffix) \ + $(IntermediateDirectory)/windows_GUIConfiguration.cpp$(ObjectSuffix) $(IntermediateDirectory)/windows_GUITools.cpp$(ObjectSuffix) $(IntermediateDirectory)/windows_ProgParams.cpp$(ObjectSuffix) $(IntermediateDirectory)/windows_SynchronizeController.cpp$(ObjectSuffix) $(IntermediateDirectory)/windows_Tools.cpp$(ObjectSuffix) $(IntermediateDirectory)/windows_WinInterface.cpp$(ObjectSuffix) $(IntermediateDirectory)/Putty_callback.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_conf.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_cproxy.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_dialog.c$(ObjectSuffix) \ + $(IntermediateDirectory)/Putty_errsock.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_import.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_int64.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_ldiscucs.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_logging.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_minibidi.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_misc.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_miscucs.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_noshare.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_pgssapi.c$(ObjectSuffix) \ + $(IntermediateDirectory)/Putty_portfwd.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_proxy.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_ssh.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshaes.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_ssharcf.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshblowf.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshbn.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshcrc.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshcrcda.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshdes.c$(ObjectSuffix) \ + $(IntermediateDirectory)/Putty_sshdh.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshdss.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshdssg.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshgssc.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshmd5.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshprime.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshpubk.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshrand.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshrsa.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshrsag.c$(ObjectSuffix) \ + $(IntermediateDirectory)/Putty_sshsh256.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshsh512.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshsha.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshshare.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshzlib.c$(ObjectSuffix) + +Objects2=$(IntermediateDirectory)/Putty_telnet.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_time.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_tree234.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_wcwidth.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_wildcard.c$(ObjectSuffix) \ + $(IntermediateDirectory)/Putty_x11fwd.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_sshecc.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_ldisc.c$(ObjectSuffix) $(IntermediateDirectory)/Putty_noterm.c$(ObjectSuffix) $(IntermediateDirectory)/charset_fromucs.c$(ObjectSuffix) $(IntermediateDirectory)/charset_localenc.c$(ObjectSuffix) $(IntermediateDirectory)/charset_macenc.c$(ObjectSuffix) $(IntermediateDirectory)/charset_mimeenc.c$(ObjectSuffix) $(IntermediateDirectory)/charset_sbcs.c$(ObjectSuffix) $(IntermediateDirectory)/charset_sbcsdat.c$(ObjectSuffix) \ + $(IntermediateDirectory)/charset_slookup.c$(ObjectSuffix) $(IntermediateDirectory)/charset_toucs.c$(ObjectSuffix) $(IntermediateDirectory)/charset_utf8.c$(ObjectSuffix) $(IntermediateDirectory)/charset_xenc.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxgen.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxgss.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxmisc.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxnet.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxnoise.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxpeer.c$(ObjectSuffix) \ + $(IntermediateDirectory)/unix_uxprint.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxproxy.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxser.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxsignal.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxstore.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxucs.c$(ObjectSuffix) $(IntermediateDirectory)/unix_xkeysym.c$(ObjectSuffix) $(IntermediateDirectory)/unix_xpmpucfg.c$(ObjectSuffix) $(IntermediateDirectory)/unix_xpmputty.c$(ObjectSuffix) $(IntermediateDirectory)/unix_uxagentc.c$(ObjectSuffix) \ + $(IntermediateDirectory)/unix_uxsel.c$(ObjectSuffix) + + + +Objects=$(Objects0) $(Objects1) $(Objects2) + +## +## Main Build Targets +## +.PHONY: all clean PreBuild PrePreBuild PostBuild MakeIntermediateDirs +all: $(OutputFile) + +$(OutputFile): $(IntermediateDirectory)/.d $(Objects) + @$(MakeDirCommand) $(@D) + @echo "" > $(IntermediateDirectory)/.d + @echo $(Objects0) > $(ObjectsFileList) + @echo $(Objects1) >> $(ObjectsFileList) + @echo $(Objects2) >> $(ObjectsFileList) + $(SharedObjectLinkerName) $(OutputSwitch)$(OutputFile) @$(ObjectsFileList) $(LibPath) $(Libs) $(LinkOptions) + @$(MakeDirCommand) "/home/lion/github/far2l/.build-debug" + @echo rebuilt > "/home/lion/github/far2l/.build-debug/netbox" + +MakeIntermediateDirs: + @test -d ./Debug || $(MakeDirCommand) ./Debug + + +$(IntermediateDirectory)/.d: + @test -d ./Debug || $(MakeDirCommand) ./Debug + +PreBuild: + @echo Executing Pre Build commands ... + mkdir -p ../Build/Plugins/netbox/ + @echo Done + + +## +## Objects +## +$(IntermediateDirectory)/base_Classes.cpp$(ObjectSuffix): src/base/Classes.cpp $(IntermediateDirectory)/base_Classes.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Classes.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Classes.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Classes.cpp$(DependSuffix): src/base/Classes.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Classes.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Classes.cpp$(DependSuffix) -MM "src/base/Classes.cpp" + +$(IntermediateDirectory)/base_Classes.cpp$(PreprocessSuffix): src/base/Classes.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Classes.cpp$(PreprocessSuffix) "src/base/Classes.cpp" + +$(IntermediateDirectory)/base_Common.cpp$(ObjectSuffix): src/base/Common.cpp $(IntermediateDirectory)/base_Common.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Common.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Common.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Common.cpp$(DependSuffix): src/base/Common.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Common.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Common.cpp$(DependSuffix) -MM "src/base/Common.cpp" + +$(IntermediateDirectory)/base_Common.cpp$(PreprocessSuffix): src/base/Common.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Common.cpp$(PreprocessSuffix) "src/base/Common.cpp" + +$(IntermediateDirectory)/base_FileBuffer.cpp$(ObjectSuffix): src/base/FileBuffer.cpp $(IntermediateDirectory)/base_FileBuffer.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/FileBuffer.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_FileBuffer.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_FileBuffer.cpp$(DependSuffix): src/base/FileBuffer.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_FileBuffer.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_FileBuffer.cpp$(DependSuffix) -MM "src/base/FileBuffer.cpp" + +$(IntermediateDirectory)/base_FileBuffer.cpp$(PreprocessSuffix): src/base/FileBuffer.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_FileBuffer.cpp$(PreprocessSuffix) "src/base/FileBuffer.cpp" + +$(IntermediateDirectory)/base_Global.cpp$(ObjectSuffix): src/base/Global.cpp $(IntermediateDirectory)/base_Global.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Global.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Global.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Global.cpp$(DependSuffix): src/base/Global.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Global.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Global.cpp$(DependSuffix) -MM "src/base/Global.cpp" + +$(IntermediateDirectory)/base_Global.cpp$(PreprocessSuffix): src/base/Global.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Global.cpp$(PreprocessSuffix) "src/base/Global.cpp" + +$(IntermediateDirectory)/base_LibraryLoader.cpp$(ObjectSuffix): src/base/LibraryLoader.cpp $(IntermediateDirectory)/base_LibraryLoader.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/LibraryLoader.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_LibraryLoader.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_LibraryLoader.cpp$(DependSuffix): src/base/LibraryLoader.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_LibraryLoader.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_LibraryLoader.cpp$(DependSuffix) -MM "src/base/LibraryLoader.cpp" + +$(IntermediateDirectory)/base_LibraryLoader.cpp$(PreprocessSuffix): src/base/LibraryLoader.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_LibraryLoader.cpp$(PreprocessSuffix) "src/base/LibraryLoader.cpp" + +$(IntermediateDirectory)/base_local.cpp$(ObjectSuffix): src/base/local.cpp $(IntermediateDirectory)/base_local.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/local.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_local.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_local.cpp$(DependSuffix): src/base/local.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_local.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_local.cpp$(DependSuffix) -MM "src/base/local.cpp" + +$(IntermediateDirectory)/base_local.cpp$(PreprocessSuffix): src/base/local.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_local.cpp$(PreprocessSuffix) "src/base/local.cpp" + +$(IntermediateDirectory)/base_Masks.cpp$(ObjectSuffix): src/base/Masks.cpp $(IntermediateDirectory)/base_Masks.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Masks.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Masks.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Masks.cpp$(DependSuffix): src/base/Masks.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Masks.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Masks.cpp$(DependSuffix) -MM "src/base/Masks.cpp" + +$(IntermediateDirectory)/base_Masks.cpp$(PreprocessSuffix): src/base/Masks.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Masks.cpp$(PreprocessSuffix) "src/base/Masks.cpp" + +$(IntermediateDirectory)/base_rtti.cpp$(ObjectSuffix): src/base/rtti.cpp $(IntermediateDirectory)/base_rtti.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/rtti.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_rtti.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_rtti.cpp$(DependSuffix): src/base/rtti.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_rtti.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_rtti.cpp$(DependSuffix) -MM "src/base/rtti.cpp" + +$(IntermediateDirectory)/base_rtti.cpp$(PreprocessSuffix): src/base/rtti.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_rtti.cpp$(PreprocessSuffix) "src/base/rtti.cpp" + +$(IntermediateDirectory)/base_StrUtils.cpp$(ObjectSuffix): src/base/StrUtils.cpp $(IntermediateDirectory)/base_StrUtils.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/StrUtils.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_StrUtils.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_StrUtils.cpp$(DependSuffix): src/base/StrUtils.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_StrUtils.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_StrUtils.cpp$(DependSuffix) -MM "src/base/StrUtils.cpp" + +$(IntermediateDirectory)/base_StrUtils.cpp$(PreprocessSuffix): src/base/StrUtils.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_StrUtils.cpp$(PreprocessSuffix) "src/base/StrUtils.cpp" + +$(IntermediateDirectory)/base_Sysutils.cpp$(ObjectSuffix): src/base/Sysutils.cpp $(IntermediateDirectory)/base_Sysutils.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Sysutils.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Sysutils.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Sysutils.cpp$(DependSuffix): src/base/Sysutils.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Sysutils.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Sysutils.cpp$(DependSuffix) -MM "src/base/Sysutils.cpp" + +$(IntermediateDirectory)/base_Sysutils.cpp$(PreprocessSuffix): src/base/Sysutils.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Sysutils.cpp$(PreprocessSuffix) "src/base/Sysutils.cpp" + +$(IntermediateDirectory)/base_UnicodeString.cpp$(ObjectSuffix): src/base/UnicodeString.cpp $(IntermediateDirectory)/base_UnicodeString.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/UnicodeString.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_UnicodeString.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_UnicodeString.cpp$(DependSuffix): src/base/UnicodeString.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_UnicodeString.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_UnicodeString.cpp$(DependSuffix) -MM "src/base/UnicodeString.cpp" + +$(IntermediateDirectory)/base_UnicodeString.cpp$(PreprocessSuffix): src/base/UnicodeString.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_UnicodeString.cpp$(PreprocessSuffix) "src/base/UnicodeString.cpp" + +$(IntermediateDirectory)/base_WideStrUtils.cpp$(ObjectSuffix): src/base/WideStrUtils.cpp $(IntermediateDirectory)/base_WideStrUtils.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/WideStrUtils.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_WideStrUtils.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_WideStrUtils.cpp$(DependSuffix): src/base/WideStrUtils.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_WideStrUtils.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_WideStrUtils.cpp$(DependSuffix) -MM "src/base/WideStrUtils.cpp" + +$(IntermediateDirectory)/base_WideStrUtils.cpp$(PreprocessSuffix): src/base/WideStrUtils.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_WideStrUtils.cpp$(PreprocessSuffix) "src/base/WideStrUtils.cpp" + +$(IntermediateDirectory)/base_Exceptions.cpp$(ObjectSuffix): src/base/Exceptions.cpp $(IntermediateDirectory)/base_Exceptions.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/base/Exceptions.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/base_Exceptions.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/base_Exceptions.cpp$(DependSuffix): src/base/Exceptions.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/base_Exceptions.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/base_Exceptions.cpp$(DependSuffix) -MM "src/base/Exceptions.cpp" + +$(IntermediateDirectory)/base_Exceptions.cpp$(PreprocessSuffix): src/base/Exceptions.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/base_Exceptions.cpp$(PreprocessSuffix) "src/base/Exceptions.cpp" + +$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(ObjectSuffix): src/NetBox/FarConfiguration.cpp $(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarConfiguration.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(DependSuffix): src/NetBox/FarConfiguration.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(DependSuffix) -MM "src/NetBox/FarConfiguration.cpp" + +$(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(PreprocessSuffix): src/NetBox/FarConfiguration.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarConfiguration.cpp$(PreprocessSuffix) "src/NetBox/FarConfiguration.cpp" + +$(IntermediateDirectory)/NetBox_FarDialog.cpp$(ObjectSuffix): src/NetBox/FarDialog.cpp $(IntermediateDirectory)/NetBox_FarDialog.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarDialog.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarDialog.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarDialog.cpp$(DependSuffix): src/NetBox/FarDialog.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarDialog.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarDialog.cpp$(DependSuffix) -MM "src/NetBox/FarDialog.cpp" + +$(IntermediateDirectory)/NetBox_FarDialog.cpp$(PreprocessSuffix): src/NetBox/FarDialog.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarDialog.cpp$(PreprocessSuffix) "src/NetBox/FarDialog.cpp" + +$(IntermediateDirectory)/NetBox_FarInterface.cpp$(ObjectSuffix): src/NetBox/FarInterface.cpp $(IntermediateDirectory)/NetBox_FarInterface.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarInterface.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarInterface.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarInterface.cpp$(DependSuffix): src/NetBox/FarInterface.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarInterface.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarInterface.cpp$(DependSuffix) -MM "src/NetBox/FarInterface.cpp" + +$(IntermediateDirectory)/NetBox_FarInterface.cpp$(PreprocessSuffix): src/NetBox/FarInterface.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarInterface.cpp$(PreprocessSuffix) "src/NetBox/FarInterface.cpp" + +$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(ObjectSuffix): src/NetBox/FarPlugin.cpp $(IntermediateDirectory)/NetBox_FarPlugin.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarPlugin.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(DependSuffix): src/NetBox/FarPlugin.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(DependSuffix) -MM "src/NetBox/FarPlugin.cpp" + +$(IntermediateDirectory)/NetBox_FarPlugin.cpp$(PreprocessSuffix): src/NetBox/FarPlugin.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarPlugin.cpp$(PreprocessSuffix) "src/NetBox/FarPlugin.cpp" + +$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(ObjectSuffix): src/NetBox/FarPluginStrings.cpp $(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarPluginStrings.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(DependSuffix): src/NetBox/FarPluginStrings.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(DependSuffix) -MM "src/NetBox/FarPluginStrings.cpp" + +$(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(PreprocessSuffix): src/NetBox/FarPluginStrings.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarPluginStrings.cpp$(PreprocessSuffix) "src/NetBox/FarPluginStrings.cpp" + +$(IntermediateDirectory)/NetBox_FarUtil.cpp$(ObjectSuffix): src/NetBox/FarUtil.cpp $(IntermediateDirectory)/NetBox_FarUtil.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/FarUtil.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_FarUtil.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_FarUtil.cpp$(DependSuffix): src/NetBox/FarUtil.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_FarUtil.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_FarUtil.cpp$(DependSuffix) -MM "src/NetBox/FarUtil.cpp" + +$(IntermediateDirectory)/NetBox_FarUtil.cpp$(PreprocessSuffix): src/NetBox/FarUtil.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_FarUtil.cpp$(PreprocessSuffix) "src/NetBox/FarUtil.cpp" + +$(IntermediateDirectory)/NetBox_NetBox.cpp$(ObjectSuffix): src/NetBox/NetBox.cpp $(IntermediateDirectory)/NetBox_NetBox.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/NetBox.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_NetBox.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_NetBox.cpp$(DependSuffix): src/NetBox/NetBox.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_NetBox.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_NetBox.cpp$(DependSuffix) -MM "src/NetBox/NetBox.cpp" + +$(IntermediateDirectory)/NetBox_NetBox.cpp$(PreprocessSuffix): src/NetBox/NetBox.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_NetBox.cpp$(PreprocessSuffix) "src/NetBox/NetBox.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(ObjectSuffix): src/NetBox/WinSCPDialogs.cpp $(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/WinSCPDialogs.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(DependSuffix): src/NetBox/WinSCPDialogs.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(DependSuffix) -MM "src/NetBox/WinSCPDialogs.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(PreprocessSuffix): src/NetBox/WinSCPDialogs.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_WinSCPDialogs.cpp$(PreprocessSuffix) "src/NetBox/WinSCPDialogs.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(ObjectSuffix): src/NetBox/WinSCPFileSystem.cpp $(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/WinSCPFileSystem.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(DependSuffix): src/NetBox/WinSCPFileSystem.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(DependSuffix) -MM "src/NetBox/WinSCPFileSystem.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(PreprocessSuffix): src/NetBox/WinSCPFileSystem.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_WinSCPFileSystem.cpp$(PreprocessSuffix) "src/NetBox/WinSCPFileSystem.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(ObjectSuffix): src/NetBox/WinSCPPlugin.cpp $(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/WinSCPPlugin.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(DependSuffix): src/NetBox/WinSCPPlugin.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(DependSuffix) -MM "src/NetBox/WinSCPPlugin.cpp" + +$(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(PreprocessSuffix): src/NetBox/WinSCPPlugin.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_WinSCPPlugin.cpp$(PreprocessSuffix) "src/NetBox/WinSCPPlugin.cpp" + +$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(ObjectSuffix): src/NetBox/XmlStorage.cpp $(IntermediateDirectory)/NetBox_XmlStorage.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/NetBox/XmlStorage.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(DependSuffix): src/NetBox/XmlStorage.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(DependSuffix) -MM "src/NetBox/XmlStorage.cpp" + +$(IntermediateDirectory)/NetBox_XmlStorage.cpp$(PreprocessSuffix): src/NetBox/XmlStorage.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/NetBox_XmlStorage.cpp$(PreprocessSuffix) "src/NetBox/XmlStorage.cpp" + +$(IntermediateDirectory)/core_Bookmarks.cpp$(ObjectSuffix): src/core/Bookmarks.cpp $(IntermediateDirectory)/core_Bookmarks.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Bookmarks.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Bookmarks.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Bookmarks.cpp$(DependSuffix): src/core/Bookmarks.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Bookmarks.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Bookmarks.cpp$(DependSuffix) -MM "src/core/Bookmarks.cpp" + +$(IntermediateDirectory)/core_Bookmarks.cpp$(PreprocessSuffix): src/core/Bookmarks.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Bookmarks.cpp$(PreprocessSuffix) "src/core/Bookmarks.cpp" + +$(IntermediateDirectory)/core_Configuration.cpp$(ObjectSuffix): src/core/Configuration.cpp $(IntermediateDirectory)/core_Configuration.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Configuration.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Configuration.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Configuration.cpp$(DependSuffix): src/core/Configuration.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Configuration.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Configuration.cpp$(DependSuffix) -MM "src/core/Configuration.cpp" + +$(IntermediateDirectory)/core_Configuration.cpp$(PreprocessSuffix): src/core/Configuration.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Configuration.cpp$(PreprocessSuffix) "src/core/Configuration.cpp" + +$(IntermediateDirectory)/core_CopyParam.cpp$(ObjectSuffix): src/core/CopyParam.cpp $(IntermediateDirectory)/core_CopyParam.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/CopyParam.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_CopyParam.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_CopyParam.cpp$(DependSuffix): src/core/CopyParam.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_CopyParam.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_CopyParam.cpp$(DependSuffix) -MM "src/core/CopyParam.cpp" + +$(IntermediateDirectory)/core_CopyParam.cpp$(PreprocessSuffix): src/core/CopyParam.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_CopyParam.cpp$(PreprocessSuffix) "src/core/CopyParam.cpp" + +$(IntermediateDirectory)/core_CoreMain.cpp$(ObjectSuffix): src/core/CoreMain.cpp $(IntermediateDirectory)/core_CoreMain.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/CoreMain.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_CoreMain.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_CoreMain.cpp$(DependSuffix): src/core/CoreMain.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_CoreMain.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_CoreMain.cpp$(DependSuffix) -MM "src/core/CoreMain.cpp" + +$(IntermediateDirectory)/core_CoreMain.cpp$(PreprocessSuffix): src/core/CoreMain.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_CoreMain.cpp$(PreprocessSuffix) "src/core/CoreMain.cpp" + +$(IntermediateDirectory)/core_Cryptography.cpp$(ObjectSuffix): src/core/Cryptography.cpp $(IntermediateDirectory)/core_Cryptography.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Cryptography.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Cryptography.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Cryptography.cpp$(DependSuffix): src/core/Cryptography.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Cryptography.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Cryptography.cpp$(DependSuffix) -MM "src/core/Cryptography.cpp" + +$(IntermediateDirectory)/core_Cryptography.cpp$(PreprocessSuffix): src/core/Cryptography.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Cryptography.cpp$(PreprocessSuffix) "src/core/Cryptography.cpp" + +$(IntermediateDirectory)/core_FileInfo.cpp$(ObjectSuffix): src/core/FileInfo.cpp $(IntermediateDirectory)/core_FileInfo.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/FileInfo.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_FileInfo.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_FileInfo.cpp$(DependSuffix): src/core/FileInfo.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_FileInfo.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_FileInfo.cpp$(DependSuffix) -MM "src/core/FileInfo.cpp" + +$(IntermediateDirectory)/core_FileInfo.cpp$(PreprocessSuffix): src/core/FileInfo.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_FileInfo.cpp$(PreprocessSuffix) "src/core/FileInfo.cpp" + +$(IntermediateDirectory)/core_FileMasks.cpp$(ObjectSuffix): src/core/FileMasks.cpp $(IntermediateDirectory)/core_FileMasks.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/FileMasks.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_FileMasks.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_FileMasks.cpp$(DependSuffix): src/core/FileMasks.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_FileMasks.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_FileMasks.cpp$(DependSuffix) -MM "src/core/FileMasks.cpp" + +$(IntermediateDirectory)/core_FileMasks.cpp$(PreprocessSuffix): src/core/FileMasks.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_FileMasks.cpp$(PreprocessSuffix) "src/core/FileMasks.cpp" + +$(IntermediateDirectory)/core_FileOperationProgress.cpp$(ObjectSuffix): src/core/FileOperationProgress.cpp $(IntermediateDirectory)/core_FileOperationProgress.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/FileOperationProgress.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_FileOperationProgress.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_FileOperationProgress.cpp$(DependSuffix): src/core/FileOperationProgress.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_FileOperationProgress.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_FileOperationProgress.cpp$(DependSuffix) -MM "src/core/FileOperationProgress.cpp" + +$(IntermediateDirectory)/core_FileOperationProgress.cpp$(PreprocessSuffix): src/core/FileOperationProgress.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_FileOperationProgress.cpp$(PreprocessSuffix) "src/core/FileOperationProgress.cpp" + +$(IntermediateDirectory)/core_FileSystems.cpp$(ObjectSuffix): src/core/FileSystems.cpp $(IntermediateDirectory)/core_FileSystems.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/FileSystems.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_FileSystems.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_FileSystems.cpp$(DependSuffix): src/core/FileSystems.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_FileSystems.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_FileSystems.cpp$(DependSuffix) -MM "src/core/FileSystems.cpp" + +$(IntermediateDirectory)/core_FileSystems.cpp$(PreprocessSuffix): src/core/FileSystems.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_FileSystems.cpp$(PreprocessSuffix) "src/core/FileSystems.cpp" + +$(IntermediateDirectory)/core_FtpFileSystem.cpp$(ObjectSuffix): src/core/FtpFileSystem.cpp $(IntermediateDirectory)/core_FtpFileSystem.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/FtpFileSystem.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_FtpFileSystem.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_FtpFileSystem.cpp$(DependSuffix): src/core/FtpFileSystem.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_FtpFileSystem.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_FtpFileSystem.cpp$(DependSuffix) -MM "src/core/FtpFileSystem.cpp" + +$(IntermediateDirectory)/core_FtpFileSystem.cpp$(PreprocessSuffix): src/core/FtpFileSystem.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_FtpFileSystem.cpp$(PreprocessSuffix) "src/core/FtpFileSystem.cpp" + +$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(ObjectSuffix): src/core/HierarchicalStorage.cpp $(IntermediateDirectory)/core_HierarchicalStorage.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/HierarchicalStorage.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(DependSuffix): src/core/HierarchicalStorage.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(DependSuffix) -MM "src/core/HierarchicalStorage.cpp" + +$(IntermediateDirectory)/core_HierarchicalStorage.cpp$(PreprocessSuffix): src/core/HierarchicalStorage.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_HierarchicalStorage.cpp$(PreprocessSuffix) "src/core/HierarchicalStorage.cpp" + +$(IntermediateDirectory)/core_Http.cpp$(ObjectSuffix): src/core/Http.cpp $(IntermediateDirectory)/core_Http.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Http.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Http.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Http.cpp$(DependSuffix): src/core/Http.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Http.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Http.cpp$(DependSuffix) -MM "src/core/Http.cpp" + +$(IntermediateDirectory)/core_Http.cpp$(PreprocessSuffix): src/core/Http.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Http.cpp$(PreprocessSuffix) "src/core/Http.cpp" + +$(IntermediateDirectory)/core_NamedObjs.cpp$(ObjectSuffix): src/core/NamedObjs.cpp $(IntermediateDirectory)/core_NamedObjs.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/NamedObjs.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_NamedObjs.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_NamedObjs.cpp$(DependSuffix): src/core/NamedObjs.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_NamedObjs.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_NamedObjs.cpp$(DependSuffix) -MM "src/core/NamedObjs.cpp" + +$(IntermediateDirectory)/core_NamedObjs.cpp$(PreprocessSuffix): src/core/NamedObjs.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_NamedObjs.cpp$(PreprocessSuffix) "src/core/NamedObjs.cpp" + +$(IntermediateDirectory)/core_NeonIntf.cpp$(ObjectSuffix): src/core/NeonIntf.cpp $(IntermediateDirectory)/core_NeonIntf.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/NeonIntf.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_NeonIntf.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_NeonIntf.cpp$(DependSuffix): src/core/NeonIntf.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_NeonIntf.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_NeonIntf.cpp$(DependSuffix) -MM "src/core/NeonIntf.cpp" + +$(IntermediateDirectory)/core_NeonIntf.cpp$(PreprocessSuffix): src/core/NeonIntf.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_NeonIntf.cpp$(PreprocessSuffix) "src/core/NeonIntf.cpp" + +$(IntermediateDirectory)/core_Option.cpp$(ObjectSuffix): src/core/Option.cpp $(IntermediateDirectory)/core_Option.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Option.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Option.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Option.cpp$(DependSuffix): src/core/Option.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Option.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Option.cpp$(DependSuffix) -MM "src/core/Option.cpp" + +$(IntermediateDirectory)/core_Option.cpp$(PreprocessSuffix): src/core/Option.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Option.cpp$(PreprocessSuffix) "src/core/Option.cpp" + +$(IntermediateDirectory)/core_PuttyIntf.cpp$(ObjectSuffix): src/core/PuttyIntf.cpp $(IntermediateDirectory)/core_PuttyIntf.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/PuttyIntf.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_PuttyIntf.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_PuttyIntf.cpp$(DependSuffix): src/core/PuttyIntf.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_PuttyIntf.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_PuttyIntf.cpp$(DependSuffix) -MM "src/core/PuttyIntf.cpp" + +$(IntermediateDirectory)/core_PuttyIntf.cpp$(PreprocessSuffix): src/core/PuttyIntf.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_PuttyIntf.cpp$(PreprocessSuffix) "src/core/PuttyIntf.cpp" + +$(IntermediateDirectory)/core_Queue.cpp$(ObjectSuffix): src/core/Queue.cpp $(IntermediateDirectory)/core_Queue.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Queue.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Queue.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Queue.cpp$(DependSuffix): src/core/Queue.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Queue.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Queue.cpp$(DependSuffix) -MM "src/core/Queue.cpp" + +$(IntermediateDirectory)/core_Queue.cpp$(PreprocessSuffix): src/core/Queue.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Queue.cpp$(PreprocessSuffix) "src/core/Queue.cpp" + +$(IntermediateDirectory)/core_RemoteFiles.cpp$(ObjectSuffix): src/core/RemoteFiles.cpp $(IntermediateDirectory)/core_RemoteFiles.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/RemoteFiles.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_RemoteFiles.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_RemoteFiles.cpp$(DependSuffix): src/core/RemoteFiles.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_RemoteFiles.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_RemoteFiles.cpp$(DependSuffix) -MM "src/core/RemoteFiles.cpp" + +$(IntermediateDirectory)/core_RemoteFiles.cpp$(PreprocessSuffix): src/core/RemoteFiles.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_RemoteFiles.cpp$(PreprocessSuffix) "src/core/RemoteFiles.cpp" + +$(IntermediateDirectory)/core_ScpFileSystem.cpp$(ObjectSuffix): src/core/ScpFileSystem.cpp $(IntermediateDirectory)/core_ScpFileSystem.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/ScpFileSystem.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_ScpFileSystem.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_ScpFileSystem.cpp$(DependSuffix): src/core/ScpFileSystem.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_ScpFileSystem.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_ScpFileSystem.cpp$(DependSuffix) -MM "src/core/ScpFileSystem.cpp" + +$(IntermediateDirectory)/core_ScpFileSystem.cpp$(PreprocessSuffix): src/core/ScpFileSystem.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_ScpFileSystem.cpp$(PreprocessSuffix) "src/core/ScpFileSystem.cpp" + +$(IntermediateDirectory)/core_SecureShell.cpp$(ObjectSuffix): src/core/SecureShell.cpp $(IntermediateDirectory)/core_SecureShell.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/SecureShell.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_SecureShell.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_SecureShell.cpp$(DependSuffix): src/core/SecureShell.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_SecureShell.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_SecureShell.cpp$(DependSuffix) -MM "src/core/SecureShell.cpp" + +$(IntermediateDirectory)/core_SecureShell.cpp$(PreprocessSuffix): src/core/SecureShell.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_SecureShell.cpp$(PreprocessSuffix) "src/core/SecureShell.cpp" + +$(IntermediateDirectory)/core_SessionData.cpp$(ObjectSuffix): src/core/SessionData.cpp $(IntermediateDirectory)/core_SessionData.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/SessionData.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_SessionData.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_SessionData.cpp$(DependSuffix): src/core/SessionData.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_SessionData.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_SessionData.cpp$(DependSuffix) -MM "src/core/SessionData.cpp" + +$(IntermediateDirectory)/core_SessionData.cpp$(PreprocessSuffix): src/core/SessionData.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_SessionData.cpp$(PreprocessSuffix) "src/core/SessionData.cpp" + +$(IntermediateDirectory)/core_SessionInfo.cpp$(ObjectSuffix): src/core/SessionInfo.cpp $(IntermediateDirectory)/core_SessionInfo.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/SessionInfo.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_SessionInfo.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_SessionInfo.cpp$(DependSuffix): src/core/SessionInfo.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_SessionInfo.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_SessionInfo.cpp$(DependSuffix) -MM "src/core/SessionInfo.cpp" + +$(IntermediateDirectory)/core_SessionInfo.cpp$(PreprocessSuffix): src/core/SessionInfo.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_SessionInfo.cpp$(PreprocessSuffix) "src/core/SessionInfo.cpp" + +$(IntermediateDirectory)/core_SftpFileSystem.cpp$(ObjectSuffix): src/core/SftpFileSystem.cpp $(IntermediateDirectory)/core_SftpFileSystem.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/SftpFileSystem.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_SftpFileSystem.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_SftpFileSystem.cpp$(DependSuffix): src/core/SftpFileSystem.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_SftpFileSystem.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_SftpFileSystem.cpp$(DependSuffix) -MM "src/core/SftpFileSystem.cpp" + +$(IntermediateDirectory)/core_SftpFileSystem.cpp$(PreprocessSuffix): src/core/SftpFileSystem.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_SftpFileSystem.cpp$(PreprocessSuffix) "src/core/SftpFileSystem.cpp" + +$(IntermediateDirectory)/core_Terminal.cpp$(ObjectSuffix): src/core/Terminal.cpp $(IntermediateDirectory)/core_Terminal.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/Terminal.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_Terminal.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_Terminal.cpp$(DependSuffix): src/core/Terminal.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_Terminal.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_Terminal.cpp$(DependSuffix) -MM "src/core/Terminal.cpp" + +$(IntermediateDirectory)/core_Terminal.cpp$(PreprocessSuffix): src/core/Terminal.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_Terminal.cpp$(PreprocessSuffix) "src/core/Terminal.cpp" + +$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(ObjectSuffix): src/core/WebDAVFileSystem.cpp $(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/WebDAVFileSystem.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(DependSuffix): src/core/WebDAVFileSystem.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(DependSuffix) -MM "src/core/WebDAVFileSystem.cpp" + +$(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(PreprocessSuffix): src/core/WebDAVFileSystem.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_WebDAVFileSystem.cpp$(PreprocessSuffix) "src/core/WebDAVFileSystem.cpp" + +$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(ObjectSuffix): src/core/WinSCPSecurity.cpp $(IntermediateDirectory)/core_WinSCPSecurity.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/core/WinSCPSecurity.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(DependSuffix): src/core/WinSCPSecurity.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(DependSuffix) -MM "src/core/WinSCPSecurity.cpp" + +$(IntermediateDirectory)/core_WinSCPSecurity.cpp$(PreprocessSuffix): src/core/WinSCPSecurity.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/core_WinSCPSecurity.cpp$(PreprocessSuffix) "src/core/WinSCPSecurity.cpp" + +$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(ObjectSuffix): src/windows/GUIConfiguration.cpp $(IntermediateDirectory)/windows_GUIConfiguration.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/GUIConfiguration.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(DependSuffix): src/windows/GUIConfiguration.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(DependSuffix) -MM "src/windows/GUIConfiguration.cpp" + +$(IntermediateDirectory)/windows_GUIConfiguration.cpp$(PreprocessSuffix): src/windows/GUIConfiguration.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_GUIConfiguration.cpp$(PreprocessSuffix) "src/windows/GUIConfiguration.cpp" + +$(IntermediateDirectory)/windows_GUITools.cpp$(ObjectSuffix): src/windows/GUITools.cpp $(IntermediateDirectory)/windows_GUITools.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/GUITools.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_GUITools.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_GUITools.cpp$(DependSuffix): src/windows/GUITools.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_GUITools.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_GUITools.cpp$(DependSuffix) -MM "src/windows/GUITools.cpp" + +$(IntermediateDirectory)/windows_GUITools.cpp$(PreprocessSuffix): src/windows/GUITools.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_GUITools.cpp$(PreprocessSuffix) "src/windows/GUITools.cpp" + +$(IntermediateDirectory)/windows_ProgParams.cpp$(ObjectSuffix): src/windows/ProgParams.cpp $(IntermediateDirectory)/windows_ProgParams.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/ProgParams.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_ProgParams.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_ProgParams.cpp$(DependSuffix): src/windows/ProgParams.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_ProgParams.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_ProgParams.cpp$(DependSuffix) -MM "src/windows/ProgParams.cpp" + +$(IntermediateDirectory)/windows_ProgParams.cpp$(PreprocessSuffix): src/windows/ProgParams.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_ProgParams.cpp$(PreprocessSuffix) "src/windows/ProgParams.cpp" + +$(IntermediateDirectory)/windows_SynchronizeController.cpp$(ObjectSuffix): src/windows/SynchronizeController.cpp $(IntermediateDirectory)/windows_SynchronizeController.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/SynchronizeController.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_SynchronizeController.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_SynchronizeController.cpp$(DependSuffix): src/windows/SynchronizeController.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_SynchronizeController.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_SynchronizeController.cpp$(DependSuffix) -MM "src/windows/SynchronizeController.cpp" + +$(IntermediateDirectory)/windows_SynchronizeController.cpp$(PreprocessSuffix): src/windows/SynchronizeController.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_SynchronizeController.cpp$(PreprocessSuffix) "src/windows/SynchronizeController.cpp" + +$(IntermediateDirectory)/windows_Tools.cpp$(ObjectSuffix): src/windows/Tools.cpp $(IntermediateDirectory)/windows_Tools.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/Tools.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_Tools.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_Tools.cpp$(DependSuffix): src/windows/Tools.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_Tools.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_Tools.cpp$(DependSuffix) -MM "src/windows/Tools.cpp" + +$(IntermediateDirectory)/windows_Tools.cpp$(PreprocessSuffix): src/windows/Tools.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_Tools.cpp$(PreprocessSuffix) "src/windows/Tools.cpp" + +$(IntermediateDirectory)/windows_WinInterface.cpp$(ObjectSuffix): src/windows/WinInterface.cpp $(IntermediateDirectory)/windows_WinInterface.cpp$(DependSuffix) + $(CXX) $(IncludePCH) $(SourceSwitch) "/home/lion/github/far2l/netbox/src/windows/WinInterface.cpp" $(CXXFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/windows_WinInterface.cpp$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/windows_WinInterface.cpp$(DependSuffix): src/windows/WinInterface.cpp + @$(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/windows_WinInterface.cpp$(ObjectSuffix) -MF$(IntermediateDirectory)/windows_WinInterface.cpp$(DependSuffix) -MM "src/windows/WinInterface.cpp" + +$(IntermediateDirectory)/windows_WinInterface.cpp$(PreprocessSuffix): src/windows/WinInterface.cpp + $(CXX) $(CXXFLAGS) $(IncludePCH) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/windows_WinInterface.cpp$(PreprocessSuffix) "src/windows/WinInterface.cpp" + +$(IntermediateDirectory)/Putty_callback.c$(ObjectSuffix): libs/Putty/callback.c $(IntermediateDirectory)/Putty_callback.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/callback.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_callback.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_callback.c$(DependSuffix): libs/Putty/callback.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_callback.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_callback.c$(DependSuffix) -MM "libs/Putty/callback.c" + +$(IntermediateDirectory)/Putty_callback.c$(PreprocessSuffix): libs/Putty/callback.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_callback.c$(PreprocessSuffix) "libs/Putty/callback.c" + +$(IntermediateDirectory)/Putty_conf.c$(ObjectSuffix): libs/Putty/conf.c $(IntermediateDirectory)/Putty_conf.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/conf.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_conf.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_conf.c$(DependSuffix): libs/Putty/conf.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_conf.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_conf.c$(DependSuffix) -MM "libs/Putty/conf.c" + +$(IntermediateDirectory)/Putty_conf.c$(PreprocessSuffix): libs/Putty/conf.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_conf.c$(PreprocessSuffix) "libs/Putty/conf.c" + +$(IntermediateDirectory)/Putty_cproxy.c$(ObjectSuffix): libs/Putty/cproxy.c $(IntermediateDirectory)/Putty_cproxy.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/cproxy.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_cproxy.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_cproxy.c$(DependSuffix): libs/Putty/cproxy.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_cproxy.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_cproxy.c$(DependSuffix) -MM "libs/Putty/cproxy.c" + +$(IntermediateDirectory)/Putty_cproxy.c$(PreprocessSuffix): libs/Putty/cproxy.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_cproxy.c$(PreprocessSuffix) "libs/Putty/cproxy.c" + +$(IntermediateDirectory)/Putty_dialog.c$(ObjectSuffix): libs/Putty/dialog.c $(IntermediateDirectory)/Putty_dialog.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/dialog.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_dialog.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_dialog.c$(DependSuffix): libs/Putty/dialog.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_dialog.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_dialog.c$(DependSuffix) -MM "libs/Putty/dialog.c" + +$(IntermediateDirectory)/Putty_dialog.c$(PreprocessSuffix): libs/Putty/dialog.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_dialog.c$(PreprocessSuffix) "libs/Putty/dialog.c" + +$(IntermediateDirectory)/Putty_errsock.c$(ObjectSuffix): libs/Putty/errsock.c $(IntermediateDirectory)/Putty_errsock.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/errsock.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_errsock.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_errsock.c$(DependSuffix): libs/Putty/errsock.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_errsock.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_errsock.c$(DependSuffix) -MM "libs/Putty/errsock.c" + +$(IntermediateDirectory)/Putty_errsock.c$(PreprocessSuffix): libs/Putty/errsock.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_errsock.c$(PreprocessSuffix) "libs/Putty/errsock.c" + +$(IntermediateDirectory)/Putty_import.c$(ObjectSuffix): libs/Putty/import.c $(IntermediateDirectory)/Putty_import.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/import.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_import.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_import.c$(DependSuffix): libs/Putty/import.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_import.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_import.c$(DependSuffix) -MM "libs/Putty/import.c" + +$(IntermediateDirectory)/Putty_import.c$(PreprocessSuffix): libs/Putty/import.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_import.c$(PreprocessSuffix) "libs/Putty/import.c" + +$(IntermediateDirectory)/Putty_int64.c$(ObjectSuffix): libs/Putty/int64.c $(IntermediateDirectory)/Putty_int64.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/int64.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_int64.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_int64.c$(DependSuffix): libs/Putty/int64.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_int64.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_int64.c$(DependSuffix) -MM "libs/Putty/int64.c" + +$(IntermediateDirectory)/Putty_int64.c$(PreprocessSuffix): libs/Putty/int64.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_int64.c$(PreprocessSuffix) "libs/Putty/int64.c" + +$(IntermediateDirectory)/Putty_ldiscucs.c$(ObjectSuffix): libs/Putty/ldiscucs.c $(IntermediateDirectory)/Putty_ldiscucs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/ldiscucs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_ldiscucs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_ldiscucs.c$(DependSuffix): libs/Putty/ldiscucs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_ldiscucs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_ldiscucs.c$(DependSuffix) -MM "libs/Putty/ldiscucs.c" + +$(IntermediateDirectory)/Putty_ldiscucs.c$(PreprocessSuffix): libs/Putty/ldiscucs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_ldiscucs.c$(PreprocessSuffix) "libs/Putty/ldiscucs.c" + +$(IntermediateDirectory)/Putty_logging.c$(ObjectSuffix): libs/Putty/logging.c $(IntermediateDirectory)/Putty_logging.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/logging.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_logging.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_logging.c$(DependSuffix): libs/Putty/logging.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_logging.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_logging.c$(DependSuffix) -MM "libs/Putty/logging.c" + +$(IntermediateDirectory)/Putty_logging.c$(PreprocessSuffix): libs/Putty/logging.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_logging.c$(PreprocessSuffix) "libs/Putty/logging.c" + +$(IntermediateDirectory)/Putty_minibidi.c$(ObjectSuffix): libs/Putty/minibidi.c $(IntermediateDirectory)/Putty_minibidi.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/minibidi.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_minibidi.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_minibidi.c$(DependSuffix): libs/Putty/minibidi.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_minibidi.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_minibidi.c$(DependSuffix) -MM "libs/Putty/minibidi.c" + +$(IntermediateDirectory)/Putty_minibidi.c$(PreprocessSuffix): libs/Putty/minibidi.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_minibidi.c$(PreprocessSuffix) "libs/Putty/minibidi.c" + +$(IntermediateDirectory)/Putty_misc.c$(ObjectSuffix): libs/Putty/misc.c $(IntermediateDirectory)/Putty_misc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/misc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_misc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_misc.c$(DependSuffix): libs/Putty/misc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_misc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_misc.c$(DependSuffix) -MM "libs/Putty/misc.c" + +$(IntermediateDirectory)/Putty_misc.c$(PreprocessSuffix): libs/Putty/misc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_misc.c$(PreprocessSuffix) "libs/Putty/misc.c" + +$(IntermediateDirectory)/Putty_miscucs.c$(ObjectSuffix): libs/Putty/miscucs.c $(IntermediateDirectory)/Putty_miscucs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/miscucs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_miscucs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_miscucs.c$(DependSuffix): libs/Putty/miscucs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_miscucs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_miscucs.c$(DependSuffix) -MM "libs/Putty/miscucs.c" + +$(IntermediateDirectory)/Putty_miscucs.c$(PreprocessSuffix): libs/Putty/miscucs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_miscucs.c$(PreprocessSuffix) "libs/Putty/miscucs.c" + +$(IntermediateDirectory)/Putty_noshare.c$(ObjectSuffix): libs/Putty/noshare.c $(IntermediateDirectory)/Putty_noshare.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/noshare.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_noshare.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_noshare.c$(DependSuffix): libs/Putty/noshare.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_noshare.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_noshare.c$(DependSuffix) -MM "libs/Putty/noshare.c" + +$(IntermediateDirectory)/Putty_noshare.c$(PreprocessSuffix): libs/Putty/noshare.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_noshare.c$(PreprocessSuffix) "libs/Putty/noshare.c" + +$(IntermediateDirectory)/Putty_pgssapi.c$(ObjectSuffix): libs/Putty/pgssapi.c $(IntermediateDirectory)/Putty_pgssapi.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/pgssapi.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_pgssapi.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_pgssapi.c$(DependSuffix): libs/Putty/pgssapi.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_pgssapi.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_pgssapi.c$(DependSuffix) -MM "libs/Putty/pgssapi.c" + +$(IntermediateDirectory)/Putty_pgssapi.c$(PreprocessSuffix): libs/Putty/pgssapi.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_pgssapi.c$(PreprocessSuffix) "libs/Putty/pgssapi.c" + +$(IntermediateDirectory)/Putty_portfwd.c$(ObjectSuffix): libs/Putty/portfwd.c $(IntermediateDirectory)/Putty_portfwd.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/portfwd.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_portfwd.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_portfwd.c$(DependSuffix): libs/Putty/portfwd.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_portfwd.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_portfwd.c$(DependSuffix) -MM "libs/Putty/portfwd.c" + +$(IntermediateDirectory)/Putty_portfwd.c$(PreprocessSuffix): libs/Putty/portfwd.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_portfwd.c$(PreprocessSuffix) "libs/Putty/portfwd.c" + +$(IntermediateDirectory)/Putty_proxy.c$(ObjectSuffix): libs/Putty/proxy.c $(IntermediateDirectory)/Putty_proxy.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/proxy.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_proxy.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_proxy.c$(DependSuffix): libs/Putty/proxy.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_proxy.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_proxy.c$(DependSuffix) -MM "libs/Putty/proxy.c" + +$(IntermediateDirectory)/Putty_proxy.c$(PreprocessSuffix): libs/Putty/proxy.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_proxy.c$(PreprocessSuffix) "libs/Putty/proxy.c" + +$(IntermediateDirectory)/Putty_ssh.c$(ObjectSuffix): libs/Putty/ssh.c $(IntermediateDirectory)/Putty_ssh.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/ssh.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_ssh.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_ssh.c$(DependSuffix): libs/Putty/ssh.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_ssh.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_ssh.c$(DependSuffix) -MM "libs/Putty/ssh.c" + +$(IntermediateDirectory)/Putty_ssh.c$(PreprocessSuffix): libs/Putty/ssh.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_ssh.c$(PreprocessSuffix) "libs/Putty/ssh.c" + +$(IntermediateDirectory)/Putty_sshaes.c$(ObjectSuffix): libs/Putty/sshaes.c $(IntermediateDirectory)/Putty_sshaes.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshaes.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshaes.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshaes.c$(DependSuffix): libs/Putty/sshaes.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshaes.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshaes.c$(DependSuffix) -MM "libs/Putty/sshaes.c" + +$(IntermediateDirectory)/Putty_sshaes.c$(PreprocessSuffix): libs/Putty/sshaes.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshaes.c$(PreprocessSuffix) "libs/Putty/sshaes.c" + +$(IntermediateDirectory)/Putty_ssharcf.c$(ObjectSuffix): libs/Putty/ssharcf.c $(IntermediateDirectory)/Putty_ssharcf.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/ssharcf.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_ssharcf.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_ssharcf.c$(DependSuffix): libs/Putty/ssharcf.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_ssharcf.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_ssharcf.c$(DependSuffix) -MM "libs/Putty/ssharcf.c" + +$(IntermediateDirectory)/Putty_ssharcf.c$(PreprocessSuffix): libs/Putty/ssharcf.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_ssharcf.c$(PreprocessSuffix) "libs/Putty/ssharcf.c" + +$(IntermediateDirectory)/Putty_sshblowf.c$(ObjectSuffix): libs/Putty/sshblowf.c $(IntermediateDirectory)/Putty_sshblowf.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshblowf.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshblowf.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshblowf.c$(DependSuffix): libs/Putty/sshblowf.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshblowf.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshblowf.c$(DependSuffix) -MM "libs/Putty/sshblowf.c" + +$(IntermediateDirectory)/Putty_sshblowf.c$(PreprocessSuffix): libs/Putty/sshblowf.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshblowf.c$(PreprocessSuffix) "libs/Putty/sshblowf.c" + +$(IntermediateDirectory)/Putty_sshbn.c$(ObjectSuffix): libs/Putty/sshbn.c $(IntermediateDirectory)/Putty_sshbn.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshbn.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshbn.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshbn.c$(DependSuffix): libs/Putty/sshbn.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshbn.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshbn.c$(DependSuffix) -MM "libs/Putty/sshbn.c" + +$(IntermediateDirectory)/Putty_sshbn.c$(PreprocessSuffix): libs/Putty/sshbn.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshbn.c$(PreprocessSuffix) "libs/Putty/sshbn.c" + +$(IntermediateDirectory)/Putty_sshcrc.c$(ObjectSuffix): libs/Putty/sshcrc.c $(IntermediateDirectory)/Putty_sshcrc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshcrc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshcrc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshcrc.c$(DependSuffix): libs/Putty/sshcrc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshcrc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshcrc.c$(DependSuffix) -MM "libs/Putty/sshcrc.c" + +$(IntermediateDirectory)/Putty_sshcrc.c$(PreprocessSuffix): libs/Putty/sshcrc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshcrc.c$(PreprocessSuffix) "libs/Putty/sshcrc.c" + +$(IntermediateDirectory)/Putty_sshcrcda.c$(ObjectSuffix): libs/Putty/sshcrcda.c $(IntermediateDirectory)/Putty_sshcrcda.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshcrcda.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshcrcda.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshcrcda.c$(DependSuffix): libs/Putty/sshcrcda.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshcrcda.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshcrcda.c$(DependSuffix) -MM "libs/Putty/sshcrcda.c" + +$(IntermediateDirectory)/Putty_sshcrcda.c$(PreprocessSuffix): libs/Putty/sshcrcda.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshcrcda.c$(PreprocessSuffix) "libs/Putty/sshcrcda.c" + +$(IntermediateDirectory)/Putty_sshdes.c$(ObjectSuffix): libs/Putty/sshdes.c $(IntermediateDirectory)/Putty_sshdes.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshdes.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshdes.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshdes.c$(DependSuffix): libs/Putty/sshdes.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshdes.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshdes.c$(DependSuffix) -MM "libs/Putty/sshdes.c" + +$(IntermediateDirectory)/Putty_sshdes.c$(PreprocessSuffix): libs/Putty/sshdes.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshdes.c$(PreprocessSuffix) "libs/Putty/sshdes.c" + +$(IntermediateDirectory)/Putty_sshdh.c$(ObjectSuffix): libs/Putty/sshdh.c $(IntermediateDirectory)/Putty_sshdh.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshdh.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshdh.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshdh.c$(DependSuffix): libs/Putty/sshdh.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshdh.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshdh.c$(DependSuffix) -MM "libs/Putty/sshdh.c" + +$(IntermediateDirectory)/Putty_sshdh.c$(PreprocessSuffix): libs/Putty/sshdh.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshdh.c$(PreprocessSuffix) "libs/Putty/sshdh.c" + +$(IntermediateDirectory)/Putty_sshdss.c$(ObjectSuffix): libs/Putty/sshdss.c $(IntermediateDirectory)/Putty_sshdss.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshdss.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshdss.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshdss.c$(DependSuffix): libs/Putty/sshdss.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshdss.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshdss.c$(DependSuffix) -MM "libs/Putty/sshdss.c" + +$(IntermediateDirectory)/Putty_sshdss.c$(PreprocessSuffix): libs/Putty/sshdss.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshdss.c$(PreprocessSuffix) "libs/Putty/sshdss.c" + +$(IntermediateDirectory)/Putty_sshdssg.c$(ObjectSuffix): libs/Putty/sshdssg.c $(IntermediateDirectory)/Putty_sshdssg.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshdssg.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshdssg.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshdssg.c$(DependSuffix): libs/Putty/sshdssg.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshdssg.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshdssg.c$(DependSuffix) -MM "libs/Putty/sshdssg.c" + +$(IntermediateDirectory)/Putty_sshdssg.c$(PreprocessSuffix): libs/Putty/sshdssg.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshdssg.c$(PreprocessSuffix) "libs/Putty/sshdssg.c" + +$(IntermediateDirectory)/Putty_sshgssc.c$(ObjectSuffix): libs/Putty/sshgssc.c $(IntermediateDirectory)/Putty_sshgssc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshgssc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshgssc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshgssc.c$(DependSuffix): libs/Putty/sshgssc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshgssc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshgssc.c$(DependSuffix) -MM "libs/Putty/sshgssc.c" + +$(IntermediateDirectory)/Putty_sshgssc.c$(PreprocessSuffix): libs/Putty/sshgssc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshgssc.c$(PreprocessSuffix) "libs/Putty/sshgssc.c" + +$(IntermediateDirectory)/Putty_sshmd5.c$(ObjectSuffix): libs/Putty/sshmd5.c $(IntermediateDirectory)/Putty_sshmd5.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshmd5.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshmd5.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshmd5.c$(DependSuffix): libs/Putty/sshmd5.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshmd5.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshmd5.c$(DependSuffix) -MM "libs/Putty/sshmd5.c" + +$(IntermediateDirectory)/Putty_sshmd5.c$(PreprocessSuffix): libs/Putty/sshmd5.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshmd5.c$(PreprocessSuffix) "libs/Putty/sshmd5.c" + +$(IntermediateDirectory)/Putty_sshprime.c$(ObjectSuffix): libs/Putty/sshprime.c $(IntermediateDirectory)/Putty_sshprime.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshprime.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshprime.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshprime.c$(DependSuffix): libs/Putty/sshprime.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshprime.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshprime.c$(DependSuffix) -MM "libs/Putty/sshprime.c" + +$(IntermediateDirectory)/Putty_sshprime.c$(PreprocessSuffix): libs/Putty/sshprime.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshprime.c$(PreprocessSuffix) "libs/Putty/sshprime.c" + +$(IntermediateDirectory)/Putty_sshpubk.c$(ObjectSuffix): libs/Putty/sshpubk.c $(IntermediateDirectory)/Putty_sshpubk.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshpubk.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshpubk.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshpubk.c$(DependSuffix): libs/Putty/sshpubk.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshpubk.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshpubk.c$(DependSuffix) -MM "libs/Putty/sshpubk.c" + +$(IntermediateDirectory)/Putty_sshpubk.c$(PreprocessSuffix): libs/Putty/sshpubk.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshpubk.c$(PreprocessSuffix) "libs/Putty/sshpubk.c" + +$(IntermediateDirectory)/Putty_sshrand.c$(ObjectSuffix): libs/Putty/sshrand.c $(IntermediateDirectory)/Putty_sshrand.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshrand.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshrand.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshrand.c$(DependSuffix): libs/Putty/sshrand.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshrand.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshrand.c$(DependSuffix) -MM "libs/Putty/sshrand.c" + +$(IntermediateDirectory)/Putty_sshrand.c$(PreprocessSuffix): libs/Putty/sshrand.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshrand.c$(PreprocessSuffix) "libs/Putty/sshrand.c" + +$(IntermediateDirectory)/Putty_sshrsa.c$(ObjectSuffix): libs/Putty/sshrsa.c $(IntermediateDirectory)/Putty_sshrsa.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshrsa.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshrsa.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshrsa.c$(DependSuffix): libs/Putty/sshrsa.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshrsa.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshrsa.c$(DependSuffix) -MM "libs/Putty/sshrsa.c" + +$(IntermediateDirectory)/Putty_sshrsa.c$(PreprocessSuffix): libs/Putty/sshrsa.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshrsa.c$(PreprocessSuffix) "libs/Putty/sshrsa.c" + +$(IntermediateDirectory)/Putty_sshrsag.c$(ObjectSuffix): libs/Putty/sshrsag.c $(IntermediateDirectory)/Putty_sshrsag.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshrsag.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshrsag.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshrsag.c$(DependSuffix): libs/Putty/sshrsag.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshrsag.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshrsag.c$(DependSuffix) -MM "libs/Putty/sshrsag.c" + +$(IntermediateDirectory)/Putty_sshrsag.c$(PreprocessSuffix): libs/Putty/sshrsag.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshrsag.c$(PreprocessSuffix) "libs/Putty/sshrsag.c" + +$(IntermediateDirectory)/Putty_sshsh256.c$(ObjectSuffix): libs/Putty/sshsh256.c $(IntermediateDirectory)/Putty_sshsh256.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshsh256.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshsh256.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshsh256.c$(DependSuffix): libs/Putty/sshsh256.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshsh256.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshsh256.c$(DependSuffix) -MM "libs/Putty/sshsh256.c" + +$(IntermediateDirectory)/Putty_sshsh256.c$(PreprocessSuffix): libs/Putty/sshsh256.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshsh256.c$(PreprocessSuffix) "libs/Putty/sshsh256.c" + +$(IntermediateDirectory)/Putty_sshsh512.c$(ObjectSuffix): libs/Putty/sshsh512.c $(IntermediateDirectory)/Putty_sshsh512.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshsh512.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshsh512.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshsh512.c$(DependSuffix): libs/Putty/sshsh512.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshsh512.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshsh512.c$(DependSuffix) -MM "libs/Putty/sshsh512.c" + +$(IntermediateDirectory)/Putty_sshsh512.c$(PreprocessSuffix): libs/Putty/sshsh512.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshsh512.c$(PreprocessSuffix) "libs/Putty/sshsh512.c" + +$(IntermediateDirectory)/Putty_sshsha.c$(ObjectSuffix): libs/Putty/sshsha.c $(IntermediateDirectory)/Putty_sshsha.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshsha.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshsha.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshsha.c$(DependSuffix): libs/Putty/sshsha.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshsha.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshsha.c$(DependSuffix) -MM "libs/Putty/sshsha.c" + +$(IntermediateDirectory)/Putty_sshsha.c$(PreprocessSuffix): libs/Putty/sshsha.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshsha.c$(PreprocessSuffix) "libs/Putty/sshsha.c" + +$(IntermediateDirectory)/Putty_sshshare.c$(ObjectSuffix): libs/Putty/sshshare.c $(IntermediateDirectory)/Putty_sshshare.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshshare.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshshare.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshshare.c$(DependSuffix): libs/Putty/sshshare.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshshare.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshshare.c$(DependSuffix) -MM "libs/Putty/sshshare.c" + +$(IntermediateDirectory)/Putty_sshshare.c$(PreprocessSuffix): libs/Putty/sshshare.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshshare.c$(PreprocessSuffix) "libs/Putty/sshshare.c" + +$(IntermediateDirectory)/Putty_sshzlib.c$(ObjectSuffix): libs/Putty/sshzlib.c $(IntermediateDirectory)/Putty_sshzlib.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshzlib.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshzlib.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshzlib.c$(DependSuffix): libs/Putty/sshzlib.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshzlib.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshzlib.c$(DependSuffix) -MM "libs/Putty/sshzlib.c" + +$(IntermediateDirectory)/Putty_sshzlib.c$(PreprocessSuffix): libs/Putty/sshzlib.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshzlib.c$(PreprocessSuffix) "libs/Putty/sshzlib.c" + +$(IntermediateDirectory)/Putty_telnet.c$(ObjectSuffix): libs/Putty/telnet.c $(IntermediateDirectory)/Putty_telnet.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/telnet.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_telnet.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_telnet.c$(DependSuffix): libs/Putty/telnet.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_telnet.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_telnet.c$(DependSuffix) -MM "libs/Putty/telnet.c" + +$(IntermediateDirectory)/Putty_telnet.c$(PreprocessSuffix): libs/Putty/telnet.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_telnet.c$(PreprocessSuffix) "libs/Putty/telnet.c" + +$(IntermediateDirectory)/Putty_time.c$(ObjectSuffix): libs/Putty/time.c $(IntermediateDirectory)/Putty_time.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/time.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_time.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_time.c$(DependSuffix): libs/Putty/time.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_time.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_time.c$(DependSuffix) -MM "libs/Putty/time.c" + +$(IntermediateDirectory)/Putty_time.c$(PreprocessSuffix): libs/Putty/time.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_time.c$(PreprocessSuffix) "libs/Putty/time.c" + +$(IntermediateDirectory)/Putty_tree234.c$(ObjectSuffix): libs/Putty/tree234.c $(IntermediateDirectory)/Putty_tree234.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/tree234.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_tree234.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_tree234.c$(DependSuffix): libs/Putty/tree234.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_tree234.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_tree234.c$(DependSuffix) -MM "libs/Putty/tree234.c" + +$(IntermediateDirectory)/Putty_tree234.c$(PreprocessSuffix): libs/Putty/tree234.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_tree234.c$(PreprocessSuffix) "libs/Putty/tree234.c" + +$(IntermediateDirectory)/Putty_wcwidth.c$(ObjectSuffix): libs/Putty/wcwidth.c $(IntermediateDirectory)/Putty_wcwidth.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/wcwidth.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_wcwidth.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_wcwidth.c$(DependSuffix): libs/Putty/wcwidth.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_wcwidth.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_wcwidth.c$(DependSuffix) -MM "libs/Putty/wcwidth.c" + +$(IntermediateDirectory)/Putty_wcwidth.c$(PreprocessSuffix): libs/Putty/wcwidth.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_wcwidth.c$(PreprocessSuffix) "libs/Putty/wcwidth.c" + +$(IntermediateDirectory)/Putty_wildcard.c$(ObjectSuffix): libs/Putty/wildcard.c $(IntermediateDirectory)/Putty_wildcard.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/wildcard.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_wildcard.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_wildcard.c$(DependSuffix): libs/Putty/wildcard.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_wildcard.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_wildcard.c$(DependSuffix) -MM "libs/Putty/wildcard.c" + +$(IntermediateDirectory)/Putty_wildcard.c$(PreprocessSuffix): libs/Putty/wildcard.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_wildcard.c$(PreprocessSuffix) "libs/Putty/wildcard.c" + +$(IntermediateDirectory)/Putty_x11fwd.c$(ObjectSuffix): libs/Putty/x11fwd.c $(IntermediateDirectory)/Putty_x11fwd.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/x11fwd.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_x11fwd.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_x11fwd.c$(DependSuffix): libs/Putty/x11fwd.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_x11fwd.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_x11fwd.c$(DependSuffix) -MM "libs/Putty/x11fwd.c" + +$(IntermediateDirectory)/Putty_x11fwd.c$(PreprocessSuffix): libs/Putty/x11fwd.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_x11fwd.c$(PreprocessSuffix) "libs/Putty/x11fwd.c" + +$(IntermediateDirectory)/Putty_sshecc.c$(ObjectSuffix): libs/Putty/sshecc.c $(IntermediateDirectory)/Putty_sshecc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/sshecc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_sshecc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_sshecc.c$(DependSuffix): libs/Putty/sshecc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_sshecc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_sshecc.c$(DependSuffix) -MM "libs/Putty/sshecc.c" + +$(IntermediateDirectory)/Putty_sshecc.c$(PreprocessSuffix): libs/Putty/sshecc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_sshecc.c$(PreprocessSuffix) "libs/Putty/sshecc.c" + +$(IntermediateDirectory)/Putty_ldisc.c$(ObjectSuffix): libs/Putty/ldisc.c $(IntermediateDirectory)/Putty_ldisc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/ldisc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_ldisc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_ldisc.c$(DependSuffix): libs/Putty/ldisc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_ldisc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_ldisc.c$(DependSuffix) -MM "libs/Putty/ldisc.c" + +$(IntermediateDirectory)/Putty_ldisc.c$(PreprocessSuffix): libs/Putty/ldisc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_ldisc.c$(PreprocessSuffix) "libs/Putty/ldisc.c" + +$(IntermediateDirectory)/Putty_noterm.c$(ObjectSuffix): libs/Putty/noterm.c $(IntermediateDirectory)/Putty_noterm.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/noterm.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/Putty_noterm.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/Putty_noterm.c$(DependSuffix): libs/Putty/noterm.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/Putty_noterm.c$(ObjectSuffix) -MF$(IntermediateDirectory)/Putty_noterm.c$(DependSuffix) -MM "libs/Putty/noterm.c" + +$(IntermediateDirectory)/Putty_noterm.c$(PreprocessSuffix): libs/Putty/noterm.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/Putty_noterm.c$(PreprocessSuffix) "libs/Putty/noterm.c" + +$(IntermediateDirectory)/charset_fromucs.c$(ObjectSuffix): libs/Putty/charset/fromucs.c $(IntermediateDirectory)/charset_fromucs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/fromucs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_fromucs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_fromucs.c$(DependSuffix): libs/Putty/charset/fromucs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_fromucs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_fromucs.c$(DependSuffix) -MM "libs/Putty/charset/fromucs.c" + +$(IntermediateDirectory)/charset_fromucs.c$(PreprocessSuffix): libs/Putty/charset/fromucs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_fromucs.c$(PreprocessSuffix) "libs/Putty/charset/fromucs.c" + +$(IntermediateDirectory)/charset_localenc.c$(ObjectSuffix): libs/Putty/charset/localenc.c $(IntermediateDirectory)/charset_localenc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/localenc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_localenc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_localenc.c$(DependSuffix): libs/Putty/charset/localenc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_localenc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_localenc.c$(DependSuffix) -MM "libs/Putty/charset/localenc.c" + +$(IntermediateDirectory)/charset_localenc.c$(PreprocessSuffix): libs/Putty/charset/localenc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_localenc.c$(PreprocessSuffix) "libs/Putty/charset/localenc.c" + +$(IntermediateDirectory)/charset_macenc.c$(ObjectSuffix): libs/Putty/charset/macenc.c $(IntermediateDirectory)/charset_macenc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/macenc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_macenc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_macenc.c$(DependSuffix): libs/Putty/charset/macenc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_macenc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_macenc.c$(DependSuffix) -MM "libs/Putty/charset/macenc.c" + +$(IntermediateDirectory)/charset_macenc.c$(PreprocessSuffix): libs/Putty/charset/macenc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_macenc.c$(PreprocessSuffix) "libs/Putty/charset/macenc.c" + +$(IntermediateDirectory)/charset_mimeenc.c$(ObjectSuffix): libs/Putty/charset/mimeenc.c $(IntermediateDirectory)/charset_mimeenc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/mimeenc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_mimeenc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_mimeenc.c$(DependSuffix): libs/Putty/charset/mimeenc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_mimeenc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_mimeenc.c$(DependSuffix) -MM "libs/Putty/charset/mimeenc.c" + +$(IntermediateDirectory)/charset_mimeenc.c$(PreprocessSuffix): libs/Putty/charset/mimeenc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_mimeenc.c$(PreprocessSuffix) "libs/Putty/charset/mimeenc.c" + +$(IntermediateDirectory)/charset_sbcs.c$(ObjectSuffix): libs/Putty/charset/sbcs.c $(IntermediateDirectory)/charset_sbcs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/sbcs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_sbcs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_sbcs.c$(DependSuffix): libs/Putty/charset/sbcs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_sbcs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_sbcs.c$(DependSuffix) -MM "libs/Putty/charset/sbcs.c" + +$(IntermediateDirectory)/charset_sbcs.c$(PreprocessSuffix): libs/Putty/charset/sbcs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_sbcs.c$(PreprocessSuffix) "libs/Putty/charset/sbcs.c" + +$(IntermediateDirectory)/charset_sbcsdat.c$(ObjectSuffix): libs/Putty/charset/sbcsdat.c $(IntermediateDirectory)/charset_sbcsdat.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/sbcsdat.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_sbcsdat.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_sbcsdat.c$(DependSuffix): libs/Putty/charset/sbcsdat.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_sbcsdat.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_sbcsdat.c$(DependSuffix) -MM "libs/Putty/charset/sbcsdat.c" + +$(IntermediateDirectory)/charset_sbcsdat.c$(PreprocessSuffix): libs/Putty/charset/sbcsdat.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_sbcsdat.c$(PreprocessSuffix) "libs/Putty/charset/sbcsdat.c" + +$(IntermediateDirectory)/charset_slookup.c$(ObjectSuffix): libs/Putty/charset/slookup.c $(IntermediateDirectory)/charset_slookup.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/slookup.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_slookup.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_slookup.c$(DependSuffix): libs/Putty/charset/slookup.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_slookup.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_slookup.c$(DependSuffix) -MM "libs/Putty/charset/slookup.c" + +$(IntermediateDirectory)/charset_slookup.c$(PreprocessSuffix): libs/Putty/charset/slookup.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_slookup.c$(PreprocessSuffix) "libs/Putty/charset/slookup.c" + +$(IntermediateDirectory)/charset_toucs.c$(ObjectSuffix): libs/Putty/charset/toucs.c $(IntermediateDirectory)/charset_toucs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/toucs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_toucs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_toucs.c$(DependSuffix): libs/Putty/charset/toucs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_toucs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_toucs.c$(DependSuffix) -MM "libs/Putty/charset/toucs.c" + +$(IntermediateDirectory)/charset_toucs.c$(PreprocessSuffix): libs/Putty/charset/toucs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_toucs.c$(PreprocessSuffix) "libs/Putty/charset/toucs.c" + +$(IntermediateDirectory)/charset_utf8.c$(ObjectSuffix): libs/Putty/charset/utf8.c $(IntermediateDirectory)/charset_utf8.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/utf8.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_utf8.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_utf8.c$(DependSuffix): libs/Putty/charset/utf8.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_utf8.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_utf8.c$(DependSuffix) -MM "libs/Putty/charset/utf8.c" + +$(IntermediateDirectory)/charset_utf8.c$(PreprocessSuffix): libs/Putty/charset/utf8.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_utf8.c$(PreprocessSuffix) "libs/Putty/charset/utf8.c" + +$(IntermediateDirectory)/charset_xenc.c$(ObjectSuffix): libs/Putty/charset/xenc.c $(IntermediateDirectory)/charset_xenc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/charset/xenc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/charset_xenc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/charset_xenc.c$(DependSuffix): libs/Putty/charset/xenc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/charset_xenc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/charset_xenc.c$(DependSuffix) -MM "libs/Putty/charset/xenc.c" + +$(IntermediateDirectory)/charset_xenc.c$(PreprocessSuffix): libs/Putty/charset/xenc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/charset_xenc.c$(PreprocessSuffix) "libs/Putty/charset/xenc.c" + +$(IntermediateDirectory)/unix_uxgen.c$(ObjectSuffix): libs/Putty/unix/uxgen.c $(IntermediateDirectory)/unix_uxgen.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxgen.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxgen.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxgen.c$(DependSuffix): libs/Putty/unix/uxgen.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxgen.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxgen.c$(DependSuffix) -MM "libs/Putty/unix/uxgen.c" + +$(IntermediateDirectory)/unix_uxgen.c$(PreprocessSuffix): libs/Putty/unix/uxgen.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxgen.c$(PreprocessSuffix) "libs/Putty/unix/uxgen.c" + +$(IntermediateDirectory)/unix_uxgss.c$(ObjectSuffix): libs/Putty/unix/uxgss.c $(IntermediateDirectory)/unix_uxgss.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxgss.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxgss.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxgss.c$(DependSuffix): libs/Putty/unix/uxgss.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxgss.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxgss.c$(DependSuffix) -MM "libs/Putty/unix/uxgss.c" + +$(IntermediateDirectory)/unix_uxgss.c$(PreprocessSuffix): libs/Putty/unix/uxgss.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxgss.c$(PreprocessSuffix) "libs/Putty/unix/uxgss.c" + +$(IntermediateDirectory)/unix_uxmisc.c$(ObjectSuffix): libs/Putty/unix/uxmisc.c $(IntermediateDirectory)/unix_uxmisc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxmisc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxmisc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxmisc.c$(DependSuffix): libs/Putty/unix/uxmisc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxmisc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxmisc.c$(DependSuffix) -MM "libs/Putty/unix/uxmisc.c" + +$(IntermediateDirectory)/unix_uxmisc.c$(PreprocessSuffix): libs/Putty/unix/uxmisc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxmisc.c$(PreprocessSuffix) "libs/Putty/unix/uxmisc.c" + +$(IntermediateDirectory)/unix_uxnet.c$(ObjectSuffix): libs/Putty/unix/uxnet.c $(IntermediateDirectory)/unix_uxnet.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxnet.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxnet.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxnet.c$(DependSuffix): libs/Putty/unix/uxnet.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxnet.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxnet.c$(DependSuffix) -MM "libs/Putty/unix/uxnet.c" + +$(IntermediateDirectory)/unix_uxnet.c$(PreprocessSuffix): libs/Putty/unix/uxnet.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxnet.c$(PreprocessSuffix) "libs/Putty/unix/uxnet.c" + +$(IntermediateDirectory)/unix_uxnoise.c$(ObjectSuffix): libs/Putty/unix/uxnoise.c $(IntermediateDirectory)/unix_uxnoise.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxnoise.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxnoise.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxnoise.c$(DependSuffix): libs/Putty/unix/uxnoise.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxnoise.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxnoise.c$(DependSuffix) -MM "libs/Putty/unix/uxnoise.c" + +$(IntermediateDirectory)/unix_uxnoise.c$(PreprocessSuffix): libs/Putty/unix/uxnoise.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxnoise.c$(PreprocessSuffix) "libs/Putty/unix/uxnoise.c" + +$(IntermediateDirectory)/unix_uxpeer.c$(ObjectSuffix): libs/Putty/unix/uxpeer.c $(IntermediateDirectory)/unix_uxpeer.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxpeer.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxpeer.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxpeer.c$(DependSuffix): libs/Putty/unix/uxpeer.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxpeer.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxpeer.c$(DependSuffix) -MM "libs/Putty/unix/uxpeer.c" + +$(IntermediateDirectory)/unix_uxpeer.c$(PreprocessSuffix): libs/Putty/unix/uxpeer.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxpeer.c$(PreprocessSuffix) "libs/Putty/unix/uxpeer.c" + +$(IntermediateDirectory)/unix_uxprint.c$(ObjectSuffix): libs/Putty/unix/uxprint.c $(IntermediateDirectory)/unix_uxprint.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxprint.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxprint.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxprint.c$(DependSuffix): libs/Putty/unix/uxprint.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxprint.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxprint.c$(DependSuffix) -MM "libs/Putty/unix/uxprint.c" + +$(IntermediateDirectory)/unix_uxprint.c$(PreprocessSuffix): libs/Putty/unix/uxprint.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxprint.c$(PreprocessSuffix) "libs/Putty/unix/uxprint.c" + +$(IntermediateDirectory)/unix_uxproxy.c$(ObjectSuffix): libs/Putty/unix/uxproxy.c $(IntermediateDirectory)/unix_uxproxy.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxproxy.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxproxy.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxproxy.c$(DependSuffix): libs/Putty/unix/uxproxy.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxproxy.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxproxy.c$(DependSuffix) -MM "libs/Putty/unix/uxproxy.c" + +$(IntermediateDirectory)/unix_uxproxy.c$(PreprocessSuffix): libs/Putty/unix/uxproxy.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxproxy.c$(PreprocessSuffix) "libs/Putty/unix/uxproxy.c" + +$(IntermediateDirectory)/unix_uxser.c$(ObjectSuffix): libs/Putty/unix/uxser.c $(IntermediateDirectory)/unix_uxser.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxser.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxser.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxser.c$(DependSuffix): libs/Putty/unix/uxser.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxser.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxser.c$(DependSuffix) -MM "libs/Putty/unix/uxser.c" + +$(IntermediateDirectory)/unix_uxser.c$(PreprocessSuffix): libs/Putty/unix/uxser.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxser.c$(PreprocessSuffix) "libs/Putty/unix/uxser.c" + +$(IntermediateDirectory)/unix_uxsignal.c$(ObjectSuffix): libs/Putty/unix/uxsignal.c $(IntermediateDirectory)/unix_uxsignal.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxsignal.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxsignal.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxsignal.c$(DependSuffix): libs/Putty/unix/uxsignal.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxsignal.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxsignal.c$(DependSuffix) -MM "libs/Putty/unix/uxsignal.c" + +$(IntermediateDirectory)/unix_uxsignal.c$(PreprocessSuffix): libs/Putty/unix/uxsignal.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxsignal.c$(PreprocessSuffix) "libs/Putty/unix/uxsignal.c" + +$(IntermediateDirectory)/unix_uxstore.c$(ObjectSuffix): libs/Putty/unix/uxstore.c $(IntermediateDirectory)/unix_uxstore.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxstore.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxstore.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxstore.c$(DependSuffix): libs/Putty/unix/uxstore.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxstore.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxstore.c$(DependSuffix) -MM "libs/Putty/unix/uxstore.c" + +$(IntermediateDirectory)/unix_uxstore.c$(PreprocessSuffix): libs/Putty/unix/uxstore.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxstore.c$(PreprocessSuffix) "libs/Putty/unix/uxstore.c" + +$(IntermediateDirectory)/unix_uxucs.c$(ObjectSuffix): libs/Putty/unix/uxucs.c $(IntermediateDirectory)/unix_uxucs.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxucs.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxucs.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxucs.c$(DependSuffix): libs/Putty/unix/uxucs.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxucs.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxucs.c$(DependSuffix) -MM "libs/Putty/unix/uxucs.c" + +$(IntermediateDirectory)/unix_uxucs.c$(PreprocessSuffix): libs/Putty/unix/uxucs.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxucs.c$(PreprocessSuffix) "libs/Putty/unix/uxucs.c" + +$(IntermediateDirectory)/unix_xkeysym.c$(ObjectSuffix): libs/Putty/unix/xkeysym.c $(IntermediateDirectory)/unix_xkeysym.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/xkeysym.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_xkeysym.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_xkeysym.c$(DependSuffix): libs/Putty/unix/xkeysym.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_xkeysym.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_xkeysym.c$(DependSuffix) -MM "libs/Putty/unix/xkeysym.c" + +$(IntermediateDirectory)/unix_xkeysym.c$(PreprocessSuffix): libs/Putty/unix/xkeysym.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_xkeysym.c$(PreprocessSuffix) "libs/Putty/unix/xkeysym.c" + +$(IntermediateDirectory)/unix_xpmpucfg.c$(ObjectSuffix): libs/Putty/unix/xpmpucfg.c $(IntermediateDirectory)/unix_xpmpucfg.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/xpmpucfg.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_xpmpucfg.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_xpmpucfg.c$(DependSuffix): libs/Putty/unix/xpmpucfg.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_xpmpucfg.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_xpmpucfg.c$(DependSuffix) -MM "libs/Putty/unix/xpmpucfg.c" + +$(IntermediateDirectory)/unix_xpmpucfg.c$(PreprocessSuffix): libs/Putty/unix/xpmpucfg.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_xpmpucfg.c$(PreprocessSuffix) "libs/Putty/unix/xpmpucfg.c" + +$(IntermediateDirectory)/unix_xpmputty.c$(ObjectSuffix): libs/Putty/unix/xpmputty.c $(IntermediateDirectory)/unix_xpmputty.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/xpmputty.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_xpmputty.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_xpmputty.c$(DependSuffix): libs/Putty/unix/xpmputty.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_xpmputty.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_xpmputty.c$(DependSuffix) -MM "libs/Putty/unix/xpmputty.c" + +$(IntermediateDirectory)/unix_xpmputty.c$(PreprocessSuffix): libs/Putty/unix/xpmputty.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_xpmputty.c$(PreprocessSuffix) "libs/Putty/unix/xpmputty.c" + +$(IntermediateDirectory)/unix_uxagentc.c$(ObjectSuffix): libs/Putty/unix/uxagentc.c $(IntermediateDirectory)/unix_uxagentc.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxagentc.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxagentc.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxagentc.c$(DependSuffix): libs/Putty/unix/uxagentc.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxagentc.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxagentc.c$(DependSuffix) -MM "libs/Putty/unix/uxagentc.c" + +$(IntermediateDirectory)/unix_uxagentc.c$(PreprocessSuffix): libs/Putty/unix/uxagentc.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxagentc.c$(PreprocessSuffix) "libs/Putty/unix/uxagentc.c" + +$(IntermediateDirectory)/unix_uxsel.c$(ObjectSuffix): libs/Putty/unix/uxsel.c $(IntermediateDirectory)/unix_uxsel.c$(DependSuffix) + $(CC) $(SourceSwitch) "/home/lion/github/far2l/netbox/libs/Putty/unix/uxsel.c" $(CFLAGS) $(ObjectSwitch)$(IntermediateDirectory)/unix_uxsel.c$(ObjectSuffix) $(IncludePath) +$(IntermediateDirectory)/unix_uxsel.c$(DependSuffix): libs/Putty/unix/uxsel.c + @$(CC) $(CFLAGS) $(IncludePath) -MG -MP -MT$(IntermediateDirectory)/unix_uxsel.c$(ObjectSuffix) -MF$(IntermediateDirectory)/unix_uxsel.c$(DependSuffix) -MM "libs/Putty/unix/uxsel.c" + +$(IntermediateDirectory)/unix_uxsel.c$(PreprocessSuffix): libs/Putty/unix/uxsel.c + $(CC) $(CFLAGS) $(IncludePath) $(PreprocessOnlySwitch) $(OutputSwitch) $(IntermediateDirectory)/unix_uxsel.c$(PreprocessSuffix) "libs/Putty/unix/uxsel.c" + + +-include $(IntermediateDirectory)/*$(DependSuffix) +## +## Clean +## +clean: + $(RM) -r ./Debug/ + + diff --git a/netbox/netbox.project b/netbox/netbox.project new file mode 100644 index 000000000..fe7ad7a49 --- /dev/null +++ b/netbox/netbox.project @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mkdir -p ../Build/Plugins/netbox/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/netbox/release/compile_setup.bat b/netbox/release/compile_setup.bat new file mode 100644 index 000000000..dd14bc0ef --- /dev/null +++ b/netbox/release/compile_setup.bat @@ -0,0 +1,5 @@ +SET ISCC="C:\Program Files\Inno Setup 5\ISCC.exe" +if exist %ISCC% ( + %ISCC% /v3 netboxsetup.iss + if errorlevel 1 echo Error creating distributive & exit 1 /b +) diff --git a/netbox/release/licence.setup b/netbox/release/licence.setup new file mode 100644 index 000000000..ff74c0095 --- /dev/null +++ b/netbox/release/licence.setup @@ -0,0 +1,94 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + + GNU GENERAL PUBLIC LICENSE + + TERMS AND CONDITIONS FOR COPYING, + DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/netbox/release/netboxsetup.iss b/netbox/release/netboxsetup.iss new file mode 100644 index 000000000..8ff30152b --- /dev/null +++ b/netbox/release/netboxsetup.iss @@ -0,0 +1,364 @@ +#define YEAR 2013 +#define STATUS "release" +#ifndef ROOT_DIR +#define ROOT_DIR ".." +#endif +#define BUILD_DIR ROOT_DIR + "/build/NetBox/ +#ifndef SOURCE_DIR +#define SOURCE_DIR ROOT_DIR + "/src/NetBox" +#endif +#ifndef OUTPUT_DIR +#define OUTPUT_DIR ROOT_DIR + "/build" +#endif +#ifndef BINARIES_DIR_FAR2 +#define BINARIES_DIR_FAR2 BUILD_DIR + "/Far2" +#endif +#ifndef BINARIES_DIR_FAR3 +#define BINARIES_DIR_FAR3 BUILD_DIR + "/Far3" +#endif + +#ifndef PUTTY_SOURCE_DIR +#define PUTTY_SOURCE_DIR "C:/Program Files/Putty" +#endif + +#define FileSourceMain_Far2x86 BINARIES_DIR_FAR2 + "/x86/NetBox.dll" +#define FileSourceMain_Far2x64 BINARIES_DIR_FAR2 + "/x64/NetBox.dll" +#define FileSourceMain_Far3x86 BINARIES_DIR_FAR3 + "/x86/NetBox.dll" +#define FileSourceMain_Far3x64 BINARIES_DIR_FAR3 + "/x64/NetBox.dll" +#define FileSourceEng SOURCE_DIR + "/NetBoxEng.lng" +#define FileSourceRus SOURCE_DIR + "/NetBoxRus.lng" +#define FileSourceChangeLog ROOT_DIR + "/ChangeLog" +#define FileSourceReadmeEng ROOT_DIR + "/README.md" +#define FileSourceReadmeRu ROOT_DIR + "/README.RU.md" +#define FileSourceLicense ROOT_DIR + "/LICENSE.txt" +#define PluginSubDirName "NetBox" + +#define Major +#define Minor +#define Rev +#define Build +#expr ParseVersion(FileSourceMain_Far2x86, Major, Minor, Rev, Build) +#define Version Str(Major) + "." + Str(Minor) + (Rev > 0 ? "." + Str(Rev) : "") + \ + (STATUS != "" ? " " + STATUS : "") + +[Setup] +AppId=netbox +AppMutex=NetBox +AppName=NetBox plugin for Far2/Far3 +AppPublisher=Michael Lukashov +AppPublisherURL=https://github.com/michaellukashov/Far-NetBox +AppSupportURL=http://forum.farmanager.com/viewtopic.php?f=39&t=6638 +AppUpdatesURL=http://plugring.farmanager.com/plugin.php?pid=859&l=en +VersionInfoCompany=Michael Lukashov +VersionInfoDescription=Setup for NetBox plugin for Far2/Far3 {#Version} +VersionInfoVersion={#Major}.{#Minor}.{#Rev}.{#Build} +VersionInfoTextVersion={#Version} +VersionInfoCopyright=(c) 2011-{#YEAR} Michael Lukashov +DefaultDirName={pf}\Far Manager\Plugins\{#PluginSubDirName} +UsePreviousAppDir=false +DisableProgramGroupPage=true +LicenseFile=licence.setup +; UninstallDisplayIcon={app}\winscp.ico +OutputDir={#OUTPUT_DIR} +DisableStartupPrompt=yes +AppVersion={#Version} +AppVerName=NetBox plugin for Far2/Far3 {#Version} +OutputBaseFilename=FarNetBox-{#Major}.{#Minor}.{#Rev}_Far2_Far3_x86_x64 +Compression=lzma2/ultra +SolidCompression=yes +PrivilegesRequired=none +Uninstallable=no +MinVersion=5.1 +DisableDirPage=yes +; AlwaysShowDirOnReadyPage=yes + +ArchitecturesInstallIn64BitMode=x64 + +[Types] +Name: full; Description: "Full installation" +; Name: compact; Description: "Compact installation" +Name: custom; Description: "Custom installation"; Flags: iscustom +; Languages: en ru + +[Components] +Name: main_far2_x86; Description: "NetBox for Far2/x86"; Types: full custom; check: IsFar2X86Installed +Name: main_far2_x64; Description: "NetBox for Far2/x64"; Types: full custom; check: IsWin64 and IsFar3X64Installed +Name: main_far3_x86; Description: "NetBox for Far3/x86"; Types: full custom; check: IsFar3X86Installed +Name: main_far3_x64; Description: "NetBox for Far3/x64"; Types: full custom; check: IsWin64 and IsFar3X64Installed +; Name: pageant; Description: "Pageant (SSH authentication agent)"; Types: full +; Name: puttygen; Description: "PuTTYgen (key generator)"; Types: full + +[Files] +Source: "{#FileSourceMain_Far2x86}"; DestName: "NetBox.dll"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceMain_Far2x64}"; DestName: "NetBox.dll"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceMain_Far3x86}"; DestName: "NetBox.dll"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceMain_Far3x64}"; DestName: "NetBox.dll"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceEng}"; DestName: "NetBoxEng.lng"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceEng}"; DestName: "NetBoxEng.lng"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceEng}"; DestName: "NetBoxEng.lng"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceEng}"; DestName: "NetBoxEng.lng"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceRus}"; DestName: "NetBoxRus.lng"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceRus}"; DestName: "NetBoxRus.lng"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceRus}"; DestName: "NetBoxRus.lng"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceRus}"; DestName: "NetBoxRus.lng"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceChangeLog}"; DestName: "ChangeLog"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceChangeLog}"; DestName: "ChangeLog"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceChangeLog}"; DestName: "ChangeLog"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceChangeLog}"; DestName: "ChangeLog"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceReadmeEng}"; DestName: "README.md"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceReadmeEng}"; DestName: "README.md"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceReadmeEng}"; DestName: "README.md"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceReadmeEng}"; DestName: "README.md"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceReadmeRu}"; DestName: "README.RU.md"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceReadmeRu}"; DestName: "README.RU.md"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceReadmeRu}"; DestName: "README.RU.md"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceReadmeRu}"; DestName: "README.RU.md"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +Source: "{#FileSourceLicense}"; DestName: "LICENSE.txt"; DestDir: "{code:GetPlugin2X86Dir}"; Components: main_far2_x86; Flags: ignoreversion +Source: "{#FileSourceLicense}"; DestName: "LICENSE.txt"; DestDir: "{code:GetPlugin2X64Dir}"; Components: main_far2_x64; Flags: ignoreversion +Source: "{#FileSourceLicense}"; DestName: "LICENSE.txt"; DestDir: "{code:GetPlugin3X86Dir}"; Components: main_far3_x86; Flags: ignoreversion +Source: "{#FileSourceLicense}"; DestName: "LICENSE.txt"; DestDir: "{code:GetPlugin3X64Dir}"; Components: main_far3_x64; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\LICENCE"; DestDir: "{code:GetPluginX86Dir}\PuTTY"; Components: main_x86 pageant puttygen; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\LICENCE"; DestDir: "{code:GetPluginX64Dir}\PuTTY"; Components: main_x64 pageant puttygen; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\putty.hlp"; DestDir: "{code:GetPluginX86Dir}\PuTTY"; Components: main_x86 pageant puttygen; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\putty.hlp"; DestDir: "{code:GetPluginX64Dir}\PuTTY"; Components: main_x64 pageant puttygen; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\pageant.exe"; DestDir: "{code:GetPluginX86Dir}\PuTTY"; Components: main_x86 pageant; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\pageant.exe"; DestDir: "{code:GetPluginX64Dir}\PuTTY"; Components: main_x64 pageant; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\puttygen.exe"; DestDir: "{code:GetPluginX86Dir}\PuTTY"; Components: main_x86 puttygen; Flags: ignoreversion +; Source: "{#PUTTY_SOURCE_DIR}\puttygen.exe"; DestDir: "{code:GetPluginX64Dir}\PuTTY"; Components: main_x64 puttygen; Flags: ignoreversion + +[InstallDelete] + +[Code] + +var + InputDirsPage: TInputDirWizardPage; + +function GetFar2X86InstallDir(): String; +var + InstallDir: String; +begin + if RegQueryStringValue(HKLM, 'Software\Far2', 'InstallDir', InstallDir) or + RegQueryStringValue(HKCU, 'Software\Far2', 'InstallDir', InstallDir) then + begin + Result := InstallDir; + end; +end; + +function GetFar2X64InstallDir(): String; +var + InstallDir: String; +begin + if RegQueryStringValue(HKCU, 'Software\Far2', 'InstallDir_x64', InstallDir) or + RegQueryStringValue(HKLM, 'Software\Far2', 'InstallDir_x64', InstallDir) then + begin + Result := InstallDir; + end; +end; + +function IsFar2X86Installed(): Boolean; +begin + Result := GetFar2X86InstallDir() <> ''; +end; + +function IsFar2X64Installed(): Boolean; +begin + Result := GetFar2X64InstallDir() <> ''; +end; + +function GetDefaultFar2X86Dir(): String; +var + InstallDir: String; +begin + InstallDir := GetFar2X86InstallDir(); + if InstallDir <> '' then + begin + Result := AddBackslash(InstallDir) + 'Plugins\{#PluginSubDirName}'; + end + else + begin + Result := ExpandConstant('{pf}\Far2\Plugins\{#PluginSubDirName}'); + end; +end; + +function GetDefaultFar2X64Dir(): String; +var + InstallDir: String; +begin + InstallDir := GetFar2X64InstallDir(); + if InstallDir <> '' then + begin + Result := AddBackslash(InstallDir) + 'Plugins\{#PluginSubDirName}'; + end + else + begin + Result := ExpandConstant('{pf}\Far2\Plugins\{#PluginSubDirName}'); + end; +end; + +function GetPlugin2X86Dir(Param: String): String; +begin + Result := InputDirsPage.Values[0]; +end; + +function GetPlugin2X64Dir(Param: String): String; +begin + Result := InputDirsPage.Values[1]; +end; + +function GetFar3X86InstallDir(): String; +var + InstallDir: String; +begin + if RegQueryStringValue(HKLM, 'Software\Far Manager', 'InstallDir', InstallDir) or + RegQueryStringValue(HKCU, 'Software\Far Manager', 'InstallDir', InstallDir) then + begin + Result := InstallDir; + end; +end; + +function GetFar3X64InstallDir(): String; +var + InstallDir: String; +begin + if RegQueryStringValue(HKLM, 'Software\Far Manager', 'InstallDir_x64', InstallDir) or + RegQueryStringValue(HKCU, 'Software\Far Manager', 'InstallDir_x64', InstallDir) then + begin + Result := InstallDir; + end; +end; + +function IsFar3X86Installed(): Boolean; +begin + Result := GetFar3X86InstallDir() <> ''; +end; + +function IsFar3X64Installed(): Boolean; +begin + Result := GetFar3X64InstallDir() <> ''; +end; + +function GetDefaultFar3X86Dir(): String; +var + InstallDir: String; +begin + InstallDir := GetFar3X86InstallDir(); + if InstallDir <> '' then + begin + Result := AddBackslash(InstallDir) + 'Plugins\{#PluginSubDirName}'; + end + else + begin + Result := ExpandConstant('{pf}\Far Manager\Plugins\{#PluginSubDirName}'); + end; +end; + +function GetDefaultFar3X64Dir(): String; +var + InstallDir: String; +begin + InstallDir := GetFar3X64InstallDir(); + if InstallDir <> '' then + begin + Result := AddBackslash(InstallDir) + 'Plugins\{#PluginSubDirName}'; + end + else + begin + Result := ExpandConstant('{pf}\Far Manager\Plugins\{#PluginSubDirName}'); + end; +end; + +function GetPlugin3X86Dir(Param: String): String; +begin + Result := InputDirsPage.Values[2]; +end; + +function GetPlugin3X64Dir(Param: String): String; +begin + Result := InputDirsPage.Values[3]; +end; + +procedure CreateTheWizardPage; +begin + // Input dirs + InputDirsPage := CreateInputDirPage(wpSelectComponents, + 'Select plugin location', 'Where plugin should be installed?', + 'Plugin will be installed in the following folder(s).'#13#10#13#10 + + 'To continue, click Next. If you would like to select a different folder, click Browse.', + False, 'Plugin folder'); + InputDirsPage.Add('Far2/x86 plugin location:'); + InputDirsPage.Values[0] := GetDefaultFar2X86Dir(); + InputDirsPage.Add('Far2/x64 plugin location:'); + InputDirsPage.Values[1] := GetDefaultFar2X64Dir(); + InputDirsPage.Add('Far3/x86 plugin location:'); + InputDirsPage.Values[2] := GetDefaultFar3X86Dir(); + InputDirsPage.Add('Far3/x64 plugin location:'); + InputDirsPage.Values[3] := GetDefaultFar3X64Dir(); +end; + +procedure SetupInputDirs(); +begin + InputDirsPage.Edits[0].Enabled := IsComponentSelected('main_far2_x86'); + InputDirsPage.Buttons[0].Enabled := IsComponentSelected('main_far2_x86'); + InputDirsPage.PromptLabels[0].Enabled := IsComponentSelected('main_far2_x86'); + + // InputDirsPage.Edits[1].Visible := IsWin64(); + // InputDirsPage.Buttons[1].Visible := IsWin64(); + // InputDirsPage.PromptLabels[1].Visible := IsWin64(); + + InputDirsPage.Edits[1].Enabled := IsComponentSelected('main_far2_x64'); + InputDirsPage.Buttons[1].Enabled := IsComponentSelected('main_far2_x64'); + InputDirsPage.PromptLabels[1].Enabled := IsComponentSelected('main_far2_x64'); + + InputDirsPage.Edits[2].Enabled := IsComponentSelected('main_far3_x86'); + InputDirsPage.Buttons[2].Enabled := IsComponentSelected('main_far3_x86'); + InputDirsPage.PromptLabels[2].Enabled := IsComponentSelected('main_far3_x86'); + + // InputDirsPage.Edits[3].Visible := IsWin64(); + // InputDirsPage.Buttons[3].Visible := IsWin64(); + // InputDirsPage.PromptLabels[3].Visible := IsWin64(); + + InputDirsPage.Edits[3].Enabled := IsComponentSelected('main_far3_x64'); + InputDirsPage.Buttons[3].Enabled := IsComponentSelected('main_far3_x64'); + InputDirsPage.PromptLabels[3].Enabled := IsComponentSelected('main_far3_x64'); +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +begin + if CurPageID = wpWelcome then + begin + // SetupComponents(); + end + else + if CurPageID = wpSelectComponents then + begin + SetupInputDirs(); + end + else + if CurPageID = InputDirsPage.ID then + begin + WizardForm.DirEdit.Text := InputDirsPage.Values[0]; + end; + Result := True; +end; + +function BackButtonClick(CurPageID: Integer): Boolean; +begin + // MsgBox('CurPageID: ' + IntToStr(CurPageID), mbInformation, mb_Ok); + if CurPageID = InputDirsPage.ID then + begin + // SetupComponents(); + end; + if CurPageID = wpReady then + begin + SetupInputDirs(); + end; + Result := True; +end; + +procedure InitializeWizard(); +begin + // Custom wizard page + CreateTheWizardPage; + + WizardForm.LicenseAcceptedRadio.Checked := True; +end; diff --git a/netbox/src/NetBox/CMakeLists.txt b/netbox/src/NetBox/CMakeLists.txt new file mode 100644 index 000000000..7c9eb0f11 --- /dev/null +++ b/netbox/src/NetBox/CMakeLists.txt @@ -0,0 +1,1475 @@ +project(NetBox) +cmake_minimum_required(VERSION 2.8) + +if(NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Default build type to Debug" FORCE) +endif() +message("Build type -- ${CMAKE_BUILD_TYPE}") + +if(NOT DEFINED FAR_VERSION OR FAR_VERSION STREQUAL "") + set(FAR_VERSION Far2) +endif() + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(PROJECT_PLATFORM "x64") +else() + set(PROJECT_PLATFORM "x86") +endif() + +if(NOT DEFINED PROJECT_ROOT OR PROJECT_ROOT STREQUAL "") +set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..) +endif() +string(REPLACE "\\" "/" PROJECT_ROOT ${PROJECT_ROOT}) + +if(NOT DEFINED BUILD_DIR OR BUILD_DIR STREQUAL "") +string(REPLACE "/" "\\" BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() + +#------------------------------------------------------------------------------- + +include(${CMAKE_ROOT}/Modules/CMakeDetermineSystem.cmake) +include(CMakeDetermineCXXCompiler) +message("System: ${CMAKE_SYSTEM_NAME}, PROJECT_PLATFORM: ${PROJECT_PLATFORM}, BUILD_DIR: ${BUILD_DIR}, CMAKE_COMPILER_IS_MINGW = ${CMAKE_COMPILER_IS_MINGW}") +message("MSVC_VERSION: ${MSVC_VERSION}") + +set(LIBS_ROOT ${PROJECT_ROOT}/libs) +set(SRC_ROOT ${PROJECT_ROOT}/src) + +#------------------------------------------------------------------------------- + +set(LIBNEON_DEFS "-DNE_LFS -DNE_HAVE_SSL -DHAVE_OPENSSL -DHAVE_EXPAT -DHAVE_EXPAT_H -DNE_HAVE_DAV -DNE_HAVE_ZLIB") #" -DUSE_GETADDRINFO") +set(LIBEXPAT_DEFS "-DCOMPILED_FROM_DSP -DXML_STATIC") +set(DLMALLOC_DEFS "-DUSE_DLMALLOC -DUSE_DL_PREFIX -DNO_MALLINFO -DUSE_LOCKS=1") +set(NETBOX_C_FLAGS "-D_WINDOWS -DWIN32 -D_WIN32_WINNT=0x0501 -D_SCL_SECURE_NO_WARNINGS -DAPR_DECLARE_STATIC -DAPR_HAS_LARGE_FILES ${LIBEXPAT_DEFS} ${LIBNEON_DEFS} -DMPEXT -DWINSCP -DSTRICT -DNOCRYPT ${DLMALLOC_DEFS}") +#-D_WINDLL -D_USRDLL +set(NETBOX_C_FLAGS "-DCOM_NO_WINDOWS_H ${NETBOX_C_FLAGS}") +if (MSVC_VERSION GREATER 1899) +set(NETBOX_C_FLAGS "${NETBOX_C_FLAGS} /Zc:threadSafeInit- /bigobj") +endif() + +if(PROJECT_PLATFORM STREQUAL "x64") +set(NETBOX_C_FLAGS "${NETBOX_C_FLAGS} -DWIN64") +else() +if (MSVC_VERSION GREATER 1699) +set(NETBOX_C_FLAGS "${NETBOX_C_FLAGS} /arch:IA32") +endif() +endif() + +set(NETBOX_FLAGS_RELEASE "-U_DEBUG -DNDEBUG") +set(NETBOX_FLAGS_DEBUG "-D_DEBUG -DDEBUG") + +if(MSVC) + +set(NETBOX_WARNING_FLAGS "") +set(NETBOX_CXX_FLAGS "${NETBOX_C_FLAGS} /GR- /EHsc /MP2 /Zi -D_UNICODE -DUNICODE ") +set(NETBOX_FLAGS_RELEASE "${NETBOX_FLAGS_RELEASE} /Gm- /MT -Ox -Ob1 -Oi -Os -GF -GS- -Gy /fp:fast") +set(NETBOX_FLAGS_DEBUG "${NETBOX_FLAGS_DEBUG} /Gm- /Od /MTd /GS /RTC1") + +elseif(CMAKE_COMPILER_IS_MINGW) + +set(NETBOX_CFLAGS "${NETBOX_CFLAGS} ${NETBOX_C_FLAGS} -static -s -O2 -Wall -Wextra -pedantic -Wold-style-cast -Wconversion -Wsign-conversion -Winit-self -Wunreachable-code") +set(NETBOX_WARNING_FLAGS "-Wno-attributes -Wno-write-strings -Wno-unknown-pragmas") +set(NETBOX_CXX_FLAGS "${NETBOX_CFLAGS} -D_UNICODE -DUNICODE -std=c++11 -Weffc++ -Woverloaded-virtual -Wnon-virtual-dtor") +set(NETBOX_FLAGS_RELEASE "${NETBOX_FLAGS_RELEASE} ${NETBOX_CFLAGS}") +set(NETBOX_FLAGS_DEBUG "${NETBOX_FLAGS_DEBUG} ${NETBOX_CFLAGS}") + +endif() + +set(CMAKE_C_FLAGS "${NETBOX_C_FLAGS} ${NETBOX_WARNING_FLAGS}") +set(CMAKE_CXX_FLAGS "${NETBOX_CXX_FLAGS} ${NETBOX_WARNING_FLAGS}") + +set(CMAKE_CXX_FLAGS_RELEASE "${NETBOX_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_RELEASE "${NETBOX_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_DEBUG "${NETBOX_FLAGS_DEBUG}") +set(CMAKE_C_FLAGS_DEBUG "${NETBOX_FLAGS_DEBUG}") + +if(MSVC) + +set(LINK_FLAGS_RELEASE + "" +) + +set(CMAKE_LINK_FLAGS + "/DEBUG + /NODEFAULTLIB:MSVCURT.LIB + /NODEFAULTLIB:MSVCURTD.LIB + /NODEFAULTLIB:MSVCPRT.LIB + /NODEFAULTLIB:MSVCPRTD.LIB + /NODEFAULTLIB:LIBC.LIB + /NODEFAULTLIB:LIBCMT.LIB + /NODEFAULTLIB:LIBCMTD.LIB + /NODEFAULTLIB:MFC100U.LIB + /NODEFAULTLIB:MFC100UD.LIB + /NODEFAULTLIB:ATL.LIB + /NODEFAULTLIB:LIBCRT.LIB + /NODEFAULTLIB:LIBCRTD.LIB + /NODEFAULTLIB:MSVCRT.LIB + /NODEFAULTLIB:MSVCRTD.LIB + /NODEFAULTLIB:MFCS100U.LIB + /NODEFAULTLIB:MFCS100UD.LIB + /NODEFAULTLIB:UAFXCW.LIB + /NODEFAULTLIB:UAFXCWD.LIB + /NODEFAULTLIB:LIBCPMT.LIB + /NODEFAULTLIB:LIBCPMTD.LIB + /NODEFAULTLIB:LIBEAY32.LIB + /NODEFAULTLIB:SSLEAY32.LIB + /NODEFAULTLIB:LIBPUTTY.LIB + /NODEFAULTLIB:KERNEL32.LIB + /NODEFAULTLIB:USER32.LIB + /NODEFAULTLIB:VERSION.LIB + /DELAYLOAD:version.dll + /DELAYLOAD:ws2_32.dll + /DELAYLOAD:oleaut32.dll + /DELAYLOAD:winhttp.dll + /DELAYLOAD:shell32.dll + /DELAYLOAD:shlwapi.dll + /DELAYLOAD:crypt32.dll + /DELAYLOAD:gdi32.dll + /MANIFEST:NO + /TLBID:1 /DYNAMICBASE /SUBSYSTEM:WINDOWS /NOLOGO + /MAP + /INCREMENTAL:NO +" +) + +elseif(CMAKE_COMPILER_IS_MINGW) +set(CMAKE_LINK_FLAGS "") +set(LINK_FLAGS_RELEASE "") +endif() + +#------------------------------------------------------- + +include_directories( + ${SRC_ROOT}/NetBox + ${SRC_ROOT}/PluginSDK/${FAR_VERSION} + ${SRC_ROOT}/include + ${SRC_ROOT}/base + ${SRC_ROOT}/core + ${SRC_ROOT}/windows + ${SRC_ROOT}/resource + ${SRC_ROOT}/Common + ${SRC_ROOT}/filezilla + ${SRC_ROOT}/filezilla/misc + ${LIBS_ROOT} + ${LIBS_ROOT}/atlmfc/include + ${LIBS_ROOT}/Putty + ${LIBS_ROOT}/Putty/windows + ${LIBS_ROOT}/tinyxml2 + ${LIBS_ROOT}/zlib/src + ${LIBS_ROOT}/expat/lib + ${LIBS_ROOT}/apr/include + ${LIBS_ROOT}/apr/include/arch/win32 + ${LIBS_ROOT}/apr/include/arch/unix + ${LIBS_ROOT}/rdestl + + ${LIBS_ROOT}/openssl/${PROJECT_PLATFORM}/inc32 + ${LIBS_ROOT}/openssl + ${LIBS_ROOT}/openssl/crypto + ${LIBS_ROOT}/openssl/crypto/asn1 + ${LIBS_ROOT}/openssl/crypto/modes + ${LIBS_ROOT}/openssl/crypto/evp +) + +#------------------------------------------------------------------------------- +# target NetBox + +set(NETBOX_SOURCES + ${SRC_ROOT}/NetBox/UnityBuildCore.cpp + ${SRC_ROOT}/NetBox/UnityBuildMain.cpp + ${SRC_ROOT}/NetBox/UnityBuildFilezilla.cpp + ${SRC_ROOT}/resource/TextsFileZilla.rc + ${SRC_ROOT}/NetBox/NetBox.rc + ${SRC_ROOT}/NetBox/NetBox.def +) + +add_custom_target(NETBOX_SOURCES SOURCES + NetBoxEng.lng + NetBoxRus.lng + resource.h.template + NetBox.rc.template +) + +if(MSVC) +if(PROJECT_PLATFORM STREQUAL "x86") + +find_program(MASM_EXECUTABLE ml) +# message("masm: ${MASM_EXECUTABLE}") +set(ASM_OBJECTS) +foreach(src vc_crt_fix) + set(ASM_SOURCE ${SRC_ROOT}/NetBox/${src}.asm) + set(ASM_OBJECT ${CMAKE_CURRENT_BINARY_DIR}/${src}.obj) + set(ASM_OBJECTS ${ASM_OBJECTS} ${ASM_OBJECT}) + add_custom_command( + OUTPUT ${ASM_OBJECT} + COMMAND ${MASM_EXECUTABLE} + ARGS /c /Fo ${ASM_OBJECT} ${ASM_SOURCE} + DEPENDS ${ASM_SOURCE} + ) +endforeach(src) + +set(NETBOX_SOURCES ${NETBOX_SOURCES} + ${SRC_ROOT}/NetBox/vc_crt_fix_ulink.cpp + ${SRC_ROOT}/NetBox/vc_crt_fix_impl.cpp + ${ASM_OBJECTS} +) + +endif(PROJECT_PLATFORM STREQUAL "x86") +endif(MSVC) + +#------------------------------------------------------------------------------- + +set(NETBOX_LIBRARIES + atlmfc + putty + zlib + tinyxml2 + openssl + neon + apr + expat + dlmalloc + rdestl + Ws2_32.lib + kernel32.lib + user32.lib + advapi32.lib + Version.lib + winhttp.lib + winspool.lib + Crypt32.lib + shell32.lib + shlwapi.lib + delayimp.lib +) + +if(MSVC) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") +set(NETBOX_LIBRARIES ${NETBOX_LIBRARIES} + libcmtd.lib + libcpmtd.lib +) +else() +set(NETBOX_LIBRARIES ${NETBOX_LIBRARIES} + libcmt.lib + libcpmt.lib +) +endif(CMAKE_BUILD_TYPE STREQUAL "Debug") +endif(MSVC) + +add_library(NetBox SHARED + ${NETBOX_SOURCES} +) + +if(MSVC) +set_target_properties(NetBox + PROPERTIES + COMPILE_FLAGS "${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) +elseif(CMAKE_COMPILER_IS_MINGW) +set_target_properties(NetBox + PROPERTIES + COMPILE_FLAGS "-fpermissive ${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) +endif(MSVC) + +#------------------------------------------------------------------------------- + +#TODO: COMMAND create_ver.py + +target_link_libraries(NetBox + ${NETBOX_LIBRARIES} +) + +add_dependencies(NetBox + atlmfc + putty + zlib + tinyxml2 + neon + apr + expat + dlmalloc + rdestl +) + +set(NETBOX_PLUGIN_DIR ${PROJECT_ROOT}/${FAR_VERSION}_${PROJECT_PLATFORM}/Plugins/NetBox) +string(REPLACE "/" "\\" NETBOX_PLUGIN_DIR ${NETBOX_PLUGIN_DIR}) + +add_custom_command(TARGET NetBox + POST_BUILD + COMMAND if not exist "${NETBOX_PLUGIN_DIR}" ( ${CMAKE_COMMAND} -E make_directory "${NETBOX_PLUGIN_DIR}" ) + COMMAND ${CMAKE_COMMAND} -E copy NetBox.dll ${NETBOX_PLUGIN_DIR} + COMMAND if exist NetBox.pdb ( ${CMAKE_COMMAND} -E copy NetBox.pdb ${NETBOX_PLUGIN_DIR} ) + COMMAND if exist NetBox.map ( ${CMAKE_COMMAND} -E copy NetBox.map ${NETBOX_PLUGIN_DIR} ) + WORKING_DIRECTORY ${BUILD_DIR} + VERBATIM +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") +add_custom_command(TARGET NetBox + POST_BUILD + DEPENDS ${BUILD_DIR}\\NetBox.dll + COMMAND scripts\\make_dist.cmd ${FAR_VERSION} ${PROJECT_PLATFORM} + WORKING_DIRECTORY ${SRC_ROOT}/NetBox + VERBATIM +) +endif() + +#------------------------------------------------------------------------------- +add_library(openssl STATIC + ${LIBS_ROOT}/openssl/ssl/t1_trce.c + ${LIBS_ROOT}/openssl/ssl/t1_reneg.c + ${LIBS_ROOT}/openssl/ssl/kssl.c + ${LIBS_ROOT}/openssl/ssl/ssl_err.c + ${LIBS_ROOT}/openssl/ssl/bio_ssl.c + ${LIBS_ROOT}/openssl/ssl/ssl_conf.c + ${LIBS_ROOT}/openssl/ssl/ssl_algs.c + ${LIBS_ROOT}/openssl/ssl/ssl_txt.c + ${LIBS_ROOT}/openssl/ssl/ssl_asn1.c + ${LIBS_ROOT}/openssl/ssl/ssl_rsa.c + ${LIBS_ROOT}/openssl/ssl/ssl_stat.c + ${LIBS_ROOT}/openssl/ssl/ssl_ciph.c + ${LIBS_ROOT}/openssl/ssl/ssl_sess.c + ${LIBS_ROOT}/openssl/ssl/ssl_cert.c + ${LIBS_ROOT}/openssl/ssl/ssl_err2.c + ${LIBS_ROOT}/openssl/ssl/ssl_lib.c + ${LIBS_ROOT}/openssl/ssl/d1_srtp.c + ${LIBS_ROOT}/openssl/ssl/d1_both.c + ${LIBS_ROOT}/openssl/ssl/d1_pkt.c + ${LIBS_ROOT}/openssl/ssl/d1_lib.c + ${LIBS_ROOT}/openssl/ssl/d1_clnt.c + ${LIBS_ROOT}/openssl/ssl/d1_srvr.c + ${LIBS_ROOT}/openssl/ssl/d1_meth.c + ${LIBS_ROOT}/openssl/ssl/t1_ext.c + ${LIBS_ROOT}/openssl/ssl/t1_enc.c + ${LIBS_ROOT}/openssl/ssl/t1_lib.c + ${LIBS_ROOT}/openssl/ssl/t1_clnt.c + ${LIBS_ROOT}/openssl/ssl/t1_srvr.c + ${LIBS_ROOT}/openssl/ssl/t1_meth.c + ${LIBS_ROOT}/openssl/ssl/s23_pkt.c + ${LIBS_ROOT}/openssl/ssl/s23_lib.c + ${LIBS_ROOT}/openssl/ssl/s23_clnt.c + ${LIBS_ROOT}/openssl/ssl/s23_srvr.c + ${LIBS_ROOT}/openssl/ssl/s23_meth.c + ${LIBS_ROOT}/openssl/ssl/s3_cbc.c + ${LIBS_ROOT}/openssl/ssl/s3_both.c + ${LIBS_ROOT}/openssl/ssl/s3_pkt.c + ${LIBS_ROOT}/openssl/ssl/s3_enc.c + ${LIBS_ROOT}/openssl/ssl/s3_lib.c + ${LIBS_ROOT}/openssl/ssl/s3_clnt.c + ${LIBS_ROOT}/openssl/ssl/s3_srvr.c + ${LIBS_ROOT}/openssl/ssl/s3_meth.c + ${LIBS_ROOT}/openssl/ssl/s2_pkt.c + ${LIBS_ROOT}/openssl/ssl/s2_enc.c + ${LIBS_ROOT}/openssl/ssl/s2_lib.c + ${LIBS_ROOT}/openssl/ssl/s2_clnt.c + ${LIBS_ROOT}/openssl/ssl/s2_srvr.c + ${LIBS_ROOT}/openssl/ssl/s2_meth.c + ${LIBS_ROOT}/openssl/engines/e_capi.c + ${LIBS_ROOT}/openssl/engines/e_padlock.c + ${LIBS_ROOT}/openssl/engines/e_ubsec.c + ${LIBS_ROOT}/openssl/engines/e_sureware.c + ${LIBS_ROOT}/openssl/engines/e_nuron.c + ${LIBS_ROOT}/openssl/engines/e_chil.c + ${LIBS_ROOT}/openssl/engines/e_gmp.c + ${LIBS_ROOT}/openssl/engines/e_cswift.c + ${LIBS_ROOT}/openssl/engines/e_atalla.c + ${LIBS_ROOT}/openssl/engines/e_aep.c + ${LIBS_ROOT}/openssl/engines/e_4758cca.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_asn1.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_conf.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_lib.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_verify_ctx.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_rsp_verify.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_rsp_sign.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_rsp_print.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_rsp_utils.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_req_print.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_req_utils.c + ${LIBS_ROOT}/openssl/crypto/ts/ts_err.c + ${LIBS_ROOT}/openssl/crypto/pqueue/pqueue.c + ${LIBS_ROOT}/openssl/crypto/krb5/krb5_asn.c + ${LIBS_ROOT}/openssl/crypto/ui/ui_compat.c + ${LIBS_ROOT}/openssl/crypto/ui/ui_util.c + ${LIBS_ROOT}/openssl/crypto/ui/ui_openssl.c + ${LIBS_ROOT}/openssl/crypto/ui/ui_lib.c + ${LIBS_ROOT}/openssl/crypto/ui/ui_err.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_err.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_vfy.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_prn.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_srv.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_cl.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_lib.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_ht.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_ext.c + ${LIBS_ROOT}/openssl/crypto/ocsp/ocsp_asn.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_rdrand.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_cryptodev.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_dyn.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_cnf.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_openssl.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_asnmth.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_pkmeth.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_digest.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_cipher.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_store.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_rand.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_ecdh.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_dh.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_ecdsa.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_dsa.c + ${LIBS_ROOT}/openssl/crypto/engine/tb_rsa.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_all.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_fat.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_pkey.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_table.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_ctrl.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_init.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_list.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_lib.c + ${LIBS_ROOT}/openssl/crypto/engine/eng_err.c + ${LIBS_ROOT}/openssl/crypto/comp/c_zlib.c + ${LIBS_ROOT}/openssl/crypto/comp/c_rle.c + ${LIBS_ROOT}/openssl/crypto/comp/comp_err.c + ${LIBS_ROOT}/openssl/crypto/comp/comp_lib.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_p8e.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_p8d.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/pk12err.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_npas.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_utl.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_mutl.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_kiss.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_key.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_init.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_decr.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_crt.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_crpt.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_attr.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_asn.c + ${LIBS_ROOT}/openssl/crypto/pkcs12/p12_add.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/bio_pk7.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_mime.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_attr.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_smime.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_doit.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pkcs7err.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_lib.c + ${LIBS_ROOT}/openssl/crypto/pkcs7/pk7_asn1.c + ${LIBS_ROOT}/openssl/crypto/txt_db/txt_db.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_sap.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_mall.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_mod.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_def.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_api.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_lib.c + ${LIBS_ROOT}/openssl/crypto/conf/conf_err.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_kari.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_pwri.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_ess.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_enc.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_env.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_cd.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_dd.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_sd.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_err.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_smime.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_io.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_att.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_asn1.c + ${LIBS_ROOT}/openssl/crypto/cms/cms_lib.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_scts.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_addr.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_asid.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_lib.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_tree.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_map.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_data.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_node.c + ${LIBS_ROOT}/openssl/crypto/x509v3/pcy_cache.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_pci.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_pcia.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_ncons.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_pcons.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_pmaps.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_akeya.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_ocsp.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_info.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_purp.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_crld.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_cpols.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_sxnet.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_enum.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_int.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_pku.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_akey.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_skey.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_alt.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_genn.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3err.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_utl.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_prn.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_lib.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_ia5.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_extku.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_conf.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_bitst.c + ${LIBS_ROOT}/openssl/crypto/x509v3/v3_bcons.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_vpm.c + ${LIBS_ROOT}/openssl/crypto/x509/by_dir.c + ${LIBS_ROOT}/openssl/crypto/x509/by_file.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_trs.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_txt.c + ${LIBS_ROOT}/openssl/crypto/x509/x_all.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_lu.c + ${LIBS_ROOT}/openssl/crypto/x509/x509type.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_att.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_ext.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_v3.c + ${LIBS_ROOT}/openssl/crypto/x509/x509name.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_err.c + ${LIBS_ROOT}/openssl/crypto/x509/x509rset.c + ${LIBS_ROOT}/openssl/crypto/x509/x509cset.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_set.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_vfy.c + ${LIBS_ROOT}/openssl/crypto/x509/x509spki.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_req.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_obj.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_cmp.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_r2x.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_d2.c + ${LIBS_ROOT}/openssl/crypto/x509/x509_def.c + ${LIBS_ROOT}/openssl/crypto/pem/pvkfmt.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_pkey.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_pk8.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_oth.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_xaux.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_x509.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_err.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_all.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_lib.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_info.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_seal.c + ${LIBS_ROOT}/openssl/crypto/pem/pem_sign.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn_moid.c + ${LIBS_ROOT}/openssl/crypto/asn1/p8_pkey.c + ${LIBS_ROOT}/openssl/crypto/asn1/p5_pbev2.c + ${LIBS_ROOT}/openssl/crypto/asn1/p5_pbe.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn_pack.c + ${LIBS_ROOT}/openssl/crypto/asn1/evp_asn1.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_strnid.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_bytes.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn1_err.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn1_lib.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn1_par.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn1_gen.c + ${LIBS_ROOT}/openssl/crypto/asn1/asn_mime.c + ${LIBS_ROOT}/openssl/crypto/asn1/bio_ndef.c + ${LIBS_ROOT}/openssl/crypto/asn1/bio_asn1.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_exten.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_bool.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_pkey.c + ${LIBS_ROOT}/openssl/crypto/asn1/f_enum.c + ${LIBS_ROOT}/openssl/crypto/asn1/f_string.c + ${LIBS_ROOT}/openssl/crypto/asn1/f_int.c + ${LIBS_ROOT}/openssl/crypto/asn1/ameth_lib.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_prn.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_typ.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_utl.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_dec.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_enc.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_fre.c + ${LIBS_ROOT}/openssl/crypto/asn1/tasn_new.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_bitst.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_spki.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_pkey.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_crl.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_x509a.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_x509.c + ${LIBS_ROOT}/openssl/crypto/asn1/t_req.c + ${LIBS_ROOT}/openssl/crypto/asn1/i2d_pr.c + ${LIBS_ROOT}/openssl/crypto/asn1/i2d_pu.c + ${LIBS_ROOT}/openssl/crypto/asn1/d2i_pr.c + ${LIBS_ROOT}/openssl/crypto/asn1/d2i_pu.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_nx509.c + ${LIBS_ROOT}/openssl/crypto/asn1/nsseq.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_spki.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_info.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_crl.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_x509a.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_x509.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_name.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_long.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_bignum.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_attrib.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_req.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_sig.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_pubkey.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_val.c + ${LIBS_ROOT}/openssl/crypto/asn1/x_algor.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_strex.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_mbstr.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_verify.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_digest.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_sign.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_utf8.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_enum.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_i2d_fp.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_d2i_fp.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_dup.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_set.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_type.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_print.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_octet.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_int.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_time.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_gentm.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_utctm.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_bitstr.c + ${LIBS_ROOT}/openssl/crypto/asn1/a_object.c + ${LIBS_ROOT}/openssl/crypto/evp/e_rc4_hmac_md5.c + ${LIBS_ROOT}/openssl/crypto/evp/e_aes_cbc_hmac_sha256.c + ${LIBS_ROOT}/openssl/crypto/evp/e_aes_cbc_hmac_sha1.c + ${LIBS_ROOT}/openssl/crypto/evp/m_sigver.c + ${LIBS_ROOT}/openssl/crypto/evp/pmeth_gn.c + ${LIBS_ROOT}/openssl/crypto/evp/pmeth_fn.c + ${LIBS_ROOT}/openssl/crypto/evp/pmeth_lib.c + ${LIBS_ROOT}/openssl/crypto/evp/e_old.c + ${LIBS_ROOT}/openssl/crypto/evp/p5_crpt2.c + ${LIBS_ROOT}/openssl/crypto/evp/p5_crpt.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_pbe.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_pkey.c + ${LIBS_ROOT}/openssl/crypto/evp/bio_ok.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_lib.c + ${LIBS_ROOT}/openssl/crypto/evp/c_alld.c + ${LIBS_ROOT}/openssl/crypto/evp/c_allc.c + ${LIBS_ROOT}/openssl/crypto/evp/c_all.c + ${LIBS_ROOT}/openssl/crypto/evp/e_null.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_err.c + ${LIBS_ROOT}/openssl/crypto/evp/bio_enc.c + ${LIBS_ROOT}/openssl/crypto/evp/bio_b64.c + ${LIBS_ROOT}/openssl/crypto/evp/bio_md.c + ${LIBS_ROOT}/openssl/crypto/evp/p_dec.c + ${LIBS_ROOT}/openssl/crypto/evp/p_enc.c + ${LIBS_ROOT}/openssl/crypto/evp/p_lib.c + ${LIBS_ROOT}/openssl/crypto/evp/p_verify.c + ${LIBS_ROOT}/openssl/crypto/evp/p_sign.c + ${LIBS_ROOT}/openssl/crypto/evp/p_seal.c + ${LIBS_ROOT}/openssl/crypto/evp/p_open.c + ${LIBS_ROOT}/openssl/crypto/evp/m_ecdsa.c + ${LIBS_ROOT}/openssl/crypto/evp/m_mdc2.c + ${LIBS_ROOT}/openssl/crypto/evp/m_dss1.c + ${LIBS_ROOT}/openssl/crypto/evp/m_dss.c + ${LIBS_ROOT}/openssl/crypto/evp/m_wp.c + ${LIBS_ROOT}/openssl/crypto/evp/m_sha1.c + ${LIBS_ROOT}/openssl/crypto/evp/m_sha.c + ${LIBS_ROOT}/openssl/crypto/evp/m_md5.c + ${LIBS_ROOT}/openssl/crypto/evp/m_md4.c + ${LIBS_ROOT}/openssl/crypto/evp/m_null.c + ${LIBS_ROOT}/openssl/crypto/evp/e_rc5.c + ${LIBS_ROOT}/openssl/crypto/evp/e_cast.c + ${LIBS_ROOT}/openssl/crypto/evp/e_rc2.c + ${LIBS_ROOT}/openssl/crypto/evp/e_xcbc_d.c + ${LIBS_ROOT}/openssl/crypto/evp/names.c + ${LIBS_ROOT}/openssl/crypto/evp/e_aes.c + ${LIBS_ROOT}/openssl/crypto/evp/e_des3.c + ${LIBS_ROOT}/openssl/crypto/evp/e_idea.c + ${LIBS_ROOT}/openssl/crypto/evp/e_bf.c + ${LIBS_ROOT}/openssl/crypto/evp/e_des.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_cnf.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_acnf.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_key.c + ${LIBS_ROOT}/openssl/crypto/evp/evp_enc.c + ${LIBS_ROOT}/openssl/crypto/evp/digest.c + ${LIBS_ROOT}/openssl/crypto/evp/encode.c + ${LIBS_ROOT}/openssl/crypto/objects/obj_xref.c + ${LIBS_ROOT}/openssl/crypto/objects/obj_err.c + ${LIBS_ROOT}/openssl/crypto/objects/obj_lib.c + ${LIBS_ROOT}/openssl/crypto/objects/obj_dat.c + ${LIBS_ROOT}/openssl/crypto/objects/o_names.c + ${LIBS_ROOT}/openssl/crypto/err/err_prn.c + ${LIBS_ROOT}/openssl/crypto/err/err_all.c + ${LIBS_ROOT}/openssl/crypto/err/err.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_nw.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_os2.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_unix.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_win.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_egd.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_err.c + ${LIBS_ROOT}/openssl/crypto/rand/rand_lib.c + ${LIBS_ROOT}/openssl/crypto/rand/randfile.c + ${LIBS_ROOT}/openssl/crypto/rand/md_rand.c + ${LIBS_ROOT}/openssl/crypto/lhash/lh_stats.c + ${LIBS_ROOT}/openssl/crypto/lhash/lhash.c + ${LIBS_ROOT}/openssl/crypto/stack/stack.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_dgram.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_bio.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_log.c + ${LIBS_ROOT}/openssl/crypto/bio/bf_nbio.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_acpt.c + ${LIBS_ROOT}/openssl/crypto/bio/b_sock.c + ${LIBS_ROOT}/openssl/crypto/bio/b_dump.c + ${LIBS_ROOT}/openssl/crypto/bio/b_print.c + ${LIBS_ROOT}/openssl/crypto/bio/bf_buff.c + ${LIBS_ROOT}/openssl/crypto/bio/bf_null.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_conn.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_sock.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_file.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_fd.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_null.c + ${LIBS_ROOT}/openssl/crypto/bio/bss_mem.c + ${LIBS_ROOT}/openssl/crypto/bio/bio_err.c + ${LIBS_ROOT}/openssl/crypto/bio/bio_cb.c + ${LIBS_ROOT}/openssl/crypto/bio/bio_lib.c + ${LIBS_ROOT}/openssl/crypto/buffer/buf_err.c + ${LIBS_ROOT}/openssl/crypto/buffer/buf_str.c + ${LIBS_ROOT}/openssl/crypto/buffer/buffer.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_err.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_vrf.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_sign.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_ossl.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_asn1.c + ${LIBS_ROOT}/openssl/crypto/ecdsa/ecs_lib.c + ${LIBS_ROOT}/openssl/crypto/ecdh/ech_kdf.c + ${LIBS_ROOT}/openssl/crypto/ecdh/ech_err.c + ${LIBS_ROOT}/openssl/crypto/ecdh/ech_key.c + ${LIBS_ROOT}/openssl/crypto/ecdh/ech_ossl.c + ${LIBS_ROOT}/openssl/crypto/ecdh/ech_lib.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_oct.c + ${LIBS_ROOT}/openssl/crypto/ec/ec2_oct.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_oct.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_nistputil.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_nistp521.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_nistp256.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_nistp224.c + ${LIBS_ROOT}/openssl/crypto/ec/eck_prn.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_pmeth.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_ameth.c + ${LIBS_ROOT}/openssl/crypto/ec/ec2_mult.c + ${LIBS_ROOT}/openssl/crypto/ec/ec2_smpl.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_key.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_asn1.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_print.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_check.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_curve.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_err.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_mult.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_cvt.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_nist.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_mont.c + ${LIBS_ROOT}/openssl/crypto/ec/ecp_smpl.c + ${LIBS_ROOT}/openssl/crypto/ec/ec_lib.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_kdf.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_rfc5114.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_prn.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_pmeth.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_ameth.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_depr.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_err.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_check.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_lib.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_key.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_gen.c + ${LIBS_ROOT}/openssl/crypto/dh/dh_asn1.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_beos.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_vms.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_win32.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_openssl.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_null.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_lib.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_err.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_dlfcn.c + ${LIBS_ROOT}/openssl/crypto/dso/dso_dl.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_prn.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_pmeth.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_ameth.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_depr.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_ossl.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_err.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_sign.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_vrf.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_asn1.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_lib.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_key.c + ${LIBS_ROOT}/openssl/crypto/dsa/dsa_gen.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_crpt.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_pmeth.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_prn.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_ameth.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_depr.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_asn1.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_x931.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_pss.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_null.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_chk.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_oaep.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_none.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_ssl.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_pk1.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_err.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_saos.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_sign.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_lib.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_gen.c + ${LIBS_ROOT}/openssl/crypto/rsa/rsa_eay.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_x931p.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_const.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_depr.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_nist.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_gf2m.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_exp2.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_mpi.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_mont.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_recp.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_asm.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_sqr.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_err.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_prime.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_gcd.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_sqrt.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_kron.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_blind.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_word.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_shift.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_rand.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_print.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_mod.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_mul.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_ctx.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_lib.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_exp.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_div.c + ${LIBS_ROOT}/openssl/crypto/bn/bn_add.c + ${LIBS_ROOT}/openssl/crypto/modes/wrap128.c + ${LIBS_ROOT}/openssl/crypto/modes/xts128.c + ${LIBS_ROOT}/openssl/crypto/modes/ccm128.c + ${LIBS_ROOT}/openssl/crypto/modes/gcm128.c + ${LIBS_ROOT}/openssl/crypto/modes/ofb128.c + ${LIBS_ROOT}/openssl/crypto/modes/cfb128.c + ${LIBS_ROOT}/openssl/crypto/modes/cts128.c + ${LIBS_ROOT}/openssl/crypto/modes/ctr128.c + ${LIBS_ROOT}/openssl/crypto/modes/cbc128.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_cbc.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_core.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_wrap.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_ige.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_ctr.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_ofb.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_cfb.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_ecb.c + ${LIBS_ROOT}/openssl/crypto/aes/aes_misc.c + ${LIBS_ROOT}/openssl/crypto/rc2/rc2ofb64.c + ${LIBS_ROOT}/openssl/crypto/rc2/rc2cfb64.c + ${LIBS_ROOT}/openssl/crypto/rc2/rc2_cbc.c + ${LIBS_ROOT}/openssl/crypto/rc2/rc2_skey.c + ${LIBS_ROOT}/openssl/crypto/rc2/rc2_ecb.c + ${LIBS_ROOT}/openssl/crypto/des/read2pwd.c + ${LIBS_ROOT}/openssl/crypto/des/des_old2.c + ${LIBS_ROOT}/openssl/crypto/des/des_old.c + ${LIBS_ROOT}/openssl/crypto/des/ede_cbcm_enc.c + ${LIBS_ROOT}/openssl/crypto/des/cbc_cksm.c + ${LIBS_ROOT}/openssl/crypto/des/rpc_enc.c + ${LIBS_ROOT}/openssl/crypto/des/xcbc_enc.c + ${LIBS_ROOT}/openssl/crypto/des/fcrypt.c + ${LIBS_ROOT}/openssl/crypto/des/fcrypt_b.c + ${LIBS_ROOT}/openssl/crypto/des/des_enc.c + ${LIBS_ROOT}/openssl/crypto/des/rand_key.c + ${LIBS_ROOT}/openssl/crypto/des/qud_cksm.c + ${LIBS_ROOT}/openssl/crypto/des/pcbc_enc.c + ${LIBS_ROOT}/openssl/crypto/des/str2key.c + ${LIBS_ROOT}/openssl/crypto/des/ofb_enc.c + ${LIBS_ROOT}/openssl/crypto/des/ofb64enc.c + ${LIBS_ROOT}/openssl/crypto/des/enc_writ.c + ${LIBS_ROOT}/openssl/crypto/des/enc_read.c + ${LIBS_ROOT}/openssl/crypto/des/ofb64ede.c + ${LIBS_ROOT}/openssl/crypto/des/cfb_enc.c + ${LIBS_ROOT}/openssl/crypto/des/cfb64ede.c + ${LIBS_ROOT}/openssl/crypto/des/cfb64enc.c + ${LIBS_ROOT}/openssl/crypto/des/ecb3_enc.c + ${LIBS_ROOT}/openssl/crypto/des/cbc_enc.c + ${LIBS_ROOT}/openssl/crypto/des/ecb_enc.c + ${LIBS_ROOT}/openssl/crypto/des/set_key.c + ${LIBS_ROOT}/openssl/crypto/cmac/cm_pmeth.c + ${LIBS_ROOT}/openssl/crypto/cmac/cm_ameth.c + ${LIBS_ROOT}/openssl/crypto/cmac/cmac.c + ${LIBS_ROOT}/openssl/crypto/hmac/hm_pmeth.c + ${LIBS_ROOT}/openssl/crypto/hmac/hm_ameth.c + ${LIBS_ROOT}/openssl/crypto/hmac/hmac.c + ${LIBS_ROOT}/openssl/crypto/mdc2/mdc2_one.c + ${LIBS_ROOT}/openssl/crypto/mdc2/mdc2dgst.c + ${LIBS_ROOT}/openssl/crypto/sha/sha512.c + ${LIBS_ROOT}/openssl/crypto/sha/sha256.c + ${LIBS_ROOT}/openssl/crypto/sha/sha1_one.c + ${LIBS_ROOT}/openssl/crypto/sha/sha_one.c + ${LIBS_ROOT}/openssl/crypto/sha/sha1dgst.c + ${LIBS_ROOT}/openssl/crypto/sha/sha_dgst.c + ${LIBS_ROOT}/openssl/crypto/md5/md5_one.c + ${LIBS_ROOT}/openssl/crypto/md5/md5_dgst.c + ${LIBS_ROOT}/openssl/crypto/md4/md4_one.c + ${LIBS_ROOT}/openssl/crypto/md4/md4_dgst.c + ${LIBS_ROOT}/openssl/crypto/mem_clr.c +# ${LIBS_ROOT}/openssl/crypto/fips_ers.c + ${LIBS_ROOT}/openssl/crypto/o_init.c + ${LIBS_ROOT}/openssl/crypto/o_fips.c + ${LIBS_ROOT}/openssl/crypto/o_dir.c + ${LIBS_ROOT}/openssl/crypto/o_str.c + ${LIBS_ROOT}/openssl/crypto/o_time.c + ${LIBS_ROOT}/openssl/crypto/uid.c + ${LIBS_ROOT}/openssl/crypto/ebcdic.c + ${LIBS_ROOT}/openssl/crypto/cpt_err.c + ${LIBS_ROOT}/openssl/crypto/ex_data.c + ${LIBS_ROOT}/openssl/crypto/cversion.c + ${LIBS_ROOT}/openssl/crypto/mem_dbg.c + ${LIBS_ROOT}/openssl/crypto/mem.c + ${LIBS_ROOT}/openssl/crypto/cryptlib.c +) + +if(MSVC) +set(OPENSSL_COMPILE_FLAGS "/TC") +elseif(CMAKE_COMPILER_IS_MINGW) +set(OPENSSL_COMPILE_FLAGS "") +endif() +if(PROJECT_PLATFORM STREQUAL "x64") +set(OPENSSL_COMPILE_FLAGS "${OPENSSL_COMPILE_FLAGS} -DOPENSSL_SYSNAME_WIN32 -DOPENSSL_SYSNAME_WIN64 -DMK1MF_PLATFORM_VC_WIN64A") +else() +set(OPENSSL_COMPILE_FLAGS "${OPENSSL_COMPILE_FLAGS} -DOPENSSL_SYSNAME_WIN32 -DMK1MF_PLATFORM_VC_WIN32") +endif() +set(OPENSSL_COMPILE_FLAGS "${OPENSSL_COMPILE_FLAGS} -DMK1MF_BUILD -DWIN32_LEAN_AND_MEAN -DVC_EXTRALEAN -DSECURITY_WIN32 -DOPENSSL_NO_CAPIENG -DNO_CHMOD -DOPENSSL_NO_DGRAM -DOPENSSL_NO_RIJNDAEL -DDSO_WIN32") + +set_target_properties(openssl + PROPERTIES + COMPILE_FLAGS "${OPENSSL_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) + +#------------------------------------------------------------------------------- + +add_library(atlmfc STATIC + ${LIBS_ROOT}/atlmfc/mfc1.cpp + ${LIBS_ROOT}/atlmfc/mfc2.cpp + ${LIBS_ROOT}/atlmfc/mfc3.cpp + ${LIBS_ROOT}/atlmfc/mfc4.cpp + ${LIBS_ROOT}/atlmfc/mfc5.cpp + ${LIBS_ROOT}/atlmfc/src/mfc/thrdcore.cpp +) + +#------------------------------------------------------------------------------- +set(ATLMFC_COMPILE_FLAGS "-D_ATL_NO_DEBUG_CRT -D_ATL_NO_UUIDOF -D_ATL_NO_CONNECTION_POINTS -D_ATL_NO_DATETIME_RESOURCES_ -D_ATL_NO_DEFAULT_LIBS -D_ATL_NO_PERF_SUPPORT -D_AFX_PORTABLE -D_ATL_NO_COMMODULE -D_AFX_NO_OLE_SUPPORT -D_AFX_NO_SOCKET_SUPPORT") + +if(MSVC) +set_target_properties(atlmfc + PROPERTIES + COMPILE_FLAGS "${ATLMFC_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" + LINK_FLAGS_DEBUG "${LINK_FLAGS_DEBUG}" +) +elseif(CMAKE_COMPILER_IS_MINGW) +set_target_properties(atlmfc + PROPERTIES + COMPILE_FLAGS "-Wno-int-to-pointer-cast -Wno-pragmas -fpermissive ${ATLMFC_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) +endif() + +#------------------------------------------------------------------------------- + +add_library(putty STATIC + ${LIBS_ROOT}/Putty/cproxy.c + ${LIBS_ROOT}/Putty/int64.c + ${LIBS_ROOT}/Putty/logging.c + ${LIBS_ROOT}/Putty/misc.c + ${LIBS_ROOT}/Putty/pgssapi.c + ${LIBS_ROOT}/Putty/portfwd.c + ${LIBS_ROOT}/Putty/proxy.c + ${LIBS_ROOT}/Putty/ssh.c + ${LIBS_ROOT}/Putty/sshaes.c + ${LIBS_ROOT}/Putty/ssharcf.c + ${LIBS_ROOT}/Putty/sshblowf.c + ${LIBS_ROOT}/Putty/sshbn.c + ${LIBS_ROOT}/Putty/sshcrc.c + ${LIBS_ROOT}/Putty/sshcrcda.c + ${LIBS_ROOT}/Putty/sshdes.c + ${LIBS_ROOT}/Putty/sshdh.c + ${LIBS_ROOT}/Putty/sshdss.c + ${LIBS_ROOT}/Putty/sshgssc.c + ${LIBS_ROOT}/Putty/sshmd5.c + ${LIBS_ROOT}/Putty/sshpubk.c + ${LIBS_ROOT}/Putty/sshrand.c + ${LIBS_ROOT}/Putty/sshrsa.c + ${LIBS_ROOT}/Putty/sshsh256.c + ${LIBS_ROOT}/Putty/sshsh512.c + ${LIBS_ROOT}/Putty/sshsha.c + ${LIBS_ROOT}/Putty/sshzlib.c + ${LIBS_ROOT}/Putty/tree234.c + ${LIBS_ROOT}/Putty/wildcard.c + ${LIBS_ROOT}/Putty/windows/wingss.c + ${LIBS_ROOT}/Putty/windows/winhandl.c + ${LIBS_ROOT}/Putty/windows/winmisc.c + ${LIBS_ROOT}/Putty/windows/winnet.c + ${LIBS_ROOT}/Putty/windows/winnoise.c + ${LIBS_ROOT}/Putty/windows/winnojmp.c + ${LIBS_ROOT}/Putty/windows/winpgntc.c + ${LIBS_ROOT}/Putty/windows/winproxy.c + ${LIBS_ROOT}/Putty/windows/winsecur.c + ${LIBS_ROOT}/Putty/windows/winstore.c + ${LIBS_ROOT}/Putty/windows/wintime.c + ${LIBS_ROOT}/Putty/windows/winhsock.c + ${LIBS_ROOT}/Putty/x11fwd.c + ${LIBS_ROOT}/Putty/conf.c + ${LIBS_ROOT}/Putty/callback.c + ${LIBS_ROOT}/Putty/sshshare.c + ${LIBS_ROOT}/Putty/noshare.c + ${LIBS_ROOT}/Putty/errsock.c + ${LIBS_ROOT}/Putty/sshecc.c + ${LIBS_ROOT}/Putty/sshccp.c + ${LIBS_ROOT}/Putty/import.c + ${LIBS_ROOT}/Putty/be_misc.c + ${LIBS_ROOT}/Putty/sshbcrypt.c +# ${LIBS_ROOT}/Putty/ldisc.c +) + +set(PUTTY_COMPILE_FLAGS "-D_CRTIMP= -DLibrary -D_MT -DMPEXT -DNET_SETUP_DIAGNOSTICS") + +if(MSVC) +set_target_properties(putty + PROPERTIES + COMPILE_FLAGS "${PUTTY_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) +elseif(CMAKE_COMPILER_IS_MINGW) +set_target_properties(putty + PROPERTIES + COMPILE_FLAGS "-x c --std=c99 ${PUTTY_COMPILE_FLAGS}" +) +endif() + +#------------------------------------------------------------------------------- + +add_library(tinyxml2 STATIC + ${LIBS_ROOT}/tinyxml2/tinyxml2.cpp +) + +#------------------------------------------------------------------------------- + +add_library(neon STATIC + ${LIBS_ROOT}/neon/src/ne_alloc.c + ${LIBS_ROOT}/neon/src/ne_auth.c + ${LIBS_ROOT}/neon/src/ne_basic.c + ${LIBS_ROOT}/neon/src/ne_compress.c + ${LIBS_ROOT}/neon/src/ne_dates.c + ${LIBS_ROOT}/neon/src/ne_i18n.c + ${LIBS_ROOT}/neon/src/ne_md5.c + ${LIBS_ROOT}/neon/src/ne_pkcs11.c + ${LIBS_ROOT}/neon/src/ne_redirect.c + ${LIBS_ROOT}/neon/src/ne_request.c + ${LIBS_ROOT}/neon/src/ne_session.c + ${LIBS_ROOT}/neon/src/ne_socket.c + ${LIBS_ROOT}/neon/src/ne_socks.c + ${LIBS_ROOT}/neon/src/ne_sspi.c + ${LIBS_ROOT}/neon/src/ne_string.c + ${LIBS_ROOT}/neon/src/ne_uri.c + ${LIBS_ROOT}/neon/src/ne_utils.c + ${LIBS_ROOT}/neon/src/ne_207.c + ${LIBS_ROOT}/neon/src/ne_xml.c + ${LIBS_ROOT}/neon/src/ne_xmlreq.c + ${LIBS_ROOT}/neon/src/ne_oldacl.c + ${LIBS_ROOT}/neon/src/ne_acl3744.c + ${LIBS_ROOT}/neon/src/ne_props.c + ${LIBS_ROOT}/neon/src/ne_locks.c + ${LIBS_ROOT}/neon/src/ne_openssl.c +) + +set(NEON_COMPILE_FLAGS "-D_CRTIMP= -D_LIB -DNOCRYPT ${LIBNEON_DEFS} ${LIBEXPAT_DEFS}") + +if(MSVC) +set_target_properties(neon + PROPERTIES + COMPILE_FLAGS "/TC ${NEON_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) +elseif(CMAKE_COMPILER_IS_MINGW) +set_target_properties(neon + PROPERTIES + COMPILE_FLAGS "-x c --std=c99 ${NEON_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) +endif() + +#------------------------------------------------------------------------------- + +add_library(apr STATIC + ${LIBS_ROOT}/apr/atomic/win32/apr_atomic.c +# ${LIBS_ROOT}/apr/dso/win32/dso.c + ${LIBS_ROOT}/apr/file_io/win32/buffer.c +# ${LIBS_ROOT}/apr/file_io/unix/copy.c + ${LIBS_ROOT}/apr/file_io/win32/dir.c + ${LIBS_ROOT}/apr/file_io/unix/fileacc.c + ${LIBS_ROOT}/apr/file_io/win32/filedup.c + ${LIBS_ROOT}/apr/file_io/win32/filepath.c + ${LIBS_ROOT}/apr/file_io/unix/filepath_util.c + ${LIBS_ROOT}/apr/file_io/win32/filestat.c + ${LIBS_ROOT}/apr/file_io/win32/filesys.c + ${LIBS_ROOT}/apr/file_io/win32/flock.c + ${LIBS_ROOT}/apr/file_io/unix/fullrw.c + ${LIBS_ROOT}/apr/file_io/unix/mktemp.c + ${LIBS_ROOT}/apr/file_io/win32/open.c + ${LIBS_ROOT}/apr/file_io/win32/pipe.c + ${LIBS_ROOT}/apr/file_io/win32/readwrite.c + ${LIBS_ROOT}/apr/file_io/win32/seek.c +# ${LIBS_ROOT}/apr/file_io/unix/tempdir.c + ${LIBS_ROOT}/apr/locks/win32/proc_mutex.c + ${LIBS_ROOT}/apr/locks/win32/thread_cond.c + ${LIBS_ROOT}/apr/locks/win32/thread_mutex.c + ${LIBS_ROOT}/apr/locks/win32/thread_rwlock.c + ${LIBS_ROOT}/apr/memory/unix/apr_pools.c + ${LIBS_ROOT}/apr/misc/win32/apr_app.c + ${LIBS_ROOT}/apr/misc/win32/charset.c +# ${LIBS_ROOT}/apr/misc/win32/env.c + ${LIBS_ROOT}/apr/misc/unix/errorcodes.c +# ${LIBS_ROOT}/apr/misc/unix/getopt.c + ${LIBS_ROOT}/apr/misc/win32/internal.c + ${LIBS_ROOT}/apr/misc/win32/misc.c +# ${LIBS_ROOT}/apr/misc/unix/otherchild.c + ${LIBS_ROOT}/apr/misc/win32/rand.c + ${LIBS_ROOT}/apr/misc/win32/start.c + ${LIBS_ROOT}/apr/misc/win32/utf8.c +# ${LIBS_ROOT}/apr/misc/unix/version.c + ${LIBS_ROOT}/apr/mmap/unix/common.c +# ${LIBS_ROOT}/apr/mmap/win32/mmap.c + ${LIBS_ROOT}/apr/network_io/unix/inet_ntop.c + ${LIBS_ROOT}/apr/network_io/unix/inet_pton.c +# ${LIBS_ROOT}/apr/network_io/unix/multicast.c + ${LIBS_ROOT}/apr/network_io/win32/sendrecv.c + ${LIBS_ROOT}/apr/network_io/unix/sockaddr.c + ${LIBS_ROOT}/apr/network_io/win32/sockets.c + ${LIBS_ROOT}/apr/network_io/unix/socket_util.c + ${LIBS_ROOT}/apr/network_io/win32/sockopt.c +# ${LIBS_ROOT}/apr/passwd/apr_getpass.c +# ${LIBS_ROOT}/apr/poll/unix/poll.c +# ${LIBS_ROOT}/apr/poll/unix/pollcb.c +# ${LIBS_ROOT}/apr/poll/unix/pollset.c +# ${LIBS_ROOT}/apr/poll/unix/select.c +# ${LIBS_ROOT}/apr/poll/unix/z_asio.c + ${LIBS_ROOT}/apr/random/unix/apr_random.c +# ${LIBS_ROOT}/apr/random/unix/sha2.c +# ${LIBS_ROOT}/apr/random/unix/sha2_glue.c +# ${LIBS_ROOT}/apr/shmem/win32/shm.c + ${LIBS_ROOT}/apr/strings/apr_cpystrn.c + ${LIBS_ROOT}/apr/strings/apr_fnmatch.c + ${LIBS_ROOT}/apr/strings/apr_snprintf.c + ${LIBS_ROOT}/apr/strings/apr_strings.c + ${LIBS_ROOT}/apr/strings/apr_strnatcmp.c + ${LIBS_ROOT}/apr/strings/apr_strtok.c + ${LIBS_ROOT}/apr/tables/apr_hash.c + ${LIBS_ROOT}/apr/tables/apr_tables.c +# ${LIBS_ROOT}/apr/tables/apr_skiplist.c + ${LIBS_ROOT}/apr/threadproc/win32/proc.c + ${LIBS_ROOT}/apr/threadproc/win32/signals.c + ${LIBS_ROOT}/apr/threadproc/win32/thread.c + ${LIBS_ROOT}/apr/threadproc/win32/threadpriv.c + ${LIBS_ROOT}/apr/time/win32/time.c + ${LIBS_ROOT}/apr/time/win32/timestr.c +# ${LIBS_ROOT}/apr/user/win32/groupinfo.c + ${LIBS_ROOT}/apr/user/win32/userinfo.c +) + +set_target_properties(apr + PROPERTIES + COMPILE_FLAGS "-D_CRTIMP= -D_LIB -DAPR_DECLARE_STATIC -DAPR_HAS_LARGE_FILES -U_UNICODE -UUNICODE" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) + +#------------------------------------------------------------------------------- + +add_library(expat STATIC + ${LIBS_ROOT}/expat/lib/xmlparse.c + ${LIBS_ROOT}/expat/lib/xmlrole.c + ${LIBS_ROOT}/expat/lib/xmltok.c + ${LIBS_ROOT}/expat/lib/xmltok_impl.c +# ${LIBS_ROOT}/expat/lib/xmltok_ns.c +) + +set(EXPAT_COMPILE_FLAGS "-D_CRTIMP= -D_LIB ${LIBEXPAT_DEFS}") + +if(MSVC) +set_target_properties(expat + PROPERTIES + COMPILE_FLAGS "/TC ${EXPAT_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) +elseif(CMAKE_COMPILER_IS_MINGW) +set_target_properties(expat + PROPERTIES + COMPILE_FLAGS "-x c --std=c99 ${EXPAT_COMPILE_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) +endif() + +#------------------------------------------------------------------------------- + +set(ZLIB_SOURCES + ${LIBS_ROOT}/zlib/src/adler32.c + ${LIBS_ROOT}/zlib/src/crc32.c + ${LIBS_ROOT}/zlib/src/infback.c + ${LIBS_ROOT}/zlib/src/inffast.c + ${LIBS_ROOT}/zlib/src/inflate.c + ${LIBS_ROOT}/zlib/src/inftrees.c + ${LIBS_ROOT}/zlib/src/trees.c + ${LIBS_ROOT}/zlib/src/zutil.c + ${LIBS_ROOT}/zlib/src/match.c + ${LIBS_ROOT}/zlib/src/deflate.c + ${LIBS_ROOT}/zlib/src/deflate_slow.c + ${LIBS_ROOT}/zlib/src/deflate_medium.c + ${LIBS_ROOT}/zlib/src/deflate_fast.c + ${LIBS_ROOT}/zlib/src/arch/x86/crc_folding.c + ${LIBS_ROOT}/zlib/src/arch/x86/fill_window_sse.c + ${LIBS_ROOT}/zlib/src/arch/x86/insert_string_sse.c + ${LIBS_ROOT}/zlib/src/arch/x86/x86.c +) + +if(MSVC) +set(ZLIB_SOURCES ${ZLIB_SOURCES} + ${LIBS_ROOT}/zlib/src/arch/x86/deflate_quick.c +) +endif() + +add_library(zlib STATIC + ${ZLIB_SOURCES} +) + +set_target_properties(zlib + PROPERTIES + COMPILE_FLAGS "-D_CRTIMP= -D_LIB" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) + +#------------------------------------------------------------------------------- + +add_library(dlmalloc STATIC + ${LIBS_ROOT}/dlmalloc/malloc.c + ${LIBS_ROOT}/dlmalloc/malloc-2.8.6.h +) + +set_target_properties(dlmalloc + PROPERTIES + COMPILE_FLAGS "-D_CRTIMP= -D_LIB ${DLMALLOC_DEFS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) + +#------------------------------------------------------------------------------- + +add_library(rdestl STATIC + ${LIBS_ROOT}/rdestl/allocator.cpp + ${LIBS_ROOT}/rdestl/intrusive_list.cpp + ${LIBS_ROOT}/rdestl/list.cpp +# ${LIBS_ROOT}/rdestl/intrusive_slist.cpp +# ${LIBS_ROOT}/rdestl/slist.cpp + ${LIBS_ROOT}/rdestl/sort.h + ${LIBS_ROOT}/rdestl/string_utils.h +# ${LIBS_ROOT}/rdestl/intrusive_list.h + ${LIBS_ROOT}/rdestl/sorted_vector.h + ${LIBS_ROOT}/rdestl/allocator.h + ${LIBS_ROOT}/rdestl/hash_map.h + ${LIBS_ROOT}/rdestl/map.h + ${LIBS_ROOT}/rdestl/basic_string.h + ${LIBS_ROOT}/rdestl/list.h + ${LIBS_ROOT}/rdestl/type_traits.h +# ${LIBS_ROOT}/rdestl/intrusive_slist.h + ${LIBS_ROOT}/rdestl/radix_sorter.h + ${LIBS_ROOT}/rdestl/stack_allocator.h + ${LIBS_ROOT}/rdestl/utility.h + ${LIBS_ROOT}/rdestl/vector.h +# ${LIBS_ROOT}/rdestl/slist.h + ${LIBS_ROOT}/rdestl/fixed_array.h + ${LIBS_ROOT}/rdestl/pair.h + ${LIBS_ROOT}/rdestl/algorithm.h + ${LIBS_ROOT}/rdestl/fixed_list.h + ${LIBS_ROOT}/rdestl/hash.h + ${LIBS_ROOT}/rdestl/alignment.h + ${LIBS_ROOT}/rdestl/fixed_vector.h + ${LIBS_ROOT}/rdestl/rb_tree.h + ${LIBS_ROOT}/rdestl/buffer_allocator.h + ${LIBS_ROOT}/rdestl/sstream.h + ${LIBS_ROOT}/rdestl/rdestl_common.h + ${LIBS_ROOT}/rdestl/stack.h + ${LIBS_ROOT}/rdestl/cow_string_storage.h + ${LIBS_ROOT}/rdestl/fixed_sorted_vector.h + ${LIBS_ROOT}/rdestl/functional.h + ${LIBS_ROOT}/rdestl/simple_string_storage.h + ${LIBS_ROOT}/rdestl/fixed_substring.h + ${LIBS_ROOT}/rdestl/int_to_type.h + ${LIBS_ROOT}/rdestl/rde_string.h + ${LIBS_ROOT}/rdestl/rdestl.h + ${LIBS_ROOT}/rdestl/set.h + ${LIBS_ROOT}/rdestl/iterator.h +) + +set_target_properties(rdestl + PROPERTIES + COMPILE_FLAGS "" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE}" +) + +#------------------------------------------------------------------------------- + +if(DEFINED OPT_COMPILE_TESTS) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + +set(TESTS_ROOT ${SRC_ROOT}/tests) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_DISABLE_ASSERTS") + +include_directories( + ${LIBS_ROOT}/boost +) + +if(PROJECT_PLATFORM STREQUAL "x64") +set(BOOST_LINK_DIR ${LIBS_ROOT}/boost/stage/x64) +else() +set(BOOST_LINK_DIR ${LIBS_ROOT}/boost/stage) +endif() +link_directories(${BOOST_LINK_DIR}) + +#------------------------------------------------------- +# target testnetbox_01 + +set(TESTNETBOX_01_DIR ${TESTS_ROOT}) + +set(TESTNETBOX_01_SOURCES + ${SRC_ROOT}/base/Classes.cpp + ${SRC_ROOT}/base/Sysutils.cpp + ${SRC_ROOT}/base/UnicodeString.cpp + ${SRC_ROOT}/base/local.cpp + ${SRC_ROOT}/base/Common.cpp + ${SRC_ROOT}/base/Exceptions.cpp + ${SRC_ROOT}/base/FileBuffer.cpp + ${TESTNETBOX_01_DIR}/testnetbox_01.cpp + ${SRC_ROOT}/resource/TextsCore1.rc + ${SRC_ROOT}/resource/rtlconsts.rc +) + +add_executable(testnetbox_01 ${TESTNETBOX_01_SOURCES}) + +set_target_properties(testnetbox_01 + PROPERTIES + COMPILE_FLAGS "${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) + +add_dependencies(testnetbox_01 + atlmfc + putty + tinyxml2 +) + +target_link_libraries(testnetbox_01 ${NETBOX_LIBRARIES}) + +#------------------------------------------------------- +# target testnetbox_02 + +set(TESTNETBOX_02_DIR ${TESTS_ROOT}) + +set(TESTNETBOX_02_SOURCES + ${SRC_ROOT}/NetBox/UnityBuildCore.cpp + ${SRC_ROOT}/NetBox/UnityBuildMain.cpp + ${SRC_ROOT}/NetBox/UnityBuildFilezilla.cpp + ${TESTNETBOX_02_DIR}/testnetbox_02.rc + ${TESTNETBOX_02_DIR}/testnetbox_02.cpp + ${SRC_ROOT}/resource/TextsCore1.rc + ${SRC_ROOT}/resource/rtlconsts.rc +) + +add_executable(testnetbox_02 ${TESTNETBOX_02_SOURCES}) + +set_target_properties(testnetbox_02 + PROPERTIES + COMPILE_FLAGS "${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS} /verbose:lib" +) + +add_dependencies(testnetbox_02 + atlmfc + putty + tinyxml2 + neon + apr + expat + zlib +) + +target_link_libraries(testnetbox_02 ${NETBOX_LIBRARIES}) + +#------------------------------------------------------- +# target testnetbox_03 + +set(TESTNETBOX_03_DIR ${TESTS_ROOT}) + +set(TESTNETBOX_03_SOURCES + ${SRC_ROOT}/NetBox/UnityBuildCore.cpp + ${SRC_ROOT}/NetBox/UnityBuildMain.cpp + ${SRC_ROOT}/NetBox/UnityBuildFilezilla.cpp + ${TESTNETBOX_03_DIR}/testnetbox_03.cpp + ${SRC_ROOT}/resource/TextsCore1.rc + ${SRC_ROOT}/resource/rtlconsts.rc +) + +add_executable(testnetbox_03 ${TESTNETBOX_03_SOURCES}) + +set_target_properties(testnetbox_03 + PROPERTIES + COMPILE_FLAGS "${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS}" +) + +add_dependencies(testnetbox_03 + putty + tinyxml2 + neon + apr + expat + zlib +) + +target_link_libraries(testnetbox_03 ${NETBOX_LIBRARIES}) + +#------------------------------------------------------- +# target testnetbox_04 + +set(TESTNETBOX_04_DIR ${TESTS_ROOT}) + +set(TESTNETBOX_04_SOURCES + ${SRC_ROOT}/NetBox/UnityBuildCore.cpp + ${SRC_ROOT}/NetBox/UnityBuildMain.cpp + ${SRC_ROOT}/NetBox/UnityBuildFilezilla.cpp + ${TESTNETBOX_04_DIR}/testnetbox_04.cpp + ${SRC_ROOT}/resource/TextsCore1.rc + ${SRC_ROOT}/resource/rtlconsts.rc +) + +add_executable(testnetbox_04 ${TESTNETBOX_04_SOURCES}) + +set_target_properties(testnetbox_04 + PROPERTIES + COMPILE_FLAGS "${CMAKE_CXX_FLAGS}" + LINK_FLAGS "${CMAKE_LINK_FLAGS} /verbose:lib" +) + +add_dependencies(testnetbox_04 ${NETBOX_LIBRARIES} + calculator_dll +) + +target_link_libraries(testnetbox_04 ${NETBOX_LIBRARIES} +) + +endif(CMAKE_BUILD_TYPE STREQUAL "Debug") +endif(DEFINED OPT_COMPILE_TESTS) diff --git a/netbox/src/NetBox/FarConfiguration.cpp b/netbox/src/NetBox/FarConfiguration.cpp new file mode 100644 index 000000000..c4a88b727 --- /dev/null +++ b/netbox/src/NetBox/FarConfiguration.cpp @@ -0,0 +1,252 @@ +#include +#pragma hdrstop + +#include + +#include "Bookmarks.h" +#include "FarConfiguration.h" +#include "FarPlugin.h" +#include "CoreMain.h" + +TFarConfiguration::TFarConfiguration(TCustomFarPlugin * APlugin) : + TGUIConfiguration(), + FFarPlugin(APlugin), + FBookmarks(new TBookmarks()), + FFarConfirmations(-1) +{ + Default(); + CacheFarSettings(); +} + +TFarConfiguration::~TFarConfiguration() +{ + SAFE_DESTROY(FBookmarks); +} + +void TFarConfiguration::Default() +{ + TGUIConfiguration::Default(); + + FForceInheritance = false; + FConfirmOverwritingOverride = false; + FConfirmSynchronizedBrowsing = true; + + SetDisksMenu(true); + SetDisksMenuHotKey(0); + SetPluginsMenu(true); + SetPluginsMenuCommands(true); + SetCommandPrefixes("netbox,ftp,scp,sftp,ftps,http,https,webdav"); + SetHostNameInTitle(true); + SetEditorDownloadDefaultMode(true); + SetEditorUploadSameOptions(true); + FEditorUploadOnSave = true; + FEditorMultiple = false; + FQueueBeep = true; + + SetCustomPanelModeDetailed(true); + SetFullScreenDetailed(true); + SetColumnTypesDetailed("N,S,DM,O,G,R"); + SetColumnWidthsDetailed("0,8,14,0,0,9"); + SetStatusColumnTypesDetailed("NR"); + SetStatusColumnWidthsDetailed("0"); + + SetApplyCommandCommand(L""); + SetApplyCommandParams(0); + + SetPuttygenPath(FormatCommand(::ExtractFilePath(ModuleFileName()) + "putty\\puttygen.exe", L"")); + SetPageantPath(FormatCommand(::ExtractFilePath(ModuleFileName()) + "putty\\pageant.exe", L"")); + + FBookmarks->Clear(); +} + +THierarchicalStorage * TFarConfiguration::CreateStorage(bool & SessionList) +{ + return TGUIConfiguration::CreateStorage(SessionList); +} + +void TFarConfiguration::Saved() +{ + TGUIConfiguration::Saved(); + FBookmarks->ModifyAll(false); +} + +// duplicated from core\configuration.cpp +#define LASTELEM(ELEM) \ + ELEM.SubString(ELEM.LastDelimiter(L".>")+1, ELEM.Length() - ELEM.LastDelimiter(L".>")) +#define BLOCK(KEY, CANCREATE, BLOCK) \ + if (Storage->OpenSubKey(KEY, CANCREATE, true)) \ + { \ + SCOPE_EXIT { Storage->CloseSubKey(); }; \ + BLOCK \ + } +#define REGCONFIG(CANCREATE) \ + BLOCK(L"Far", CANCREATE, \ + KEY(Bool, DisksMenu); \ + KEY(Integer, DisksMenuHotKey); \ + KEY(Bool, PluginsMenu); \ + KEY(Bool, PluginsMenuCommands); \ + KEY(String, CommandPrefixes); \ + KEY(Bool, CustomPanelModeDetailed); \ + KEY(Bool, FullScreenDetailed); \ + KEY(String, ColumnTypesDetailed); \ + KEY(String, ColumnWidthsDetailed); \ + KEY(String, StatusColumnTypesDetailed); \ + KEY(String, StatusColumnWidthsDetailed); \ + KEY(Bool, HostNameInTitle); \ + KEY(Bool, ConfirmOverwritingOverride); \ + KEY(Bool, EditorDownloadDefaultMode); \ + KEY(Bool, EditorUploadSameOptions); \ + KEY(Bool, EditorUploadOnSave); \ + KEY(Bool, EditorMultiple); \ + KEY(Bool, QueueBeep); \ + KEY(String, PuttygenPath); \ + KEY(String, PageantPath); \ + KEY(String, ApplyCommandCommand); \ + KEY(Integer, ApplyCommandParams); \ + KEY(Bool, ConfirmSynchronizedBrowsing); \ + ); + +void TFarConfiguration::SaveData(THierarchicalStorage * Storage, bool All) +{ + TGUIConfiguration::SaveData(Storage, All); + + // duplicated from core\configuration.cpp + #define KEY(TYPE, VAR) Storage->Write ## TYPE(LASTELEM(MB2W(#VAR)), Get##VAR()) + REGCONFIG(true); + #undef KEY + + if (Storage->OpenSubKey(L"Bookmarks", /*CanCreate=*/true)) + { + FBookmarks->Save(Storage, All); + + Storage->CloseSubKey(); + } +} + +void TFarConfiguration::LoadData(THierarchicalStorage * Storage) +{ + TGUIConfiguration::LoadData(Storage); + + // duplicated from core\configuration.cpp + #define KEY(TYPE, VAR) Set##VAR(Storage->Read ## TYPE(LASTELEM(MB2W(#VAR)), Get##VAR())) + REGCONFIG(false); + #undef KEY + + if (Storage->OpenSubKey(L"Bookmarks", false)) + { + FBookmarks->Load(Storage); + Storage->CloseSubKey(); + } +} + +void TFarConfiguration::Load() +{ + std::unique_ptr Storage(CreateConfigStorage()); + FForceInheritance = true; + SCOPE_EXIT + { + FForceInheritance = false; + }; + TGUIConfiguration::Load(Storage.get()); +} + +void TFarConfiguration::Save(bool All, bool Explicit) +{ + FForceInheritance = true; + SCOPE_EXIT + { + FForceInheritance = false; + }; + TGUIConfiguration::DoSave(All, Explicit); +} + +void TFarConfiguration::SetPlugin(TCustomFarPlugin * Value) +{ + if (GetPlugin() != Value) + { + DebugAssert(!GetPlugin() || !Value); + FFarPlugin = Value; + } +} + +void TFarConfiguration::CacheFarSettings() +{ + if (GetPlugin()) + { + FFarConfirmations = GetPlugin()->FarAdvControl(ACTL_GETCONFIRMATIONS); + } +} + +intptr_t TFarConfiguration::FarConfirmations() const +{ + if (GetPlugin() && (GetCurrentThreadId() == GetPlugin()->GetFarThreadId())) + { + return GetPlugin()->FarAdvControl(ACTL_GETCONFIRMATIONS); + } + else + { + DebugAssert(FFarConfirmations >= 0); + return FFarConfirmations; + } +} + +bool TFarConfiguration::GetConfirmOverwriting() const +{ + if (FForceInheritance || FConfirmOverwritingOverride) + { + return TGUIConfiguration::GetConfirmOverwriting(); + } + else + { + // DebugAssert(GetPlugin()); + return (FarConfirmations() & FCS_COPYOVERWRITE) != 0; + } +} + +void TFarConfiguration::SetConfirmOverwriting(bool Value) +{ + if (FForceInheritance) + { + TGUIConfiguration::SetConfirmOverwriting(Value); + } + else + { + if (GetConfirmOverwriting() != Value) + { + FConfirmOverwritingOverride = true; + TGUIConfiguration::SetConfirmOverwriting(Value); + } + } +} + +bool TFarConfiguration::GetConfirmDeleting() const +{ + DebugAssert(GetPlugin()); + return (FarConfirmations() & FCS_DELETE) != 0; +} + +UnicodeString TFarConfiguration::ModuleFileName() const +{ + DebugAssert(GetPlugin()); + return GetPlugin()->GetModuleName(); +} + +void TFarConfiguration::SetBookmarks(const UnicodeString & Key, + TBookmarkList * Value) +{ + FBookmarks->SetBookmarks(Key, Value); + Changed(); +} + +TBookmarkList * TFarConfiguration::GetBookmarks(const UnicodeString & Key) +{ + return FBookmarks->GetBookmarks(Key); +} + +TFarConfiguration * GetFarConfiguration() +{ + return NB_STATIC_DOWNCAST(TFarConfiguration, GetConfiguration()); +} + +NB_IMPLEMENT_CLASS(TFarConfiguration, NB_GET_CLASS_INFO(TGUIConfiguration), nullptr) + diff --git a/netbox/src/NetBox/FarConfiguration.h b/netbox/src/NetBox/FarConfiguration.h new file mode 100644 index 000000000..bfe2207a1 --- /dev/null +++ b/netbox/src/NetBox/FarConfiguration.h @@ -0,0 +1,124 @@ +#pragma once + +#include "GUIConfiguration.h" + +class TCustomFarPlugin; +class TBookmarks; +class TBookmarkList; + +class TFarConfiguration : public TGUIConfiguration +{ +NB_DISABLE_COPY(TFarConfiguration) +NB_DECLARE_CLASS(TFarConfiguration) +public: + explicit TFarConfiguration(TCustomFarPlugin * APlugin); + virtual ~TFarConfiguration(); + + virtual void Load(); + virtual void Save(bool All, bool Explicit); + virtual void Default(); + virtual THierarchicalStorage * CreateStorage(bool & SessionList); + void CacheFarSettings(); + + const TCustomFarPlugin * GetPlugin() const { return FFarPlugin; } + TCustomFarPlugin * GetPlugin() { return FFarPlugin; } + void SetPlugin(TCustomFarPlugin * Value); + bool GetConfirmOverwritingOverride() const { return FConfirmOverwritingOverride; } + void SetConfirmOverwritingOverride(bool Value) { FConfirmOverwritingOverride = Value; } + bool GetConfirmDeleting() const; + bool GetConfirmSynchronizedBrowsing() const { return FConfirmSynchronizedBrowsing; } + void SetConfirmSynchronizedBrowsing(bool Value) { FConfirmSynchronizedBrowsing = Value; } + bool GetDisksMenu() const { return FDisksMenu; } + void SetDisksMenu(bool Value) { FDisksMenu = Value; } + intptr_t GetDisksMenuHotKey() const { return FDisksMenuHotKey; } + void SetDisksMenuHotKey(intptr_t Value) { FDisksMenuHotKey = Value; } + bool GetPluginsMenu() const { return FPluginsMenu; } + void SetPluginsMenu(bool Value) { FPluginsMenu = Value; } + bool GetPluginsMenuCommands() const { return FPluginsMenuCommands; } + void SetPluginsMenuCommands(bool Value) { FPluginsMenuCommands = Value; } + UnicodeString GetCommandPrefixes() const { return FCommandPrefixes; } + void SetCommandPrefixes(const UnicodeString & Value) { FCommandPrefixes = Value; } + bool GetHostNameInTitle() const { return FHostNameInTitle; } + void SetHostNameInTitle(bool Value) { FHostNameInTitle = Value; } + + bool GetCustomPanelModeDetailed() const { return FCustomPanelModeDetailed; } + void SetCustomPanelModeDetailed(bool Value) { FCustomPanelModeDetailed = Value; } + bool GetFullScreenDetailed() const { return FFullScreenDetailed; } + void SetFullScreenDetailed(bool Value) { FFullScreenDetailed = Value; } + UnicodeString GetColumnTypesDetailed() const { return FColumnTypesDetailed; } + void SetColumnTypesDetailed(const UnicodeString & Value) { FColumnTypesDetailed = Value; } + UnicodeString GetColumnWidthsDetailed() const { return FColumnWidthsDetailed; } + void SetColumnWidthsDetailed(const UnicodeString & Value) { FColumnWidthsDetailed = Value; } + UnicodeString GetStatusColumnTypesDetailed() const { return FStatusColumnTypesDetailed; } + void SetStatusColumnTypesDetailed(const UnicodeString & Value) { FStatusColumnTypesDetailed = Value; } + UnicodeString GetStatusColumnWidthsDetailed() const { return FStatusColumnWidthsDetailed; } + void SetStatusColumnWidthsDetailed(const UnicodeString & Value) { FStatusColumnWidthsDetailed = Value; } + bool GetEditorDownloadDefaultMode() const { return FEditorDownloadDefaultMode; } + void SetEditorDownloadDefaultMode(bool Value) { FEditorDownloadDefaultMode = Value; } + bool GetEditorUploadSameOptions() const { return FEditorUploadSameOptions; } + void SetEditorUploadSameOptions(bool Value) { FEditorUploadSameOptions = Value; } + bool GetEditorUploadOnSave() const { return FEditorUploadOnSave; } + void SetEditorUploadOnSave(bool Value) { FEditorUploadOnSave = Value; } + bool GetEditorMultiple() const { return FEditorMultiple; } + void SetEditorMultiple(bool Value) { FEditorMultiple = Value; } + bool GetQueueBeep() const { return FQueueBeep; } + void SetQueueBeep(bool Value) { FQueueBeep = Value; } + + UnicodeString GetApplyCommandCommand() const { return FApplyCommandCommand; } + void SetApplyCommandCommand(const UnicodeString & Value) { FApplyCommandCommand = Value; } + intptr_t GetApplyCommandParams() const { return FApplyCommandParams; } + void SetApplyCommandParams(intptr_t Value) { FApplyCommandParams = Value; } + + UnicodeString GetPageantPath() const { return FPageantPath; } + void SetPageantPath(const UnicodeString & Value) { FPageantPath = Value; } + UnicodeString GetPuttygenPath() const { return FPuttygenPath; } + void SetPuttygenPath(const UnicodeString & Value) { FPuttygenPath = Value; } + TBookmarkList * GetBookmarks(const UnicodeString & Key); + void SetBookmarks(const UnicodeString & Key, TBookmarkList * Value); + +protected: + virtual bool GetConfirmOverwriting() const; + virtual void SetConfirmOverwriting(bool Value); + + virtual void SaveData(THierarchicalStorage * Storage, bool All); + virtual void LoadData(THierarchicalStorage * Storage); + + virtual UnicodeString ModuleFileName() const; + virtual void Saved(); + +private: + TCustomFarPlugin * FFarPlugin; + TBookmarks * FBookmarks; + intptr_t FFarConfirmations; + bool FConfirmOverwritingOverride; + bool FConfirmSynchronizedBrowsing; + bool FForceInheritance; + bool FDisksMenu; + intptr_t FDisksMenuHotKey; + bool FPluginsMenu; + bool FPluginsMenuCommands; + UnicodeString FCommandPrefixes; + bool FHostNameInTitle; + bool FEditorDownloadDefaultMode; + bool FEditorUploadSameOptions; + bool FEditorUploadOnSave; + bool FEditorMultiple; + bool FQueueBeep; + UnicodeString FPageantPath; + UnicodeString FPuttygenPath; + UnicodeString FApplyCommandCommand; + intptr_t FApplyCommandParams; + + bool FCustomPanelModeDetailed; + bool FFullScreenDetailed; + UnicodeString FColumnTypesDetailed; + UnicodeString FColumnWidthsDetailed; + UnicodeString FStatusColumnTypesDetailed; + UnicodeString FStatusColumnWidthsDetailed; + +private: + intptr_t FarConfirmations() const; +}; + +TFarConfiguration * GetFarConfiguration(); + diff --git a/netbox/src/NetBox/FarDialog.cpp b/netbox/src/NetBox/FarDialog.cpp new file mode 100644 index 000000000..8b6917e78 --- /dev/null +++ b/netbox/src/NetBox/FarDialog.cpp @@ -0,0 +1,2762 @@ +#include +#pragma hdrstop + +#include + +#pragma warning(push, 1) +#include +#include +#pragma warning(pop) + +#include + +#include "FarDialog.h" + +inline TRect Rect(int Left, int Top, int Right, int Bottom) +{ + return TRect(Left, Top, Right, Bottom); +} + +TFarDialog::TFarDialog(TCustomFarPlugin * AFarPlugin) : + TObject(), + FFarPlugin(AFarPlugin), + FBounds(-1, -1, 40, 10), + FFlags(0), + FHelpTopic(), + FVisible(false), + FItems(new TObjectList()), + FContainers(new TObjectList()), + FHandle(0), + FDefaultButton(nullptr), + FBorderBox(nullptr), + FNextItemPosition(ipNewLine), + FDefaultGroup(0), + FTag(0), + FItemFocused(nullptr), + FOnKey(nullptr), + FDialogItems(nullptr), + FDialogItemsCapacity(0), + FChangesLocked(0), + FChangesPending(false), + FResult(-1), + FNeedsSynchronize(false), + FSynchronizeMethod(nullptr) +{ + DebugAssert(AFarPlugin); + FSynchronizeObjects[0] = INVALID_HANDLE_VALUE; + FSynchronizeObjects[1] = INVALID_HANDLE_VALUE; + + FBorderBox = new TFarBox(this); + FBorderBox->SetBounds(TRect(3, 1, -4, -2)); + FBorderBox->SetDouble(true); +} + +TFarDialog::~TFarDialog() +{ + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + GetItem(Index)->Detach(); + } + SAFE_DESTROY(FItems); + nb_free(FDialogItems); + FDialogItemsCapacity = 0; + SAFE_DESTROY(FContainers); + if (FSynchronizeObjects[0] != INVALID_HANDLE_VALUE) + { + ::CloseHandle(FSynchronizeObjects[0]); + } + if (FSynchronizeObjects[1] != INVALID_HANDLE_VALUE) + { + ::CloseHandle(FSynchronizeObjects[1]); + } +} + +void TFarDialog::SetBounds(const TRect & Value) +{ + if (GetBounds() != Value) + { + LockChanges(); + { + SCOPE_EXIT + { + UnlockChanges(); + }; + FBounds = Value; + if (GetHandle()) + { + COORD Coord; + Coord.X = static_cast(GetSize().x); + Coord.Y = static_cast(GetSize().y); + SendDlgMessage(DM_RESIZEDIALOG, 0, reinterpret_cast(&Coord)); + Coord.X = static_cast(FBounds.Left); + Coord.Y = static_cast(FBounds.Top); + SendDlgMessage(DM_MOVEDIALOG, (int)true, reinterpret_cast(&Coord)); + } + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + GetItem(Index)->DialogResized(); + } + } + } +} + +TRect TFarDialog::GetClientRect() const +{ + TRect R; + if (FBorderBox) + { + R = FBorderBox->GetBounds(); + R.Left += 2; + R.Right -= 2; + R.Top++; + R.Bottom--; + } + else + { + R.Left = 0; + R.Top = 0; + R.Bottom = 0; + R.Right = 0; + } + return R; +} + +TPoint TFarDialog::GetClientSize() const +{ + TPoint S; + if (FBorderBox) + { + TRect R = FBorderBox->GetActualBounds(); + S.x = R.Width() + 1; + S.y = R.Height() + 1; + S.x -= S.x > 4 ? 4 : S.x; + S.y -= S.y > 2 ? 2 : S.y; + } + else + { + S = GetSize(); + } + return S; +} + +TPoint TFarDialog::GetMaxSize() +{ + TPoint P = GetFarPlugin()->TerminalInfo(); + P.x -= 2; + P.y -= 3; + return P; +} + +void TFarDialog::SetHelpTopic(const UnicodeString & Value) +{ + if (FHelpTopic != Value) + { + DebugAssert(!GetHandle()); + FHelpTopic = Value; + } +} + +void TFarDialog::SetFlags(DWORD Value) +{ + if (GetFlags() != Value) + { + DebugAssert(!GetHandle()); + FFlags = Value; + } +} + +void TFarDialog::SetCentered(bool Value) +{ + if (GetCentered() != Value) + { + DebugAssert(!GetHandle()); + TRect B = GetBounds(); + B.Left = Value ? -1 : 0; + B.Top = Value ? -1 : 0; + SetBounds(B); + } +} + +bool TFarDialog::GetCentered() const +{ + return (GetBounds().Left < 0) && (GetBounds().Top < 0); +} + +TPoint TFarDialog::GetSize() const +{ + if (GetCentered()) + { + return TPoint(GetBounds().Right, GetBounds().Bottom); + } + else + { + return TPoint(GetBounds().Width() + 1, GetBounds().Height() + 1); + } +} + +void TFarDialog::SetSize(TPoint Value) +{ + TRect B = GetBounds(); + if (GetCentered()) + { + B.Right = Value.x; + B.Bottom = Value.y; + } + else + { + B.Right = FBounds.Left + Value.x - 1; + B.Bottom = FBounds.Top + Value.y - 1; + } + SetBounds(B); +} + +void TFarDialog::SetWidth(intptr_t Value) +{ + SetSize(TPoint((int)Value, (int)GetHeight())); +} + +intptr_t TFarDialog::GetWidth() const +{ + return GetSize().x; +} + +void TFarDialog::SetHeight(intptr_t Value) +{ + SetSize(TPoint((int)GetWidth(), (int)Value)); +} + +intptr_t TFarDialog::GetHeight() const +{ + return GetSize().y; +} + +void TFarDialog::SetCaption(const UnicodeString & Value) +{ + if (GetCaption() != Value) + { + FBorderBox->SetCaption(Value); + } +} + +UnicodeString TFarDialog::GetCaption() const +{ + return FBorderBox->GetCaption(); +} + +intptr_t TFarDialog::GetItemCount() const +{ + return FItems->GetCount(); +} + +intptr_t TFarDialog::GetItem(TFarDialogItem * Item) const +{ + if (!Item) + return -1; + return Item->GetItem(); +} + +TFarDialogItem * TFarDialog::GetItem(intptr_t Index) const +{ + TFarDialogItem * DialogItem; + if (GetItemCount()) + { + DebugAssert(Index >= 0 && Index < FItems->GetCount()); + DialogItem = NB_STATIC_DOWNCAST(TFarDialogItem, (*GetItems())[Index]); + DebugAssert(DialogItem); + } + else + { + DialogItem = nullptr; + } + return DialogItem; +} + +void TFarDialog::Add(TFarDialogItem * DialogItem) +{ + TRect R = GetClientRect(); + intptr_t Left, Top; + GetNextItemPosition(Left, Top); + R.Left = static_cast(Left); + R.Top = static_cast(Top); + + if (FDialogItemsCapacity == GetItems()->GetCount()) + { + int DialogItemsDelta = 10; + FarDialogItem * NewDialogItems; + NewDialogItems = static_cast( + nb_malloc(sizeof(FarDialogItem) * (GetItems()->GetCount() + DialogItemsDelta))); + if (FDialogItems) + { + memmove(NewDialogItems, FDialogItems, FDialogItemsCapacity * sizeof(FarDialogItem)); + nb_free(FDialogItems); + } + ::memset(NewDialogItems + FDialogItemsCapacity, 0, DialogItemsDelta * sizeof(FarDialogItem)); + FDialogItems = NewDialogItems; + FDialogItemsCapacity += DialogItemsDelta; + } + + DebugAssert(DialogItem); + DialogItem->SetItem(GetItems()->Add(DialogItem)); + + R.Bottom = R.Top; + DialogItem->SetBounds(R); + DialogItem->SetGroup(GetDefaultGroup()); +} + +void TFarDialog::Add(TFarDialogContainer * Container) +{ + FContainers->Add(Container); +} + +void TFarDialog::GetNextItemPosition(intptr_t & Left, intptr_t & Top) +{ + TRect R = GetClientRect(); + Left = R.Left; + Top = R.Top; + + TFarDialogItem * LastItem = GetItem(GetItemCount() - 1); + LastItem = LastItem == FBorderBox ? nullptr : LastItem; + + if (LastItem) + { + switch (GetNextItemPosition()) + { + case ipNewLine: + Top = LastItem->GetBottom() + 1; + break; + + case ipBelow: + Top = LastItem->GetBottom() + 1; + Left = LastItem->GetLeft(); + break; + + case ipRight: + Top = LastItem->GetTop(); + Left = LastItem->GetRight() + 3; + break; + } + } +} + +LONG_PTR WINAPI TFarDialog::DialogProcGeneral(HANDLE Handle, int Msg, int Param1, LONG_PTR Param2) +{ + TFarPluginEnvGuard Guard; + + static std::map Dialogs; + TFarDialog * Dialog = nullptr; + LONG_PTR Result = 0; + if (Msg == DN_INITDIALOG) + { + DebugAssert(Dialogs.find(Handle) == Dialogs.end()); + Dialogs[Handle] = Param2; + Dialog = reinterpret_cast(Param2); + Dialog->FHandle = Handle; + } + else + { + if (Dialogs.find(Handle) == Dialogs.end()) + { + // DM_CLOSE is sent after DN_CLOSE, if the dialog was closed programmatically + // by SendMessage(DM_CLOSE, ...) + DebugAssert(Msg == DM_CLOSE); + Result = static_cast(0); + } + else + { + Dialog = reinterpret_cast(Dialogs[Handle]); + } + } + + if (Dialog != nullptr) + { + Result = Dialog->DialogProc(Msg, static_cast(Param1), Param2); + } + + if ((Msg == DN_CLOSE) && Result) + { + if (Dialog != nullptr) + { + Dialog->FHandle = 0; + } + Dialogs.erase(Handle); + } + return Result; +} + +LONG_PTR TFarDialog::DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + intptr_t Result = 0; + bool Handled = false; + + try + { + if (FNeedsSynchronize) + { + try + { + FNeedsSynchronize = false; + FSynchronizeMethod(); + ::ReleaseSemaphore(FSynchronizeObjects[0], 1, nullptr); + BreakSynchronize(); + } + catch (...) + { + DebugAssert(false); + } + } + + bool Changed = false; + + switch (Msg) + { + case DN_BTNCLICK: + case DN_EDITCHANGE: + case DN_GOTFOCUS: + case DN_KILLFOCUS: + case DN_LISTCHANGE: + Changed = true; + + case DN_MOUSECLICK: + case DN_CTLCOLORDLGITEM: + case DN_CTLCOLORDLGLIST: + case DN_DRAWDLGITEM: + case DN_HOTKEY: + case DN_KEY: + if (Param1 >= 0) + { + TFarDialogItem * Item = GetItem(Param1); + try + { + Result = Item->ItemProc(Msg, Param2); + } + catch (Exception & E) + { + Handled = true; + DEBUG_PRINTF("before GetFarPlugin()->HandleException"); + GetFarPlugin()->HandleException(&E); + Result = Item->FailItemProc(Msg, Param2); + } + + if (!Result && (Msg == DN_KEY)) + { + Result = Key(Item, Param2); + } + Handled = true; + } + + // FAR WORKAROUND + // When pressing Enter FAR forces dialog to close without calling + // DN_BTNCLICK on default button. This fixes the scenario. + // (first check if focused dialog item is not another button) + if (!Result && (Msg == DN_KEY) && + (Param2 == KEY_ENTER) && + ((Param1 < 0) || + ((NB_STATIC_DOWNCAST(TFarButton, GetItem(Param1)) == nullptr))) && + GetDefaultButton()->GetEnabled() && + (GetDefaultButton()->GetOnClick())) + { + bool Close = (GetDefaultButton()->GetResult() != 0); + GetDefaultButton()->GetOnClick()(GetDefaultButton(), Close); + Handled = true; + if (!Close) + { + Result = 1; + } + } + break; + + case DN_MOUSEEVENT: + Result = MouseEvent(reinterpret_cast(Param2)); + Handled = true; + break; + } + if (!Handled) + { + switch (Msg) + { + case DN_INITDIALOG: + Init(); + Result = 1; + break; + + case DN_DRAGGED: + if (Param1 == 1) + { + RefreshBounds(); + } + break; + + case DN_DRAWDIALOG: + // before drawing the dialog, make sure we know correct coordinates + // (especially while the dialog is being dragged) + RefreshBounds(); + break; + + case DN_CLOSE: + Result = 1; + if (Param1 >= 0) + { + TFarButton * Button = NB_STATIC_DOWNCAST(TFarButton, GetItem(Param1)); + // FAR WORKAROUND + // FAR 1.70 alpha 6 calls DN_CLOSE even for non-button dialog items + // (list boxes in particular), while FAR 1.70 beta 5 used ID of + // default button in such case. + // Particularly for listbox, we can prevent closing dialog using + // flag DIF_LISTNOCLOSE. + if (Button == nullptr) + { + DebugAssert(NB_STATIC_DOWNCAST(TFarListBox, GetItem(Param1)) != nullptr); + Result = static_cast(false); + } + else + { + FResult = Button->GetResult(); + } + } + else + { + FResult = -1; + } + if (Result) + { + Result = CloseQuery(); + if (!Result) + { + FResult = -1; + } + } + Handled = true; + break; + + case DN_ENTERIDLE: + Idle(); + break; + } + + if (!Handled) + { + Result = DefaultDialogProc(Msg, Param1, Param2); + } + } + if (Changed) + { + Change(); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before GetFarPlugin()->HandleException"); + GetFarPlugin()->HandleException(&E); + if (!Handled) + { + Result = FailDialogProc(Msg, Param1, Param2); + } + } + return Result; +} + +LONG_PTR TFarDialog::DefaultDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (GetHandle()) + { + TFarEnvGuard Guard; + return GetFarPlugin()->GetPluginStartupInfo()->DefDlgProc(GetHandle(), Msg, static_cast(Param1), Param2); + } + return 0; +} + +LONG_PTR TFarDialog::FailDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + intptr_t Result = 0; + switch (Msg) + { + case DN_CLOSE: + Result = 0; + break; + + default: + Result = DefaultDialogProc(Msg, Param1, Param2); + break; + } + return Result; +} + +void TFarDialog::Idle() +{ + // nothing +} + +bool TFarDialog::MouseEvent(MOUSE_EVENT_RECORD * Event) +{ + bool Result = true; + bool Handled = false; + if (FLAGSET(Event->dwEventFlags, MOUSE_MOVED)) + { + int X = Event->dwMousePosition.X - GetBounds().Left; + int Y = Event->dwMousePosition.Y - GetBounds().Top; + TFarDialogItem * Item = ItemAt(X, Y); + if (Item != nullptr) + { + Result = Item->MouseMove(X, Y, Event); + Handled = true; + } + } + else + { + Handled = false; + } + + if (!Handled) + { + Result = DefaultDialogProc(DN_MOUSEEVENT, 0, reinterpret_cast(Event)) != 0; + } + + return Result; +} + +bool TFarDialog::Key(TFarDialogItem * Item, LONG_PTR KeyCode) +{ + bool Result = false; + if (FOnKey) + { + FOnKey(this, Item, static_cast(KeyCode), Result); + } + return Result; +} + +bool TFarDialog::HotKey(uintptr_t Key) const +{ + bool Result = false; + char HotKey = 0; + if ((KEY_ALTA <= Key) && (Key <= KEY_ALTZ)) + { + Result = true; + HotKey = static_cast('a' + static_cast(Key - KEY_ALTA)); + } + + if (Result) + { + Result = false; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + if (GetItem(Index)->HotKey(HotKey)) + { + Result = true; + } + } + } + + return Result; +} + +TFarDialogItem * TFarDialog::ItemAt(int X, int Y) +{ + TFarDialogItem * Result = nullptr; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TRect Bounds = GetItem(Index)->GetActualBounds(); + if ((Bounds.Left <= X) && (X <= Bounds.Right) && + (Bounds.Top <= Y) && (Y <= Bounds.Bottom)) + { + Result = GetItem(Index); + } + } + return Result; +} + +bool TFarDialog::CloseQuery() +{ + bool Result = true; + for (intptr_t Index = 0; Index < GetItemCount() && Result; ++Index) + { + if (!GetItem(Index)->CloseQuery()) + { + Result = false; + } + } + return Result; +} + +void TFarDialog::RefreshBounds() +{ + SMALL_RECT Rect = {0}; + SendDlgMessage(DM_GETDLGRECT, 0, reinterpret_cast(&Rect)); + FBounds.Left = Rect.Left; + FBounds.Top = Rect.Top; + FBounds.Right = Rect.Right; + FBounds.Bottom = Rect.Bottom; +} + +void TFarDialog::Init() +{ + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + GetItem(Index)->Init(); + } + + RefreshBounds(); + + Change(); +} + +intptr_t TFarDialog::ShowModal() +{ + FResult = -1; + + TFarDialog * PrevTopDialog = GetFarPlugin()->FTopDialog; + GetFarPlugin()->FTopDialog = this; + HANDLE Handle = INVALID_HANDLE_VALUE; + { + SCOPE_EXIT + { + GetFarPlugin()->FTopDialog = PrevTopDialog; + if (Handle != INVALID_HANDLE_VALUE) + { + GetFarPlugin()->GetPluginStartupInfo()->DialogFree(Handle); + } + }; + DebugAssert(GetDefaultButton()); + DebugAssert(GetDefaultButton()->GetDefault()); + + UnicodeString HelpTopic = GetHelpTopic(); + intptr_t BResult = 0; + + { + TFarEnvGuard Guard; + TRect Bounds = GetBounds(); + const PluginStartupInfo & Info = *GetFarPlugin()->GetPluginStartupInfo(); + Handle = Info.DialogInit( + Info.ModuleNumber, + Bounds.Left, Bounds.Top, Bounds.Right, Bounds.Bottom, + HelpTopic.c_str(), FDialogItems, + static_cast(GetItemCount()), + 0, GetFlags(), + DialogProcGeneral, reinterpret_cast(this)); + BResult = Info.DialogRun(Handle); + } + + if (BResult >= 0) + { + TFarButton * Button = NB_STATIC_DOWNCAST(TFarButton, GetItem(BResult)); + DebugAssert(Button); + // correct result should be already set by TFarButton + DebugAssert(FResult == Button->GetResult()); + FResult = Button->GetResult(); + } + else + { + // allow only one negative value = -1 + FResult = -1; + } + } + + return FResult; +} + +void TFarDialog::BreakSynchronize() +{ + ::SetEvent(FSynchronizeObjects[1]); +} + +void TFarDialog::Synchronize(TThreadMethod Event) +{ + if (FSynchronizeObjects[0] == INVALID_HANDLE_VALUE) + { + FSynchronizeObjects[0] = ::CreateSemaphore(nullptr, 0, 2, nullptr); + FSynchronizeObjects[1] = ::CreateEvent(nullptr, false, false, nullptr); + } + FSynchronizeMethod = Event; + FNeedsSynchronize = true; + ::WaitForMultipleObjects(_countof(FSynchronizeObjects), + reinterpret_cast(&FSynchronizeObjects), false, INFINITE); +} + +void TFarDialog::Close(TFarButton * Button) +{ + DebugAssert(Button != nullptr); + SendDlgMessage(DM_CLOSE, Button->GetItem(), 0); +} + +void TFarDialog::Change() +{ + if (FChangesLocked > 0) + { + FChangesPending = true; + } + else + { + std::unique_ptr NotifiedContainers(new TList()); + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TFarDialogItem * DItem = GetItem(Index); + DItem->Change(); + if (DItem->GetContainer() && NotifiedContainers->IndexOf(DItem->GetContainer()) == NPOS) + { + NotifiedContainers->Add(DItem->GetContainer()); + } + } + + for (intptr_t Index = 0; Index < NotifiedContainers->GetCount(); ++Index) + { + NB_STATIC_DOWNCAST(TFarDialogContainer, (*NotifiedContainers)[Index])->Change(); + } + } +} + +LONG_PTR TFarDialog::SendDlgMessage(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (GetHandle()) + { + TFarEnvGuard Guard; + return GetFarPlugin()->GetPluginStartupInfo()->SendDlgMessage(GetHandle(), + Msg, static_cast(Param1), Param2); + } + return 0; +} + +uintptr_t TFarDialog::GetSystemColor(intptr_t Index) +{ + return static_cast(GetFarPlugin()->FarAdvControl(ACTL_GETCOLOR, ToPtr(Index))); +} + +void TFarDialog::Redraw() +{ + SendDlgMessage(DM_REDRAW, 0, 0); +} + +void TFarDialog::ShowGroup(intptr_t Group, bool Show) +{ + ProcessGroup(Group, MAKE_CALLBACK(TFarDialog::ShowItem, this), &Show); +} + +void TFarDialog::EnableGroup(intptr_t Group, bool Enable) +{ + ProcessGroup(Group, MAKE_CALLBACK(TFarDialog::EnableItem, this), &Enable); +} + +void TFarDialog::ProcessGroup(intptr_t Group, TFarProcessGroupEvent Callback, + void * Arg) +{ + LockChanges(); + { + SCOPE_EXIT + { + UnlockChanges(); + }; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TFarDialogItem * Item = GetItem(Index); + if (Item->GetGroup() == Group) + { + Callback(Item, Arg); + } + } + } +} + +void TFarDialog::ShowItem(TFarDialogItem * Item, void * Arg) +{ + Item->SetVisible(*static_cast(Arg)); +} + +void TFarDialog::EnableItem(TFarDialogItem * Item, void * Arg) +{ + Item->SetEnabled(*static_cast(Arg)); +} + +void TFarDialog::SetItemFocused(TFarDialogItem * Value) +{ + if (Value != GetItemFocused()) + { + DebugAssert(Value); + Value->SetFocus(); + } +} + +UnicodeString TFarDialog::GetMsg(intptr_t MsgId) +{ + return FFarPlugin->GetMsg(MsgId); +} + +void TFarDialog::LockChanges() +{ + DebugAssert(FChangesLocked < 10); + FChangesLocked++; + if (FChangesLocked == 1) + { + DebugAssert(!FChangesPending); + if (GetHandle()) + { + SendDlgMessage(DM_ENABLEREDRAW, static_cast(false), 0); + } + } +} + +void TFarDialog::UnlockChanges() +{ + DebugAssert(FChangesLocked > 0); + FChangesLocked--; + if (FChangesLocked == 0) + { + SCOPE_EXIT + { + if (GetHandle()) + { + this->SendDlgMessage(DM_ENABLEREDRAW, TRUE, 0); + } + }; + if (FChangesPending) + { + FChangesPending = false; + Change(); + } + } +} + +bool TFarDialog::ChangesLocked() +{ + return (FChangesLocked > 0); +} + +TFarDialogContainer::TFarDialogContainer(TFarDialog * ADialog) : + TObject(), + FLeft(0), + FTop(0), + FItems(new TObjectList()), + FDialog(ADialog), + FEnabled(true) +{ + DebugAssert(ADialog); + FItems->SetOwnsObjects(false); + GetDialog()->Add(this); + GetDialog()->GetNextItemPosition(FLeft, FTop); +} + +TFarDialogContainer::~TFarDialogContainer() +{ + SAFE_DESTROY(FItems); +} + +UnicodeString TFarDialogContainer::GetMsg(int MsgId) +{ + return GetDialog()->GetMsg(MsgId); +} + +void TFarDialogContainer::Add(TFarDialogItem * Item) +{ + DebugAssert(FItems->IndexOf(Item) == NPOS); + Item->SetContainer(this); + if (FItems->IndexOf(Item) == NPOS) + FItems->Add(Item); +} + +void TFarDialogContainer::Remove(TFarDialogItem * Item) +{ + DebugAssert(FItems->IndexOf(Item) != NPOS); + Item->SetContainer(nullptr); + FItems->Remove(Item); + if (FItems->GetCount() == 0) + { + delete this; + } +} + +void TFarDialogContainer::SetPosition(intptr_t AIndex, intptr_t Value) +{ + intptr_t & Position = AIndex ? FTop : FLeft; + if (Position != Value) + { + Position = Value; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + NB_STATIC_DOWNCAST(TFarDialogItem, (*FItems)[Index])->DialogResized(); + } + } +} + +void TFarDialogContainer::Change() +{ +} + +void TFarDialogContainer::SetEnabled(bool Value) +{ + if (FEnabled != Value) + { + FEnabled = true; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + NB_STATIC_DOWNCAST(TFarDialogItem, (*FItems)[Index])->UpdateEnabled(); + } + } +} + +intptr_t TFarDialogContainer::GetItemCount() const +{ + return FItems->GetCount(); +} + +TFarDialogItem::TFarDialogItem(TFarDialog * ADialog, uintptr_t AType) : + TObject(), + FDefaultType(AType), + FGroup(0), + FTag(0), + FOnExit(nullptr), + FOnMouseClick(nullptr), + FDialog(ADialog), + FEnabledFollow(nullptr), + FEnabledDependency(nullptr), + FEnabledDependencyNegative(nullptr), + FContainer(nullptr), + FItem(NPOS), + FEnabled(true), + FIsEnabled(true), + FColors(0), + FColorMask(0) +{ + DebugAssert(ADialog); + GetDialog()->Add(this); + + GetDialogItem()->Type = static_cast(AType); +} + +TFarDialogItem::~TFarDialogItem() +{ + TFarDialog * Dlg = GetDialog(); + DebugAssert(!Dlg); + if (Dlg) + { + nb_free((void*)GetDialogItem()->PtrData); + } +} + +const FarDialogItem * TFarDialogItem::GetDialogItem() const +{ + return const_cast(this)->GetDialogItem(); +} + +FarDialogItem * TFarDialogItem::GetDialogItem() +{ + TFarDialog * Dlg = GetDialog(); + DebugAssert(Dlg); + return &Dlg->FDialogItems[GetItem()]; +} + +void TFarDialogItem::SetBounds(const TRect & Value) +{ + if (FBounds != Value) + { + FBounds = Value; + UpdateBounds(); + } +} + +void TFarDialogItem::Detach() +{ + nb_free((void*)GetDialogItem()->PtrData); + FDialog = nullptr; +} + +void TFarDialogItem::DialogResized() +{ + UpdateBounds(); +} + +void TFarDialogItem::ResetBounds() +{ + TRect B = FBounds; + FarDialogItem * DItem = GetDialogItem(); + #define BOUND(DIB, BB, DB, CB) DItem->DIB = B.BB >= 0 ? \ + (GetContainer() ? (int)GetContainer()->CB : 0) + B.BB : GetDialog()->GetSize().DB + B.BB + BOUND(X1, Left, x, GetLeft()); + BOUND(Y1, Top, y, GetTop()); + BOUND(X2, Right, x, GetLeft()); + BOUND(Y2, Bottom, y, GetTop()); + #undef BOUND +} + +void TFarDialogItem::UpdateBounds() +{ + ResetBounds(); + + if (GetDialog()->GetHandle()) + { + TRect B = GetActualBounds(); + SMALL_RECT Rect = {0}; + Rect.Left = static_cast(B.Left); + Rect.Top = static_cast(B.Top); + Rect.Right = static_cast(B.Right); + Rect.Bottom = static_cast(B.Bottom); + SendMessage(DM_SETITEMPOSITION, reinterpret_cast(&Rect)); + } +} + +char TFarDialogItem::GetColor(intptr_t Index) const +{ + return *((reinterpret_cast(&FColors)) + Index); +} + +void TFarDialogItem::SetColor(intptr_t Index, char Value) +{ + if (GetColor(Index) != Value) + { + *((reinterpret_cast(&FColors)) + Index) = Value; + FColorMask |= (0xFF << (Index * 8)); + } +} + +const struct PluginStartupInfo * TFarDialogItem::GetPluginStartupInfo() const +{ + return GetDialog()->GetFarPlugin()->GetPluginStartupInfo(); +} + +void TFarDialogItem::SetFlags(DWORD Value) +{ + if (GetFlags() != Value) + { + DebugAssert(!GetDialog()->GetHandle()); + UpdateFlags(Value); + } +} + +void TFarDialogItem::UpdateFlags(DWORD Value) +{ + if (GetFlags() != Value) + { + GetDialogItem()->Flags = Value; + DialogChange(); + } +} + +TRect TFarDialogItem::GetActualBounds() const +{ + return TRect(GetDialogItem()->X1, GetDialogItem()->Y1, + GetDialogItem()->X2, GetDialogItem()->Y2); +} + +DWORD TFarDialogItem::GetFlags() const +{ + return GetDialogItem()->Flags; +} + +void TFarDialogItem::SetDataInternal(const UnicodeString & Value) +{ + UnicodeString FarData = Value.c_str(); + if (GetDialog()->GetHandle()) + { + SendMessage(DM_SETTEXTPTR, reinterpret_cast(FarData.c_str())); + } + nb_free((void*)GetDialogItem()->PtrData); + GetDialogItem()->PtrData = TCustomFarPlugin::DuplicateStr(FarData, /*AllowEmpty=*/true); + DialogChange(); +} + +void TFarDialogItem::SetData(const UnicodeString & Value) +{ + if (GetData() != Value) + { + SetDataInternal(Value); + } +} + +void TFarDialogItem::UpdateData(const UnicodeString & Value) +{ + UnicodeString FarData = Value.c_str(); + nb_free((void*)GetDialogItem()->PtrData); + GetDialogItem()->PtrData = TCustomFarPlugin::DuplicateStr(FarData, /*AllowEmpty=*/true); +} + +UnicodeString TFarDialogItem::GetData() const +{ + return const_cast(this)->GetData(); +} + +UnicodeString TFarDialogItem::GetData() +{ + UnicodeString Result; + if (GetDialogItem()->PtrData) + { + Result = GetDialogItem()->PtrData; + } + return Result; +} + +void TFarDialogItem::SetType(intptr_t Value) +{ + if (GetType() != Value) + { + DebugAssert(!GetDialog()->GetHandle()); + GetDialogItem()->Type = static_cast(Value); + } +} + +intptr_t TFarDialogItem::GetType() const +{ + return static_cast(GetDialogItem()->Type); +} + +void TFarDialogItem::SetAlterType(intptr_t Index, bool Value) +{ + if (GetAlterType(Index) != Value) + { + SetType(Value ? Index : FDefaultType); + } +} + +bool TFarDialogItem::GetAlterType(intptr_t Index) const +{ + return const_cast(this)->GetAlterType(Index); +} + +bool TFarDialogItem::GetAlterType(intptr_t Index) +{ + return (GetType() == Index); +} + +bool TFarDialogItem::GetFlag(intptr_t Index) const +{ + bool Result = (GetFlags() & (Index & 0xFFFFFFFFFFFFFF00ULL)) != 0; + if (Index & 0x000000FFUL) + { + Result = !Result; + } + return Result; +} + +void TFarDialogItem::SetFlag(intptr_t Index, bool Value) +{ + if (GetFlag(Index) != Value) + { + if (Index & DIF_INVERSE) + { + Value = !Value; + } + + DWORD F = GetFlags(); + FarDialogItemFlags Flag = (FarDialogItemFlags)(Index & 0xFFFFFF00ULL); + bool ToHandle = true; + + switch (Flag) + { + case DIF_DISABLE: + if (GetDialog()->GetHandle()) + { + SendMessage(DM_ENABLE, !Value); + } + break; + + case DIF_HIDDEN: + if (GetDialog()->GetHandle()) + { + SendMessage(DM_SHOWITEM, !Value); + } + break; + + case DIF_3STATE: + if (GetDialog()->GetHandle()) + { + SendMessage(DM_SET3STATE, Value); + } + break; + } + + if (ToHandle) + { + if (Value) + { + F |= Flag; + } + else + { + F &= ~Flag; + } + UpdateFlags(F); + } + } +} + +void TFarDialogItem::SetEnabledFollow(TFarDialogItem * Value) +{ + if (GetEnabledFollow() != Value) + { + FEnabledFollow = Value; + Change(); + } +} + +void TFarDialogItem::SetEnabledDependency(TFarDialogItem * Value) +{ + if (GetEnabledDependency() != Value) + { + FEnabledDependency = Value; + Change(); + } +} + +void TFarDialogItem::SetEnabledDependencyNegative(TFarDialogItem * Value) +{ + if (GetEnabledDependencyNegative() != Value) + { + FEnabledDependencyNegative = Value; + Change(); + } +} + +bool TFarDialogItem::GetIsEmpty() const +{ + return GetData().IsEmpty(); +} + +LONG_PTR TFarDialogItem::FailItemProc(int Msg, LONG_PTR Param) +{ + intptr_t Result = 0; + switch (Msg) + { + case DN_KILLFOCUS: + Result = static_cast(GetItem()); + break; + + default: + Result = DefaultItemProc(Msg, Param); + break; + } + return Result; +} + +LONG_PTR TFarDialogItem::ItemProc(int Msg, LONG_PTR Param) +{ + LONG_PTR Result = 0; + bool Handled = false; + + if (Msg == DN_GOTFOCUS) + { + DoFocus(); + UpdateFocused(true); + } + else if (Msg == DN_KILLFOCUS) + { + DoExit(); + UpdateFocused(false); + } + else if (Msg == DN_MOUSECLICK) + { + MOUSE_EVENT_RECORD * Event = reinterpret_cast(Param); + if (FLAGCLEAR(Event->dwEventFlags, MOUSE_MOVED)) + { + Result = MouseClick(Event); + Handled = true; + } + } + + if (!Handled) + { + Result = DefaultItemProc(Msg, Param); + } + + if (Msg == DN_CTLCOLORDLGITEM && FColorMask) + { + Result &= ~FColorMask; + Result |= (FColors & FColorMask); + } + return Result; +} + +void TFarDialogItem::DoFocus() +{ +} + +void TFarDialogItem::DoExit() +{ + if (FOnExit) + { + FOnExit(this); + } +} + +LONG_PTR TFarDialogItem::DefaultItemProc(int Msg, LONG_PTR Param) +{ + if (GetDialog() && GetDialog()->GetHandle()) + { + TFarEnvGuard Guard; + return GetPluginStartupInfo()->DefDlgProc(GetDialog()->GetHandle(), + Msg, static_cast(GetItem()), Param); + } + return 0; +} + +LONG_PTR TFarDialogItem::DefaultDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (GetDialog() && GetDialog()->GetHandle()) + { + TFarEnvGuard Guard; + return GetPluginStartupInfo()->DefDlgProc(GetDialog()->GetHandle(), + Msg, static_cast(Param1), Param2); + } + return 0; +} + +void TFarDialogItem::Change() +{ + if (GetEnabledFollow() || GetEnabledDependency() || GetEnabledDependencyNegative()) + { + UpdateEnabled(); + } +} + +void TFarDialogItem::SetEnabled(bool Value) +{ + if (GetEnabled() != Value) + { + FEnabled = Value; + UpdateEnabled(); + } +} + +void TFarDialogItem::UpdateEnabled() +{ + bool Value = + GetEnabled() && + (!GetEnabledFollow() || GetEnabledFollow()->GetIsEnabled()) && + (!GetEnabledDependency() || + (!GetEnabledDependency()->GetIsEmpty() && GetEnabledDependency()->GetIsEnabled())) && + (!GetEnabledDependencyNegative() || + (GetEnabledDependencyNegative()->GetIsEmpty() || !GetEnabledDependencyNegative()->GetIsEnabled())) && + (!GetContainer() || GetContainer()->GetEnabled()); + + if (Value != GetIsEnabled()) + { + FIsEnabled = Value; + SetFlag(DIF_DISABLE | DIF_INVERSE, GetIsEnabled()); + } +} + +void TFarDialogItem::DialogChange() +{ + TFarDialog * Dlg = GetDialog(); + DebugAssert(Dlg); + Dlg->Change(); +} + +LONG_PTR TFarDialogItem::SendDialogMessage(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + return GetDialog()->SendDlgMessage(Msg, Param1, Param2); +} + +LONG_PTR TFarDialogItem::SendMessage(int Msg, LONG_PTR Param) +{ + return GetDialog()->SendDlgMessage(Msg, GetItem(), Param); +} + +void TFarDialogItem::SetSelected(intptr_t Value) +{ + if (GetSelected() != Value) + { + if (GetDialog()->GetHandle()) + { + SendMessage(DM_SETCHECK, Value); + } + UpdateSelected(Value); + } +} + +void TFarDialogItem::UpdateSelected(intptr_t Value) +{ + if (GetSelected() != Value) + { + GetDialogItem()->Selected = static_cast(Value); + DialogChange(); + } +} + +intptr_t TFarDialogItem::GetSelected() const +{ + return static_cast(GetDialogItem()->Selected); +} + +bool TFarDialogItem::GetFocused() const +{ + return GetDialogItem()->Focus != 0; +} + +void TFarDialogItem::SetFocused(bool Value) +{ + GetDialogItem()->Focus = Value; +} + +bool TFarDialogItem::GetChecked() const +{ + return GetSelected() == BSTATE_CHECKED; +} + +void TFarDialogItem::SetChecked(bool Value) +{ + SetSelected(Value ? BSTATE_CHECKED : BSTATE_UNCHECKED); +} + +void TFarDialogItem::Move(intptr_t DeltaX, intptr_t DeltaY) +{ + TRect R = GetBounds(); + + R.Left += static_cast(DeltaX); + R.Right += static_cast(DeltaX); + R.Top += static_cast(DeltaY); + R.Bottom += static_cast(DeltaY); + + SetBounds(R); +} + +void TFarDialogItem::MoveAt(intptr_t X, intptr_t Y) +{ + Move(X - GetLeft(), Y - GetTop()); +} + +void TFarDialogItem::SetCoordinate(intptr_t Index, intptr_t Value) +{ + DebugAssert(sizeof(TRect) == sizeof(int) * 4); + TRect R = GetBounds(); + int * D = reinterpret_cast(&R); + D += Index; + *D = static_cast(Value); + SetBounds(R); +} + +intptr_t TFarDialogItem::GetCoordinate(intptr_t Index) const +{ + DebugAssert(sizeof(TRect) == sizeof(int) * 4); + TRect R = GetBounds(); + int * D = reinterpret_cast(&R); + D += Index; + return static_cast(*D); +} + +void TFarDialogItem::SetWidth(intptr_t Value) +{ + TRect R = GetBounds(); + if (R.Left >= 0) + { + R.Right = R.Left + static_cast(Value - 1); + } + else + { + DebugAssert(R.Right < 0); + R.Left = R.Right - static_cast(Value + 1); + } + SetBounds(R); +} + +intptr_t TFarDialogItem::GetWidth() const +{ + return static_cast(GetActualBounds().Width() + 1); +} + +void TFarDialogItem::SetHeight(intptr_t Value) +{ + TRect R = GetBounds(); + if (R.Top >= 0) + { + R.Bottom = static_cast(R.Top + Value - 1); + } + else + { + DebugAssert(R.Bottom < 0); + R.Top = static_cast(R.Bottom - Value + 1); + } + SetBounds(R); +} + +intptr_t TFarDialogItem::GetHeight() const +{ + return static_cast(GetActualBounds().Height() + 1); +} + +bool TFarDialogItem::CanFocus() const +{ + intptr_t Type = GetType(); + return GetVisible() && GetEnabled() && GetTabStop() && + (Type == DI_EDIT || Type == DI_PSWEDIT || Type == DI_FIXEDIT || + Type == DI_BUTTON || Type == DI_CHECKBOX || Type == DI_RADIOBUTTON || + Type == DI_COMBOBOX || Type == DI_LISTBOX || Type == DI_USERCONTROL); +} + +bool TFarDialogItem::Focused() const +{ + return GetFocused(); +} + +void TFarDialogItem::UpdateFocused(bool Value) +{ + SetFocused(Value); + TFarDialog * Dlg = GetDialog(); + DebugAssert(Dlg); + Dlg->SetItemFocused(Value ? this : nullptr); +} + +void TFarDialogItem::SetFocus() +{ + DebugAssert(CanFocus()); + if (!Focused()) + { + if (GetDialog()->GetHandle()) + { + SendMessage(DM_SETFOCUS, 0); + } + else + { + if (GetDialog()->GetItemFocused()) + { + DebugAssert(GetDialog()->GetItemFocused() != this); + GetDialog()->GetItemFocused()->UpdateFocused(false); + } + UpdateFocused(true); + } + } +} + +void TFarDialogItem::Init() +{ + if (GetFlag(DIF_CENTERGROUP)) + { + SMALL_RECT Rect; + ClearStruct(Rect); + + // at least for "text" item, returned item size is not correct (on 1.70 final) + SendMessage(DM_GETITEMPOSITION, reinterpret_cast(&Rect)); + + TRect B = GetBounds(); + B.Left = Rect.Left; + B.Right = Rect.Right; + SetBounds(B); + } +} + +bool TFarDialogItem::CloseQuery() +{ + if (Focused() && (GetDialog()->GetResult() >= 0)) + { + DoExit(); + } + return true; +} + +TPoint TFarDialogItem::MouseClientPosition(MOUSE_EVENT_RECORD * Event) +{ + TPoint Result; + if (GetType() == DI_USERCONTROL) + { + Result = TPoint(Event->dwMousePosition.X, Event->dwMousePosition.Y); + } + else + { + Result = TPoint( + static_cast(Event->dwMousePosition.X - GetDialog()->GetBounds().Left - GetLeft()), + static_cast(Event->dwMousePosition.Y - GetDialog()->GetBounds().Top - GetTop())); + } + return Result; +} + +bool TFarDialogItem::MouseClick(MOUSE_EVENT_RECORD * Event) +{ + if (FOnMouseClick) + { + FOnMouseClick(this, Event); + } + return DefaultItemProc(DN_MOUSECLICK, reinterpret_cast(Event)) != 0; +} + +bool TFarDialogItem::MouseMove(int /*X*/, int /*Y*/, + MOUSE_EVENT_RECORD * Event) +{ + return DefaultDialogProc(DN_MOUSEEVENT, 0, reinterpret_cast(Event)) != 0; +} + +void TFarDialogItem::Text(int X, int Y, uintptr_t Color, const UnicodeString & Str) +{ + TFarEnvGuard Guard; + GetPluginStartupInfo()->Text( + static_cast(GetDialog()->GetBounds().Left + GetLeft() + X), + static_cast(GetDialog()->GetBounds().Top + GetTop() + Y), + static_cast(Color), Str.c_str()); +} + +void TFarDialogItem::Redraw() +{ + // do not know how to force redraw of the item only + GetDialog()->Redraw(); +} + +void TFarDialogItem::SetContainer(TFarDialogContainer * Value) +{ + if (GetContainer() != Value) + { + TFarDialogContainer * PrevContainer = GetContainer(); + FContainer = Value; + if (PrevContainer) + { + PrevContainer->Remove(this); + } + if (GetContainer()) + { + GetContainer()->Add(this); + } + UpdateBounds(); + UpdateEnabled(); + } +} + +bool TFarDialogItem::HotKey(char /*HotKey*/) +{ + return false; +} + +TFarBox::TFarBox(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_SINGLEBOX) +{ +} + +TFarButton::TFarButton(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_BUTTON), + FResult(0), + FOnClick(nullptr), + FBrackets(brNormal) +{ +} + +void TFarButton::SetDataInternal(const UnicodeString & AValue) +{ + UnicodeString Value; + switch (FBrackets) + { + case brTight: + Value = L"[" + AValue + L"]"; + break; + + case brSpace: + Value = L" " + AValue + L" "; + break; + + default: + Value = AValue; + break; + } + + TFarDialogItem::SetDataInternal(Value); + + if ((GetLeft() >= 0) || (GetRight() >= 0)) + { + int Margin = 0; + switch (FBrackets) + { + case brNone: + Margin = 0; + break; + + case brTight: + case brSpace: + Margin = 1; + break; + + case brNormal: + Margin = 2; + break; + } + SetWidth(Margin + ::StripHotkey(Value).GetLength() + Margin); + } +} + +UnicodeString TFarButton::GetData() +{ + UnicodeString Result = TFarDialogItem::GetData(); + if ((FBrackets == brTight) || (FBrackets == brSpace)) + { + bool HasBrackets = (Result.Length() >= 2) && + (Result[1] == ((FBrackets == brSpace) ? L' ' : L'[')) && + (Result[Result.Length()] == ((FBrackets == brSpace) ? L' ' : L']')); + DebugAssert(HasBrackets); + if (HasBrackets) + { + Result = Result.SubString(2, Result.Length() - 2); + } + } + return Result; +} + +void TFarButton::SetDefault(bool Value) +{ + if (GetDefault() != Value) + { + DebugAssert(!GetDialog()->GetHandle()); + GetDialogItem()->DefaultButton = Value; + if (Value) + { + if (GetDialog()->GetDefaultButton() && (GetDialog()->GetDefaultButton() != this)) + { + GetDialog()->GetDefaultButton()->SetDefault(false); + } + GetDialog()->FDefaultButton = this; + } + else if (GetDialog()->GetDefaultButton() == this) + { + GetDialog()->FDefaultButton = nullptr; + } + DialogChange(); + } +} + +bool TFarButton::GetDefault() const +{ + return GetDialogItem()->DefaultButton != 0; +} + +void TFarButton::SetBrackets(TFarButtonBrackets Value) +{ + if (FBrackets != Value) + { + UnicodeString Data = GetData(); + SetFlag(DIF_NOBRACKETS, (Value != brNormal)); + FBrackets = Value; + SetDataInternal(Data); + } +} + +LONG_PTR TFarButton::ItemProc(int Msg, LONG_PTR Param) +{ + if (Msg == DN_BTNCLICK) + { + if (!GetEnabled()) + { + return 1; + } + else + { + bool Close = (GetResult() != 0); + if (FOnClick) + { + FOnClick(this, Close); + } + if (!Close) + { + return 1; + } + } + } + return TFarDialogItem::ItemProc(Msg, Param); +} + +bool TFarButton::HotKey(char HotKey) +{ + UnicodeString Caption = GetCaption(); + intptr_t P = Caption.Pos(L'&'); + bool Result = + GetVisible() && GetEnabled() && + (P > 0) && (P < Caption.Length()) && + (Caption[P + 1] == HotKey); + if (Result) + { + bool Close = (GetResult() != 0); + if (FOnClick) + { + FOnClick(this, Close); + } + + if (Close) + { + GetDialog()->Close(this); + } + } + return Result; +} + +TFarCheckBox::TFarCheckBox(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_CHECKBOX), + FOnAllowChange(nullptr) +{ +} + +LONG_PTR TFarCheckBox::ItemProc(int Msg, LONG_PTR Param) +{ + if (Msg == DN_BTNCLICK) + { + bool Allow = true; + if (FOnAllowChange) + { + FOnAllowChange(this, Param, Allow); + } + if (Allow) + { + UpdateSelected(Param); + } + return static_cast(Allow); + } + else + { + return TFarDialogItem::ItemProc(Msg, Param); + } +} + +bool TFarCheckBox::GetIsEmpty() const +{ + return GetChecked() != BSTATE_CHECKED; +} + +void TFarCheckBox::SetData(const UnicodeString & Value) +{ + TFarDialogItem::SetData(Value); + if (GetLeft() >= 0 || GetRight() >= 0) + { + SetWidth(4 + ::StripHotkey(Value).Length()); + } +} + +TFarRadioButton::TFarRadioButton(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_RADIOBUTTON), + FOnAllowChange(nullptr) +{ +} + +LONG_PTR TFarRadioButton::ItemProc(int Msg, LONG_PTR Param) +{ + if (Msg == DN_BTNCLICK) + { + bool Allow = true; + if (FOnAllowChange) + { + FOnAllowChange(this, Param, Allow); + } + if (Allow) + { + // FAR WORKAROUND + // This does not correspond to FAR API Manual, but it works so. + // Manual says that Param should contain ID of previously selected dialog item + UpdateSelected(Param); + } + return static_cast(Allow); + } + else + { + return TFarDialogItem::ItemProc(Msg, Param); + } +} + +bool TFarRadioButton::GetIsEmpty() const +{ + return !GetChecked(); +} + +void TFarRadioButton::SetData(const UnicodeString & Value) +{ + TFarDialogItem::SetData(Value); + if (GetLeft() >= 0 || GetRight() >= 0) + { + SetWidth(4 + ::StripHotkey(Value).Length()); + } +} + +TFarEdit::TFarEdit(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_EDIT) +{ + SetAutoSelect(false); +} + +void TFarEdit::Detach() +{ + nb_free((void*)GetDialogItem()->Mask); + // nb_free((void*)GetDialogItem()->History); + TFarDialogItem::Detach(); +} + +LONG_PTR TFarEdit::ItemProc(int Msg, LONG_PTR Param) +{ + if (Msg == DN_EDITCHANGE) + { + UnicodeString Data = (reinterpret_cast(Param))->PtrData; + nb_free((void*)GetDialogItem()->PtrData); + GetDialogItem()->PtrData = TCustomFarPlugin::DuplicateStr(Data, /*AllowEmpty=*/true); + } + return TFarDialogItem::ItemProc(Msg, Param); +} + +UnicodeString TFarEdit::GetHistoryMask(size_t Index) const +{ + UnicodeString Result = + ((Index == 0) && (GetFlags() & DIF_HISTORY)) || + ((Index == 1) && (GetFlags() & DIF_MASKEDIT)) ? GetDialogItem()->Mask : L""; + return Result; +} + +void TFarEdit::SetHistoryMask(size_t Index, const UnicodeString & Value) +{ + if (GetHistoryMask(Index) != Value) + { + DebugAssert(!GetDialog()->GetHandle()); + DebugAssert(&GetDialogItem()->Mask == &GetDialogItem()->History); + + nb_free((void*)GetDialogItem()->Mask); + if (Value.IsEmpty()) + { + GetDialogItem()->Mask = nullptr; + } + else + { + GetDialogItem()->Mask = TCustomFarPlugin::DuplicateStr(Value); + } + bool PrevHistory = !GetHistory().IsEmpty(); + SetFlag(DIF_HISTORY, (Index == 0) && !Value.IsEmpty()); + bool Masked = (Index == 1) && !Value.IsEmpty(); + SetFlag(DIF_MASKEDIT, Masked); + if (Masked) + { + SetFixed(true); + } + bool CurrHistory = !GetHistory().IsEmpty(); + if (PrevHistory != CurrHistory) + { + // add/remove space for history arrow + SetWidth(GetWidth() + (CurrHistory ? -1 : 1)); + } + DialogChange(); + } +} + +void TFarEdit::SetAsInteger(intptr_t Value) +{ + intptr_t Int = GetAsInteger(); + if (!Int || (Int != Value)) + { + SetText(::IntToStr(Value)); + DialogChange(); + } +} + +intptr_t TFarEdit::GetAsInteger() +{ + return ::StrToIntDef(::Trim(GetText()), 0); +} + +TFarSeparator::TFarSeparator(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_TEXT) +{ + SetLeft(-1); + SetFlag(DIF_SEPARATOR, true); +} + +void TFarSeparator::ResetBounds() +{ + TFarDialogItem::ResetBounds(); + if (GetBounds().Left < 0) + { + GetDialogItem()->X1 = -1; + } +} + +void TFarSeparator::SetDouble(bool Value) +{ + if (GetDouble() != Value) + { + DebugAssert(!GetDialog()->GetHandle()); + SetFlag(DIF_SEPARATOR, !Value); + SetFlag(DIF_SEPARATOR2, Value); + } +} + +bool TFarSeparator::GetDouble() +{ + return GetFlag(DIF_SEPARATOR2); +} + +void TFarSeparator::SetPosition(intptr_t Value) +{ + TRect R = GetBounds(); + R.Top = static_cast(Value); + R.Bottom = static_cast(Value); + SetBounds(R); +} + +int TFarSeparator::GetPosition() +{ + return GetBounds().Top; +} + +TFarText::TFarText(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_TEXT) +{ +} + +void TFarText::SetData(const UnicodeString & Value) +{ + TFarDialogItem::SetData(Value); + if (GetLeft() >= 0 || GetRight() >= 0) + { + SetWidth(::StripHotkey(Value).Length()); + } +} + +TFarList::TFarList(TFarDialogItem * ADialogItem) : + TStringList(), + FDialogItem(ADialogItem), + FNoDialogUpdate(false) +{ + DebugAssert((ADialogItem == nullptr) || + (ADialogItem->GetType() == DI_COMBOBOX) || (ADialogItem->GetType() == DI_LISTBOX)); + FListItems = static_cast(nb_calloc(1, sizeof(FarList))); +} + +TFarList::~TFarList() +{ + for (intptr_t Index = 0; Index < FListItems->ItemsNumber; ++Index) + { + nb_free((void*)FListItems->Items[Index].Text); + } + nb_free(FListItems->Items); + nb_free(FListItems); +} + +void TFarList::Assign(const TPersistent * Source) +{ + TStringList::Assign(Source); + + const TFarList * FarList = NB_STATIC_DOWNCAST_CONST(TFarList, Source); + if (FarList != nullptr) + { + for (intptr_t Index = 0; Index < FarList->GetCount(); ++Index) + { + SetFlags(Index, FarList->GetFlags(Index)); + } + } +} + +void TFarList::UpdateItem(intptr_t Index) +{ + FarListItem * ListItem = &FListItems->Items[Index]; + nb_free((void*)ListItem->Text); + ListItem->Text = TCustomFarPlugin::DuplicateStr(GetString(Index), /*AllowEmpty=*/true); + + FarListUpdate ListUpdate; + ClearStruct(ListUpdate); + ListUpdate.Index = static_cast(Index); + ListUpdate.Item = *ListItem; + GetDialogItem()->SendMessage(DM_LISTUPDATE, reinterpret_cast(&ListUpdate)); +} + +void TFarList::Put(intptr_t Index, const UnicodeString & Str) +{ + if ((GetDialogItem() != nullptr) && GetDialogItem()->GetDialog()->GetHandle()) + { + FNoDialogUpdate = true; + SCOPE_EXIT + { + FNoDialogUpdate = false; + }; + TStringList::SetString(Index, Str); + if (GetUpdateCount() == 0) + { + UpdateItem(Index); + } + } + else + { + TStringList::SetString(Index, Str); + } +} + +void TFarList::Changed() +{ + TStringList::Changed(); + + if ((GetUpdateCount() == 0) && !FNoDialogUpdate) + { + intptr_t PrevSelected = 0; + intptr_t PrevTopIndex = 0; + if ((GetDialogItem() != nullptr) && GetDialogItem()->GetDialog()->GetHandle()) + { + PrevSelected = GetSelected(); + PrevTopIndex = GetTopIndex(); + } + intptr_t Count = GetCount(); + if (FListItems->ItemsNumber != Count) + { + FarListItem * Items = FListItems->Items; + intptr_t ItemsNumber = FListItems->ItemsNumber; + if (Count) + { + FListItems->Items = static_cast( + nb_calloc(1, sizeof(FarListItem) * Count)); + for (intptr_t Index = 0; Index < Count; ++Index) + { + if (Index < FListItems->ItemsNumber) + { + FListItems->Items[Index].Flags = Items[Index].Flags; + } + } + } + else + { + FListItems->Items = nullptr; + } + for (intptr_t Index = 0; Index < ItemsNumber; ++Index) + { + nb_free((void*)Items[Index].Text); + } + nb_free(Items); + FListItems->ItemsNumber = static_cast(GetCount()); + } + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + FListItems->Items[Index].Text = TCustomFarPlugin::DuplicateStr(GetString(Index), /*AllowEmpty=*/true); + } + if ((GetDialogItem() != nullptr) && GetDialogItem()->GetDialog()->GetHandle()) + { + GetDialogItem()->GetDialog()->LockChanges(); + SCOPE_EXIT + { + GetDialogItem()->GetDialog()->UnlockChanges(); + }; + GetDialogItem()->SendMessage(DM_LISTSET, reinterpret_cast(FListItems)); + if (PrevTopIndex + GetDialogItem()->GetHeight() > GetCount()) + { + PrevTopIndex = GetCount() > GetDialogItem()->GetHeight() ? GetCount() - GetDialogItem()->GetHeight() : 0; + } + SetCurPos((PrevSelected >= GetCount()) ? (GetCount() - 1) : PrevSelected, + PrevTopIndex); + } + } +} + +void TFarList::SetSelected(intptr_t Value) +{ + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + if (GetSelectedInt(false) != Value) + { + if (DialogItem->GetDialog()->GetHandle()) + { + UpdatePosition(Value); + } + else + { + DialogItem->SetData(GetString(Value)); + } + } +} + +void TFarList::UpdatePosition(intptr_t Position) +{ + if (Position >= 0) + { + intptr_t ATopIndex = GetTopIndex(); + // even if new position is visible already, FAR will scroll the view so + // that the new selected item is the last one, following prevents the scroll + if ((ATopIndex <= Position) && (Position < ATopIndex + GetVisibleCount())) + { + SetCurPos(Position, ATopIndex); + } + else + { + SetCurPos(Position, -1); + } + } +} + +void TFarList::SetCurPos(intptr_t Position, intptr_t TopIndex) +{ + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + TFarDialog * Dlg = DialogItem->GetDialog(); + DebugAssert(Dlg); + DebugAssert(Dlg->GetHandle()); + DebugUsedParam(Dlg); + FarListPos ListPos; + ListPos.SelectPos = static_cast(Position); + ListPos.TopPos = static_cast(TopIndex); + DialogItem->SendMessage(DM_LISTSETCURPOS, reinterpret_cast(&ListPos)); +} + +void TFarList::SetTopIndex(intptr_t Value) +{ + if (Value != GetTopIndex()) + { + SetCurPos(NPOS, Value); + } +} + +intptr_t TFarList::GetPosition() const +{ + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + return DialogItem->SendMessage(DM_LISTGETCURPOS, 0); +} + +intptr_t TFarList::GetTopIndex() const +{ + intptr_t Result = -1; + if (GetCount() != 0) + { + FarListPos ListPos; + ClearStruct(ListPos); + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + DialogItem->SendMessage(DM_LISTGETCURPOS, reinterpret_cast(&ListPos)); + Result = static_cast(ListPos.TopPos); + } + return Result; +} + +intptr_t TFarList::GetMaxLength() const +{ + intptr_t Result = 0; + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (Result < GetString(Index).Length()) + { + Result = GetString(Index).Length(); + } + } + return Result; +} + +intptr_t TFarList::GetVisibleCount() const +{ + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + return DialogItem->GetHeight() - (GetDialogItem()->GetFlag(DIF_LISTNOBOX) ? 0 : 2); +} + +intptr_t TFarList::GetSelectedInt(bool Init) const +{ + intptr_t Result = NPOS; + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + if (GetCount() == 0) + { + Result = NPOS; + } + else if (DialogItem->GetDialog()->GetHandle() && !Init) + { + Result = GetPosition(); + } + else + { + const wchar_t * PtrData = DialogItem->GetDialogItem()->PtrData; + if (PtrData) + { + Result = IndexOf(PtrData); + } + } + + return Result; +} + +intptr_t TFarList::GetSelected() const +{ + intptr_t Result = GetSelectedInt(false); + + if ((Result == NPOS) && (GetCount() > 0)) + { + Result = 0; + } + + return Result; +} + +DWORD TFarList::GetFlags(intptr_t Index) const +{ + return FListItems->Items[Index].Flags; +} + +void TFarList::SetFlags(intptr_t Index, DWORD Value) +{ + if (FListItems->Items[Index].Flags != Value) + { + FListItems->Items[Index].Flags = Value; + if ((GetDialogItem() != nullptr) && GetDialogItem()->GetDialog()->GetHandle() && (GetUpdateCount() == 0)) + { + UpdateItem(Index); + } + } +} + +bool TFarList::GetFlag(intptr_t Index, DWORD Flag) const +{ + return FLAGSET(GetFlags(Index), Flag); +} + +void TFarList::SetFlag(intptr_t Index, DWORD Flag, bool Value) +{ + SetFlags(Index, (GetFlags(Index) & ~Flag) | FLAGMASK(Value, Flag)); +} + +void TFarList::Init() +{ + UpdatePosition(GetSelectedInt(true)); +} + +LONG_PTR TFarList::ItemProc(int Msg, LONG_PTR Param) +{ + TFarDialogItem * DialogItem = GetDialogItem(); + DebugAssert(DialogItem != nullptr); + if (Msg == DN_LISTCHANGE) + { + if ((Param < 0) || ((Param == 0) && (GetCount() == 0))) + { + DialogItem->UpdateData(L""); + } + else + { + DebugAssert(Param >= 0 && Param < GetCount()); + DialogItem->UpdateData(GetString(Param)); + } + } + return 0; +} + +TFarListBox::TFarListBox(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_LISTBOX), + FAutoSelect(asOnlyFocus), + FDenyClose(false) +{ + FList = new TFarList(this); + GetDialogItem()->ListItems = FList->GetListItems(); +} + +TFarListBox::~TFarListBox() +{ + SAFE_DESTROY(FList); +} + +LONG_PTR TFarListBox::ItemProc(int Msg, LONG_PTR Param) +{ + intptr_t Result = 0; + // FAR WORKAROUND + // Since 1.70 final, hotkeys do not work when list box has focus. + if ((Msg == DN_KEY) && GetDialog()->HotKey(Param)) + { + Result = 1; + } + else if (FList->ItemProc(Msg, Param)) + { + Result = 1; + } + else + { + Result = TFarDialogItem::ItemProc(Msg, Param); + } + return Result; +} + +void TFarListBox::Init() +{ + TFarDialogItem::Init(); + GetItems()->Init(); + UpdateMouseReaction(); +} + +void TFarListBox::SetAutoSelect(TFarListBoxAutoSelect Value) +{ + if (GetAutoSelect() != Value) + { + FAutoSelect = Value; + if (GetDialog()->GetHandle()) + { + UpdateMouseReaction(); + } + } +} + +void TFarListBox::UpdateMouseReaction() +{ + SendMessage(DM_LISTSETMOUSEREACTION, static_cast(GetAutoSelect())); +} + +void TFarListBox::SetItems(TStrings * Value) +{ + FList->Assign(Value); +} + +void TFarListBox::SetList(TFarList * Value) +{ + SetItems(Value); +} + +bool TFarListBox::CloseQuery() +{ + return true; +} + +TFarComboBox::TFarComboBox(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_COMBOBOX), + FList(nullptr) +{ + FList = new TFarList(this); + GetDialogItem()->ListItems = FList->GetListItems(); + SetAutoSelect(false); +} + +TFarComboBox::~TFarComboBox() +{ + SAFE_DESTROY(FList); +} + +void TFarComboBox::ResizeToFitContent() +{ + SetWidth(FList->GetMaxLength()); +} + +LONG_PTR TFarComboBox::ItemProc(int Msg, LONG_PTR Param) +{ + if (Msg == DN_EDITCHANGE) + { + UnicodeString Data = (reinterpret_cast(Param))->PtrData; + nb_free((void*)GetDialogItem()->PtrData); + GetDialogItem()->PtrData = TCustomFarPlugin::DuplicateStr(Data, /*AllowEmpty=*/true); + } + + if (FList->ItemProc(Msg, Param)) + { + return 1; + } + else + { + return TFarDialogItem::ItemProc(Msg, Param); + } +} + +void TFarComboBox::Init() +{ + TFarDialogItem::Init(); + GetItems()->Init(); +} + +TFarLister::TFarLister(TFarDialog * ADialog) : + TFarDialogItem(ADialog, DI_USERCONTROL), + FItems(new TStringList()), + FTopIndex(0) +{ + FItems->SetOnChange(MAKE_CALLBACK(TFarLister::ItemsChange, this)); +} + +TFarLister::~TFarLister() +{ + SAFE_DESTROY(FItems); +} + +void TFarLister::ItemsChange(TObject * /*Sender*/) +{ + FTopIndex = 0; + if (GetDialog()->GetHandle()) + { + Redraw(); + } +} + +bool TFarLister::GetScrollBar() const +{ + return (GetItems()->GetCount() > GetHeight()); +} + +void TFarLister::SetTopIndex(intptr_t Value) +{ + if (GetTopIndex() != Value) + { + FTopIndex = Value; + Redraw(); + } +} + +TStrings * TFarLister::GetItems() const +{ + return FItems; +} + +void TFarLister::SetItems(const TStrings * Value) +{ + if (!FItems->Equals(Value)) + { + FItems->Assign(Value); + } +} + +void TFarLister::DoFocus() +{ + TFarDialogItem::DoFocus(); + TODO("hide cursor"); +} + +LONG_PTR TFarLister::ItemProc(int Msg, LONG_PTR Param) +{ + intptr_t Result = 0; + + if (Msg == DN_DRAWDLGITEM) + { + bool AScrollBar = GetScrollBar(); + intptr_t ScrollBarPos = 0; + if (GetItems()->GetCount() > GetHeight()) + { + ScrollBarPos = static_cast((static_cast(GetHeight() - 3) * (static_cast(FTopIndex) / (GetItems()->GetCount() - GetHeight())))) + 1; + } + intptr_t DisplayWidth = GetWidth() - (AScrollBar ? 1 : 0); + uintptr_t Color = GetDialog()->GetSystemColor( + FLAGSET(GetDialog()->GetFlags(), FDLG_WARNING) ? COL_WARNDIALOGLISTTEXT : COL_DIALOGLISTTEXT); + UnicodeString Buf; + for (intptr_t Row = 0; Row < GetHeight(); Row++) + { + intptr_t Index = GetTopIndex() + Row; + Buf = L" "; + if (Index < GetItems()->GetCount()) + { + UnicodeString Value = GetItems()->GetString(Index).SubString(1, DisplayWidth - 1); + Buf += Value; + } + UnicodeString Value = ::StringOfChar(' ', DisplayWidth - Buf.Length()); + Value.SetLength(DisplayWidth - Buf.Length()); + Buf += Value; + if (AScrollBar) + { + if (Row == 0) + { + Buf += static_cast(0x25B2); // ucUpScroll + } + else if (Row == ScrollBarPos) + { + Buf += static_cast(0x2592); // ucBox50 + } + else if (Row == GetHeight() - 1) + { + Buf += static_cast(0x25BC); // ucDnScroll + } + else + { + Buf += static_cast(0x2591); // ucBox25 + } + } + Text(0, (int)Row, Color, Buf); + } + } + else if (Msg == DN_KEY) + { + Result = 1; + + intptr_t NewTopIndex = GetTopIndex(); + if ((Param == KEY_UP) || (Param == KEY_LEFT)) + { + if (NewTopIndex > 0) + { + --NewTopIndex; + } + else + { + long ShiftTab = KEY_SHIFTTAB; + SendDialogMessage(DM_KEY, 1, reinterpret_cast(&ShiftTab)); + } + } + else if ((Param == KEY_DOWN) || (Param == KEY_RIGHT)) + { + if (NewTopIndex < GetItems()->GetCount() - GetHeight()) + { + ++NewTopIndex; + } + else + { + intptr_t Tab = KEY_TAB; + SendDialogMessage(DM_KEY, 1, reinterpret_cast(&Tab)); + } + } + else if (Param == KEY_PGUP) + { + if (NewTopIndex > GetHeight() - 1) + { + NewTopIndex -= GetHeight() - 1; + } + else + { + NewTopIndex = 0; + } + } + else if (Param == KEY_PGDN) + { + if (NewTopIndex < GetItems()->GetCount() - GetHeight() - GetHeight() + 1) + { + NewTopIndex += GetHeight() - 1; + } + else + { + NewTopIndex = GetItems()->GetCount() - GetHeight(); + } + } + else if (Param == KEY_HOME) + { + NewTopIndex = 0; + } + else if (Param == KEY_END) + { + NewTopIndex = GetItems()->GetCount() - GetHeight(); + } + else + { + Result = TFarDialogItem::ItemProc(Msg, Param); + } + + SetTopIndex(NewTopIndex); + } + else if (Msg == DN_MOUSECLICK) + { + if (!Focused() && CanFocus()) + { + SetFocus(); + } + + MOUSE_EVENT_RECORD * Event = reinterpret_cast(Param); + TPoint P = MouseClientPosition(Event); + + if (FLAGSET(Event->dwEventFlags, DOUBLE_CLICK) && + (P.x < GetWidth() - 1)) + { + Result = TFarDialogItem::ItemProc(Msg, Param); + } + else + { + intptr_t NewTopIndex = GetTopIndex(); + + if (((P.x == static_cast(GetWidth()) - 1) && (P.y == 0)) || + ((P.x < static_cast(GetWidth() - 1)) && (P.y < static_cast(GetHeight() / 2)))) + { + if (NewTopIndex > 0) + { + --NewTopIndex; + } + } + else if (((P.x == GetWidth() - 1) && (P.y == static_cast(GetHeight() - 1))) || + ((P.x < GetWidth() - 1) && (P.y >= static_cast(GetHeight() / 2)))) + { + if (NewTopIndex < GetItems()->GetCount() - GetHeight()) + { + ++NewTopIndex; + } + } + else + { + DebugAssert(P.x == GetWidth() - 1); + DebugAssert((P.y > 0) && (P.y < static_cast(GetHeight() - 1))); + NewTopIndex = static_cast(ceil(static_cast(P.y - 1) / (GetHeight() - 2) * (GetItems()->GetCount() - GetHeight() + 1))); + } + + Result = 1; + + SetTopIndex(NewTopIndex); + } + } + else + { + Result = TFarDialogItem::ItemProc(Msg, Param); + } + + return Result; +} + +NB_IMPLEMENT_CLASS(TFarDialog, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TFarDialogItem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TFarCheckBox, NB_GET_CLASS_INFO(TFarDialogItem), nullptr) +NB_IMPLEMENT_CLASS(TFarButton, NB_GET_CLASS_INFO(TFarDialogItem), nullptr) +NB_IMPLEMENT_CLASS(TFarListBox, NB_GET_CLASS_INFO(TFarDialogItem), nullptr) +NB_IMPLEMENT_CLASS(TFarEdit, NB_GET_CLASS_INFO(TFarDialogItem), nullptr) +NB_IMPLEMENT_CLASS(TFarText, NB_GET_CLASS_INFO(TFarDialogItem), nullptr) +NB_IMPLEMENT_CLASS(TFarList, NB_GET_CLASS_INFO(TStringList), nullptr) +NB_IMPLEMENT_CLASS(TFarDialogContainer, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/NetBox/FarDialog.h b/netbox/src/NetBox/FarDialog.h new file mode 100644 index 000000000..92f8925a6 --- /dev/null +++ b/netbox/src/NetBox/FarDialog.h @@ -0,0 +1,666 @@ +#pragma once + +#include "FarPlugin.h" + +#define MAX_SIZE -1 + +class TFarDialogContainer; +class TFarDialogItem; +class TFarButton; +class TFarSeparator; +class TFarBox; +class TFarList; +struct FarDialogItem; +enum TItemPosition +{ + ipNewLine, + ipBelow, + ipRight +}; + +DEFINE_CALLBACK_TYPE4(TFarKeyEvent, void, + TFarDialog * /*Sender*/, TFarDialogItem * /*Item*/, long /*KeyCode*/, bool & /*Handled*/); +DEFINE_CALLBACK_TYPE2(TFarMouseClickEvent, void, + TFarDialogItem * /*Item*/, MOUSE_EVENT_RECORD * /*Event*/); +DEFINE_CALLBACK_TYPE2(TFarProcessGroupEvent, void, + TFarDialogItem * /*Item*/, void * /*Arg*/); + +class TFarDialog : public TObject +{ +friend TFarDialogItem; +friend TFarDialogContainer; +friend TFarButton; +friend TFarList; +friend class TFarListBox; +NB_DISABLE_COPY(TFarDialog) +NB_DECLARE_CLASS(TFarDialog) +public: + explicit TFarDialog(TCustomFarPlugin * AFarPlugin); + virtual ~TFarDialog(); + + intptr_t ShowModal(); + void ShowGroup(intptr_t Group, bool Show); + void EnableGroup(intptr_t Group, bool Enable); + + TRect GetBounds() const { return FBounds; } + TRect GetClientRect() const; + UnicodeString GetHelpTopic() const { return FHelpTopic; } + void SetHelpTopic(const UnicodeString & Value); + DWORD GetFlags() const { return FFlags; } + void SetFlags(DWORD Value); + bool GetCentered() const; + void SetCentered(bool Value); + TPoint GetSize() const; + void SetSize(TPoint Value); + TPoint GetClientSize() const; + intptr_t GetWidth() const; + void SetWidth(intptr_t Value); + intptr_t GetHeight() const; + void SetHeight(intptr_t Value); + UnicodeString GetCaption() const; + void SetCaption(const UnicodeString & Value); + HANDLE GetHandle() const { return FHandle; } + TFarButton * GetDefaultButton() const { return FDefaultButton; } + TFarBox * GetBorderBox() const { return FBorderBox; } + intptr_t GetType(TFarDialogItem * Item) const; + intptr_t GetItem(TFarDialogItem * Item) const; + TFarDialogItem * GetItem(intptr_t Index) const; + TFarDialogItem * GetControl(intptr_t Index) const { return GetItem(Index); } + intptr_t GetItemCount() const; + intptr_t GetControlCount() const { return GetItemCount(); } + TItemPosition GetNextItemPosition() const { return FNextItemPosition; } + void SetNextItemPosition(const TItemPosition Value) { FNextItemPosition = Value; } + intptr_t GetDefaultGroup() const { return FDefaultGroup; } + void SetDefaultGroup(intptr_t Value) { FDefaultGroup = Value; } + intptr_t GetTag() const { return FTag; } + void SetTag(intptr_t Value) { FTag = Value; } + TFarDialogItem * GetItemFocused() const { return FItemFocused; } + void SetItemFocused(TFarDialogItem * Value); + intptr_t GetResult() const { return FResult; } + TPoint GetMaxSize(); + + TFarKeyEvent & GetOnKey() { return FOnKey; } + void SetOnKey(TFarKeyEvent Value) { FOnKey = Value; } + + void Redraw(); + void LockChanges(); + void UnlockChanges(); + uintptr_t GetSystemColor(intptr_t Index); + bool HotKey(uintptr_t Key) const; + +protected: + TCustomFarPlugin * GetFarPlugin() const { return FFarPlugin; } + TCustomFarPlugin * GetFarPlugin() { return FFarPlugin; } + TObjectList * GetItems() const { return FItems; } + TObjectList * GetItems() { return FItems; } + void Add(TFarDialogItem * Item); + void Add(TFarDialogContainer * Container); + LONG_PTR SendDlgMessage(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual LONG_PTR DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual LONG_PTR FailDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + LONG_PTR DefaultDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual bool MouseEvent(MOUSE_EVENT_RECORD * Event); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + virtual void Change(); + virtual void Init(); + virtual bool CloseQuery(); + UnicodeString GetMsg(intptr_t MsgId); + void GetNextItemPosition(intptr_t & Left, intptr_t & Top); + void RefreshBounds(); + virtual void Idle(); + void BreakSynchronize(); + void Synchronize(TThreadMethod Method); + void Close(TFarButton * Button); + void ProcessGroup(intptr_t Group, TFarProcessGroupEvent Callback, void * Arg); + void ShowItem(TFarDialogItem * Item, void * Arg); + void EnableItem(TFarDialogItem * Item, void * Arg); + bool ChangesLocked(); + TFarDialogItem * ItemAt(int X, int Y); + + static LONG_PTR WINAPI DialogProcGeneral(HANDLE Handle, int Msg, int Param1, LONG_PTR Param2); + + virtual void SetBounds(const TRect & Value); + +private: + TCustomFarPlugin * FFarPlugin; + TRect FBounds; + DWORD FFlags; + UnicodeString FHelpTopic; + bool FVisible; + TObjectList * FItems; + TObjectList * FContainers; + HANDLE FHandle; + TFarButton * FDefaultButton; + TFarBox * FBorderBox; + TItemPosition FNextItemPosition; + intptr_t FDefaultGroup; + intptr_t FTag; + TFarDialogItem * FItemFocused; + TFarKeyEvent FOnKey; + FarDialogItem * FDialogItems; + intptr_t FDialogItemsCapacity; + intptr_t FChangesLocked; + bool FChangesPending; + intptr_t FResult; + bool FNeedsSynchronize; + HANDLE FSynchronizeObjects[2]; + TThreadMethod FSynchronizeMethod; +}; + +class TFarDialogContainer : public TObject +{ +friend TFarDialog; +friend TFarDialogItem; +NB_DISABLE_COPY(TFarDialogContainer) +NB_DECLARE_CLASS(TFarDialogContainer) +public: + intptr_t GetLeft() const { return FLeft; } + void SetLeft(intptr_t Value) { SetPosition(0, Value); } + intptr_t GetTop() const { return FTop; } + void SetTop(intptr_t Value) { SetPosition(1, Value); } + bool GetEnabled() const { return FEnabled; } + void SetEnabled(bool Value); + void SetPosition(intptr_t AIndex, intptr_t Value); + intptr_t GetItemCount() const; + +protected: + explicit TFarDialogContainer(TFarDialog * ADialog); + virtual ~TFarDialogContainer(); + + TFarDialog * GetDialog() const { return FDialog; } + TFarDialog * GetDialog() { return FDialog; } + + void Add(TFarDialogItem * Item); + void Remove(TFarDialogItem * Item); + virtual void Change(); + UnicodeString GetMsg(int MsgId); + +private: + intptr_t FLeft; + intptr_t FTop; + TObjectList * FItems; + TFarDialog * FDialog; + bool FEnabled; +}; + +#define DIF_INVERSE 0x00000001UL + +class TFarDialogItem : public TObject +{ +friend TFarDialog; +friend TFarMessageDialog; +friend TFarDialogContainer; +friend TFarList; +NB_DISABLE_COPY(TFarDialogItem) +NB_DECLARE_CLASS(TFarDialogItem) +public: + TRect GetBounds() const { return FBounds; } + TRect GetActualBounds() const; + intptr_t GetLeft() const { return GetCoordinate(0); } + void SetLeft(intptr_t Value) { SetCoordinate(0, Value); } + intptr_t GetTop() const { return GetCoordinate(1); } + void SetTop(intptr_t Value) { SetCoordinate(1, Value); } + intptr_t GetRight() const { return GetCoordinate(2); } + void SetRight(intptr_t Value) { SetCoordinate(2, Value); } + intptr_t GetBottom() const { return GetCoordinate(3); } + void SetBottom(intptr_t Value) { SetCoordinate(3, Value); } + intptr_t GetWidth() const; + void SetWidth(intptr_t Value); + intptr_t GetHeight() const; + void SetHeight(intptr_t Value); + bool GetEnabled() const { return FEnabled; } + void SetEnabled(bool Value); + bool GetIsEnabled() const { return FIsEnabled; } + TFarDialogItem * GetEnabledFollow() const { return FEnabledFollow; } + void SetEnabledFollow(TFarDialogItem * Value); + TFarDialogItem * GetEnabledDependency() const { return FEnabledDependency; } + void SetEnabledDependency(TFarDialogItem * Value); + TFarDialogItem * GetEnabledDependencyNegative() const { return FEnabledDependencyNegative; } + void SetEnabledDependencyNegative(TFarDialogItem * Value); + virtual bool GetIsEmpty() const; + intptr_t GetGroup() const { return FGroup; } + void SetGroup(intptr_t Value) { FGroup = Value; } + bool GetVisible() const { return GetFlag(DIF_HIDDEN | DIF_INVERSE); } + void SetVisible(bool Value) { SetFlag(DIF_HIDDEN | DIF_INVERSE, Value); } + bool GetTabStop() const { return GetFlag(DIF_NOFOCUS | DIF_INVERSE); } + void SetTabStop(bool Value) { SetFlag(DIF_NOFOCUS | DIF_INVERSE, Value); } + intptr_t GetTag() const { return FTag; } + void SetTag(intptr_t Value) { FTag = Value; } + TFarDialog * GetDialog() const { return FDialog; } + TFarDialog * GetDialog() { return FDialog; } + const TFarDialog * GetOwner() const { return FDialog; } + TFarDialog * GetOwner() { return FDialog; } + + TNotifyEvent & GetOnExit() { return FOnExit; } + void SetOnExit(TNotifyEvent Value) { FOnExit = Value; } + TFarMouseClickEvent & GetOnMouseClick() { return FOnMouseClick; } + void SetOnMouseClick(TFarMouseClickEvent Value) { FOnMouseClick = Value; } + bool GetFocused() const; + void SetFocused(bool Value); + + void Move(intptr_t DeltaX, intptr_t DeltaY); + void MoveAt(intptr_t X, intptr_t Y); + virtual bool CanFocus() const; + bool Focused() const; + void SetFocus(); + void SetItem(intptr_t Value) { FItem = Value; } + +public: + virtual void SetDataInternal(const UnicodeString & Value); + void UpdateData(const UnicodeString & Value); + void UpdateSelected(intptr_t Value); + + bool GetFlag(intptr_t Index) const; + void SetFlag(intptr_t Index, bool Value); + + virtual void DoFocus(); + virtual void DoExit(); + + char GetColor(intptr_t Index) const; + void SetColor(intptr_t Index, char Value); + +protected: + uintptr_t FDefaultType; + intptr_t FGroup; + intptr_t FTag; + TNotifyEvent FOnExit; + TFarMouseClickEvent FOnMouseClick; + + explicit TFarDialogItem(TFarDialog * ADialog, uintptr_t AType); + virtual ~TFarDialogItem(); + + const FarDialogItem * GetDialogItem() const; + FarDialogItem * GetDialogItem(); + bool GetCenterGroup() const { return GetFlag(DIF_CENTERGROUP); } + void SetCenterGroup(bool Value) { SetFlag(DIF_CENTERGROUP, Value); } + virtual UnicodeString GetData() const; + virtual UnicodeString GetData(); + virtual void SetData(const UnicodeString & Value); + intptr_t GetType() const; + void SetType(intptr_t Value); + intptr_t GetItem() const { return FItem; } + intptr_t GetSelected() const; + void SetSelected(intptr_t Value); + TFarDialogContainer * GetContainer() const { return FContainer; } + void SetContainer(TFarDialogContainer * Value); + bool GetChecked() const; + void SetChecked(bool Value); + void SetBounds(const TRect & Value); + DWORD GetFlags() const; + void SetFlags(DWORD Value); + void UpdateFlags(DWORD Value); + intptr_t GetCoordinate(intptr_t Index) const; + void SetCoordinate(intptr_t Index, intptr_t Value); + TFarDialogItem * GetPrevItem() const; + void UpdateFocused(bool Value); + void UpdateEnabled(); + + virtual void Detach(); + void DialogResized(); + LONG_PTR SendMessage(int Msg, LONG_PTR Param); + LONG_PTR SendDialogMessage(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + LONG_PTR DefaultItemProc(int Msg, LONG_PTR Param); + LONG_PTR DefaultDialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual LONG_PTR FailItemProc(int Msg, LONG_PTR Param); + virtual void Change(); + void DialogChange(); + bool GetAlterType(intptr_t Index) const; + bool GetAlterType(intptr_t Index); + void SetAlterType(intptr_t Index, bool Value); + virtual void UpdateBounds(); + virtual void ResetBounds(); + virtual void Init(); + virtual bool CloseQuery(); + virtual bool MouseMove(int X, int Y, MOUSE_EVENT_RECORD * Event); + virtual bool MouseClick(MOUSE_EVENT_RECORD * Event); + TPoint MouseClientPosition(MOUSE_EVENT_RECORD * Event); + void Text(int X, int Y, uintptr_t Color, const UnicodeString & Str); + void Redraw(); + virtual bool HotKey(char HotKey); + +private: + const struct PluginStartupInfo * GetPluginStartupInfo() const; + +private: + TFarDialog * FDialog; + TRect FBounds; + TFarDialogItem * FEnabledFollow; + TFarDialogItem * FEnabledDependency; + TFarDialogItem * FEnabledDependencyNegative; + TFarDialogContainer * FContainer; + intptr_t FItem; + bool FEnabled; + bool FIsEnabled; + uint32_t FColors; + uint32_t FColorMask; +}; + +class TFarBox : public TFarDialogItem +{ +public: + explicit TFarBox(TFarDialog * ADialog); + + virtual UnicodeString GetCaption() const { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + virtual bool GetDouble() const { return GetAlterType(DI_DOUBLEBOX); } + virtual void SetDouble(bool Value) { SetAlterType(DI_DOUBLEBOX, Value); } +}; + +DEFINE_CALLBACK_TYPE2(TFarButtonClickEvent, void, + TFarButton * /*Sender*/, bool & /*Close*/); +enum TFarButtonBrackets +{ + brNone, + brTight, + brSpace, + brNormal +}; + +class TFarButton : public TFarDialogItem +{ +NB_DECLARE_CLASS(TFarButton) +public: + explicit TFarButton(TFarDialog * ADialog); + virtual ~TFarButton() {} + + virtual UnicodeString GetCaption() const { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + virtual intptr_t GetModalResult() const { return FResult; } + virtual intptr_t GetResult() const { return FResult; } + virtual void SetResult(intptr_t Value) { FResult = Value; } + virtual UnicodeString GetData() const { return const_cast(this)->GetData(); } + virtual UnicodeString GetData(); + bool GetDefault() const; + void SetDefault(bool Value); + TFarButtonBrackets GetBrackets() const { return FBrackets; } + void SetBrackets(TFarButtonBrackets Value); + bool GetCenterGroup() const { return TFarDialogItem::GetCenterGroup(); } + void SetCenterGroup(bool Value) { TFarDialogItem::SetCenterGroup(Value); } + virtual TFarButtonClickEvent & GetOnClick() { return FOnClick; } + virtual void SetOnClick(TFarButtonClickEvent Value) { FOnClick = Value; } + +protected: + virtual void SetDataInternal(const UnicodeString & AValue); + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual bool HotKey(char HotKey); + +private: + intptr_t FResult; + TFarButtonClickEvent FOnClick; + TFarButtonBrackets FBrackets; +}; + +DEFINE_CALLBACK_TYPE3(TFarAllowChangeEvent, void, + TFarDialogItem * /*Sender*/, intptr_t /*NewState*/, bool & /*AllowChange*/); + +class TFarCheckBox : public TFarDialogItem +{ +NB_DISABLE_COPY(TFarCheckBox) +NB_DECLARE_CLASS(TFarCheckBox) +public: + explicit TFarCheckBox(TFarDialog * ADialog); + + virtual UnicodeString GetCaption() const { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + bool GetAllowGrayed() const { return GetFlag(DIF_3STATE); } + void SetAllowGrayed(bool Value) { SetFlag(DIF_3STATE, Value); } + virtual TFarAllowChangeEvent & GetOnAllowChange() { return FOnAllowChange; } + virtual void SetOnAllowChange(TFarAllowChangeEvent Value) { FOnAllowChange = Value; } + bool GetChecked() const { return TFarDialogItem::GetChecked(); } + void SetChecked(bool Value) { TFarDialogItem::SetChecked(Value); } + intptr_t GetSelected() const { return TFarDialogItem::GetSelected(); } + void SetSelected(intptr_t Value) { TFarDialogItem::SetSelected(Value); } + +protected: + TFarAllowChangeEvent FOnAllowChange; + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual bool GetIsEmpty() const; + virtual void SetData(const UnicodeString & Value); +}; + +class TFarRadioButton : public TFarDialogItem +{ +public: + explicit TFarRadioButton(TFarDialog * ADialog); + + bool GetChecked() const { return TFarDialogItem::GetChecked(); } + void SetChecked(bool Value) { TFarDialogItem::SetChecked(Value); } + virtual UnicodeString GetCaption() const { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + virtual TFarAllowChangeEvent & GetOnAllowChange() { return FOnAllowChange; } + virtual void SetOnAllowChange(TFarAllowChangeEvent Value) { FOnAllowChange = Value; } + +protected: + TFarAllowChangeEvent FOnAllowChange; + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual bool GetIsEmpty() const; + virtual void SetData(const UnicodeString & Value); +}; + +class TFarEdit : public TFarDialogItem +{ +NB_DECLARE_CLASS(TFarEdit) +public: + explicit TFarEdit(TFarDialog * ADialog); + + virtual UnicodeString GetText() const { return GetData(); } + virtual void SetText(const UnicodeString & Value) { SetData(Value); } + intptr_t GetAsInteger(); + void SetAsInteger(intptr_t Value); + virtual bool GetPassword() const { return GetAlterType(DI_PSWEDIT); } + virtual void SetPassword(bool Value) { SetAlterType(DI_PSWEDIT, Value); } + virtual bool GetFixed() const { return GetAlterType(DI_FIXEDIT); } + virtual void SetFixed(bool Value) { SetAlterType(DI_FIXEDIT, Value); } + virtual UnicodeString GetMask() const { return GetHistoryMask(1); } + virtual void SetMask(const UnicodeString & Value) { SetHistoryMask(1, Value); } + virtual UnicodeString GetHistory() const { return GetHistoryMask(0); } + virtual void SetHistory(const UnicodeString & Value) { SetHistoryMask(0, Value); } + bool GetExpandEnvVars() const { return GetFlag(DIF_EDITEXPAND); } + void SetExpandEnvVars(bool Value) { SetFlag(DIF_EDITEXPAND, Value); } + bool GetAutoSelect() const { return GetFlag(DIF_SELECTONENTRY); } + void SetAutoSelect(bool Value) { SetFlag(DIF_SELECTONENTRY, Value); } + bool GetReadOnly() const { return GetFlag(DIF_READONLY); } + void SetReadOnly(bool Value) { SetFlag(DIF_READONLY, Value); } + +protected: + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual void Detach(); + +private: + UnicodeString GetHistoryMask(size_t Index) const; + void SetHistoryMask(size_t Index, const UnicodeString & Value); +}; + +class TFarSeparator : public TFarDialogItem +{ +public: + explicit TFarSeparator(TFarDialog * ADialog); + + bool GetDouble(); + void SetDouble(bool Value); + virtual UnicodeString GetCaption() { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + void SetPosition(intptr_t Value); + int GetPosition(); + +protected: + virtual void ResetBounds(); +}; + +class TFarText : public TFarDialogItem +{ +NB_DECLARE_CLASS(TFarText) +public: + explicit TFarText(TFarDialog * ADialog); + + virtual UnicodeString GetCaption() const { return GetData(); } + virtual void SetCaption(const UnicodeString & Value) { SetData(Value); } + bool GetCenterGroup() const { return TFarDialogItem::GetCenterGroup(); } + void SetCenterGroup(bool Value) { TFarDialogItem::SetCenterGroup(Value); } + char GetColor() const { return TFarDialogItem::GetColor(0); } + void SetColor(char Value) { TFarDialogItem::SetColor(0, Value); } + +protected: + virtual void SetData(const UnicodeString & Value); +}; + +class TFarListBox; +class TFarComboBox; +class TFarLister; + +class TFarList : public TStringList +{ +friend TFarListBox; +friend TFarLister; +friend TFarComboBox; +NB_DISABLE_COPY(TFarList) +NB_DECLARE_CLASS(TFarList) +public: + explicit TFarList(TFarDialogItem * ADialogItem = nullptr); + virtual ~TFarList(); + + virtual void Assign(const TPersistent * Source); + + intptr_t GetSelected() const; + void SetSelected(intptr_t Value); + intptr_t GetTopIndex() const; + void SetTopIndex(intptr_t Value); + inline intptr_t GetSelectedInt(bool Init) const; + bool GetFlag(intptr_t Index, DWORD Flag) const; + void SetFlag(intptr_t Index, DWORD Flag, bool Value); + DWORD GetFlags(intptr_t Index) const; + void SetFlags(intptr_t Index, DWORD Value); + intptr_t GetMaxLength() const; + intptr_t GetVisibleCount() const; + bool GetDisabled(intptr_t Index) const { return GetFlag(Index, LIF_DISABLE); } + void SetDisabled(intptr_t Index, bool Value) { SetFlag(Index, LIF_DISABLE, Value); } + bool GetChecked(intptr_t Index) const { return GetFlag(Index, LIF_CHECKED); } + void SetChecked(intptr_t Index, bool Value) { SetFlag(Index, LIF_CHECKED, Value); } + +protected: + virtual void Changed(); + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual void Init(); + void UpdatePosition(intptr_t Position); + intptr_t GetPosition() const; + virtual void Put(intptr_t Index, const UnicodeString & Str); + void SetCurPos(intptr_t Position, intptr_t TopIndex); + void UpdateItem(intptr_t Index); + + FarList * GetListItems() const { return FListItems; } + FarList * GetListItems() { return FListItems; } + TFarDialogItem * GetDialogItem() const { return FDialogItem; } + TFarDialogItem * GetDialogItem() { return FDialogItem; } + +private: + FarList * FListItems; + TFarDialogItem * FDialogItem; + bool FNoDialogUpdate; +}; + +enum TFarListBoxAutoSelect +{ + asOnlyFocus, + asAlways, + asNever +}; + +class TFarListBox : public TFarDialogItem +{ +NB_DISABLE_COPY(TFarListBox) +NB_DECLARE_CLASS(TFarListBox) +public: + explicit TFarListBox(TFarDialog * ADialog); + virtual ~TFarListBox(); + + void SetItems(TStrings * Value); + + bool GetNoAmpersand() const { return GetFlag(DIF_LISTNOAMPERSAND); } + void SetNoAmpersand(bool Value) { SetFlag(DIF_LISTNOAMPERSAND, Value); } + bool GetAutoHighlight() const { return GetFlag(DIF_LISTAUTOHIGHLIGHT); } + void SetAutoHighlight(bool Value) { SetFlag(DIF_LISTAUTOHIGHLIGHT, Value); } + bool GetNoBox() const { return GetFlag(DIF_LISTNOBOX); } + void SetNoBox(bool Value) { SetFlag(DIF_LISTNOBOX, Value); } + bool GetWrapMode() const { return GetFlag(DIF_LISTWRAPMODE); } + void SetWrapMode(bool Value) { SetFlag(DIF_LISTWRAPMODE, Value); } + TFarList * GetItems() const { return FList; } + TFarList * GetItems() { return FList; } + void SetList(TFarList * Value); + TFarListBoxAutoSelect GetAutoSelect() { return FAutoSelect; } + void SetAutoSelect(TFarListBoxAutoSelect Value); + +protected: + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual void Init(); + virtual bool CloseQuery(); + +private: + void UpdateMouseReaction(); + +private: + TFarList * FList; + TFarListBoxAutoSelect FAutoSelect; + bool FDenyClose; +}; + +class TFarComboBox : public TFarDialogItem +{ +NB_DISABLE_COPY(TFarComboBox) +public: + explicit TFarComboBox(TFarDialog * ADialog); + virtual ~TFarComboBox(); + + void ResizeToFitContent(); + + bool GetNoAmpersand() const { return GetFlag(DIF_LISTNOAMPERSAND); } + void SetNoAmpersand(bool Value) { SetFlag(DIF_LISTNOAMPERSAND, Value); } + bool GetAutoHighlight() const { return GetFlag(DIF_LISTAUTOHIGHLIGHT); } + void SetAutoHighlight(bool Value) { SetFlag(DIF_LISTAUTOHIGHLIGHT, Value); } + bool GetWrapMode() const { return GetFlag(DIF_LISTWRAPMODE); } + void SetWrapMode(bool Value) { SetFlag(DIF_LISTWRAPMODE, Value); } + TFarList * GetItems() const { return FList; } + virtual UnicodeString GetText() const { return GetData(); } + virtual void SetText(const UnicodeString & Value) { SetData(Value); } + bool GetAutoSelect() const { return GetFlag(DIF_SELECTONENTRY); } + void SetAutoSelect(bool Value) { SetFlag(DIF_SELECTONENTRY, Value); } + bool GetDropDownList() const { return GetFlag(DIF_DROPDOWNLIST); } + void SetDropDownList(bool Value) { SetFlag(DIF_DROPDOWNLIST, Value); } + intptr_t GetItemIndex() const { return FList->GetSelected(); } + void SetItemIndex(intptr_t Index) { FList->SetSelected(Index); } + +protected: + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual void Init(); + +private: + TFarList * FList; +}; + +class TFarLister : public TFarDialogItem +{ +NB_DISABLE_COPY(TFarLister) +public: + explicit TFarLister(TFarDialog * ADialog); + virtual ~TFarLister(); + + TStrings * GetItems() const; + void SetItems(const TStrings * Value); + intptr_t GetTopIndex() const { return FTopIndex; } + void SetTopIndex(intptr_t Value); + bool GetScrollBar() const; + +protected: + virtual LONG_PTR ItemProc(int Msg, LONG_PTR Param); + virtual void DoFocus(); + +private: + void ItemsChange(TObject * Sender); + +private: + TStringList * FItems; + intptr_t FTopIndex; +}; + +inline TRect Rect(int Left, int Top, int Right, int Bottom); + diff --git a/netbox/src/NetBox/FarInterface.cpp b/netbox/src/NetBox/FarInterface.cpp new file mode 100644 index 000000000..72e67398a --- /dev/null +++ b/netbox/src/NetBox/FarInterface.cpp @@ -0,0 +1,109 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#include "CoreMain.h" +#include "FarConfiguration.h" +#include "WinSCPPlugin.h" +#include "FarDialog.h" +#include "FarInterface.h" + +TConfiguration * CreateConfiguration() +{ + return new TFarConfiguration(FarPlugin); +} + +void ShowExtendedException(Exception * E) +{ + DebugAssert(FarPlugin != nullptr); + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + DebugAssert(WinSCPPlugin != nullptr); + WinSCPPlugin->ShowExtendedException(E); +} + +UnicodeString GetAppNameString() +{ + return "NetBox"; +} + +UnicodeString GetRegistryKey() +{ + return "Software\\Far2\\Plugins\\NetBox 2"; +} + +void Busy(bool /*Start*/) +{ + // nothing +} + +UnicodeString GetSshVersionString() +{ + return FORMAT(L"NetBox-FAR-release-%s", GetConfiguration()->GetProductVersion().c_str()); +} + +DWORD WINAPI threadstartroutine(void * Parameter) +{ + TSimpleThread * SimpleThread = NB_STATIC_DOWNCAST(TSimpleThread, Parameter); + return TSimpleThread::ThreadProc(SimpleThread); +} + +HANDLE BeginThread(void * SecurityAttributes, DWORD StackSize, + void * Parameter, DWORD CreationFlags, + DWORD & ThreadId) +{ + HANDLE Result = ::CreateThread(static_cast(SecurityAttributes), + static_cast(StackSize), + static_cast(&threadstartroutine), + Parameter, + CreationFlags, &ThreadId); + return Result; +} + +HANDLE StartThread(void * SecurityAttributes, DWORD StackSize, + void * Parameter, DWORD CreationFlags, + TThreadID & ThreadId) +{ + return BeginThread(SecurityAttributes, StackSize, Parameter, + CreationFlags, ThreadId); +} + +void CopyToClipboard(const UnicodeString & AText) +{ + DebugAssert(FarPlugin != nullptr); + FarPlugin->FarCopyToClipboard(AText); +} + +//from windows/GUITools.cpp +template +void ValidateMaskEditT(const UnicodeString & Mask, TEditControl * Edit, int ForceDirectoryMasks) +{ + DebugAssert(Edit != nullptr); + TFileMasks Masks(ForceDirectoryMasks); + try + { + Masks = Mask; + } + catch (EFileMasksException & E) + { + ShowExtendedException(&E); + Edit->SetFocus(); + // This does not work for TEdit and TMemo (descendants of TCustomEdit) anymore, + // as it re-selects whole text on exception in TCustomEdit.CMExit +// Edit->SelStart = E.ErrorStart - 1; +// Edit->SelLength = E.ErrorLen; + Abort(); + } +} + +void ValidateMaskEdit(TFarComboBox * Edit) +{ + ValidateMaskEditT(Edit->GetText(), Edit, -1); +} + +void ValidateMaskEdit(TFarEdit * Edit) +{ + ValidateMaskEditT(Edit->GetText(), Edit, -1); +} diff --git a/netbox/src/NetBox/FarInterface.h b/netbox/src/NetBox/FarInterface.h new file mode 100644 index 000000000..640bd713c --- /dev/null +++ b/netbox/src/NetBox/FarInterface.h @@ -0,0 +1,34 @@ +//from windows/WinInterface.h +#pragma once +#include +#include +#include +#include + +enum TMsgDlgType +{ + mtConfirmation, + mtInformation, + mtError, + mtWarning, +}; + +// forms\MessageDlg.cpp +void AnswerNameAndCaption( + uintptr_t Answer, UnicodeString & Name, UnicodeString & Caption); +TFarDialog * CreateMoreMessageDialog(const UnicodeString & Msg, + TStrings * MoreMessages, TMsgDlgType DlgType, uintptr_t Answers, + const TQueryButtonAlias * Aliases, uintptr_t AliasesCount, + uintptr_t TimeoutAnswer, TFarButton ** TimeoutButton, + const UnicodeString & ImageName, const UnicodeString & NeverAskAgainCaption, + const UnicodeString & MoreMessagesUrl, TSize MoreMessagesSize, + const UnicodeString & CustomCaption); +TFarDialog * CreateMoreMessageDialogEx(const UnicodeString & Message, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, UnicodeString HelpKeyword, const TMessageParams * Params); +uintptr_t ExecuteMessageDialog(TFarDialog * Dialog, uintptr_t Answers, const TMessageParams * Params); +/*void InsertPanelToMessageDialog(TFarDialog * Form, TPanel * Panel); +void NavigateMessageDialogToUrl(TFarDialog * Form, const UnicodeString & Url);*/ + +//from windows/GUITools.h +void ValidateMaskEdit(TFarComboBox * Edit); +void ValidateMaskEdit(TFarEdit * Edit); diff --git a/netbox/src/NetBox/FarPlugin.cpp b/netbox/src/NetBox/FarPlugin.cpp new file mode 100644 index 000000000..84dd39338 --- /dev/null +++ b/netbox/src/NetBox/FarPlugin.cpp @@ -0,0 +1,2977 @@ +#include +#pragma hdrstop + +#include +#include "FarPlugin.h" +#include "WinSCPPlugin.h" +#include "FarPluginStrings.h" +#include "FarDialog.h" +#include "TextsCore.h" +#include "FileMasks.h" +#include "RemoteFiles.h" +#include "puttyexp.h" +#include "plugin_version.hpp" + +TCustomFarPlugin * FarPlugin = nullptr; +#define FAR_TITLE_SUFFIX L" - Far" + +TFarMessageParams::TFarMessageParams() : + MoreMessages(nullptr), + CheckBox(false), + Timer(0), + TimerAnswer(0), + TimerEvent(nullptr), + Timeout(0), + TimeoutButton(0), + DefaultButton(0), + ClickEvent(nullptr), + Token(nullptr) +{ +} + +TCustomFarPlugin::TCustomFarPlugin(HINSTANCE HInst) : + TObject(), + FOpenedPlugins(new TObjectList()), + FTopDialog(nullptr), + FSavedTitles(new TStringList()) +{ + ::InitPlatformId(); + FFarThreadId = GetCurrentThreadId(); + FHandle = HInst; + FFarVersion = 0; + FTerminalScreenShowing = false; + + FOpenedPlugins->SetOwnsObjects(false); + FCurrentProgress = -1; + FValidFarSystemSettings = false; + FFarSystemSettings = 0; + + ClearStruct(FPluginInfo); + ClearPluginInfo(FPluginInfo); + ClearStruct(FStartupInfo); + ClearStruct(FFarStandardFunctions); + + // far\Examples\Compare\compare.cpp +#ifndef __linux__ + FConsoleInput = ::CreateFile(L"CONIN$", GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, 0, nullptr); + FConsoleOutput = ::CreateFile(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (ConsoleWindowState() == SW_SHOWNORMAL) + { + FNormalConsoleSize = TerminalInfo(); + } + else +#endif + { + FNormalConsoleSize = TPoint(-1, -1); + } +} + +TCustomFarPlugin::~TCustomFarPlugin() +{ + DebugAssert(FTopDialog == nullptr); + + ResetCachedInfo(); + ::CloseHandle(FConsoleInput); + FConsoleInput = INVALID_HANDLE_VALUE; + ::CloseHandle(FConsoleOutput); + FConsoleOutput = INVALID_HANDLE_VALUE; + + ClearPluginInfo(FPluginInfo); + DebugAssert(FOpenedPlugins->GetCount() == 0); + SAFE_DESTROY(FOpenedPlugins); + for (intptr_t Index = 0; Index < FSavedTitles->GetCount(); ++Index) + { + TObject * Object = FSavedTitles->GetObj(Index); + SAFE_DESTROY(Object); + } + SAFE_DESTROY(FSavedTitles); + TGlobalFunctionsIntf * Intf = GetGlobalFunctions(); + SAFE_DESTROY_EX(TGlobalFunctionsIntf, Intf); +} + +bool TCustomFarPlugin::HandlesFunction(THandlesFunction /*Function*/) const +{ + return false; +} + +intptr_t TCustomFarPlugin::GetMinFarVersion() const +{ + return 0; +} + +void TCustomFarPlugin::SetStartupInfo(const struct PluginStartupInfo * Info) +{ + try + { + ResetCachedInfo(); + ClearStruct(FStartupInfo); + memmove(&FStartupInfo, Info, + Info->StructSize >= static_cast(sizeof(FStartupInfo)) ? + sizeof(FStartupInfo) : static_cast(Info->StructSize)); + // the minimum we really need + DebugAssert(FStartupInfo.GetMsg != nullptr); + DebugAssert(FStartupInfo.Message != nullptr); + + ClearStruct(FFarStandardFunctions); + size_t FSFOffset = (static_cast(reinterpret_cast(&Info->FSF)) - + static_cast(reinterpret_cast(Info))); + if (static_cast(Info->StructSize) > FSFOffset) + { + memmove(&FFarStandardFunctions, Info->FSF, + static_cast(Info->FSF->StructSize) >= sizeof(FFarStandardFunctions) ? + sizeof(FFarStandardFunctions) : Info->FSF->StructSize); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + } +} + +void TCustomFarPlugin::ExitFAR() +{ +} + +void TCustomFarPlugin::GetPluginInfo(struct PluginInfo * Info) +{ + try + { + ResetCachedInfo(); + Info->StructSize = sizeof(PluginInfo); + TStringList DiskMenuStrings; + TStringList PluginMenuStrings; + TStringList PluginConfigStrings; + TStringList CommandPrefixes; + + ClearPluginInfo(FPluginInfo); + + GetPluginInfoEx(FPluginInfo.Flags, &DiskMenuStrings, &PluginMenuStrings, + &PluginConfigStrings, &CommandPrefixes); + + #define COMPOSESTRINGARRAY(NAME) \ + if (NAME.GetCount()) \ + { \ + wchar_t ** StringArray = static_cast(nb_calloc(1, sizeof(wchar_t *) * (1 + NAME.GetCount()))); \ + FPluginInfo.NAME = StringArray; \ + FPluginInfo.NAME ## Number = static_cast(NAME.GetCount()); \ + for (intptr_t Index = 0; Index < NAME.GetCount(); ++Index) \ + { \ + StringArray[Index] = DuplicateStr(NAME.GetString(Index)); \ + } \ + } + + COMPOSESTRINGARRAY(DiskMenuStrings); + COMPOSESTRINGARRAY(PluginMenuStrings); + COMPOSESTRINGARRAY(PluginConfigStrings); + + #undef COMPOSESTRINGARRAY + UnicodeString CommandPrefix; + for (intptr_t Index = 0; Index < CommandPrefixes.GetCount(); ++Index) + { + CommandPrefix = CommandPrefix + (CommandPrefix.IsEmpty() ? L"" : L":") + + CommandPrefixes.GetString(Index); + } + FPluginInfo.CommandPrefix = DuplicateStr(CommandPrefix); + + memmove(Info, &FPluginInfo, sizeof(FPluginInfo)); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + } +} + +UnicodeString TCustomFarPlugin::GetModuleName() const +{ + return FStartupInfo.ModuleName; +} + +void TCustomFarPlugin::ClearPluginInfo(PluginInfo & Info) +{ + if (Info.StructSize) + { + #define FREESTRINGARRAY(NAME) \ + for (intptr_t Index = 0; Index < Info.NAME ## Number; ++Index) \ + { \ + nb_free((void*)Info.NAME[Index]); \ + } \ + nb_free((void*)Info.NAME); \ + Info.NAME = nullptr; + + FREESTRINGARRAY(DiskMenuStrings); + FREESTRINGARRAY(PluginMenuStrings); + FREESTRINGARRAY(PluginConfigStrings); + + #undef FREESTRINGARRAY + + nb_free((void*)Info.CommandPrefix); + } + ClearStruct(Info); + Info.StructSize = sizeof(Info); +} + +wchar_t * TCustomFarPlugin::DuplicateStr(const UnicodeString & Str, bool AllowEmpty) +{ + if (Str.IsEmpty() && !AllowEmpty) + { + return nullptr; + } + else + { + const size_t sz = Str.Length() + 1; + wchar_t * Result = static_cast( + nb_calloc(1, sizeof(wchar_t) * (1 + sz))); + wcsncpy(Result, Str.c_str(), sz); + return Result; + } +} + +RECT TCustomFarPlugin::GetPanelBounds(HANDLE PanelHandle) +{ + PanelInfo Info; + ClearStruct(Info); + FarControl(FCTL_GETPANELINFO, 0, reinterpret_cast(&Info), PanelHandle); + RECT Bounds; + ClearStruct(Bounds); + if (Info.Plugin) + { + Bounds = Info.PanelRect; + } + return Bounds; +} + +TCustomFarFileSystem * TCustomFarPlugin::GetPanelFileSystem(bool Another, + HANDLE /*Plugin*/) +{ + TCustomFarFileSystem * Result = nullptr; + RECT ActivePanelBounds = GetPanelBounds(PANEL_ACTIVE); + RECT PassivePanelBounds = GetPanelBounds(PANEL_PASSIVE); + + TCustomFarFileSystem * FarFileSystem = nullptr; + intptr_t Index = 0; + while (!Result && (Index < FOpenedPlugins->GetCount())) + { + FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, FOpenedPlugins->GetObj(Index)); + DebugAssert(FarFileSystem); + RECT Bounds = GetPanelBounds(FarFileSystem); + if (Another && CompareRects(Bounds, PassivePanelBounds)) + { + Result = FarFileSystem; + } + else if (!Another && CompareRects(Bounds, ActivePanelBounds)) + { + Result = FarFileSystem; + } + ++Index; + } + return Result; +} + +void TCustomFarPlugin::InvalidateOpenPluginInfo() +{ + for (intptr_t Index = 0; Index < FOpenedPlugins->GetCount(); ++Index) + { + TCustomFarFileSystem * FarFileSystem = + NB_STATIC_DOWNCAST(TCustomFarFileSystem, FOpenedPlugins->GetObj(Index)); + FarFileSystem->InvalidateOpenPluginInfo(); + } +} + +intptr_t TCustomFarPlugin::Configure(intptr_t Item) +{ + try + { + ResetCachedInfo(); + intptr_t Result = ConfigureEx(Item); + InvalidateOpenPluginInfo(); + + return Result; + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + return 0; + } +} + +void * TCustomFarPlugin::OpenPlugin(int OpenFrom, intptr_t Item) +{ +#ifdef USE_DLMALLOC + // dlmallopt(M_GRANULARITY, 128 * 1024); +#endif + + try + { + ResetCachedInfo(); + + UnicodeString Buf; + if ((OpenFrom == OPEN_SHORTCUT) || (OpenFrom == OPEN_COMMANDLINE)) + { + Buf = reinterpret_cast(Item); + Item = reinterpret_cast(Buf.c_str()); + } + + TCustomFarFileSystem * Result = OpenPluginEx(OpenFrom, Item); + + if (Result) + { + FOpenedPlugins->Add(Result); + } + else + { + Result = reinterpret_cast(INVALID_HANDLE_VALUE); + } + + return Result; + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + return INVALID_HANDLE_VALUE; + } +} + +void TCustomFarPlugin::ClosePlugin(void * Plugin) +{ + try + { + ResetCachedInfo(); + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + { + SCOPE_EXIT + { + FOpenedPlugins->Remove(FarFileSystem); + }; + TGuard Guard(FarFileSystem->GetCriticalSection()); + FarFileSystem->Close(); + } + SAFE_DESTROY(FarFileSystem); +#ifdef USE_DLMALLOC + // dlmalloc_trim(0); // 64 * 1024); +#endif + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + } +} + +void TCustomFarPlugin::HandleFileSystemException( + TCustomFarFileSystem * FarFileSystem, Exception * E, int OpMode) +{ + // This method is called as last-resort exception handler before + // leaving plugin API. Especially for API functions that must update + // panel contents on themselves (like ProcessKey), the instance of filesystem + // may not exist anymore. + // Check against object pointer is stupid, but no other idea so far. + if (FOpenedPlugins->IndexOf(FarFileSystem) != NPOS) + { + DEBUG_PRINTF("before FileSystem->HandleException"); + FarFileSystem->HandleException(E, OpMode); + } + else + { + DEBUG_PRINTF("before HandleException"); + HandleException(E, OpMode); + } +} + +void TCustomFarPlugin::GetOpenPluginInfo(HANDLE Plugin, + struct OpenPluginInfo * Info) +{ + if (!Info) + return; + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + if (!FOpenedPlugins || !FarFileSystem) + return; + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + TGuard Guard(FarFileSystem->GetCriticalSection()); + FarFileSystem->GetOpenPluginInfo(Info); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E); + } +} + +intptr_t TCustomFarPlugin::GetFindData(HANDLE Plugin, + struct PluginPanelItem ** PanelItem, int * ItemsNumber, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->GetFindData(PanelItem, ItemsNumber, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + return 0; + } +} + +void TCustomFarPlugin::FreeFindData(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + FarFileSystem->FreeFindData(PanelItem, ItemsNumber); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E); + } +} + +intptr_t TCustomFarPlugin::ProcessHostFile(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + if (HandlesFunction(hfProcessHostFile)) + { + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->ProcessHostFile(PanelItem, ItemsNumber, OpMode); + } + } + else + { + return 0; + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + return 0; + } +} + +intptr_t TCustomFarPlugin::ProcessKey(HANDLE Plugin, int Key, + DWORD ControlState) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + if (HandlesFunction(hfProcessKey)) + { + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->ProcessKey(Key, ControlState); + } + } + else + { + return 0; + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E); + // when error occurs, assume that key can be handled by plugin and + // should not be processed by FAR + return 1; + } +} + +intptr_t TCustomFarPlugin::ProcessEvent(HANDLE Plugin, int Event, void * Param) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + //ResetCachedInfo(); + if (HandlesFunction(hfProcessEvent)) + { + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + UnicodeString Buf; + if ((Event == FE_CHANGEVIEWMODE) || (Event == FE_COMMAND)) + { + Buf = static_cast(Param); + Param = const_cast(reinterpret_cast(Buf.c_str())); + } + else if ((Event == FE_GOTFOCUS) || (Event == FE_KILLFOCUS)) + { + } + + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->ProcessEvent(Event, Param); + } + else + { + return 0; + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E); + return Event == FE_COMMAND ? 1 : 0; + } +} + +intptr_t TCustomFarPlugin::SetDirectory(HANDLE Plugin, const wchar_t * Dir, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + DebugAssert(FarFileSystem); + if (!FarFileSystem) + { + return 0; + } + UnicodeString PrevCurrentDirectory = FarFileSystem->GetCurrDirectory(); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->SetDirectory(Dir, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + if (FarFileSystem->GetOpenPluginInfoValid() && !PrevCurrentDirectory.IsEmpty()) + { + try + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->SetDirectory(PrevCurrentDirectory.c_str(), OpMode); + } + catch (Exception &) + { + return 0; + } + } + return 0; + } +} + +intptr_t TCustomFarPlugin::MakeDirectory(HANDLE Plugin, const wchar_t ** Name, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->MakeDirectory(Name, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + return 0; + } +} + +intptr_t TCustomFarPlugin::DeleteFiles(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->DeleteFiles(PanelItem, ItemsNumber, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + return 0; + } +} + +intptr_t TCustomFarPlugin::GetFiles(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int Move, + const wchar_t ** DestPath, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->GetFiles(PanelItem, ItemsNumber, Move, DestPath, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + // display error even for OPM_FIND + HandleFileSystemException(FarFileSystem, &E, OpMode & ~OPM_FIND); + return 0; + } +} + +intptr_t TCustomFarPlugin::PutFiles(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int Move, const wchar_t * srcPath, int OpMode) +{ + TCustomFarFileSystem * FarFileSystem = NB_STATIC_DOWNCAST(TCustomFarFileSystem, Plugin); + try + { + ResetCachedInfo(); + DebugAssert(FOpenedPlugins->IndexOf(FarFileSystem) != NPOS); + + { + TGuard Guard(FarFileSystem->GetCriticalSection()); + return FarFileSystem->PutFiles(PanelItem, ItemsNumber, Move, srcPath, OpMode); + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleFileSystemException"); + HandleFileSystemException(FarFileSystem, &E, OpMode); + return 0; + } +} + +intptr_t TCustomFarPlugin::ProcessEditorEvent(int Event, void * Param) +{ + try + { + ResetCachedInfo(); + + return ProcessEditorEventEx(Event, Param); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + return 0; + } +} + +intptr_t TCustomFarPlugin::ProcessEditorInput(const INPUT_RECORD * Rec) +{ + try + { + ResetCachedInfo(); + + return ProcessEditorInputEx(Rec); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + // when error occurs, assume that input event can be handled by plugin and + // should not be processed by FAR + return 1; + } +} + +intptr_t TCustomFarPlugin::MaxMessageLines() +{ + return static_cast(TerminalInfo().y - 5); +} + +intptr_t TCustomFarPlugin::MaxMenuItemLength() +{ + // got from maximal length of path in FAR's folders history + return static_cast(TerminalInfo().x - 13); +} + +intptr_t TCustomFarPlugin::MaxLength(TStrings * Strings) +{ + intptr_t Result = 0; + for (intptr_t Index = 0; Index < Strings->GetCount(); ++Index) + { + if (Result < Strings->GetString(Index).Length()) + { + Result = Strings->GetString(Index).Length(); + } + } + return Result; +} + +class TFarMessageDialog : public TFarDialog +{ +public: + explicit TFarMessageDialog(TCustomFarPlugin * Plugin, + TFarMessageParams * Params); + void Init(uintptr_t AFlags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons); + + intptr_t Execute(bool & ACheckBox); + +protected: + virtual void Change(); + virtual void Idle(); + +private: + void ButtonClick(TFarButton * Sender, bool & Close); + +private: + bool FCheckBoxChecked; + TFarMessageParams * FParams; + TDateTime FStartTime; + TDateTime FLastTimerTime; + TFarButton * FTimeoutButton; + UnicodeString FTimeoutButtonCaption; + TFarCheckBox * FCheckBox; +}; + +TFarMessageDialog::TFarMessageDialog(TCustomFarPlugin * Plugin, + TFarMessageParams * Params) : + TFarDialog(Plugin), + FCheckBoxChecked(false), + FParams(Params), + FTimeoutButton(nullptr), + FCheckBox(nullptr) +{ + DebugAssert(FParams != nullptr); +} + +void TFarMessageDialog::Init(uintptr_t AFlags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons) +{ + DebugAssert(FLAGCLEAR(AFlags, FMSG_ERRORTYPE)); + DebugAssert(FLAGCLEAR(AFlags, FMSG_KEEPBACKGROUND)); + // FIXME DebugAssert(FLAGCLEAR(AFlags, FMSG_DOWN)); + DebugAssert(FLAGCLEAR(AFlags, FMSG_ALLINONE)); + std::unique_ptr MessageLines(new TStringList()); + FarWrapText(Message, MessageLines.get(), MaxMessageWidth); + intptr_t MaxLen = GetFarPlugin()->MaxLength(MessageLines.get()); + TStrings * MoreMessageLines = nullptr; + std::unique_ptr MoreMessageLinesPtr; + if (FParams->MoreMessages != nullptr) + { + MoreMessageLines = new TStringList(); + MoreMessageLinesPtr.reset(MoreMessageLines); + UnicodeString MoreMessages = FParams->MoreMessages->GetText(); + while ((MoreMessages.Length() > 0) && (MoreMessages[MoreMessages.Length()] == L'\n' || + MoreMessages[MoreMessages.Length()] == L'\r')) + { + MoreMessages.SetLength(MoreMessages.Length() - 1); + } + FarWrapText(MoreMessages, MoreMessageLines, MaxMessageWidth); + intptr_t MoreMaxLen = GetFarPlugin()->MaxLength(MoreMessageLines); + if (MaxLen < MoreMaxLen) + { + MaxLen = MoreMaxLen; + } + } + + // temporary + SetSize(TPoint(MaxMessageWidth, 10)); + SetCaption(Title); + SetFlags(GetFlags() | + FLAGMASK(FLAGSET(AFlags, FMSG_WARNING), FDLG_WARNING)); + + for (intptr_t Index = 0; Index < MessageLines->GetCount(); ++Index) + { + TFarText * Text = new TFarText(this); + Text->SetCaption(MessageLines->GetString(Index)); + } + + TFarLister * MoreMessagesLister = nullptr; + TFarSeparator * MoreMessagesSeparator = nullptr; + + if (FParams->MoreMessages != nullptr) + { + new TFarSeparator(this); + + MoreMessagesLister = new TFarLister(this); + MoreMessagesLister->GetItems()->Assign(MoreMessageLines); + MoreMessagesLister->SetLeft(GetBorderBox()->GetLeft() + 1); + + MoreMessagesSeparator = new TFarSeparator(this); + } + + int ButtonOffset = (FParams->CheckBoxLabel.IsEmpty() ? -1 : -2); + int ButtonLines = 1; + TFarButton * Button = nullptr; + FTimeoutButton = nullptr; + for (uintptr_t Index = 0; Index < (uintptr_t)Buttons->GetCount(); ++Index) + { + TFarButton * PrevButton = Button; + Button = new TFarButton(this); + Button->SetDefault(FParams->DefaultButton == Index); + Button->SetBrackets(brNone); + Button->SetOnClick(MAKE_CALLBACK(TFarMessageDialog::ButtonClick, this)); + UnicodeString Caption = Buttons->GetString(Index); + if ((FParams->Timeout > 0) && + (FParams->TimeoutButton == static_cast(Index))) + { + FTimeoutButtonCaption = Caption; + Caption = FORMAT(FParams->TimeoutStr.c_str(), Caption.c_str(), static_cast(FParams->Timeout / 1000)); + FTimeoutButton = Button; + } + Button->SetCaption(FORMAT(L" %s ", Caption.c_str())); + Button->SetTop(GetBorderBox()->GetBottom() + ButtonOffset); + Button->SetBottom(Button->GetTop()); + Button->SetResult(Index + 1); + Button->SetCenterGroup(true); + Button->SetTag(reinterpret_cast(Buttons->GetObj(Index))); + if (PrevButton != nullptr) + { + Button->Move(PrevButton->GetRight() - Button->GetLeft() + 1, 0); + } + + if (MaxMessageWidth < Button->GetRight() - GetBorderBox()->GetLeft()) + { + for (intptr_t PIndex = 0; PIndex < GetItemCount(); ++PIndex) + { + TFarButton * PrevButton2 = NB_STATIC_DOWNCAST(TFarButton, GetItem(PIndex)); + if ((PrevButton2 != nullptr) && (PrevButton2 != Button)) + { + PrevButton2->Move(0, -1); + } + } + Button->Move(-(Button->GetLeft() - GetBorderBox()->GetLeft()), 0); + ButtonLines++; + } + + if (MaxLen < Button->GetRight() - GetBorderBox()->GetLeft()) + { + MaxLen = static_cast(Button->GetRight() - GetBorderBox()->GetLeft() + 2); + } + + SetNextItemPosition(ipRight); + } + + if (!FParams->CheckBoxLabel.IsEmpty()) + { + SetNextItemPosition(ipNewLine); + FCheckBox = new TFarCheckBox(this); + FCheckBox->SetCaption(FParams->CheckBoxLabel); + + if (MaxLen < FCheckBox->GetRight() - GetBorderBox()->GetLeft()) + { + MaxLen = static_cast(FCheckBox->GetRight() - GetBorderBox()->GetLeft()); + } + } + else + { + FCheckBox = nullptr; + } + + TRect rect = GetClientRect(); + TPoint S( + static_cast(rect.Left + MaxLen - rect.Right), + static_cast(rect.Top + MessageLines->GetCount() + + (FParams->MoreMessages != nullptr ? 1 : 0) + ButtonLines + + (!FParams->CheckBoxLabel.IsEmpty() ? 1 : 0) + + (-(rect.Bottom + 1)))); + + if (FParams->MoreMessages != nullptr) + { + intptr_t MoreMessageHeight = static_cast(GetFarPlugin()->TerminalInfo().y - S.y - 1); + DebugAssert(MoreMessagesLister != nullptr); + if (MoreMessageHeight > MoreMessagesLister->GetItems()->GetCount()) + { + MoreMessageHeight = MoreMessagesLister->GetItems()->GetCount(); + } + MoreMessagesLister->SetHeight(MoreMessageHeight); + MoreMessagesLister->SetRight( + GetBorderBox()->GetRight() - (MoreMessagesLister->GetScrollBar() ? 0 : 1)); + MoreMessagesLister->SetTabStop(MoreMessagesLister->GetScrollBar()); + DebugAssert(MoreMessagesSeparator != nullptr); + MoreMessagesSeparator->SetPosition( + MoreMessagesLister->GetTop() + MoreMessagesLister->GetHeight()); + S.y += static_cast(MoreMessagesLister->GetHeight()) + 1; + } + SetSize(S); +} + +void TFarMessageDialog::Idle() +{ + TFarDialog::Idle(); + + if (FParams->Timer > 0) + { + size_t SinceLastTimer = static_cast((Now() - FLastTimerTime).GetValue() * MSecsPerDay); + if (SinceLastTimer >= FParams->Timeout) + { + DebugAssert(FParams->TimerEvent); + if (FParams->TimerEvent) + { + FParams->TimerAnswer = 0; + FParams->TimerEvent(FParams->TimerAnswer); + if (FParams->TimerAnswer != 0) + { + Close(GetDefaultButton()); + } + FLastTimerTime = Now(); + } + } + } + + if (FParams->Timeout > 0) + { + size_t Running = static_cast((Now() - FStartTime).GetValue() * MSecsPerDay); + if (Running >= FParams->Timeout) + { + DebugAssert(FTimeoutButton != nullptr); + Close(FTimeoutButton); + } + else + { + UnicodeString Caption = + FORMAT(L" %s ", ::Format(FParams->TimeoutStr.c_str(), + FTimeoutButtonCaption.c_str(), static_cast((FParams->Timeout - Running) / 1000)).c_str()).c_str(); + intptr_t sz = FTimeoutButton->GetCaption().Length() > Caption.Length() ? FTimeoutButton->GetCaption().Length() - Caption.Length() : 0; + Caption += ::StringOfChar(L' ', sz); + FTimeoutButton->SetCaption(Caption); + } + } +} + +void TFarMessageDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle() != nullptr) + { + if ((FCheckBox != nullptr) && (FCheckBoxChecked != FCheckBox->GetChecked())) + { + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TFarButton * Button = NB_STATIC_DOWNCAST(TFarButton, GetItem(Index)); + if ((Button != nullptr) && (Button->GetTag() == 0)) + { + Button->SetEnabled(!FCheckBox->GetChecked()); + } + } + FCheckBoxChecked = FCheckBox->GetChecked(); + } + } +} + +intptr_t TFarMessageDialog::Execute(bool & ACheckBox) +{ + if (GetDefaultButton()->CanFocus()) + GetDefaultButton()->UpdateFocused(true); + FStartTime = Now(); + FLastTimerTime = FStartTime; + FCheckBoxChecked = !ACheckBox; + if (FCheckBox != nullptr) + { + FCheckBox->SetChecked(ACheckBox); + } + + intptr_t Result = ShowModal(); + DebugAssert(Result != 0); + if (Result > 0) + { + if (FCheckBox != nullptr) + { + ACheckBox = FCheckBox->GetChecked(); + } + Result--; + } + return Result; +} + +void TFarMessageDialog::ButtonClick(TFarButton * Sender, bool & Close) +{ + if (FParams->ClickEvent) + { + FParams->ClickEvent(FParams->Token, Sender->GetResult() - 1, Close); + } +} + +intptr_t TCustomFarPlugin::DialogMessage(DWORD Flags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons, + TFarMessageParams * Params) +{ + std::unique_ptr Dialog(new TFarMessageDialog(this, Params)); + Dialog->Init(Flags, Title, Message, Buttons); + intptr_t Result = Dialog->Execute(Params->CheckBox); + return Result; +} + +intptr_t TCustomFarPlugin::FarMessage(DWORD Flags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons, + TFarMessageParams * Params) +{ + DebugAssert(Params != nullptr); + + wchar_t ** Items = nullptr; + SCOPE_EXIT + { + nb_free(Items); + }; + UnicodeString FullMessage = Message; + if (Params->MoreMessages != nullptr) + { + FullMessage += UnicodeString(L"\n\x01\n") + Params->MoreMessages->GetText(); + while (FullMessage[FullMessage.Length()] == L'\n' || + FullMessage[FullMessage.Length()] == L'\r') + { + FullMessage.SetLength(FullMessage.Length() - 1); + } + FullMessage += L"\n\x01\n"; + } + + TStringList * MessageLines = new TStringList(); + std::unique_ptr MessageLinesPtr(MessageLines); + MessageLines->Add(Title); + FarWrapText(FullMessage, MessageLines, MaxMessageWidth); + + // FAR WORKAROUND + // When there is too many lines to fit on screen, far uses not-shown + // lines as button captions instead of real captions at the end of the list + intptr_t MaxLines = MaxMessageLines(); + while (MessageLines->GetCount() > MaxLines) + { + MessageLines->Delete(MessageLines->GetCount() - 1); + } + + for (intptr_t Index = 0; Index < Buttons->GetCount(); ++Index) + { + MessageLines->Add(Buttons->GetString(Index)); + } + + Items = static_cast( + nb_calloc(1, sizeof(wchar_t *) * (1 + MessageLines->GetCount()))); + for (intptr_t Index = 0; Index < MessageLines->GetCount(); ++Index) + { + UnicodeString S = MessageLines->GetString(Index); + MessageLines->SetString(Index, UnicodeString(S)); + Items[Index] = const_cast(MessageLines->GetString(Index).c_str()); + } + + TFarEnvGuard Guard; + intptr_t Result = static_cast(FStartupInfo.Message(FStartupInfo.ModuleNumber, + Flags | FMSG_LEFTALIGN, nullptr, Items, static_cast(MessageLines->GetCount()), + static_cast(Buttons->GetCount()))); + + return Result; +} + +intptr_t TCustomFarPlugin::Message(DWORD Flags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons, + TFarMessageParams * Params) +{ + // when message is shown while some "custom" output is on screen, + // make the output actually background of FAR screen + if (FTerminalScreenShowing) + { + FarControl(FCTL_SETUSERSCREEN, 0, 0); + } + + intptr_t Result = -1; + if (Buttons != nullptr) + { + TFarMessageParams DefaultParams; + TFarMessageParams * AParams = (Params == nullptr ? &DefaultParams : Params); + Result = DialogMessage(Flags, Title, Message, Buttons, AParams); + } + else + { + DebugAssert(Params == nullptr); + UnicodeString Items = Title + L"\n" + Message; + TFarEnvGuard Guard; + Result = static_cast(FStartupInfo.Message(FStartupInfo.ModuleNumber, + Flags | FMSG_ALLINONE | FMSG_LEFTALIGN, + nullptr, + static_cast(static_cast(Items.c_str())), 0, 0)); + } + return Result; +} + +intptr_t TCustomFarPlugin::Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, const FarMenuItem * Items, intptr_t Count, + const int * BreakKeys, int & BreakCode) +{ + DebugAssert(Items); + + TFarEnvGuard Guard; + return static_cast(FStartupInfo.Menu(FStartupInfo.ModuleNumber, -1, -1, 0, + Flags, Title.c_str(), Bottom.c_str(), nullptr, BreakKeys, + &BreakCode, Items, static_cast(Count))); +} + +intptr_t TCustomFarPlugin::Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, TStrings * Items, const int * BreakKeys, + int & BreakCode) +{ + DebugAssert(Items && Items->GetCount()); + intptr_t Result = 0; + FarMenuItemEx * MenuItems = static_cast( + nb_calloc(1, sizeof(FarMenuItemEx) * (1 + Items->GetCount()))); + SCOPE_EXIT + { + nb_free(MenuItems); + }; + intptr_t Selected = NPOS; + intptr_t Count = 0; + for (intptr_t Index = 0; Index < Items->GetCount(); ++Index) + { + uintptr_t Flags2 = reinterpret_cast(Items->GetObj(Index)); + if (FLAGCLEAR(Flags2, MIF_HIDDEN)) + { + ClearStruct(MenuItems[Count]); + MenuItems[Count].Flags = static_cast(Flags2); + if (MenuItems[Count].Flags & MIF_SELECTED) + { + DebugAssert(Selected == NPOS); + Selected = Index; + } + MenuItems[Count].Text = Items->GetString(Index).c_str(); + MenuItems[Count].UserData = Index; + Count++; + } + } + + intptr_t ResultItem = Menu(Flags | FMENU_USEEXT, Title, Bottom, + reinterpret_cast(MenuItems), Count, BreakKeys, BreakCode); + + if (ResultItem >= 0) + { + Result = MenuItems[ResultItem].UserData; + if (Selected >= 0) + { + Items->SetObj(Selected, reinterpret_cast(reinterpret_cast(Items->GetObj(Selected)) & ~MIF_SELECTED)); + } + Items->SetObj(Result, reinterpret_cast(reinterpret_cast(Items->GetObj(Result)) | MIF_SELECTED)); + } + else + { + Result = ResultItem; + } + return Result; +} + +intptr_t TCustomFarPlugin::Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, TStrings * Items) +{ + int BreakCode; + return Menu(Flags, Title, Bottom, Items, nullptr, BreakCode); +} + +bool TCustomFarPlugin::InputBox(const UnicodeString & Title, + const UnicodeString & Prompt, UnicodeString & Text, DWORD Flags, + const UnicodeString & HistoryName, intptr_t MaxLen, TFarInputBoxValidateEvent OnValidate) +{ + bool Repeat = false; + int Result = 0; + do + { + UnicodeString DestText; + DestText.SetLength(MaxLen + 1); + HANDLE ScreenHandle = 0; + SaveScreen(ScreenHandle); + { + TFarEnvGuard Guard; + Result = FStartupInfo.InputBox( + Title.c_str(), + Prompt.c_str(), + HistoryName.c_str(), + Text.c_str(), + const_cast(DestText.c_str()), + static_cast(MaxLen), + nullptr, + FIB_ENABLEEMPTY | FIB_BUTTONS | Flags); + } + RestoreScreen(ScreenHandle); + Repeat = false; + if (Result) + { + Text = DestText.c_str(); + if (OnValidate) + { + try + { + OnValidate(Text); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + Repeat = true; + } + } + } + } + while (Repeat); + + return (Result != 0); +} + +void TCustomFarPlugin::Text(int X, int Y, int Color, const UnicodeString & Str) +{ + TFarEnvGuard Guard; + FStartupInfo.Text(X, Y, Color, Str.c_str()); +} + +void TCustomFarPlugin::FlushText() +{ + TFarEnvGuard Guard; + FStartupInfo.Text(0, 0, 0, nullptr); +} + +void TCustomFarPlugin::FarWriteConsole(const UnicodeString & Str) +{ + DWORD Written; + ::WriteConsole(FConsoleOutput, Str.c_str(), static_cast(Str.Length()), &Written, nullptr); +} + +void TCustomFarPlugin::FarCopyToClipboard(const UnicodeString & Str) +{ + TFarEnvGuard Guard; + FFarStandardFunctions.CopyToClipboard(Str.c_str()); +} + +void TCustomFarPlugin::FarCopyToClipboard(const TStrings * Strings) +{ + if (Strings->GetCount() > 0) + { + if (Strings->GetCount() == 1) + { + FarCopyToClipboard(Strings->GetString(0)); + } + else + { + FarCopyToClipboard(Strings->GetText()); + } + } +} + +TPoint TCustomFarPlugin::TerminalInfo(TPoint * Size, TPoint * Cursor) const +{ + CONSOLE_SCREEN_BUFFER_INFO BufferInfo; + ClearStruct(BufferInfo); + ::GetConsoleScreenBufferInfo(FConsoleOutput, &BufferInfo); + if (FarPlugin) + FarAdvControl(ACTL_GETFARRECT, &BufferInfo.srWindow); + + TPoint Result( + BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1, + BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1); + + if (Size != nullptr) + { + *Size = Result; + } + + if (Cursor != nullptr) + { + Cursor->x = BufferInfo.dwCursorPosition.X - BufferInfo.srWindow.Left; + Cursor->y = BufferInfo.dwCursorPosition.Y; // - BufferInfo.srWindow.Top; + } + return Result; +} + +HWND TCustomFarPlugin::GetConsoleWindow() const +{ +#ifndef __linux__ + wchar_t Title[1024]; + ::GetConsoleTitle(Title, _countof(Title)); + HWND Result = ::FindWindow(nullptr, Title); + return Result; +#else + return (HWND)0; +#endif +} + +uintptr_t TCustomFarPlugin::ConsoleWindowState() const +{ +#ifndef __linux__ + uintptr_t Result = SW_SHOWNORMAL; + HWND Window = GetConsoleWindow(); + if (Window != nullptr) + { + WINDOWPLACEMENT WindowPlacement; + ClearStruct(WindowPlacement); + WindowPlacement.length = sizeof(WindowPlacement); + ::Win32Check(::GetWindowPlacement(Window, &WindowPlacement) > 0); + Result = WindowPlacement.showCmd; + } + return Result; +#else + return 0; +#endif +} + +void TCustomFarPlugin::ToggleVideoMode() +{ +#ifndef __linux__ + HWND Window = GetConsoleWindow(); + if (Window != nullptr) + { + if (ConsoleWindowState() == SW_SHOWMAXIMIZED) + { + if (FNormalConsoleSize.x >= 0) + { + COORD Size = { static_cast(FNormalConsoleSize.x), static_cast(FNormalConsoleSize.y) }; + + ::Win32Check(::ShowWindow(Window, SW_RESTORE) > 0); + + SMALL_RECT WindowSize; + WindowSize.Left = 0; + WindowSize.Top = 0; + WindowSize.Right = static_cast(Size.X - 1); + WindowSize.Bottom = static_cast(Size.Y - 1); + ::Win32Check(::SetConsoleWindowInfo(FConsoleOutput, true, &WindowSize) > 0); + + ::Win32Check(::SetConsoleScreenBufferSize(FConsoleOutput, Size) > 0); + } + } + else + { + COORD Size = ::GetLargestConsoleWindowSize(FConsoleOutput); + ::Win32Check((Size.X != 0) || (Size.Y != 0)); + + FNormalConsoleSize = TerminalInfo(); + + ::Win32Check(::ShowWindow(Window, SW_MAXIMIZE) > 0); + + ::Win32Check(::SetConsoleScreenBufferSize(FConsoleOutput, Size) > 0); + + CONSOLE_SCREEN_BUFFER_INFO BufferInfo; + ::Win32Check(::GetConsoleScreenBufferInfo(FConsoleOutput, &BufferInfo) > 0); + + SMALL_RECT WindowSize; + WindowSize.Left = 0; + WindowSize.Top = 0; + WindowSize.Right = static_cast(BufferInfo.dwMaximumWindowSize.X - 1); + WindowSize.Bottom = static_cast(BufferInfo.dwMaximumWindowSize.Y - 1); + ::Win32Check(::SetConsoleWindowInfo(FConsoleOutput, true, &WindowSize) > 0); + } + } +#endif +} + +void TCustomFarPlugin::ScrollTerminalScreen(int Rows) +{ + TPoint Size = TerminalInfo(); + + SMALL_RECT Source; + COORD Dest; + CHAR_INFO Fill; + Source.Left = 0; + Source.Top = static_cast(Rows); + Source.Right = static_cast(Size.x); + Source.Bottom = static_cast(Size.y); + Dest.X = 0; + Dest.Y = 0; + Fill.Char.UnicodeChar = L' '; + Fill.Attributes = 7; + ::ScrollConsoleScreenBuffer(FConsoleOutput, &Source, nullptr, Dest, &Fill); +} + +void TCustomFarPlugin::ShowTerminalScreen() +{ + DebugAssert(!FTerminalScreenShowing); + TPoint Size, Cursor; + TerminalInfo(&Size, &Cursor); + + if (Size.y >= 2) + { + // clean menu keybar area before command output + UnicodeString Blank = ::StringOfChar(L' ', static_cast(Size.x)); + for (int Y = Size.y - 2; Y < Size.y; Y++) + { + Text(0, Y, 7 /*LIGHTGRAY*/, Blank); + } + } + FlushText(); + + COORD Coord; + Coord.X = 0; + Coord.Y = static_cast(Cursor.y); + ::SetConsoleCursorPosition(FConsoleOutput, Coord); + FTerminalScreenShowing = true; +} + +void TCustomFarPlugin::SaveTerminalScreen() +{ + FTerminalScreenShowing = false; + FarControl(FCTL_SETUSERSCREEN, 0, 0); +} + +class TConsoleTitleParam : public TObject +{ +NB_DECLARE_CLASS(TConsoleTitleParam) +public: + explicit TConsoleTitleParam() : + Progress(0), + Own(0) + {} + short Progress; + short Own; +}; + +void TCustomFarPlugin::ShowConsoleTitle(const UnicodeString & Title) +{ + wchar_t SaveTitle[1024]; + ::GetConsoleTitle(SaveTitle, _countof(SaveTitle)); + TConsoleTitleParam * Param = new TConsoleTitleParam(); + Param->Progress = FCurrentProgress; + Param->Own = !FCurrentTitle.IsEmpty() && (FormatConsoleTitle() == SaveTitle); + if (Param->Own) + { + FSavedTitles->AddObject(FCurrentTitle, Param); + } + else + { + FSavedTitles->AddObject(SaveTitle, Param); + } + FCurrentTitle = Title; + FCurrentProgress = -1; + UpdateCurrentConsoleTitle(); +} + +void TCustomFarPlugin::ClearConsoleTitle() +{ + DebugAssert(FSavedTitles->GetCount() > 0); + UnicodeString Title = FSavedTitles->GetString(FSavedTitles->GetCount() - 1); + TObject * Object = FSavedTitles->GetObj(FSavedTitles->GetCount() - 1); + TConsoleTitleParam * Param = NB_STATIC_DOWNCAST(TConsoleTitleParam, Object); + if (Param->Own) + { + FCurrentTitle = Title; + FCurrentProgress = Param->Progress; + UpdateCurrentConsoleTitle(); + } + else + { + FCurrentTitle.Clear(); + FCurrentProgress = -1; + ::SetConsoleTitle(Title.c_str()); + UpdateProgress(PS_NOPROGRESS, 0); + } + { + TObject * Obj = FSavedTitles->GetObj(FSavedTitles->GetCount() - 1); + SAFE_DESTROY(Obj); + } + FSavedTitles->Delete(FSavedTitles->GetCount() - 1); +} + +void TCustomFarPlugin::UpdateConsoleTitle(const UnicodeString & Title) +{ + FCurrentTitle = Title; + UpdateCurrentConsoleTitle(); +} + +void TCustomFarPlugin::UpdateConsoleTitleProgress(short Progress) +{ + FCurrentProgress = Progress; + UpdateCurrentConsoleTitle(); +} + +UnicodeString TCustomFarPlugin::FormatConsoleTitle() +{ + UnicodeString Title; + if (FCurrentProgress >= 0) + { + Title = FORMAT(L"{%d%%} %s", FCurrentProgress, FCurrentTitle.c_str()); + } + else + { + Title = FCurrentTitle; + } + Title += FAR_TITLE_SUFFIX; + return Title; +} + +void TCustomFarPlugin::UpdateProgress(intptr_t State, intptr_t Progress) +{ + FarAdvControl(ACTL_SETPROGRESSSTATE, ToPtr(State)); + if (State == PS_NORMAL) + { + PROGRESSVALUE pv; + pv.Completed = Progress < 0 ? 0 : Progress > 100 ? 100 : Progress; + pv.Total = 100; + FarAdvControl(ACTL_SETPROGRESSVALUE, static_cast(&pv)); + } +} + +void TCustomFarPlugin::UpdateCurrentConsoleTitle() +{ + UnicodeString Title = FormatConsoleTitle(); + ::SetConsoleTitle(Title.c_str()); + short progress = FCurrentProgress != -1 ? FCurrentProgress : 0; + UpdateProgress(progress != 0 ? PS_NORMAL : PS_NOPROGRESS, progress); +} + +void TCustomFarPlugin::SaveScreen(HANDLE & Screen) +{ + DebugAssert(!Screen); + TFarEnvGuard Guard; + Screen = static_cast(FStartupInfo.SaveScreen(0, 0, -1, -1)); + DebugAssert(Screen); +} + +void TCustomFarPlugin::RestoreScreen(HANDLE & Screen) +{ + DebugAssert(Screen); + TFarEnvGuard Guard; + FStartupInfo.RestoreScreen(Screen); + Screen = 0; +} + +void TCustomFarPlugin::HandleException(Exception * E, int /*OpMode*/) +{ + DebugAssert(E); + Message(FMSG_WARNING | FMSG_MB_OK, L"", E ? E->Message : L""); +} + +UnicodeString TCustomFarPlugin::GetMsg(intptr_t MsgId) const +{ + TFarEnvGuard Guard; + UnicodeString Result = FStartupInfo.GetMsg(FStartupInfo.ModuleNumber, (int)MsgId); + return Result; +} + +bool TCustomFarPlugin::CheckForEsc() +{ + static uint32_t LastTicks; + uint32_t Ticks = ::GetTickCount(); + if ((LastTicks == 0) || (Ticks - LastTicks > 500)) + { + LastTicks = Ticks; + + INPUT_RECORD Rec; + DWORD ReadCount; + while (::PeekConsoleInput(FConsoleInput, &Rec, 1, &ReadCount) && ReadCount) + { + ::ReadConsoleInput(FConsoleInput, &Rec, 1, &ReadCount); + if (Rec.EventType == KEY_EVENT && + Rec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE && + Rec.Event.KeyEvent.bKeyDown) + { + return true; + } + } + } + return false; +} + +bool TCustomFarPlugin::Viewer(const UnicodeString & AFileName, + const UnicodeString & Title, DWORD Flags) +{ + TFarEnvGuard Guard; + int Result = FStartupInfo.Viewer( + AFileName.c_str(), + Title.c_str(), 0, 0, -1, -1, Flags, + CP_AUTODETECT); + return Result > 0; +} + +bool TCustomFarPlugin::Editor(const UnicodeString & AFileName, + const UnicodeString & Title, DWORD Flags) +{ + TFarEnvGuard Guard; + int Result = FStartupInfo.Editor( + AFileName.c_str(), + Title.c_str(), 0, 0, -1, -1, Flags, -1, -1, + CP_AUTODETECT); + return (Result == EEC_MODIFIED) || (Result == EEC_NOT_MODIFIED); +} + +void TCustomFarPlugin::ResetCachedInfo() +{ + FValidFarSystemSettings = false; +} + +intptr_t TCustomFarPlugin::GetFarSystemSettings() const +{ + if (!FValidFarSystemSettings) + { + FFarSystemSettings = FarAdvControl(ACTL_GETSYSTEMSETTINGS); + FValidFarSystemSettings = true; + } + return FFarSystemSettings; +} + +intptr_t TCustomFarPlugin::FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2, HANDLE Plugin) +{ + switch (Command) + { + case FCTL_CLOSEPLUGIN: + case FCTL_SETPANELDIR: + case FCTL_SETCMDLINE: + case FCTL_INSERTCMDLINE: + break; + + case FCTL_GETCMDLINE: + case FCTL_GETCMDLINESELECTEDTEXT: + // ANSI/OEM translation not implemented yet + DebugAssert(false); + break; + } + + TFarEnvGuard Guard; + return FStartupInfo.Control(Plugin, static_cast(Command), static_cast(Param1), Param2); +} + +intptr_t TCustomFarPlugin::FarAdvControl(uintptr_t Command, void * Param) const +{ + TFarEnvGuard Guard; + return FStartupInfo.AdvControl(FStartupInfo.ModuleNumber, static_cast(Command), Param); +} + +intptr_t TCustomFarPlugin::FarEditorControl(uintptr_t Command, void * Param) +{ + switch (Command) + { + case ECTL_GETINFO: + case ECTL_SETPARAM: + case ECTL_GETFILENAME: + // noop + break; + + case ECTL_SETTITLE: + break; + + default: + // for other commands, OEM/ANSI conversion to be verified + DebugAssert(false); + break; + } + + TFarEnvGuard Guard; + return static_cast(FStartupInfo.EditorControl(static_cast(Command), Param)); +} + +TFarEditorInfo * TCustomFarPlugin::EditorInfo() +{ + TFarEditorInfo * Result; + ::EditorInfo * Info = static_cast< ::EditorInfo *>( + nb_malloc(sizeof(::EditorInfo))); + try + { + if (FarEditorControl(ECTL_GETINFO, Info)) + { + Result = new TFarEditorInfo(Info); + } + else + { + nb_free(Info); + Result = nullptr; + } + } + catch (...) + { + nb_free(Info); + throw; + } + return Result; +} + +intptr_t TCustomFarPlugin::GetFarVersion() const +{ + if (FFarVersion == 0) + { + FFarVersion = FarAdvControl(ACTL_GETFARVERSION); + } + return FFarVersion; +} + +UnicodeString TCustomFarPlugin::FormatFarVersion(intptr_t Version) const +{ + return FORMAT(L"%d.%d.%d", (Version >> 8) & 0xFF, Version & 0xFF, Version >> 16); +} + +UnicodeString TCustomFarPlugin::GetTemporaryDir() const +{ + UnicodeString Result(4096, 0); + TFarEnvGuard Guard; + FFarStandardFunctions.MkTemp(const_cast(Result.c_str()), (DWORD)Result.Length(), nullptr); + PackStr(Result); + return Result; +} + +intptr_t TCustomFarPlugin::InputRecordToKey(const INPUT_RECORD * Rec) +{ + int Result; + if (FFarStandardFunctions.FarInputRecordToKey != nullptr) + { + TFarEnvGuard Guard; + Result = FFarStandardFunctions.FarInputRecordToKey(Rec); + } + else + { + Result = 0; + } + return static_cast(Result); +} + +#ifdef NETBOX_DEBUG +void TCustomFarPlugin::RunTests() +{ + { + TFileMasks m(L"*.txt;*.log"); + bool res = m.Matches(L"test.exe"); + } + { + random_ref(); + random_unref(); + } +} +#endif + +uintptr_t TCustomFarFileSystem::FInstances = 0; + +TCustomFarFileSystem::TCustomFarFileSystem(TCustomFarPlugin * APlugin) : + TObject(), + FPlugin(APlugin), + FClosed(false), + FOpenPluginInfoValid(false) +{ + ::memset(FPanelInfo, 0, sizeof(FPanelInfo)); + ClearStruct(FOpenPluginInfo); +} + +void TCustomFarFileSystem::Init() +{ + FPanelInfo[0] = nullptr; + FPanelInfo[1] = nullptr; + FClosed = false; + + ClearStruct(FOpenPluginInfo); + ClearOpenPluginInfo(FOpenPluginInfo); + FInstances++; +} + +TCustomFarFileSystem::~TCustomFarFileSystem() +{ + FInstances--; + ResetCachedInfo(); + ClearOpenPluginInfo(FOpenPluginInfo); +} + +void TCustomFarFileSystem::HandleException(Exception * E, int OpMode) +{ + DEBUG_PRINTF("before FPlugin->HandleException"); + FPlugin->HandleException(E, OpMode); +} + +void TCustomFarFileSystem::Close() +{ + FClosed = true; +} + +void TCustomFarFileSystem::InvalidateOpenPluginInfo() +{ + FOpenPluginInfoValid = false; +} + +void TCustomFarFileSystem::ClearOpenPluginInfo(OpenPluginInfo & Info) +{ + if (Info.StructSize) + { + nb_free((void*)Info.HostFile); + nb_free((void*)Info.CurDir); + nb_free((void*)Info.Format); + nb_free((void*)Info.PanelTitle); + DebugAssert(!Info.InfoLines); + DebugAssert(!Info.InfoLinesNumber); + DebugAssert(!Info.DescrFiles); + DebugAssert(!Info.DescrFilesNumber); + DebugAssert(Info.PanelModesNumber == 0 || Info.PanelModesNumber == PANEL_MODES_COUNT); + for (intptr_t Index = 0; Index < Info.PanelModesNumber; ++Index) + { + DebugAssert(Info.PanelModesArray); + TFarPanelModes::ClearPanelMode( + const_cast(Info.PanelModesArray[Index])); + } + nb_free((void*)Info.PanelModesArray); + if (Info.KeyBar) + { + TFarKeyBarTitles::ClearKeyBarTitles(const_cast(*Info.KeyBar)); + nb_free((void*)Info.KeyBar); + } + nb_free((void*)Info.ShortcutData); + } + ClearStruct(Info); + Info.StructSize = sizeof(Info); + InvalidateOpenPluginInfo(); +} + +void TCustomFarFileSystem::GetOpenPluginInfo(struct OpenPluginInfo * Info) +{ + ResetCachedInfo(); + if (FClosed) + { + // FAR WORKAROUND + // if plugin is closed from ProcessEvent(FE_IDLE), is does not close, + // so we close it here on the very next opportunity + ClosePlugin(); + } + else + { + if (!FOpenPluginInfoValid) + { + ClearOpenPluginInfo(FOpenPluginInfo); + UnicodeString HostFile, CurDir, Format, PanelTitle, ShortcutData; + std::unique_ptr PanelModes(new TFarPanelModes()); + std::unique_ptr KeyBarTitles(new TFarKeyBarTitles()); + bool StartSortOrder = false; + + GetOpenPluginInfoEx(FOpenPluginInfo.Flags, HostFile, CurDir, Format, + PanelTitle, PanelModes.get(), FOpenPluginInfo.StartPanelMode, + FOpenPluginInfo.StartSortMode, StartSortOrder, KeyBarTitles.get(), ShortcutData); + + FOpenPluginInfo.HostFile = TCustomFarPlugin::DuplicateStr(HostFile); + FOpenPluginInfo.CurDir = TCustomFarPlugin::DuplicateStr(::StringReplaceAll(CurDir, L"\\", L"/")); + FOpenPluginInfo.Format = TCustomFarPlugin::DuplicateStr(Format); + FOpenPluginInfo.PanelTitle = TCustomFarPlugin::DuplicateStr(PanelTitle); + PanelModes->FillOpenPluginInfo(&FOpenPluginInfo); + FOpenPluginInfo.StartSortOrder = StartSortOrder; + KeyBarTitles->FillOpenPluginInfo(&FOpenPluginInfo); + FOpenPluginInfo.ShortcutData = TCustomFarPlugin::DuplicateStr(ShortcutData); + + FOpenPluginInfoValid = true; + } + + memmove(Info, &FOpenPluginInfo, sizeof(FOpenPluginInfo)); + } +} + +intptr_t TCustomFarFileSystem::GetFindData( + struct PluginPanelItem ** PanelItem, int * ItemsNumber, int OpMode) +{ + ResetCachedInfo(); + std::unique_ptr PanelItems(new TObjectList()); + bool Result = !FClosed && GetFindDataEx(PanelItems.get(), OpMode); + if (Result && PanelItems->GetCount()) + { + *PanelItem = static_cast( + nb_calloc(1, sizeof(PluginPanelItem) * PanelItems->GetCount())); + *ItemsNumber = static_cast(PanelItems->GetCount()); + for (intptr_t Index = 0; Index < PanelItems->GetCount(); ++Index) + { + NB_STATIC_DOWNCAST(TCustomFarPanelItem, PanelItems->GetObj(Index))->FillPanelItem( + &((*PanelItem)[Index])); + } + } + else + { + *PanelItem = nullptr; + *ItemsNumber = 0; + } + + return Result; +} + +void TCustomFarFileSystem::FreeFindData( + struct PluginPanelItem * PanelItem, int ItemsNumber) +{ + ResetCachedInfo(); + if (PanelItem) + { + DebugAssert(ItemsNumber > 0); + for (intptr_t Index = 0; Index < ItemsNumber; ++Index) + { + nb_free((void*)PanelItem[Index].FindData.lpwszFileName); + nb_free((void*)PanelItem[Index].Description); + nb_free((void*)PanelItem[Index].Owner); + for (intptr_t CustomIndex = 0; CustomIndex < PanelItem[Index].CustomColumnNumber; ++CustomIndex) + { + nb_free((void*)PanelItem[Index].CustomColumnData[CustomIndex]); + } + nb_free((void*)PanelItem[Index].CustomColumnData); + } + nb_free(PanelItem); + } +} + +intptr_t TCustomFarFileSystem::ProcessHostFile(struct PluginPanelItem * PanelItem, + int ItemsNumber, int OpMode) +{ + ResetCachedInfo(); + std::unique_ptr PanelItems(CreatePanelItemList(PanelItem, ItemsNumber)); + bool Result = ProcessHostFileEx(PanelItems.get(), OpMode); + return Result; +} + +intptr_t TCustomFarFileSystem::ProcessKey(intptr_t Key, uintptr_t ControlState) +{ + ResetCachedInfo(); + return ProcessKeyEx(Key, ControlState); +} + +intptr_t TCustomFarFileSystem::ProcessEvent(intptr_t Event, void * Param) +{ + ResetCachedInfo(); + return ProcessEventEx(Event, Param); +} + +intptr_t TCustomFarFileSystem::SetDirectory(const wchar_t * Dir, int OpMode) +{ + ResetCachedInfo(); + InvalidateOpenPluginInfo(); + intptr_t Result = SetDirectoryEx(Dir, OpMode); + InvalidateOpenPluginInfo(); + return Result; +} + +intptr_t TCustomFarFileSystem::MakeDirectory(const wchar_t ** Name, int OpMode) +{ + ResetCachedInfo(); + FNameStr = *Name; + intptr_t Result = 0; + SCOPE_EXIT + { + if (FNameStr != *Name) + { + *Name = FNameStr.c_str(); + } + }; + Result = MakeDirectoryEx(FNameStr, OpMode); + return Result; +} + +intptr_t TCustomFarFileSystem::DeleteFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int OpMode) +{ + ResetCachedInfo(); + std::unique_ptr PanelItems(CreatePanelItemList(PanelItem, ItemsNumber)); + bool Result = DeleteFilesEx(PanelItems.get(), OpMode); + return Result; +} + +intptr_t TCustomFarFileSystem::GetFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t ** DestPath, int OpMode) +{ + ResetCachedInfo(); + std::unique_ptr PanelItems(CreatePanelItemList(PanelItem, ItemsNumber)); + intptr_t Result = 0; + FDestPathStr = *DestPath; + { + SCOPE_EXIT + { + if (FDestPathStr != *DestPath) + { + *DestPath = FDestPathStr.c_str(); + } + }; + Result = GetFilesEx(PanelItems.get(), Move > 0, FDestPathStr, OpMode); + } + + return Result; +} + +intptr_t TCustomFarFileSystem::PutFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t * srcPath, int OpMode) +{ + (void)srcPath; + ResetCachedInfo(); + intptr_t Result = 0; + std::unique_ptr PanelItems(CreatePanelItemList(PanelItem, ItemsNumber)); + Result = PutFilesEx(PanelItems.get(), Move > 0, OpMode); + return Result; +} + +void TCustomFarFileSystem::ResetCachedInfo() +{ + if (FPanelInfo[0]) + { + SAFE_DESTROY(FPanelInfo[false]); + } + if (FPanelInfo[1]) + { + SAFE_DESTROY(FPanelInfo[true]); + } +} + +TFarPanelInfo * const * TCustomFarFileSystem::GetPanelInfo(int Another) const +{ + return const_cast(this)->GetPanelInfo(Another); +} + +TFarPanelInfo ** TCustomFarFileSystem::GetPanelInfo(int Another) +{ + bool bAnother = Another != 0; + if (FPanelInfo[bAnother] == nullptr) + { + PanelInfo * Info = static_cast( + nb_calloc(1, sizeof(PanelInfo))); + bool Res = (FPlugin->FarControl(FCTL_GETPANELINFO, 0, reinterpret_cast(Info), + !bAnother ? PANEL_ACTIVE : PANEL_PASSIVE) > 0); + if (!Res) + { + DebugAssert(false); + } + FPanelInfo[bAnother] = new TFarPanelInfo(Info, !bAnother ? this : nullptr); + } + return &FPanelInfo[bAnother]; +} + +intptr_t TCustomFarFileSystem::FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2) +{ + return FPlugin->FarControl(Command, Param1, Param2, this); +} + +intptr_t TCustomFarFileSystem::FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2, HANDLE Plugin) +{ + return FPlugin->FarControl(Command, Param1, Param2, Plugin); +} + +bool TCustomFarFileSystem::UpdatePanel(bool ClearSelection, bool Another) +{ + uintptr_t PrevInstances = FInstances; + InvalidateOpenPluginInfo(); + FPlugin->FarControl(FCTL_UPDATEPANEL, !ClearSelection, 0, Another ? PANEL_PASSIVE : PANEL_ACTIVE); + return (FInstances >= PrevInstances); +} + +void TCustomFarFileSystem::RedrawPanel(bool Another) +{ + FPlugin->FarControl(FCTL_REDRAWPANEL, 0, reinterpret_cast(static_cast(0)), Another ? PANEL_PASSIVE : PANEL_ACTIVE); +} + +void TCustomFarFileSystem::ClosePlugin() +{ + FClosed = true; + FarControl(FCTL_CLOSEPLUGIN, 0, 0); +} + +UnicodeString TCustomFarFileSystem::GetMsg(int MsgId) +{ + return FPlugin->GetMsg(MsgId); +} + +TCustomFarFileSystem * TCustomFarFileSystem::GetOppositeFileSystem() +{ + return FPlugin->GetPanelFileSystem(true, this); +} + +bool TCustomFarFileSystem::IsActiveFileSystem() +{ + // Cannot use PanelInfo::Focus as it occasionally does not work from editor; + return (this == FPlugin->GetPanelFileSystem()); +} + +bool TCustomFarFileSystem::IsLeft() +{ + return ((*GetPanelInfo(0))->GetBounds().Left <= 0); +} + +bool TCustomFarFileSystem::IsRight() +{ + return !IsLeft(); +} + +bool TCustomFarFileSystem::ProcessHostFileEx(TObjectList * /*PanelItems*/, int /*OpMode*/) +{ + return false; +} + +bool TCustomFarFileSystem::ProcessKeyEx(intptr_t /*Key*/, uintptr_t /*ControlState*/) +{ + return false; +} + +bool TCustomFarFileSystem::ProcessEventEx(intptr_t /*Event*/, void * /*Param*/) +{ + return false; +} + +bool TCustomFarFileSystem::SetDirectoryEx(const UnicodeString & /*Dir*/, int /*OpMode*/) +{ + return false; +} + +intptr_t TCustomFarFileSystem::MakeDirectoryEx(UnicodeString & /*Name*/, int /*OpMode*/) +{ + return -1; +} + +bool TCustomFarFileSystem::DeleteFilesEx(TObjectList * /*PanelItems*/, int /*OpMode*/) +{ + return false; +} + +intptr_t TCustomFarFileSystem::GetFilesEx(TObjectList * /*PanelItems*/, bool /*Move*/, + UnicodeString & /*DestPath*/, int /*OpMode*/) +{ + return 0; +} + +intptr_t TCustomFarFileSystem::PutFilesEx(TObjectList * /*PanelItems*/, + bool /*Move*/, int /*OpMode*/) +{ + return 0; +} + +TObjectList * TCustomFarFileSystem::CreatePanelItemList( + struct PluginPanelItem * PanelItem, int ItemsNumber) +{ + std::unique_ptr PanelItems(new TObjectList()); + PanelItems->SetOwnsObjects(true); + for (intptr_t Index = 0; Index < ItemsNumber; ++Index) + { + PanelItems->Add(new TFarPanelItem(&PanelItem[Index], false)); + } + return PanelItems.release(); +} + +TFarPanelModes::TFarPanelModes() : TObject(), + FReferenced(false) +{ + ::memset(&FPanelModes, 0, sizeof(FPanelModes)); +} + +TFarPanelModes::~TFarPanelModes() +{ + if (!FReferenced) + { + for (intptr_t Index = 0; Index < static_cast(_countof(FPanelModes)); ++Index) + { + ClearPanelMode(FPanelModes[Index]); + } + } +} + +void TFarPanelModes::SetPanelMode(size_t Mode, const UnicodeString & ColumnTypes, + const UnicodeString & ColumnWidths, TStrings * ColumnTitles, + bool FullScreen, bool DetailedStatus, bool AlignExtensions, + bool CaseConversion, const UnicodeString & StatusColumnTypes, + const UnicodeString & StatusColumnWidths) +{ + intptr_t ColumnTypesCount = !ColumnTypes.IsEmpty() ? CommaCount(ColumnTypes) + 1 : 0; + DebugAssert(Mode != NPOS && Mode < _countof(FPanelModes)); + DebugAssert(!ColumnTitles || (ColumnTitles->GetCount() == ColumnTypesCount)); + + ClearPanelMode(FPanelModes[Mode]); + wchar_t ** Titles = static_cast( + nb_calloc(1, sizeof(wchar_t *) * (1 + ColumnTypesCount))); + FPanelModes[Mode].ColumnTypes = TCustomFarPlugin::DuplicateStr(ColumnTypes); + FPanelModes[Mode].ColumnWidths = TCustomFarPlugin::DuplicateStr(ColumnWidths); + if (ColumnTitles) + { + for (intptr_t Index = 0; Index < ColumnTypesCount; ++Index) + { + Titles[Index] = TCustomFarPlugin::DuplicateStr(ColumnTitles->GetString(Index)); + } + FPanelModes[Mode].ColumnTitles = Titles; + } + else + { + FPanelModes[Mode].ColumnTitles = nullptr; + } + FPanelModes[Mode].FullScreen = FullScreen; + FPanelModes[Mode].DetailedStatus = DetailedStatus; + FPanelModes[Mode].AlignExtensions = AlignExtensions; + FPanelModes[Mode].CaseConversion = CaseConversion; + + FPanelModes[Mode].StatusColumnTypes = TCustomFarPlugin::DuplicateStr(StatusColumnTypes); + FPanelModes[Mode].StatusColumnWidths = TCustomFarPlugin::DuplicateStr(StatusColumnWidths); +} + +void TFarPanelModes::ClearPanelMode(PanelMode & Mode) +{ + if (Mode.ColumnTypes) + { + intptr_t ColumnTypesCount = Mode.ColumnTypes ? + CommaCount(UnicodeString(Mode.ColumnTypes)) + 1 : 0; + + nb_free((void*)Mode.ColumnTypes); + nb_free((void*)Mode.ColumnWidths); + if (Mode.ColumnTitles) + { + for (intptr_t Index = 0; Index < ColumnTypesCount; ++Index) + { + nb_free((void*)Mode.ColumnTitles[Index]); + } + nb_free((void*)Mode.ColumnTitles); + } + nb_free((void*)Mode.StatusColumnTypes); + nb_free((void*)Mode.StatusColumnWidths); + ClearStruct(Mode); + } +} + +void TFarPanelModes::FillOpenPluginInfo(struct OpenPluginInfo * Info) +{ + DebugAssert(Info); + Info->PanelModesNumber = _countof(FPanelModes); + PanelMode * PanelModesArray = static_cast(nb_calloc(1, sizeof(PanelMode) * _countof(FPanelModes))); + memmove(PanelModesArray, &FPanelModes, sizeof(FPanelModes)); + Info->PanelModesArray = PanelModesArray; + FReferenced = true; +} + +intptr_t TFarPanelModes::CommaCount(const UnicodeString & ColumnTypes) +{ + intptr_t Count = 0; + for (intptr_t Index = 1; Index <= ColumnTypes.Length(); ++Index) + { + if (ColumnTypes[Index] == L',') + { + Count++; + } + } + return Count; +} + +TFarKeyBarTitles::TFarKeyBarTitles() : + FReferenced(false) +{ + ::memset(&FKeyBarTitles, 0, sizeof(FKeyBarTitles)); +} + +TFarKeyBarTitles::~TFarKeyBarTitles() +{ + if (!FReferenced) + { + ClearKeyBarTitles(FKeyBarTitles); + } +} + +void TFarKeyBarTitles::ClearFileKeyBarTitles() +{ + ClearKeyBarTitle(fsNone, 3, 8); + ClearKeyBarTitle(fsCtrl, 4, 11); + ClearKeyBarTitle(fsAlt, 3, 7); + ClearKeyBarTitle(fsShift, 1, 8); + ClearKeyBarTitle(fsCtrlShift, 3, 4); +} + +void TFarKeyBarTitles::ClearKeyBarTitle(TFarShiftStatus ShiftStatus, + intptr_t FunctionKeyStart, intptr_t FunctionKeyEnd) +{ + if (!FunctionKeyEnd) + { + FunctionKeyEnd = FunctionKeyStart; + } + for (intptr_t Index = FunctionKeyStart; Index <= FunctionKeyEnd; ++Index) + { + SetKeyBarTitle(ShiftStatus, Index, L""); + } +} + +void TFarKeyBarTitles::SetKeyBarTitle(TFarShiftStatus ShiftStatus, + intptr_t FunctionKey, const UnicodeString & Title) +{ + DebugAssert(FunctionKey >= 1 && FunctionKey <= static_cast(_countof(FKeyBarTitles.Titles))); + wchar_t ** Titles = nullptr; + switch (ShiftStatus) + { + case fsNone: + Titles = FKeyBarTitles.Titles; + break; + case fsCtrl: + Titles = FKeyBarTitles.CtrlTitles; + break; + case fsAlt: + Titles = FKeyBarTitles.AltTitles; + break; + case fsShift: + Titles = FKeyBarTitles.ShiftTitles; + break; + case fsCtrlShift: + Titles = FKeyBarTitles.CtrlShiftTitles; + break; + case fsAltShift: + Titles = FKeyBarTitles.AltShiftTitles; + break; + case fsCtrlAlt: + Titles = FKeyBarTitles.CtrlAltTitles; + break; + default: + DebugAssert(false); + } + if (Titles) + { + if (Titles[FunctionKey - 1]) + { + nb_free(Titles[FunctionKey - 1]); + } + Titles[FunctionKey - 1] = TCustomFarPlugin::DuplicateStr(Title, /*AllowEmpty=*/true); + } +} + +void TFarKeyBarTitles::ClearKeyBarTitles(KeyBarTitles & Titles) +{ + for (intptr_t Index = 0; Index < static_cast(_countof(Titles.Titles)); ++Index) + { + nb_free(Titles.Titles[Index]); + nb_free(Titles.CtrlTitles[Index]); + nb_free(Titles.AltTitles[Index]); + nb_free(Titles.ShiftTitles[Index]); + nb_free(Titles.CtrlShiftTitles[Index]); + nb_free(Titles.AltShiftTitles[Index]); + nb_free(Titles.CtrlAltTitles[Index]); + } +} + +void TFarKeyBarTitles::FillOpenPluginInfo(struct OpenPluginInfo * Info) +{ + DebugAssert(Info); + KeyBarTitles * KeyBar = static_cast( + nb_malloc(sizeof(KeyBarTitles))); + Info->KeyBar = KeyBar; + memmove(KeyBar, &FKeyBarTitles, sizeof(FKeyBarTitles)); + FReferenced = true; +} + +UnicodeString TCustomFarPanelItem::GetCustomColumnData(size_t /*Column*/) +{ + DebugAssert(false); + return L""; +} + +void TCustomFarPanelItem::FillPanelItem(struct PluginPanelItem * PanelItem) +{ + DebugAssert(PanelItem); + + UnicodeString FileName; + int64_t Size = 0; + TDateTime LastWriteTime; + TDateTime LastAccess; + UnicodeString Description; + UnicodeString Owner; + + void * UserData = ToPtr(PanelItem->UserData); + GetData(PanelItem->Flags, FileName, Size, PanelItem->FindData.dwFileAttributes, + LastWriteTime, LastAccess, PanelItem->NumberOfLinks, Description, Owner, + UserData, PanelItem->CustomColumnNumber); + PanelItem->UserData = reinterpret_cast(UserData); + FILETIME FileTime = ::DateTimeToFileTime(LastWriteTime, dstmWin); + FILETIME FileTimeA = ::DateTimeToFileTime(LastAccess, dstmWin); + PanelItem->FindData.ftCreationTime = FileTime; + PanelItem->FindData.ftLastAccessTime = FileTimeA; + PanelItem->FindData.ftLastWriteTime = FileTime; + PanelItem->FindData.nFileSize = Size; + + PanelItem->FindData.lpwszFileName = TCustomFarPlugin::DuplicateStr(FileName); + PanelItem->Description = TCustomFarPlugin::DuplicateStr(Description); + PanelItem->Owner = TCustomFarPlugin::DuplicateStr(Owner); + wchar_t ** CustomColumnData = static_cast( + nb_calloc(1, sizeof(wchar_t *) * (1 + PanelItem->CustomColumnNumber))); + for (intptr_t Index = 0; Index < PanelItem->CustomColumnNumber; ++Index) + { + CustomColumnData[Index] = + TCustomFarPlugin::DuplicateStr(GetCustomColumnData(Index)); + } + PanelItem->CustomColumnData = CustomColumnData; +} + +TFarPanelItem::TFarPanelItem(PluginPanelItem * APanelItem, bool OwnsItem) : + TCustomFarPanelItem(), + FPanelItem(APanelItem), + FOwnsItem(OwnsItem) +{ + DebugAssert(FPanelItem); +} + +TFarPanelItem::~TFarPanelItem() +{ + if (FOwnsItem) + nb_free(FPanelItem); + FPanelItem = nullptr; +} + +void TFarPanelItem::GetData( + DWORD & /*Flags*/, UnicodeString & /*FileName*/, int64_t & /*Size*/, + DWORD & /*FileAttributes*/, + TDateTime & /*LastWriteTime*/, TDateTime & /*LastAccess*/, + DWORD & /*NumberOfLinks*/, UnicodeString & /*Description*/, + UnicodeString & /*Owner*/, void *& /*UserData*/, int & /*CustomColumnNumber*/) +{ + DebugAssert(false); +} + +UnicodeString TFarPanelItem::GetCustomColumnData(size_t /*Column*/) +{ + DebugAssert(false); + return L""; +} + +uintptr_t TFarPanelItem::GetFlags() const +{ + return static_cast(FPanelItem->Flags); +} + +UnicodeString TFarPanelItem::GetFileName() const +{ + UnicodeString Result = FPanelItem->FindData.lpwszFileName; + return Result; +} + +void * TFarPanelItem::GetUserData() const +{ + return ToPtr(FPanelItem->UserData); +} + +bool TFarPanelItem::GetSelected() const +{ + return (FPanelItem->Flags & PPIF_SELECTED) != 0; +} + +void TFarPanelItem::SetSelected(bool Value) +{ + if (Value) + { + FPanelItem->Flags |= PPIF_SELECTED; + } + else + { + FPanelItem->Flags &= ~PPIF_SELECTED; + } +} + +uintptr_t TFarPanelItem::GetFileAttrs() const +{ + return static_cast(FPanelItem->FindData.dwFileAttributes); +} + +bool TFarPanelItem::GetIsParentDirectory() const +{ + return (GetFileName() == PARENTDIRECTORY); +} + +bool TFarPanelItem::GetIsFile() const +{ + return (GetFileAttrs() & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +THintPanelItem::THintPanelItem(const UnicodeString & AHint) : + TCustomFarPanelItem(), + FHint(AHint) +{ +} + +void THintPanelItem::GetData( + DWORD & /*Flags*/, UnicodeString & AFileName, int64_t & /*Size*/, + DWORD & /*FileAttributes*/, + TDateTime & /*LastWriteTime*/, TDateTime & /*LastAccess*/, + DWORD & /*NumberOfLinks*/, UnicodeString & /*Description*/, + UnicodeString & /*Owner*/, void *& /*UserData*/, int & /*CustomColumnNumber*/) +{ + AFileName = FHint; +} + +TFarPanelInfo::TFarPanelInfo(PanelInfo * APanelInfo, TCustomFarFileSystem * AOwner) : + TObject(), + FPanelInfo(APanelInfo), + FItems(nullptr), + FOwner(AOwner) +{ + // if (!FPanelInfo) throw ExtException(L""); + DebugAssert(FPanelInfo); +} + +TFarPanelInfo::~TFarPanelInfo() +{ + nb_free(FPanelInfo); + SAFE_DESTROY(FItems); +} + +intptr_t TFarPanelInfo::GetItemCount() const +{ + return static_cast(FPanelInfo->ItemsNumber); +} + +TRect TFarPanelInfo::GetBounds() const +{ + RECT rect = FPanelInfo->PanelRect; + return TRect(rect.left, rect.top, rect.right, rect.bottom); +} + +intptr_t TFarPanelInfo::GetSelectedCount(bool CountCurrentItem) const +{ + intptr_t Count = static_cast(FPanelInfo->SelectedItemsNumber); + + if ((Count == 1) && FOwner && !CountCurrentItem) + { + intptr_t size = FOwner->FarControl(FCTL_GETSELECTEDPANELITEM, 0, 0); + PluginPanelItem * ppi = static_cast(nb_calloc(1, size)); + FOwner->FarControl(FCTL_GETSELECTEDPANELITEM, 0, reinterpret_cast(ppi)); + if ((ppi->Flags & PPIF_SELECTED) == 0) + { + Count = 0; + } + nb_free(ppi); + } + + return Count; +} + +TObjectList * TFarPanelInfo::GetItems() +{ + if (!FItems) + { + FItems = new TObjectList(); + } + if (FOwner) + { + // DebugAssert(FItems->GetCount() == 0); + if (!FItems->GetCount()) + FItems->Clear(); + for (intptr_t Index = 0; Index < FPanelInfo->ItemsNumber; ++Index) + { + TODO("move to common function"); + intptr_t size = FOwner->FarControl(FCTL_GETPANELITEM, Index, 0); + PluginPanelItem * ppi = static_cast(nb_calloc(1, size)); + FOwner->FarControl(FCTL_GETPANELITEM, Index, reinterpret_cast(ppi)); + FItems->Add(new TFarPanelItem(ppi, /*OwnsItem=*/true)); + } + } + return FItems; +} + +TFarPanelItem * TFarPanelInfo::FindFileName(const UnicodeString & AFileName) const +{ + const TObjectList * Items = FItems; + if (!Items) + Items = GetItems(); + for (intptr_t Index = 0; Index < Items->GetCount(); ++Index) + { + TFarPanelItem * PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, Items->GetObj(Index)); + if (PanelItem->GetFileName() == AFileName) + { + return PanelItem; + } + } + return nullptr; +} + +const TFarPanelItem * TFarPanelInfo::FindUserData(const void * UserData) const +{ + return const_cast(this)->FindUserData(UserData); +} + +TFarPanelItem * TFarPanelInfo::FindUserData(const void * UserData) +{ + TObjectList * Items = GetItems(); + for (intptr_t Index = 0; Index < Items->GetCount(); ++Index) + { + TFarPanelItem * PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, Items->GetObj(Index)); + if (PanelItem->GetUserData() == UserData) + { + return PanelItem; + } + } + return nullptr; +} + +void TFarPanelInfo::ApplySelection() +{ + // for "another panel info", there's no owner + DebugAssert(FOwner != nullptr); + FOwner->FarControl(FCTL_SETSELECTION, 0, reinterpret_cast(FPanelInfo)); +} + +TFarPanelItem * TFarPanelInfo::GetFocusedItem() const +{ + const TObjectList * Items = FItems; + if (!Items) + Items = GetItems(); + intptr_t Index = GetFocusedIndex(); + if (Items->GetCount() > 0) + { + DebugAssert(Index < Items->GetCount()); + return NB_STATIC_DOWNCAST(TFarPanelItem, Items->GetObj(Index)); + } + else + { + return nullptr; + } +} + +void TFarPanelInfo::SetFocusedItem(const TFarPanelItem * Value) +{ + if (FItems && FItems->GetCount()) + { + intptr_t Index = FItems->IndexOf(Value); + DebugAssert(Index != NPOS); + SetFocusedIndex(Index); + } +} + +intptr_t TFarPanelInfo::GetFocusedIndex() const +{ + return static_cast(FPanelInfo->CurrentItem); +} + +void TFarPanelInfo::SetFocusedIndex(intptr_t Value) +{ + // for "another panel info", there's no owner + DebugAssert(FOwner != nullptr); + if (GetFocusedIndex() != Value) + { + DebugAssert(Value != NPOS && Value < (intptr_t)FPanelInfo->ItemsNumber); + FPanelInfo->CurrentItem = static_cast(Value); + PanelRedrawInfo PanelInfo; + PanelInfo.CurrentItem = FPanelInfo->CurrentItem; + PanelInfo.TopPanelItem = FPanelInfo->TopPanelItem; + FOwner->FarControl(FCTL_REDRAWPANEL, 0, reinterpret_cast(&PanelInfo)); + } +} + +TFarPanelType TFarPanelInfo::GetType() const +{ + switch (FPanelInfo->PanelType) + { + case PTYPE_FILEPANEL: + return ptFile; + + case PTYPE_TREEPANEL: + return ptTree; + + case PTYPE_QVIEWPANEL: + return ptQuickView; + + case PTYPE_INFOPANEL: + return ptInfo; + + default: + DebugAssert(false); + return ptFile; + } +} + +bool TFarPanelInfo::GetIsPlugin() const +{ + return (FPanelInfo->Plugin != 0); +} + +UnicodeString TFarPanelInfo::GetCurrDirectory() const +{ + UnicodeString Result; + intptr_t Size = FarPlugin->FarControl(FCTL_GETPANELDIR, + 0, + 0, + FOwner != nullptr ? PANEL_ACTIVE : PANEL_PASSIVE); + if (Size) + { + Result.SetLength(Size); + FarPlugin->FarControl(FCTL_GETPANELDIR, + Size, + reinterpret_cast(Result.c_str()), + FOwner != nullptr ? PANEL_ACTIVE : PANEL_PASSIVE); + } + return Result.c_str(); +} + +TFarMenuItems::TFarMenuItems() : + TStringList(), + FItemFocused(NPOS) +{ +} + +void TFarMenuItems::Clear() +{ + FItemFocused = NPOS; + TStringList::Clear(); +} + +void TFarMenuItems::Delete(intptr_t Index) +{ + if (Index == FItemFocused) + { + FItemFocused = NPOS; + } + TStringList::Delete(Index); +} + +void TFarMenuItems::SetObj(intptr_t Index, TObject * AObject) +{ + TStringList::SetObj(Index, AObject); + bool Focused = (reinterpret_cast(AObject) & MIF_SEPARATOR) != 0; + if ((Index == GetItemFocused()) && !Focused) + { + FItemFocused = NPOS; + } + if (Focused) + { + if (GetItemFocused() != NPOS) + { + SetFlag(GetItemFocused(), MIF_SELECTED, false); + } + FItemFocused = Index; + } +} + +intptr_t TFarMenuItems::Add(const UnicodeString & Text, bool Visible) +{ + intptr_t Result = TStringList::Add(Text); + if (!Visible) + { + SetFlag(GetCount() - 1, MIF_HIDDEN, true); + } + return Result; +} + +void TFarMenuItems::AddSeparator(bool Visible) +{ + Add(L""); + SetFlag(GetCount() - 1, MIF_SEPARATOR, true); + if (!Visible) + { + SetFlag(GetCount() - 1, MIF_HIDDEN, true); + } +} + +void TFarMenuItems::SetItemFocused(intptr_t Value) +{ + if (GetItemFocused() != Value) + { + if (GetItemFocused() != NPOS) + { + SetFlag(GetItemFocused(), MIF_SELECTED, false); + } + FItemFocused = Value; + SetFlag(GetItemFocused(), MIF_SELECTED, true); + } +} + +void TFarMenuItems::SetFlag(intptr_t Index, uintptr_t Flag, bool Value) +{ + if (GetFlag(Index, Flag) != Value) + { + uintptr_t F = reinterpret_cast(GetObj(Index)); + if (Value) + { + F |= Flag; + } + else + { + F &= ~Flag; + } + SetObj(Index, reinterpret_cast(F)); + } +} + +bool TFarMenuItems::GetFlag(intptr_t Index, uintptr_t Flag) const +{ + return (reinterpret_cast(GetObj(Index)) & Flag) > 0; +} + +TFarEditorInfo::TFarEditorInfo(EditorInfo * Info) : + FEditorInfo(Info) +{ +} + +TFarEditorInfo::~TFarEditorInfo() +{ + nb_free(FEditorInfo); +} + +intptr_t TFarEditorInfo::GetEditorID() const +{ + return static_cast(FEditorInfo->EditorID); +} + +UnicodeString TFarEditorInfo::GetFileName() +{ + UnicodeString Result; + intptr_t BuffLen = FarPlugin->FarEditorControl(ECTL_GETFILENAME, nullptr); + if (BuffLen) + { + Result.SetLength(BuffLen + 1); + FarPlugin->FarEditorControl(ECTL_GETFILENAME, const_cast(Result.c_str())); + } + return Result.c_str(); +} + +TFarEnvGuard::TFarEnvGuard() +{ + DebugAssert(FarPlugin != nullptr); +} + +TFarEnvGuard::~TFarEnvGuard() +{ + DebugAssert(FarPlugin != nullptr); + /* + if (!FarPlugin->GetANSIApis()) + { + DebugAssert(!AreFileApisANSI()); + SetFileApisToANSI(); + } + else + { + DebugAssert(AreFileApisANSI()); + } + */ +} + +TFarPluginEnvGuard::TFarPluginEnvGuard() +{ + DebugAssert(FarPlugin != nullptr); +} + +TFarPluginEnvGuard::~TFarPluginEnvGuard() +{ + DebugAssert(FarPlugin != nullptr); +} + +void FarWrapText(const UnicodeString & Text, TStrings * Result, intptr_t MaxWidth) +{ + size_t TabSize = 8; + TStringList Lines; + Lines.SetText(Text); + TStringList WrappedLines; + for (intptr_t Index = 0; Index < Lines.GetCount(); ++Index) + { + UnicodeString WrappedLine = Lines.GetString(Index); + if (!WrappedLine.IsEmpty()) + { + WrappedLine = ::ReplaceChar(WrappedLine, L'\'', L'\3'); + WrappedLine = ::ReplaceChar(WrappedLine, L'\"', L'\4'); + WrappedLine = ::WrapText(WrappedLine, MaxWidth); + WrappedLine = ::ReplaceChar(WrappedLine, L'\3', L'\''); + WrappedLine = ::ReplaceChar(WrappedLine, L'\4', L'\"'); + WrappedLines.SetText(WrappedLine); + for (intptr_t WrappedIndex = 0; WrappedIndex < WrappedLines.GetCount(); ++WrappedIndex) + { + UnicodeString FullLine = WrappedLines.GetString(WrappedIndex); + do + { + // WrapText does not wrap when not possible, enforce it + // (it also does not wrap when the line is longer than maximum only + // because of trailing dot or similar) + UnicodeString Line = FullLine.SubString(1, MaxWidth); + FullLine.Delete(1, MaxWidth); + + intptr_t P = 0; + while ((P = Line.Pos(L'\t')) > 0) + { + Line.Delete(P, 1); + Line.Insert(::StringOfChar(' ', + ((P / TabSize) + ((P % TabSize) > 0 ? 1 : 0)) * TabSize - P + 1), + P); + } + Result->Add(Line); + } + while (!FullLine.IsEmpty()); + } + } + else + { + Result->Add(L""); + } + } +} + +TGlobalFunctionsIntf * GetGlobalFunctions() +{ + static TGlobalFunctions * GlobalFunctions = nullptr; + if (!GlobalFunctions) + { + GlobalFunctions = new TGlobalFunctions(); + } + return GlobalFunctions; +} + +HINSTANCE TGlobalFunctions::GetInstanceHandle() const +{ + HINSTANCE Result = nullptr; + if (FarPlugin) + { + Result = FarPlugin->GetHandle(); + } + return Result; +} + +UnicodeString TGlobalFunctions::GetMsg(intptr_t Id) const +{ + // map Id to PluginString value + intptr_t PluginStringId = Id; + const TFarPluginStrings * CurFarPluginStrings = &FarPluginStrings[0]; + while (CurFarPluginStrings->Id) + { + if (CurFarPluginStrings->Id == Id) + { + PluginStringId = CurFarPluginStrings->FarPluginStringId; + break; + } + ++CurFarPluginStrings; + } + return FarPlugin->GetMsg(PluginStringId); +} + +UnicodeString TGlobalFunctions::GetCurrDirectory() const +{ + UnicodeString Result; + UnicodeString Path(32 * 1014, 0); + if (FarPlugin) + { + FarPlugin->GetFarStandardFunctions().GetCurrentDirectory((DWORD)Path.Length(), (wchar_t *)Path.c_str()); + } + else + { + ::GetCurrentDirectory((DWORD)Path.Length(), (wchar_t *)Path.c_str()); + } + Result = Path; + return Result; +} + +UnicodeString TGlobalFunctions::GetStrVersionNumber() const +{ + return NETBOX_VERSION_NUMBER.c_str(); +} + +//bool InputBox(const UnicodeString & Title, const UnicodeString & Prompt, +// UnicodeString & Text, DWORD Flags, const UnicodeString & HistoryName = UnicodeString(), +// intptr_t MaxLen = 255, TFarInputBoxValidateEvent OnValidate = nullptr); +bool TGlobalFunctions::InputDialog(const UnicodeString & ACaption, const UnicodeString & APrompt, + UnicodeString & Value, const UnicodeString & HelpKeyword, + TStrings * History, bool PathInput, + TInputDialogInitializeEvent OnInitialize, bool Echo) +{ + DebugUsedParam(HelpKeyword); + DebugUsedParam(History); + DebugUsedParam(PathInput); + DebugUsedParam(OnInitialize); + DebugUsedParam(Echo); + + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + return WinSCPPlugin->InputBox(ACaption, APrompt, Value, 0); +} + +uintptr_t TGlobalFunctions::MoreMessageDialog(const UnicodeString & Message, TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, const TMessageParams * Params) +{ + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + return WinSCPPlugin->MoreMessageDialog(Message, MoreMessages, Type, Answers, Params); +} + +NB_IMPLEMENT_CLASS(TCustomFarFileSystem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TCustomFarPlugin, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TConsoleTitleParam, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TCustomFarPanelItem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TFarPanelItem, NB_GET_CLASS_INFO(TCustomFarPanelItem), nullptr) + diff --git a/netbox/src/NetBox/FarPlugin.h b/netbox/src/NetBox/FarPlugin.h new file mode 100644 index 000000000..643871d6b --- /dev/null +++ b/netbox/src/NetBox/FarPlugin.h @@ -0,0 +1,551 @@ +#pragma once + +#pragma warning(push, 1) +#include +#include +#include +#pragma warning(pop) + +#include + +class TCustomFarFileSystem; +class TFarPanelModes; +class TFarKeyBarTitles; +class TFarPanelInfo; +class TFarDialog; +class TWinSCPFileSystem; +class TFarDialogItem; +class TFarMessageDialog; +class TFarEditorInfo; +class TFarPluginGuard; + +const int MaxMessageWidth = 64; + +enum TFarShiftStatus +{ + fsNone, + fsCtrl, + fsAlt, + fsShift, + fsCtrlShift, + fsAltShift, + fsCtrlAlt +}; +enum THandlesFunction +{ + hfProcessKey, + hfProcessHostFile, + hfProcessEvent +}; +DEFINE_CALLBACK_TYPE1(TFarInputBoxValidateEvent, void, UnicodeString & /*Text*/); + +DEFINE_CALLBACK_TYPE1(TFarMessageTimerEvent, void, intptr_t & /*Result*/); +DEFINE_CALLBACK_TYPE3(TFarMessageClickEvent, void, void * /*Token*/, + uintptr_t /*Result*/, bool & /*Close*/); + +struct TFarMessageParams : public TObject +{ +NB_DISABLE_COPY(TFarMessageParams) +public: + TFarMessageParams(); + + TStrings * MoreMessages; + UnicodeString CheckBoxLabel; + bool CheckBox; + uintptr_t Timer; + intptr_t TimerAnswer; + TFarMessageTimerEvent TimerEvent; + uintptr_t Timeout; + uintptr_t TimeoutButton; + uintptr_t DefaultButton; + UnicodeString TimeoutStr; + TFarMessageClickEvent ClickEvent; + void * Token; +}; + +class TCustomFarPlugin : public TObject +{ +friend class TCustomFarFileSystem; +friend class TFarDialog; +friend class TWinSCPFileSystem; +friend class TFarDialogItem; +friend class TFarMessageDialog; +friend class TFarPluginGuard; +NB_DISABLE_COPY(TCustomFarPlugin) +NB_DECLARE_CLASS(TCustomFarPlugin) +public: + explicit TCustomFarPlugin(HINSTANCE HInst); + virtual ~TCustomFarPlugin(); + virtual intptr_t GetMinFarVersion() const; + virtual void SetStartupInfo(const struct PluginStartupInfo * Info); + virtual const struct PluginStartupInfo * GetPluginStartupInfo() const { return &FStartupInfo; } + virtual void ExitFAR(); + virtual void GetPluginInfo(struct PluginInfo * Info); + virtual intptr_t Configure(intptr_t Item); + virtual void * OpenPlugin(int OpenFrom, intptr_t Item); + virtual void ClosePlugin(void * Plugin); + virtual void GetOpenPluginInfo(HANDLE Plugin, struct OpenPluginInfo * Info); + virtual intptr_t GetFindData(HANDLE Plugin, + struct PluginPanelItem ** PanelItem, int * ItemsNumber, int OpMode); + virtual void FreeFindData(HANDLE Plugin, struct PluginPanelItem * PanelItem, + int ItemsNumber); + virtual intptr_t ProcessHostFile(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int OpMode); + virtual intptr_t ProcessKey(HANDLE Plugin, int Key, DWORD ControlState); + virtual intptr_t ProcessEvent(HANDLE Plugin, int Event, void * Param); + virtual intptr_t SetDirectory(HANDLE Plugin, const wchar_t * Dir, int OpMode); + virtual intptr_t MakeDirectory(HANDLE Plugin, const wchar_t ** Name, int OpMode); + virtual intptr_t DeleteFiles(HANDLE Plugin, struct PluginPanelItem * PanelItem, + int ItemsNumber, int OpMode); + virtual intptr_t GetFiles(HANDLE Plugin, struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t ** DestPath, int OpMode); + virtual intptr_t PutFiles(HANDLE Plugin, struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t * srcPath, int OpMode); + virtual intptr_t ProcessEditorEvent(int Event, void * Param); + virtual intptr_t ProcessEditorInput(const INPUT_RECORD * Rec); + + virtual void HandleException(Exception * E, int OpMode = 0); + + static wchar_t * DuplicateStr(const UnicodeString & Str, bool AllowEmpty = false); + intptr_t Message(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Message, TStrings * Buttons = nullptr, + TFarMessageParams * Params = nullptr); + intptr_t MaxMessageLines(); + intptr_t MaxMenuItemLength(); + intptr_t Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, TStrings * Items, const int * BreakKeys, + int & BreakCode); + intptr_t Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, TStrings * Items); + intptr_t Menu(DWORD Flags, const UnicodeString & Title, + const UnicodeString & Bottom, const FarMenuItem * Items, intptr_t Count, + const int * BreakKeys, int & BreakCode); + bool InputBox(const UnicodeString & Title, const UnicodeString & Prompt, + UnicodeString & Text, DWORD Flags, const UnicodeString & HistoryName = UnicodeString(), + intptr_t MaxLen = 255, TFarInputBoxValidateEvent OnValidate = nullptr); + UnicodeString GetMsg(intptr_t MsgId) const; + void SaveScreen(HANDLE & Screen); + void RestoreScreen(HANDLE & Screen); + bool CheckForEsc(); + bool Viewer(const UnicodeString & AFileName, const UnicodeString & Title, DWORD Flags); + bool Editor(const UnicodeString & AFileName, const UnicodeString & Title, DWORD Flags); + + intptr_t FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2, HANDLE Plugin = INVALID_HANDLE_VALUE); + intptr_t FarAdvControl(uintptr_t Command, void * Param = nullptr) const; + intptr_t FarEditorControl(uintptr_t Command, void * Param); + intptr_t GetFarSystemSettings() const; + void Text(int X, int Y, int Color, const UnicodeString & Str); + void FlushText(); + void FarWriteConsole(const UnicodeString & Str); + void FarCopyToClipboard(const UnicodeString & Str); + void FarCopyToClipboard(const TStrings * Strings); + intptr_t GetFarVersion() const; + UnicodeString FormatFarVersion(intptr_t Version) const; + UnicodeString GetTemporaryDir() const; + intptr_t InputRecordToKey(const INPUT_RECORD * Rec); + TFarEditorInfo * EditorInfo(); + + void ShowConsoleTitle(const UnicodeString & Title); + void ClearConsoleTitle(); + void UpdateConsoleTitle(const UnicodeString & Title); + void UpdateConsoleTitleProgress(short Progress); + void ShowTerminalScreen(); + void SaveTerminalScreen(); + void ScrollTerminalScreen(int Rows); + TPoint TerminalInfo(TPoint * Size = nullptr, TPoint * Cursor = nullptr) const; + uintptr_t ConsoleWindowState() const; + void ToggleVideoMode(); + + TCustomFarFileSystem * GetPanelFileSystem(bool Another = false, + HANDLE Plugin = INVALID_HANDLE_VALUE); + + UnicodeString GetModuleName() const; + TFarDialog * GetTopDialog() const { return FTopDialog; } + HINSTANCE GetHandle() const { return FHandle; } + uintptr_t GetFarThreadId() const { return FFarThreadId; } + FarStandardFunctions & GetFarStandardFunctions() { return FFarStandardFunctions; } + +protected: + PluginStartupInfo FStartupInfo; + FarStandardFunctions FFarStandardFunctions; + HINSTANCE FHandle; + TObjectList * FOpenedPlugins; + TFarDialog * FTopDialog; + HANDLE FConsoleInput; + HANDLE FConsoleOutput; + mutable intptr_t FFarVersion; + bool FTerminalScreenShowing; + TCriticalSection FCriticalSection; + uintptr_t FFarThreadId; + mutable bool FValidFarSystemSettings; + mutable intptr_t FFarSystemSettings; + TPoint FNormalConsoleSize; + + virtual bool HandlesFunction(THandlesFunction Function) const; + virtual void GetPluginInfoEx(DWORD & Flags, + TStrings * DiskMenuStrings, TStrings * PluginMenuStrings, + TStrings * PluginConfigStrings, TStrings * CommandPrefixes) = 0; + virtual TCustomFarFileSystem * OpenPluginEx(intptr_t OpenFrom, intptr_t Item) = 0; + virtual bool ConfigureEx(intptr_t Item) = 0; + virtual intptr_t ProcessEditorEventEx(intptr_t Event, void * Param) = 0; + virtual intptr_t ProcessEditorInputEx(const INPUT_RECORD * Rec) = 0; + virtual void HandleFileSystemException(TCustomFarFileSystem * FarFileSystem, + Exception * E, int OpMode = 0); + void ResetCachedInfo(); + intptr_t MaxLength(TStrings * Strings); + intptr_t FarMessage(DWORD Flags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons, + TFarMessageParams * Params); + intptr_t DialogMessage(DWORD Flags, + const UnicodeString & Title, const UnicodeString & Message, TStrings * Buttons, + TFarMessageParams * Params); + void InvalidateOpenPluginInfo(); + + const TCriticalSection & GetCriticalSection() const { return FCriticalSection; } + +#ifdef NETBOX_DEBUG +public: + void RunTests(); +#endif +private: + void UpdateProgress(intptr_t State, intptr_t Progress); + +private: + PluginInfo FPluginInfo; + TStringList * FSavedTitles; + UnicodeString FCurrentTitle; + short FCurrentProgress; + + void ClearPluginInfo(PluginInfo & Info); + void UpdateCurrentConsoleTitle(); + UnicodeString FormatConsoleTitle(); + HWND GetConsoleWindow() const; + RECT GetPanelBounds(HANDLE PanelHandle); + bool CompareRects(const RECT & lhs, const RECT & rhs) const + { + return + lhs.left == rhs.left && + lhs.top == rhs.top && + lhs.right == rhs.right && + lhs.bottom == rhs.bottom; + } +}; + +class TCustomFarFileSystem : public TObject +{ +friend class TFarPanelInfo; +friend class TCustomFarPlugin; +NB_DISABLE_COPY(TCustomFarFileSystem) +NB_DECLARE_CLASS(TCustomFarFileSystem) +public: + explicit TCustomFarFileSystem(TCustomFarPlugin * APlugin); + void Init(); + virtual ~TCustomFarFileSystem(); + + void GetOpenPluginInfo(struct OpenPluginInfo * Info); + intptr_t GetFindData(struct PluginPanelItem ** PanelItem, + int * ItemsNumber, int OpMode); + void FreeFindData(struct PluginPanelItem * PanelItem, int ItemsNumber); + intptr_t ProcessHostFile(struct PluginPanelItem * PanelItem, + int ItemsNumber, int OpMode); + intptr_t ProcessKey(intptr_t Key, uintptr_t ControlState); + intptr_t ProcessEvent(intptr_t Event, void * Param); + intptr_t SetDirectory(const wchar_t * Dir, int OpMode); + intptr_t MakeDirectory(const wchar_t ** Name, int OpMode); + intptr_t DeleteFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int OpMode); + intptr_t GetFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t ** DestPath, int OpMode); + intptr_t PutFiles(struct PluginPanelItem * PanelItem, + int ItemsNumber, int Move, const wchar_t * srcPath, int OpMode); + virtual void Close(); + +protected: + virtual UnicodeString GetCurrDirectory() const = 0; + +protected: + TCustomFarPlugin * FPlugin; + bool FClosed; + + virtual void GetOpenPluginInfoEx(DWORD & Flags, + UnicodeString & HostFile, UnicodeString & CurDir, UnicodeString & Format, + UnicodeString & PanelTitle, TFarPanelModes * PanelModes, int & StartPanelMode, + int & StartSortMode, bool & StartSortOrder, TFarKeyBarTitles * KeyBarTitles, + UnicodeString & ShortcutData) = 0; + virtual bool GetFindDataEx(TObjectList * PanelItems, int OpMode) = 0; + virtual bool ProcessHostFileEx(TObjectList * PanelItems, int OpMode); + virtual bool ProcessKeyEx(intptr_t Key, uintptr_t ControlState); + virtual bool ProcessEventEx(intptr_t Event, void * Param); + virtual bool SetDirectoryEx(const UnicodeString & Dir, int OpMode); + virtual intptr_t MakeDirectoryEx(UnicodeString & Name, int OpMode); + virtual bool DeleteFilesEx(TObjectList * PanelItems, int OpMode); + virtual intptr_t GetFilesEx(TObjectList * PanelItems, bool Move, + UnicodeString & DestPath, int OpMode); + virtual intptr_t PutFilesEx(TObjectList * PanelItems, bool Move, int OpMode); + + void ResetCachedInfo(); + intptr_t FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2); + intptr_t FarControl(uintptr_t Command, intptr_t Param1, intptr_t Param2, HANDLE Plugin); + bool UpdatePanel(bool ClearSelection = false, bool Another = false); + void RedrawPanel(bool Another = false); + void ClosePlugin(); + UnicodeString GetMsg(int MsgId); + TCustomFarFileSystem * GetOppositeFileSystem(); + bool IsActiveFileSystem(); + bool IsLeft(); + bool IsRight(); + + virtual void HandleException(Exception * E, int OpMode = 0); + + TFarPanelInfo * const * GetPanelInfo() const { return GetPanelInfo(0); } + TFarPanelInfo ** GetPanelInfo() { return GetPanelInfo(0); } + TFarPanelInfo * const * GetAnotherPanelInfo() const { return GetPanelInfo(1); } + TFarPanelInfo ** GetAnotherPanelInfo() { return GetPanelInfo(1); } + const TCriticalSection & GetCriticalSection() const { return FCriticalSection; } + TCriticalSection & GetCriticalSection() { return FCriticalSection; } + bool GetOpenPluginInfoValid() const { return FOpenPluginInfoValid; } + +protected: + TCriticalSection FCriticalSection; + void InvalidateOpenPluginInfo(); + +private: + UnicodeString FNameStr; + UnicodeString FDestPathStr; + OpenPluginInfo FOpenPluginInfo; + bool FOpenPluginInfoValid; + TFarPanelInfo * FPanelInfo[2]; + static uintptr_t FInstances; + + void ClearOpenPluginInfo(OpenPluginInfo & Info); + TObjectList * CreatePanelItemList(struct PluginPanelItem * PanelItem, + int ItemsNumber); + TFarPanelInfo * const * GetPanelInfo(int Another) const; + TFarPanelInfo ** GetPanelInfo(int Another); +}; + +#define PANEL_MODES_COUNT 10 +class TFarPanelModes : public TObject +{ + friend class TCustomFarFileSystem; +public: + TFarPanelModes(); + virtual ~TFarPanelModes(); + + void SetPanelMode(size_t Mode, const UnicodeString & ColumnTypes = UnicodeString(), + const UnicodeString & ColumnWidths = UnicodeString(), TStrings * ColumnTitles = nullptr, + bool FullScreen = false, bool DetailedStatus = true, bool AlignExtensions = true, + bool CaseConversion = true, const UnicodeString & StatusColumnTypes = UnicodeString(), + const UnicodeString & StatusColumnWidths = UnicodeString()); + +private: + PanelMode FPanelModes[PANEL_MODES_COUNT]; + bool FReferenced; + + void FillOpenPluginInfo(struct OpenPluginInfo * Info); + static void ClearPanelMode(PanelMode & Mode); + static intptr_t CommaCount(const UnicodeString & ColumnTypes); +}; + +class TFarKeyBarTitles : public TObject +{ +friend class TCustomFarFileSystem; +public: + TFarKeyBarTitles(); + virtual ~TFarKeyBarTitles(); + + void ClearFileKeyBarTitles(); + void ClearKeyBarTitle(TFarShiftStatus ShiftStatus, + intptr_t FunctionKeyStart, intptr_t FunctionKeyEnd = 0); + void SetKeyBarTitle(TFarShiftStatus ShiftStatus, intptr_t FunctionKey, + const UnicodeString & Title); + +private: + KeyBarTitles FKeyBarTitles; + bool FReferenced; + + void FillOpenPluginInfo(struct OpenPluginInfo * Info); + static void ClearKeyBarTitles(KeyBarTitles & Titles); +}; + +class TCustomFarPanelItem : public TObject +{ +friend class TCustomFarFileSystem; +NB_DECLARE_CLASS(TCustomFarPanelItem) +protected: + virtual ~TCustomFarPanelItem() + {} + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber) = 0; + virtual UnicodeString GetCustomColumnData(size_t Column); + + void FillPanelItem(struct PluginPanelItem * PanelItem); +}; + +class TFarPanelItem : public TCustomFarPanelItem +{ +NB_DISABLE_COPY(TFarPanelItem) +NB_DECLARE_CLASS(TFarPanelItem) +public: + explicit TFarPanelItem(PluginPanelItem * APanelItem, bool OwnsItem); + virtual ~TFarPanelItem(); + + uintptr_t GetFlags() const; + uintptr_t GetFileAttrs() const; + UnicodeString GetFileName() const; + void * GetUserData() const; + bool GetSelected() const; + void SetSelected(bool Value); + bool GetIsParentDirectory() const; + bool GetIsFile() const; + +protected: + PluginPanelItem * FPanelItem; + bool FOwnsItem; + + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber); + virtual UnicodeString GetCustomColumnData(size_t Column); +}; + +class THintPanelItem : public TCustomFarPanelItem +{ +public: + explicit THintPanelItem(const UnicodeString & AHint); + virtual ~THintPanelItem() {} + +protected: + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber); + +private: + UnicodeString FHint; +}; + +enum TFarPanelType +{ + ptFile, + ptTree, + ptQuickView, + ptInfo +}; + +class TFarPanelInfo : public TObject +{ +NB_DISABLE_COPY(TFarPanelInfo) +public: + explicit TFarPanelInfo(PanelInfo * APanelInfo, TCustomFarFileSystem * AOwner); + virtual ~TFarPanelInfo(); + + const TObjectList * GetItems() const { return const_cast(this)->GetItems(); } + TObjectList * GetItems(); + intptr_t GetItemCount() const; + TFarPanelItem * GetFocusedItem() const; + void SetFocusedItem(const TFarPanelItem * Value); + intptr_t GetFocusedIndex() const; + void SetFocusedIndex(intptr_t Value); + intptr_t GetSelectedCount(bool CountCurrentItem = false) const; + TRect GetBounds() const; + TFarPanelType GetType() const; + bool GetIsPlugin() const; + UnicodeString GetCurrDirectory() const; + + void ApplySelection(); + TFarPanelItem * FindFileName(const UnicodeString & AFileName) const; + const TFarPanelItem * FindUserData(const void * UserData) const; + TFarPanelItem * FindUserData(const void * UserData); + +private: + PanelInfo * FPanelInfo; + TObjectList * FItems; + TCustomFarFileSystem * FOwner; +}; + +class TFarMenuItems : public TStringList +{ +public: + explicit TFarMenuItems(); + virtual ~TFarMenuItems() {} + void AddSeparator(bool Visible = true); + virtual intptr_t Add(const UnicodeString & Text, bool Visible = true); + + virtual void Clear(); + virtual void Delete(intptr_t Index); + + intptr_t GetItemFocused() const { return FItemFocused; } + void SetItemFocused(intptr_t Value); + bool GetDisabled(intptr_t Index) const { return GetFlag(Index, MIF_DISABLE); } + void SetDisabled(intptr_t Index, bool Value) { SetFlag(Index, MIF_DISABLE, Value); } + bool GetChecked(intptr_t Index) const { return GetFlag(Index, MIF_CHECKED); } + void SetChecked(intptr_t Index, bool Value) { SetFlag(Index, MIF_CHECKED, Value); } + + void SetFlag(intptr_t Index, uintptr_t Flag, bool Value); + bool GetFlag(intptr_t Index, uintptr_t Flag) const; + +protected: + virtual void SetObj(intptr_t Index, TObject * AObject); + +private: + intptr_t FItemFocused; +}; + +class TFarEditorInfo : public TObject +{ +NB_DISABLE_COPY(TFarEditorInfo) +public: + explicit TFarEditorInfo(EditorInfo * Info); + ~TFarEditorInfo(); + + intptr_t GetEditorID() const; + static UnicodeString GetFileName(); + +private: + EditorInfo * FEditorInfo; +}; + +class TFarEnvGuard : public TObject +{ +public: + TFarEnvGuard(); + ~TFarEnvGuard(); +}; + +class TFarPluginEnvGuard : public TObject +{ +public: + TFarPluginEnvGuard(); + ~TFarPluginEnvGuard(); +}; + +void FarWrapText(const UnicodeString & Text, TStrings * Result, intptr_t MaxWidth); + +extern TCustomFarPlugin * FarPlugin; + +class TGlobalFunctions : public TGlobalFunctionsIntf, public TObject +{ +public: + virtual HINSTANCE GetInstanceHandle() const; + virtual UnicodeString GetMsg(intptr_t Id) const; + virtual UnicodeString GetCurrDirectory() const; + virtual UnicodeString GetStrVersionNumber() const; + virtual bool InputDialog(const UnicodeString & ACaption, + const UnicodeString & APrompt, UnicodeString & Value, const UnicodeString & HelpKeyword, + TStrings * History, bool PathInput, + TInputDialogInitializeEvent OnInitialize, bool Echo); + virtual uintptr_t MoreMessageDialog(const UnicodeString & Message, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, + const TMessageParams * Params); +}; + diff --git a/netbox/src/NetBox/FarPluginStrings.cpp b/netbox/src/NetBox/FarPluginStrings.cpp new file mode 100644 index 000000000..d08fd8a77 --- /dev/null +++ b/netbox/src/NetBox/FarPluginStrings.cpp @@ -0,0 +1,449 @@ +#include +#include "FarPluginStrings.h" +#include "TextsFileZilla.h" +#include "TextsCore.h" +#include "rtlconsts.h" + + +const TFarPluginStrings FarPluginStrings[] = +{ + { CORE_ERROR_STRINGS, MSG_CORE_ERROR_STRINGS }, + { KEY_NOT_VERIFIED, MSG_KEY_NOT_VERIFIED }, + { CONNECTION_FAILED, MSG_CONNECTION_FAILED }, + { USER_TERMINATED, MSG_USER_TERMINATED }, + { LOST_CONNECTION, MSG_LOST_CONNECTION }, + { CANT_DETECT_RETURN_CODE, MSG_CANT_DETECT_RETURN_CODE }, + { COMMAND_FAILED, MSG_COMMAND_FAILED }, + { COMMAND_FAILED_CODEONLY, MSG_COMMAND_FAILED_CODEONLY }, + { INVALID_OUTPUT_ERROR, MSG_INVALID_OUTPUT_ERROR }, + { READ_CURRENT_DIR_ERROR, MSG_READ_CURRENT_DIR_ERROR }, + { SKIP_STARTUP_MESSAGE_ERROR, MSG_SKIP_STARTUP_MESSAGE_ERROR }, + { CHANGE_DIR_ERROR, MSG_CHANGE_DIR_ERROR }, + { LIST_DIR_ERROR, MSG_LIST_DIR_ERROR }, + { LIST_LINE_ERROR, MSG_LIST_LINE_ERROR }, + { RIGHTS_ERROR, MSG_RIGHTS_ERROR }, + { CLEANUP_CONFIG_ERROR, MSG_CLEANUP_CONFIG_ERROR }, + { CLEANUP_HOSTKEYS_ERROR, MSG_CLEANUP_HOSTKEYS_ERROR }, + { CLEANUP_SEEDFILE_ERROR, MSG_CLEANUP_SEEDFILE_ERROR }, + { CLEANUP_SESSIONS_ERROR, MSG_CLEANUP_SESSIONS_ERROR }, + { DETECT_RETURNVAR_ERROR, MSG_DETECT_RETURNVAR_ERROR }, + { LOOKUP_GROUPS_ERROR, MSG_LOOKUP_GROUPS_ERROR }, + { FILE_NOT_EXISTS, MSG_FILE_NOT_EXISTS }, + { CANT_GET_ATTRS, MSG_CANT_GET_ATTRS }, + { OPENFILE_ERROR, MSG_OPENFILE_ERROR }, + { READ_ERROR, MSG_READ_ERROR }, + { COPY_FATAL, MSG_COPY_FATAL }, + { TOREMOTE_COPY_ERROR, MSG_TOREMOTE_COPY_ERROR }, + { TOLOCAL_COPY_ERROR, MSG_TOLOCAL_COPY_ERROR }, + { SCP_EMPTY_LINE, MSG_SCP_EMPTY_LINE }, + { SCP_ILLEGAL_TIME_FORMAT, MSG_SCP_ILLEGAL_TIME_FORMAT }, + { SCP_INVALID_CONTROL_RECORD, MSG_SCP_INVALID_CONTROL_RECORD }, + { COPY_ERROR, MSG_COPY_ERROR }, + { SCP_ILLEGAL_FILE_DESCRIPTOR, MSG_SCP_ILLEGAL_FILE_DESCRIPTOR }, + { NOT_DIRECTORY_ERROR, MSG_NOT_DIRECTORY_ERROR }, + { CREATE_DIR_ERROR, MSG_CREATE_DIR_ERROR }, + { CREATE_FILE_ERROR, MSG_CREATE_FILE_ERROR }, + { WRITE_ERROR, MSG_WRITE_ERROR }, + { CANT_SET_ATTRS, MSG_CANT_SET_ATTRS }, + { REMOTE_ERROR, MSG_REMOTE_ERROR }, + { DELETE_FILE_ERROR, MSG_DELETE_FILE_ERROR }, + { LOG_GEN_ERROR, MSG_LOG_GEN_ERROR }, + { LOG_OPENERROR, MSG_LOG_OPENERROR }, + { RENAME_FILE_ERROR, MSG_RENAME_FILE_ERROR }, + { RENAME_CREATE_FILE_EXISTS, MSG_RENAME_CREATE_FILE_EXISTS }, + { RENAME_CREATE_DIR_EXISTS, MSG_RENAME_CREATE_DIR_EXISTS }, + { CHANGE_HOMEDIR_ERROR, MSG_CHANGE_HOMEDIR_ERROR }, + { UNALIAS_ALL_ERROR, MSG_UNALIAS_ALL_ERROR }, + { UNSET_NATIONAL_ERROR, MSG_UNSET_NATIONAL_ERROR }, + { FIRST_LINE_EXPECTED, MSG_FIRST_LINE_EXPECTED }, + { CLEANUP_INIFILE_ERROR, MSG_CLEANUP_INIFILE_ERROR }, + { AUTHENTICATION_LOG, MSG_AUTHENTICATION_LOG }, + { AUTHENTICATION_FAILED, MSG_AUTHENTICATION_FAILED }, + { NOT_CONNECTED, MSG_NOT_CONNECTED }, + { SAVE_KEY_ERROR, MSG_SAVE_KEY_ERROR }, + { SSH_EXITCODE, MSG_SSH_EXITCODE }, + { SFTP_INVALID_TYPE, MSG_SFTP_INVALID_TYPE }, + { SFTP_VERSION_NOT_SUPPORTED, MSG_SFTP_VERSION_NOT_SUPPORTED }, + { SFTP_MESSAGE_NUMBER, MSG_SFTP_MESSAGE_NUMBER }, + { SFTP_STATUS_OK, MSG_SFTP_STATUS_OK }, + { SFTP_STATUS_EOF, MSG_SFTP_STATUS_EOF }, + { SFTP_STATUS_NO_SUCH_FILE, MSG_SFTP_STATUS_NO_SUCH_FILE }, + { SFTP_STATUS_PERMISSION_DENIED, MSG_SFTP_STATUS_PERMISSION_DENIED }, + { SFTP_STATUS_FAILURE, MSG_SFTP_STATUS_FAILURE }, + { SFTP_STATUS_BAD_MESSAGE, MSG_SFTP_STATUS_BAD_MESSAGE }, + { SFTP_STATUS_NO_CONNECTION, MSG_SFTP_STATUS_NO_CONNECTION }, + { SFTP_STATUS_CONNECTION_LOST, MSG_SFTP_STATUS_CONNECTION_LOST }, + { SFTP_STATUS_OP_UNSUPPORTED, MSG_SFTP_STATUS_OP_UNSUPPORTED }, + { SFTP_ERROR_FORMAT3, MSG_SFTP_ERROR_FORMAT3 }, + { SFTP_STATUS_UNKNOWN, MSG_SFTP_STATUS_UNKNOWN }, + { READ_SYMLINK_ERROR, MSG_READ_SYMLINK_ERROR }, + { EMPTY_DIRECTORY, MSG_EMPTY_DIRECTORY }, + { SFTP_NON_ONE_FXP_NAME_PACKET, MSG_SFTP_NON_ONE_FXP_NAME_PACKET }, + { SFTP_REALPATH_ERROR, MSG_SFTP_REALPATH_ERROR }, + { CHANGE_PROPERTIES_ERROR, MSG_CHANGE_PROPERTIES_ERROR }, + { SFTP_INITIALIZE_ERROR, MSG_SFTP_INITIALIZE_ERROR }, + { TIMEZONE_ERROR, MSG_TIMEZONE_ERROR }, + { SFTP_CREATE_FILE_ERROR, MSG_SFTP_CREATE_FILE_ERROR }, + { SFTP_OPEN_FILE_ERROR, MSG_SFTP_OPEN_FILE_ERROR }, + { SFTP_CLOSE_FILE_ERROR, MSG_SFTP_CLOSE_FILE_ERROR }, + { NOT_FILE_ERROR, MSG_NOT_FILE_ERROR }, + { RENAME_AFTER_RESUME_ERROR, MSG_RENAME_AFTER_RESUME_ERROR }, + { CREATE_LINK_ERROR, MSG_CREATE_LINK_ERROR }, + { INVALID_SHELL_COMMAND, MSG_INVALID_SHELL_COMMAND }, + { SFTP_SERVER_MESSAGE_UNSUPPORTED, MSG_SFTP_SERVER_MESSAGE_UNSUPPORTED }, + { INVALID_OCTAL_PERMISSIONS, MSG_INVALID_OCTAL_PERMISSIONS }, + { SFTP_INVALID_EOL, MSG_SFTP_INVALID_EOL }, + { SFTP_UNKNOWN_FILE_TYPE, MSG_SFTP_UNKNOWN_FILE_TYPE }, + { SFTP_STATUS_INVALID_HANDLE, MSG_SFTP_STATUS_INVALID_HANDLE }, + { SFTP_STATUS_NO_SUCH_PATH, MSG_SFTP_STATUS_NO_SUCH_PATH }, + { SFTP_STATUS_FILE_ALREADY_EXISTS, MSG_SFTP_STATUS_FILE_ALREADY_EXISTS }, + { SFTP_STATUS_WRITE_PROTECT, MSG_SFTP_STATUS_WRITE_PROTECT }, + { SFTP_STATUS_NO_MEDIA, MSG_SFTP_STATUS_NO_MEDIA }, + { DECODE_UTF_ERROR, MSG_DECODE_UTF_ERROR }, + { CUSTOM_COMMAND_ERROR, MSG_CUSTOM_COMMAND_ERROR }, + { LOCALE_LOAD_ERROR, MSG_LOCALE_LOAD_ERROR }, + { SFTP_INCOMPLETE_BEFORE_EOF, MSG_SFTP_INCOMPLETE_BEFORE_EOF }, + { CALCULATE_SIZE_ERROR, MSG_CALCULATE_SIZE_ERROR }, + { SFTP_PACKET_TOO_BIG, MSG_SFTP_PACKET_TOO_BIG }, + { SCP_INIT_ERROR, MSG_SCP_INIT_ERROR }, + { DUPLICATE_BOOKMARK, MSG_DUPLICATE_BOOKMARK }, + { MOVE_FILE_ERROR, MSG_MOVE_FILE_ERROR }, + { SFTP_PACKET_TOO_BIG_INIT_EXPLAIN, MSG_SFTP_PACKET_TOO_BIG_INIT_EXPLAIN }, + { PRESERVE_TIME_PERM_ERROR3, MSG_PRESERVE_TIME_PERM_ERROR3 }, + { ACCESS_VIOLATION_ERROR3, MSG_ACCESS_VIOLATION_ERROR3 }, + { SFTP_STATUS_NO_SPACE_ON_FILESYSTEM, MSG_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM }, + { SFTP_STATUS_QUOTA_EXCEEDED, MSG_SFTP_STATUS_QUOTA_EXCEEDED }, + { SFTP_STATUS_UNKNOWN_PRINCIPAL, MSG_SFTP_STATUS_UNKNOWN_PRINCIPAL }, + { COPY_FILE_ERROR, MSG_COPY_FILE_ERROR }, + { CUSTOM_COMMAND_UNTERMINATED, MSG_CUSTOM_COMMAND_UNTERMINATED }, + { CUSTOM_COMMAND_UNKNOWN, MSG_CUSTOM_COMMAND_UNKNOWN }, + { CUSTOM_COMMAND_FILELIST_ERROR, MSG_CUSTOM_COMMAND_FILELIST_ERROR }, + + { UNKNOWN_SOCKET_STATUS, MSG_UNKNOWN_SOCKET_STATUS }, + { DELETE_ON_RESUME_ERROR, MSG_DELETE_ON_RESUME_ERROR }, + { SFTP_PACKET_ERROR, MSG_SFTP_PACKET_ERROR }, + { ITEM_NAME_INVALID, MSG_ITEM_NAME_INVALID }, + { SFTP_STATUS_LOCK_CONFLICT, MSG_SFTP_STATUS_LOCK_CONFLICT }, + { SFTP_STATUS_DIR_NOT_EMPTY, MSG_SFTP_STATUS_DIR_NOT_EMPTY }, + { SFTP_STATUS_NOT_A_DIRECTORY, MSG_SFTP_STATUS_NOT_A_DIRECTORY }, + { SFTP_STATUS_INVALID_FILENAME, MSG_SFTP_STATUS_INVALID_FILENAME }, + { SFTP_STATUS_LINK_LOOP, MSG_SFTP_STATUS_LINK_LOOP }, + { SFTP_STATUS_CANNOT_DELETE, MSG_SFTP_STATUS_CANNOT_DELETE }, + { SFTP_STATUS_INVALID_PARAMETER, MSG_SFTP_STATUS_INVALID_PARAMETER }, + { SFTP_STATUS_FILE_IS_A_DIRECTORY, MSG_SFTP_STATUS_FILE_IS_A_DIRECTORY }, + { SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT, MSG_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT }, + { SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED, MSG_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED }, + { SFTP_STATUS_DELETE_PENDING, MSG_SFTP_STATUS_DELETE_PENDING }, + { SFTP_STATUS_FILE_CORRUPT, MSG_SFTP_STATUS_FILE_CORRUPT }, + { KEY_TYPE_UNKNOWN2, MSG_KEY_TYPE_UNKNOWN2 }, + { KEY_TYPE_UNSUPPORTED2, MSG_KEY_TYPE_UNSUPPORTED2 }, + { KEY_TYPE_DIFFERENT_SSH, MSG_KEY_TYPE_DIFFERENT_SSH }, + { SFTP_OVERWRITE_FILE_ERROR2, MSG_SFTP_OVERWRITE_FILE_ERROR2 }, + { SFTP_OVERWRITE_DELETE_BUTTON, MSG_SFTP_OVERWRITE_DELETE_BUTTON }, + { SPACE_AVAILABLE_ERROR, MSG_SPACE_AVAILABLE_ERROR }, + { TUNNEL_NO_FREE_PORT, MSG_TUNNEL_NO_FREE_PORT }, + { EVENT_SELECT_ERROR, MSG_EVENT_SELECT_ERROR }, + { UNEXPECTED_CLOSE_ERROR, MSG_UNEXPECTED_CLOSE_ERROR }, + { TUNNEL_ERROR, MSG_TUNNEL_ERROR }, + { CHECKSUM_ERROR, MSG_CHECKSUM_ERROR }, + { INTERNAL_ERROR, MSG_INTERNAL_ERROR }, + { FZ_NOTSUPPORTED, MSG_FZ_NOTSUPPORTED }, + { FTP_ACCESS_DENIED, MSG_FTP_ACCESS_DENIED }, + { FTP_CREDENTIAL_PROMPT, MSG_FTP_CREDENTIAL_PROMPT }, + { FTP_RESPONSE_ERROR, MSG_FTP_RESPONSE_ERROR }, + { FTP_UNSUPPORTED, MSG_FTP_UNSUPPORTED }, + + { TRANSFER_ERROR, MSG_TRANSFER_ERROR }, + { EXECUTE_APP_ERROR, MSG_EXECUTE_APP_ERROR }, + { FILE_NOT_FOUND, MSG_FILE_NOT_FOUND }, + { DOCUMENT_WAIT_ERROR, MSG_DOCUMENT_WAIT_ERROR }, + { SPEED_INVALID, MSG_SPEED_INVALID }, + { CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, MSG_CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT }, + { CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, MSG_CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD }, + { CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, MSG_CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD }, + { CERT_ERR_INVALID_CA, MSG_CERT_ERR_INVALID_CA }, + { CERT_ERR_INVALID_PURPOSE, MSG_CERT_ERR_INVALID_PURPOSE }, + { CERT_ERR_KEYUSAGE_NO_CERTSIGN, MSG_CERT_ERR_KEYUSAGE_NO_CERTSIGN }, + { CERT_ERR_PATH_LENGTH_EXCEEDED, MSG_CERT_ERR_PATH_LENGTH_EXCEEDED }, + { CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN, MSG_CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN }, + { CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, MSG_CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY }, + { CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, MSG_CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE }, + { CERT_ERR_UNABLE_TO_GET_ISSUER_CERT, MSG_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT }, + { CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, MSG_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY }, + { CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, MSG_CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE }, + { CERT_ERR_UNKNOWN, MSG_CERT_ERR_UNKNOWN }, + { CERT_ERRDEPTH, MSG_CERT_ERRDEPTH }, + { MASK_ERROR, MSG_MASK_ERROR }, + { FTP_CANNOT_OPEN_ACTIVE_CONNECTION2, MSG_FTP_CANNOT_OPEN_ACTIVE_CONNECTION2 }, + { CORE_DELETE_LOCAL_FILE_ERROR, MSG_CORE_DELETE_LOCAL_FILE_ERROR }, + { URL_OPTION_BOOL_VALUE_ERROR, MSG_URL_OPTION_BOOL_VALUE_ERROR }, + { FTP_ACCESS_DENIED_EMPTY_PASSWORD, MSG_FTP_ACCESS_DENIED_EMPTY_PASSWORD }, + + { NET_TRANSL_NO_ROUTE2, MSG_NET_TRANSL_NO_ROUTE2 }, + { NET_TRANSL_CONN_ABORTED, MSG_NET_TRANSL_CONN_ABORTED }, + { NET_TRANSL_HOST_NOT_EXIST2, MSG_NET_TRANSL_HOST_NOT_EXIST2 }, + { NET_TRANSL_PACKET_GARBLED, MSG_NET_TRANSL_PACKET_GARBLED }, + { REPORT_ERROR, MSG_REPORT_ERROR }, + { TLS_CERT_DECODE_ERROR, MSG_TLS_CERT_DECODE_ERROR }, + { FIND_FILE_ERROR, MSG_FIND_FILE_ERROR }, + { CERT_NAME_MISMATCH, MSG_CERT_NAME_MISMATCH }, + + { CERT_ERR_BAD_CHAIN, MSG_CERT_ERR_BAD_CHAIN }, + { CERT_OK, MSG_CERT_OK }, + { CERT_ERR_CERT_CHAIN_TOO_LONG, MSG_CERT_ERR_CERT_CHAIN_TOO_LONG }, + { CERT_ERR_CERT_HAS_EXPIRED, MSG_CERT_ERR_CERT_HAS_EXPIRED }, + { CERT_ERR_CERT_NOT_YET_VALID, MSG_CERT_ERR_CERT_NOT_YET_VALID }, + { CERT_ERR_CERT_REJECTED, MSG_CERT_ERR_CERT_REJECTED }, + { CERT_ERR_CERT_SIGNATURE_FAILURE, MSG_CERT_ERR_CERT_SIGNATURE_FAILURE }, + { CERT_ERR_CERT_UNTRUSTED, MSG_CERT_ERR_CERT_UNTRUSTED }, + { REQUEST_REDIRECTED, MSG_REQUEST_REDIRECTED }, + { TOO_MANY_REDIRECTS, MSG_TOO_MANY_REDIRECTS }, + { REDIRECT_LOOP, MSG_REDIRECT_LOOP }, + { INVALID_URL, MSG_INVALID_URL }, + { PROXY_AUTHENTICATION_FAILED, MSG_PROXY_AUTHENTICATION_FAILED }, + { CONFIGURED_KEY_NOT_MATCH, MSG_CONFIGURED_KEY_NOT_MATCH }, + { SFTP_STATUS_OWNER_INVALID, MSG_SFTP_STATUS_OWNER_INVALID }, + { SFTP_STATUS_GROUP_INVALID, MSG_SFTP_STATUS_GROUP_INVALID }, + { SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK, MSG_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK }, + { KEY_TYPE_UNOPENABLE, MSG_KEY_TYPE_UNOPENABLE }, + { UNKNOWN_CHECKSUM, MSG_UNKNOWN_CHECKSUM }, + { CIPHER_NOT_VERIFIED, MSG_CIPHER_NOT_VERIFIED }, + { KEX_NOT_VERIFIED, MSG_KEX_NOT_VERIFIED }, + { SFTP_STATUS_4, MSG_SFTP_STATUS_4 }, + { CERTIFICATE_OPEN_ERROR, MSG_CERTIFICATE_OPEN_ERROR }, + { CERTIFICATE_READ_ERROR, MSG_CERTIFICATE_READ_ERROR }, + { CERTIFICATE_DECODE_ERROR_INFO, MSG_CERTIFICATE_DECODE_ERROR_INFO }, + { CERTIFICATE_DECODE_ERROR, MSG_CERTIFICATE_DECODE_ERROR }, + { CERTIFICATE_PUBLIC_KEY_NOT_FOUND, MSG_CERTIFICATE_PUBLIC_KEY_NOT_FOUND }, + { LOCK_FILE_ERROR, MSG_LOCK_FILE_ERROR }, + { UNLOCK_FILE_ERROR, MSG_UNLOCK_FILE_ERROR }, + { NOT_LOCKED, MSG_NOT_LOCKED }, + { KEY_SAVE_ERROR, MSG_KEY_SAVE_ERROR }, + { NEON_INIT_FAILED, MSG_NEON_INIT_FAILED }, + { SCRIPT_AMBIGUOUS_SLASH_IN_PATH, MSG_SCRIPT_AMBIGUOUS_SLASH_IN_PATH }, + { CERT_IP_CANNOT_VERIFY, MSG_CERT_IP_CANNOT_VERIFY }, + { HOSTKEY_NOT_CONFIGURED, MSG_HOSTKEY_NOT_CONFIGURED }, + { UNENCRYPTED_REDIRECT, MSG_UNENCRYPTED_REDIRECT }, + { HTTP_ERROR2, MSG_HTTP_ERROR2 }, + { FILEZILLA_SITE_MANAGER_NOT_FOUND, MSG_FILEZILLA_SITE_MANAGER_NOT_FOUND }, + { FILEZILLA_NO_SITES, MSG_FILEZILLA_NO_SITES }, + { FILEZILLA_SITE_NOT_EXIST, MSG_FILEZILLA_SITE_NOT_EXIST }, + { SFTP_AS_FTP_ERROR, MSG_SFTP_AS_FTP_ERROR }, + + { CORE_CONFIRMATION_STRINGS, MSG_CORE_CONFIRMATION_STRINGS }, + { CONFIRM_PROLONG_TIMEOUT3, MSG_CONFIRM_PROLONG_TIMEOUT3 }, + { PROMPT_KEY_PASSPHRASE, MSG_PROMPT_KEY_PASSPHRASE }, + { PROMPT_FILE_OVERWRITE, MSG_PROMPT_FILE_OVERWRITE }, + { DIRECTORY_OVERWRITE, MSG_DIRECTORY_OVERWRITE }, + { CIPHER_BELOW_TRESHOLD, MSG_CIPHER_BELOW_TRESHOLD }, + { CIPHER_TYPE_BOTH, MSG_CIPHER_TYPE_BOTH }, + { CIPHER_TYPE_CS, MSG_CIPHER_TYPE_CS }, + { CIPHER_TYPE_SC, MSG_CIPHER_TYPE_SC }, + { RESUME_TRANSFER2, MSG_RESUME_TRANSFER2 }, + { PARTIAL_BIGGER_THAN_SOURCE, MSG_PARTIAL_BIGGER_THAN_SOURCE }, + { APPEND_OR_RESUME2, MSG_APPEND_OR_RESUME2 }, + { FILE_OVERWRITE_DETAILS, MSG_FILE_OVERWRITE_DETAILS }, + { READ_ONLY_OVERWRITE, MSG_READ_ONLY_OVERWRITE }, + { LOCAL_FILE_OVERWRITE2, MSG_LOCAL_FILE_OVERWRITE2 }, + { REMOTE_FILE_OVERWRITE2, MSG_REMOTE_FILE_OVERWRITE2 }, + { TIMEOUT_STILL_WAITING3, MSG_TIMEOUT_STILL_WAITING3 }, + { KEX_BELOW_TRESHOLD, MSG_KEX_BELOW_TRESHOLD }, + { RECONNECT_BUTTON, MSG_RECONNECT_BUTTON }, + { RENAME_BUTTON, MSG_RENAME_BUTTON }, + { TUNNEL_SESSION_NAME, MSG_TUNNEL_SESSION_NAME }, + { PASSWORD_TITLE, MSG_PASSWORD_TITLE }, + { PASSPHRASE_TITLE, MSG_PASSPHRASE_TITLE }, + { SERVER_PROMPT_TITLE, MSG_SERVER_PROMPT_TITLE }, + { USERNAME_TITLE, MSG_USERNAME_TITLE }, + { USERNAME_PROMPT2, MSG_USERNAME_PROMPT2 }, + { SERVER_PROMPT_TITLE2, MSG_SERVER_PROMPT_TITLE2 }, + { NEW_PASSWORD_TITLE, MSG_NEW_PASSWORD_TITLE }, + { PROMPT_PROMPT, MSG_PROMPT_PROMPT }, + { TIS_INSTRUCTION, MSG_TIS_INSTRUCTION }, + { CRYPTOCARD_INSTRUCTION, MSG_CRYPTOCARD_INSTRUCTION }, + { PASSWORD_PROMPT, MSG_PASSWORD_PROMPT }, + { KEYBINTER_INSTRUCTION, MSG_KEYBINTER_INSTRUCTION }, + { NEW_PASSWORD_CURRENT_PROMPT, MSG_NEW_PASSWORD_CURRENT_PROMPT }, + { NEW_PASSWORD_NEW_PROMPT, MSG_NEW_PASSWORD_NEW_PROMPT }, + { NEW_PASSWORD_CONFIRM_PROMPT, MSG_NEW_PASSWORD_CONFIRM_PROMPT }, + { TUNNEL_INSTRUCTION, MSG_TUNNEL_INSTRUCTION }, + { RENAME_TITLE, MSG_RENAME_TITLE }, + { RENAME_PROMPT2, MSG_RENAME_PROMPT2 }, + { VERIFY_CERT_PROMPT3, MSG_VERIFY_CERT_PROMPT3 }, + { VERIFY_CERT_CONTACT, MSG_VERIFY_CERT_CONTACT }, + { VERIFY_CERT_CONTACT_LIST, MSG_VERIFY_CERT_CONTACT_LIST }, + { CERT_TEXT, MSG_CERT_TEXT }, + + { CERTIFICATE_PASSPHRASE_PROMPT, MSG_CERTIFICATE_PASSPHRASE_PROMPT }, + { CERTIFICATE_PASSPHRASE_TITLE, MSG_CERTIFICATE_PASSPHRASE_TITLE }, + { KEY_TYPE_CONVERT3, MSG_KEY_TYPE_CONVERT3 }, + { MULTI_FILES_TO_ONE, MSG_MULTI_FILES_TO_ONE }, + + { CORE_INFORMATION_STRINGS, MSG_CORE_INFORMATION_STRINGS }, + { YES_STR, MSG_YES_STR }, + { NO_STR, MSG_NO_STR }, + { SESSION_INFO_TIP2, MSG_SESSION_INFO_TIP2 }, + { VERSION2, MSG_VERSION2 }, + { CLOSED_ON_COMPLETION, MSG_CLOSED_ON_COMPLETION }, + { SFTP_PROTOCOL_NAME2, MSG_SFTP_PROTOCOL_NAME2 }, + { FS_RENAME_NOT_SUPPORTED, MSG_FS_RENAME_NOT_SUPPORTED }, + { SFTP_NO_EXTENSION_INFO, MSG_SFTP_NO_EXTENSION_INFO }, + { SFTP_EXTENSION_INFO, MSG_SFTP_EXTENSION_INFO }, + { APPEND_BUTTON, MSG_APPEND_BUTTON }, + { YES_TO_NEWER_BUTTON, MSG_YES_TO_NEWER_BUTTON }, + + { SKIP_ALL_BUTTON, MSG_SKIP_ALL_BUTTON }, + + { COPY_PARAM_PRESET_ASCII, MSG_COPY_PARAM_PRESET_ASCII }, + { COPY_PARAM_PRESET_BINARY, MSG_COPY_PARAM_PRESET_BINARY }, + { COPY_PARAM_PRESET_EXCLUDE_ALL_DIR, MSG_COPY_PARAM_PRESET_EXCLUDE_ALL_DIR }, + { COPY_INFO_TRANSFER_TYPE2, MSG_COPY_INFO_TRANSFER_TYPE2 }, + { COPY_INFO_FILENAME, MSG_COPY_INFO_FILENAME }, + { COPY_INFO_PERMISSIONS, MSG_COPY_INFO_PERMISSIONS }, + { COPY_INFO_ADD_X_TO_DIRS, MSG_COPY_INFO_ADD_X_TO_DIRS }, + { COPY_INFO_TIMESTAMP, MSG_COPY_INFO_TIMESTAMP }, + { COPY_INFO_FILE_MASK, MSG_COPY_INFO_FILE_MASK }, + { COPY_INFO_CLEAR_ARCHIVE, MSG_COPY_INFO_CLEAR_ARCHIVE }, + { COPY_INFO_DONT_REPLACE_INV_CHARS, MSG_COPY_INFO_DONT_REPLACE_INV_CHARS }, + { COPY_INFO_DONT_PRESERVE_TIME, MSG_COPY_INFO_DONT_PRESERVE_TIME }, + { COPY_INFO_DONT_CALCULATE_SIZE, MSG_COPY_INFO_DONT_CALCULATE_SIZE }, + { COPY_INFO_DEFAULT, MSG_COPY_INFO_DEFAULT }, + { COPY_RULE_HOSTNAME, MSG_COPY_RULE_HOSTNAME }, + { COPY_RULE_USERNAME, MSG_COPY_RULE_USERNAME }, + { COPY_RULE_REMOTE_DIR, MSG_COPY_RULE_REMOTE_DIR }, + { COPY_RULE_LOCAL_DIR, MSG_COPY_RULE_LOCAL_DIR }, + { SYNCHRONIZE_SCAN, MSG_SYNCHRONIZE_SCAN }, + { SYNCHRONIZE_START, MSG_SYNCHRONIZE_START }, + { SYNCHRONIZE_CHANGE, MSG_SYNCHRONIZE_CHANGE }, + { SYNCHRONIZE_UPLOADED, MSG_SYNCHRONIZE_UPLOADED }, + { SYNCHRONIZE_DELETED, MSG_SYNCHRONIZE_DELETED }, + { COPY_INFO_NOT_USABLE, MSG_COPY_INFO_NOT_USABLE }, + { COPY_INFO_IGNORE_PERM_ERRORS, MSG_COPY_INFO_IGNORE_PERM_ERRORS }, + { AUTH_TRANSL_USERNAME, MSG_AUTH_TRANSL_USERNAME }, + { AUTH_TRANSL_KEYB_INTER, MSG_AUTH_TRANSL_KEYB_INTER }, + { AUTH_TRANSL_PUBLIC_KEY, MSG_AUTH_TRANSL_PUBLIC_KEY }, + { AUTH_TRANSL_WRONG_PASSPHRASE, MSG_AUTH_TRANSL_WRONG_PASSPHRASE }, + { AUTH_TRANSL_ACCESS_DENIED, MSG_AUTH_TRANSL_ACCESS_DENIED }, + { AUTH_TRANSL_PUBLIC_KEY_AGENT, MSG_AUTH_TRANSL_PUBLIC_KEY_AGENT }, + { AUTH_TRANSL_TRY_PUBLIC_KEY, MSG_AUTH_TRANSL_TRY_PUBLIC_KEY }, + { AUTH_PASSWORD, MSG_AUTH_PASSWORD }, + { OPEN_TUNNEL, MSG_OPEN_TUNNEL }, + { NETBOX_STATUS_CLOSED, MSG_NETBOX_STATUS_CLOSED }, + { STATUS_LOOKUPHOST, MSG_STATUS_LOOKUPHOST }, + { STATUS_CONNECT, MSG_STATUS_CONNECT }, + { STATUS_AUTHENTICATE, MSG_STATUS_AUTHENTICATE }, + { STATUS_AUTHENTICATED, MSG_STATUS_AUTHENTICATED }, + { STATUS_STARTUP, MSG_STATUS_STARTUP }, + { STATUS_READY, MSG_STATUS_READY }, + { STATUS_OPEN_DIRECTORY, MSG_STATUS_OPEN_DIRECTORY }, + { USING_TUNNEL, MSG_USING_TUNNEL }, + { AUTH_TRANSL_KEY_REFUSED, MSG_AUTH_TRANSL_KEY_REFUSED }, + { PFWD_TRANSL_ADMIN, MSG_PFWD_TRANSL_ADMIN }, + { PFWD_TRANSL_CONNECT, MSG_PFWD_TRANSL_CONNECT }, + { NET_TRANSL_REFUSED2, MSG_NET_TRANSL_REFUSED2 }, + { NET_TRANSL_RESET, MSG_NET_TRANSL_RESET }, + { NET_TRANSL_TIMEOUT2, MSG_NET_TRANSL_TIMEOUT2 }, + { SESSION_INFO_TIP_NO_SSH, MSG_SESSION_INFO_TIP_NO_SSH }, + { RESUME_BUTTON, MSG_RESUME_BUTTON }, + { FTP_NO_FEATURE_INFO, MSG_FTP_NO_FEATURE_INFO }, + { FTP_FEATURE_INFO, MSG_FTP_FEATURE_INFO }, + { COPY_INFO_CPS_LIMIT2, MSG_COPY_INFO_CPS_LIMIT2 }, + { COPY_KEY_BUTTON, MSG_COPY_KEY_BUTTON }, + { UPDATE_KEY_BUTTON, MSG_UPDATE_KEY_BUTTON }, + { ADD_KEY_BUTTON, MSG_ADD_KEY_BUTTON }, + { COPY_INFO_PRESERVE_READONLY, MSG_COPY_INFO_PRESERVE_READONLY }, + + { SPEED_UNLIMITED, MSG_SPEED_UNLIMITED }, + { FTPS_IMPLICIT, MSG_FTPS_IMPLICIT }, + { FTPS_EXPLICIT, MSG_FTPS_EXPLICIT }, + + { HOSTKEY, MSG_HOSTKEY }, + + { COPY_PARAM_NEWER_ONLY, MSG_COPY_PARAM_NEWER_ONLY }, + { FTP_SUGGESTION, MSG_FTP_SUGGESTION }, + + { ANY_HOSTKEY, MSG_ANY_HOSTKEY }, + { ANY_CERTIFICATE, MSG_ANY_CERTIFICATE }, + + { COPY_INFO_REMOVE_CTRLZ, MSG_COPY_INFO_REMOVE_CTRLZ }, + { COPY_INFO_REMOVE_BOM, MSG_COPY_INFO_REMOVE_BOM }, + + { VERSION_BUILD, MSG_VERSION_BUILD }, + { VERSION_DEV_BUILD, MSG_VERSION_DEV_BUILD }, + { VERSION_DEBUG_BUILD, MSG_VERSION_DEBUG_BUILD }, + { VERSION_DONT_DISTRIBUTE, MSG_VERSION_DONT_DISTRIBUTE }, + { WEBDAV_EXTENSION_INFO, MSG_WEBDAV_EXTENSION_INFO }, + { COPY_PARAM_PRESET_EXCLUDE_ALL_DIR, MSG_COPY_PARAM_PRESET_EXCLUDE_ALL_DIR }, + { SCRIPT_CHECKSUM_DESC, MSG_SCRIPT_CHECKSUM_DESC }, + { CLIENT_CERTIFICATE_LOADING, MSG_CLIENT_CERTIFICATE_LOADING }, + { NEED_CLIENT_CERTIFICATE, MSG_NEED_CLIENT_CERTIFICATE }, + { LOCKED, MSG_LOCKED }, + { EXECUTABLE, MSG_EXECUTABLE }, + { SCRIPT_CMDLINE_PARAMETERS, MSG_SCRIPT_CMDLINE_PARAMETERS }, + { SCRIPTING_USE_HOSTKEY, MSG_SCRIPTING_USE_HOSTKEY }, + { SCRIPT_SITE_WARNING, MSG_SCRIPT_SITE_WARNING }, + { CODE_SESSION_OPTIONS, MSG_CODE_SESSION_OPTIONS }, + { CODE_CONNECT, MSG_CODE_CONNECT }, + { CODE_YOUR_CODE, MSG_CODE_YOUR_CODE }, + { CODE_PS_ADD_TYPE, MSG_CODE_PS_ADD_TYPE }, + { COPY_INFO_PRESERVE_TIME_DIRS, MSG_COPY_INFO_PRESERVE_TIME_DIRS }, + + { CORE_VARIABLE_STRINGS, MSG_CORE_VARIABLE_STRINGS }, + { PUTTY_BASED_ON, MSG_PUTTY_BASED_ON }, + { PUTTY_VERSION, MSG_PUTTY_VERSION }, + { PUTTY_COPYRIGHT, MSG_PUTTY_COPYRIGHT }, + { PUTTY_URL, MSG_PUTTY_URL }, + { FILEZILLA_BASED_ON2, MSG_FILEZILLA_BASED_ON2 }, + { FILEZILLA_VERSION, MSG_FILEZILLA_VERSION }, + { FILEZILLA_COPYRIGHT2, MSG_FILEZILLA_COPYRIGHT2 }, + { FILEZILLA_URL, MSG_FILEZILLA_URL }, + { OPENSSL_BASED_ON, MSG_OPENSSL_BASED_ON }, + { OPENSSL_COPYRIGHT2, MSG_OPENSSL_COPYRIGHT2 }, + { OPENSSL_VERSION, MSG_OPENSSL_VERSION }, + { OPENSSL_URL, MSG_OPENSSL_URL }, + { NEON_BASED_ON, MSG_NEON_BASED_ON }, + { NEON_COPYRIGHT, MSG_NEON_COPYRIGHT }, + { NEON_URL, MSG_NEON_URL }, + { EXPAT_BASED_ON, MSG_EXPAT_BASED_ON }, + { EXPAT_COPYRIGHT, MSG_EXPAT_COPYRIGHT }, + { EXPAT_URL, MSG_EXPAT_URL }, + { PUTTY_LICENSE_URL, MSG_PUTTY_LICENSE_URL }, + { MAIN_MSG_TAG, MSG_MAIN_MSG_TAG }, + { INTERACTIVE_MSG_TAG, MSG_INTERACTIVE_MSG_TAG }, + + { WINSCPFAR_NAME, MSG_WINSCPFAR_NAME }, + { WINSCP_VERSION, MSG_WINSCP_VERSION }, + { WINSCPFAR_VERSION, MSG_WINSCPFAR_VERSION }, + { WINSCPFAR_BASED_ON, MSG_WINSCPFAR_BASED_ON }, + { WINSCPFAR_BASED_VERSION, MSG_WINSCPFAR_BASED_VERSION }, + { WINSCPFAR_BASED_COPYRIGHT, MSG_WINSCPFAR_BASED_COPYRIGHT }, + + { UNKNOWN_KEY3, MSG_UNKNOWN_KEY3 }, + { DIFFERENT_KEY4, MSG_DIFFERENT_KEY4 }, + { OLD_KEY, MSG_OLD_KEY }, + + // rtlconsts.rc + { SDuplicateString, MSG_SDuplicateString }, + { SListCountError, MSG_SListCountError }, + { SListIndexError, MSG_SListIndexError }, + { SMemoryStreamError, MSG_SMemoryStreamError }, + { SReadError, MSG_SReadError }, + { SSortedListError, MSG_SSortedListError }, + { STimeEncodeError, MSG_STimeEncodeError }, + { SWriteError, MSG_SWriteError }, + { SNotImplemented, MSG_SNotImplemented }, + { SOSError, MSG_SOSError }, + { SUnkOSError, MSG_SUnkOSError }, + + { SDateEncodeError, MSG_SDateEncodeError }, + { SCannotOpenClipboard, MSG_SCannotOpenClipboard }, + + { IDS_ERRORMSG_TIMEOUT, MSG_IDS_ERRORMSG_TIMEOUT }, + { IDS_STATUSMSG_DISCONNECTED, MSG_IDS_STATUSMSG_DISCONNECTED }, + + { CONVERTKEY_SAVE_TITLE, MSG_CONVERTKEY_SAVE_TITLE }, + { CONVERTKEY_SAVE_FILTER, MSG_CONVERTKEY_SAVE_FILTER }, + { CONVERTKEY_SAVED, MSG_CONVERTKEY_SAVED }, + + { 0, 0 } +}; diff --git a/netbox/src/NetBox/FarPluginStrings.h b/netbox/src/NetBox/FarPluginStrings.h new file mode 100644 index 000000000..6619eb48f --- /dev/null +++ b/netbox/src/NetBox/FarPluginStrings.h @@ -0,0 +1,18 @@ +#pragma once +#include + +#if defined(_MSC_VER) +#pragma pack(push, 2) +#endif + +struct TFarPluginStrings +{ + uint16_t Id; + uint16_t FarPluginStringId; +}; + +#if defined(_MSC_VER) +#pragma pack(pop) +#endif + +extern const TFarPluginStrings FarPluginStrings[]; diff --git a/netbox/src/NetBox/FarUtil.cpp b/netbox/src/NetBox/FarUtil.cpp new file mode 100644 index 000000000..221b280f5 --- /dev/null +++ b/netbox/src/NetBox/FarUtil.cpp @@ -0,0 +1,138 @@ +#include +#pragma hdrstop + +#include +#include + +#include "FarUtil.h" + +bool CNBFile::OpenWrite(const wchar_t *fileName) +{ + DebugAssert(m_File == INVALID_HANDLE_VALUE); + DebugAssert(fileName); + m_LastError = ERROR_SUCCESS; + + m_File = ::CreateFile(fileName, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (m_File == INVALID_HANDLE_VALUE) + { + m_LastError = ::GetLastError(); + } + return (m_LastError == ERROR_SUCCESS); +} + +bool CNBFile::OpenRead(const wchar_t *fileName) +{ + DebugAssert(m_File == INVALID_HANDLE_VALUE); + DebugAssert(fileName); + m_LastError = ERROR_SUCCESS; + + m_File = ::CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (m_File == INVALID_HANDLE_VALUE) + { + m_LastError = ::GetLastError(); + } + return (m_LastError == ERROR_SUCCESS); +} + +bool CNBFile::Read(void *buff, size_t &buffSize) +{ + DebugAssert(m_File != INVALID_HANDLE_VALUE); + m_LastError = ERROR_SUCCESS; + + DWORD bytesRead = static_cast(buffSize); + if (!ReadFile(m_File, buff, bytesRead, &bytesRead, nullptr)) + { + m_LastError = ::GetLastError(); + buffSize = 0; + } + else + { + buffSize = static_cast(bytesRead); + } + return (m_LastError == ERROR_SUCCESS); +} + +bool CNBFile::Write(const void *buff, const size_t buffSize) +{ + DebugAssert(m_File != INVALID_HANDLE_VALUE); + m_LastError = ERROR_SUCCESS; + + DWORD bytesWritten; + if (!WriteFile(m_File, buff, static_cast(buffSize), &bytesWritten, nullptr)) + { + m_LastError = ::GetLastError(); + } + return (m_LastError == ERROR_SUCCESS); +} + +int64_t CNBFile::GetFileSize() +{ + DebugAssert(m_File != INVALID_HANDLE_VALUE); + m_LastError = ERROR_SUCCESS; + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(m_File, &fileSize)) + { + m_LastError = ::GetLastError(); + return -1; + } + return fileSize.QuadPart; +} + +void CNBFile::Close() +{ + if (m_File != INVALID_HANDLE_VALUE) + { + ::CloseHandle(m_File); + m_File = INVALID_HANDLE_VALUE; + } +} + +DWORD CNBFile::LastError() const +{ + return m_LastError; +} + +DWORD CNBFile::SaveFile(const wchar_t *fileName, const std::vector& fileContent) +{ + CNBFile f; + if (f.OpenWrite(fileName) && !fileContent.empty()) + { + f.Write(&fileContent[0], fileContent.size()); + } + return f.LastError(); +} + +DWORD CNBFile::SaveFile(const wchar_t *fileName, const char *fileContent) +{ + DebugAssert(fileContent); + CNBFile f; + if (f.OpenWrite(fileName) && *fileContent) + { + f.Write(fileContent, strlen(fileContent)); + } + return f.LastError(); +} + +DWORD CNBFile::LoadFile(const wchar_t *fileName, std::vector& fileContent) +{ + fileContent.clear(); + + CNBFile f; + if (f.OpenRead(fileName)) + { + const int64_t fs = f.GetFileSize(); + if (fs < 0) + { + return f.LastError(); + } + if (fs == 0) + { + return ERROR_SUCCESS; + } + size_t s = static_cast(fs); + fileContent.resize(s); + f.Read(&fileContent[0], s); + } + return f.LastError(); +} diff --git a/netbox/src/NetBox/FarUtil.h b/netbox/src/NetBox/FarUtil.h new file mode 100644 index 000000000..075641a07 --- /dev/null +++ b/netbox/src/NetBox/FarUtil.h @@ -0,0 +1,84 @@ +#pragma once + +#include "FarDialog.h" + +/** + * File read/write wrapper + */ +class CNBFile : public TObject +{ + NB_DISABLE_COPY(CNBFile) + public: + CNBFile() : m_File(INVALID_HANDLE_VALUE), m_LastError(0) {} + ~CNBFile() + { + Close(); + } + + /** + * Open file for writing + * \param fileName file name + * \return false if error + */ + bool OpenWrite(const wchar_t *fileName); + /** + * Open file for reading + * \param fileName file name + * \return false if error + */ + bool OpenRead(const wchar_t *fileName); + /** + * Read file + * \param buff read buffer + * \param buffSize on input - buffer size, on output - read size in bytes + * \return false if error + */ + bool Read(void *buff, size_t &buffSize); + /** + * Write file + * \param buff write buffer + * \param buffSize buffer size + * \return false if error + */ + bool Write(const void *buff, const size_t buffSize); + /** + * Get file size + * \return file size or -1 if error + */ + int64_t GetFileSize(); + /** + * Close file + */ + void Close(); + /** + * Get last errno + * \return last errno + */ + DWORD LastError() const; + /** + * Save file + * \param fileName file name + * \param fileContent file content + * \return error code + */ + static DWORD SaveFile(const wchar_t *fileName, const std::vector& fileContent); + /** + * Save file + * \param fileName file name + * \param fileContent file content + * \return error code + */ + static DWORD SaveFile(const wchar_t *fileName, const char *fileContent); + /** + * Load file + * \param fileName file name + * \param fileContent file content + * \return error code + */ + static DWORD LoadFile(const wchar_t *fileName, std::vector& fileContent); + +private: + HANDLE m_File; ///< File handle + DWORD m_LastError; ///< Last errno +}; + diff --git a/netbox/src/NetBox/NetBox.cpp b/netbox/src/NetBox/NetBox.cpp new file mode 100644 index 000000000..7d79766fe --- /dev/null +++ b/netbox/src/NetBox/NetBox.cpp @@ -0,0 +1,249 @@ +#ifndef __linux__ +#include "afxdll.h" +#endif +#include + +#include +#include +#include + +#include "FarUtil.h" +#include "resource.h" + +extern TCustomFarPlugin * CreateFarPlugin(HINSTANCE HInst); + +class TFarPluginGuard : public TFarPluginEnvGuard, public TGuard +{ +public: + inline TFarPluginGuard() : + TGuard(FarPlugin->GetCriticalSection()) + { + } +}; + +extern "C" +{ + +int WINAPI GetMinFarVersionW() +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->GetMinFarVersion()); +} + +void WINAPI SetStartupInfoW(const struct PluginStartupInfo * psi) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->SetStartupInfo(psi); +} + +void WINAPI ExitFARW() +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->ExitFAR(); +} + +void WINAPI GetPluginInfoW(PluginInfo * pi) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->GetPluginInfo(pi); +} + +int WINAPI ConfigureW(int item) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->Configure(static_cast(item))); +} + +HANDLE WINAPI OpenPluginW(int openFrom, intptr_t item) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return FarPlugin->OpenPlugin(openFrom, item); +} + +void WINAPI ClosePluginW(HANDLE Plugin) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->ClosePlugin(Plugin); +} + +void WINAPI GetOpenPluginInfoW(HANDLE Plugin, OpenPluginInfo * pluginInfo) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->GetOpenPluginInfo(Plugin, pluginInfo); +} + +int WINAPI GetFindDataW(HANDLE Plugin, PluginPanelItem ** PanelItem, int * itemsNumber, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->GetFindData(Plugin, PanelItem, itemsNumber, OpMode)); +} + +void WINAPI FreeFindDataW(HANDLE Plugin, PluginPanelItem * PanelItem, int itemsNumber) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + FarPlugin->FreeFindData(Plugin, PanelItem, itemsNumber); +} + +int WINAPI ProcessHostFileW(HANDLE Plugin, + struct PluginPanelItem * PanelItem, int ItemsNumber, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->ProcessHostFile(Plugin, PanelItem, ItemsNumber, OpMode)); +} + +int WINAPI ProcessKeyW(HANDLE Plugin, int key, unsigned int controlState) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->ProcessKey(Plugin, key, controlState)); +} + +int WINAPI ProcessEventW(HANDLE Plugin, int Event, void * Param) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->ProcessEvent(Plugin, Event, Param)); +} + +int WINAPI SetDirectoryW(HANDLE Plugin, const wchar_t * Dir, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + int Result = static_cast(FarPlugin->SetDirectory(Plugin, Dir, OpMode)); + return Result; +} + +int WINAPI MakeDirectoryW(HANDLE Plugin, const wchar_t ** Name, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + int Result = static_cast(FarPlugin->MakeDirectory(Plugin, Name, OpMode)); + return Result; +} + +int WINAPI DeleteFilesW(HANDLE Plugin, PluginPanelItem * PanelItem, int itemsNumber, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->DeleteFiles(Plugin, PanelItem, itemsNumber, OpMode)); +} + +int WINAPI GetFilesW(HANDLE Plugin, PluginPanelItem * PanelItem, int itemsNumber, + int Move, const wchar_t ** destPath, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->GetFiles(Plugin, PanelItem, itemsNumber, + Move, destPath, OpMode)); +} + +int WINAPI PutFilesW(HANDLE Plugin, PluginPanelItem * PanelItem, int itemsNumber, int Move, const wchar_t * SrcPath, int OpMode) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + int Result = static_cast(FarPlugin->PutFiles(Plugin, PanelItem, itemsNumber, + Move, SrcPath, OpMode)); + return Result; +} + +int WINAPI ProcessEditorEventW(int Event, void * Param) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->ProcessEditorEvent(Event, Param)); +} + +int WINAPI ProcessEditorInputW(const INPUT_RECORD * Rec) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + return static_cast(FarPlugin->ProcessEditorInput(Rec)); +} + +HANDLE WINAPI OpenFilePluginW(const wchar_t * fileName, const uint8_t * fileHeader, int fileHeaderSize, int /*OpMode*/) +{ + DebugAssert(FarPlugin); + TFarPluginGuard Guard; + if (!fileName) + { + return INVALID_HANDLE_VALUE; + } + + const size_t fileNameLen = wcslen(fileName); +#ifndef __linux__ + if (fileNameLen < 8 || _wcsicmp(fileName + fileNameLen - 7, L".netbox") != 0) +#else + if (fileNameLen < 8 || wcscmp(fileName + fileNameLen - 7, L".netbox") != 0) +#endif + { + return INVALID_HANDLE_VALUE; + } + if (fileHeaderSize > 4 && strncmp(reinterpret_cast(fileHeader), "(FarPlugin->OpenPlugin(OPEN_ANALYSE, + reinterpret_cast(fileName))); + return Handle; +} + +#ifndef __linux__ + static int Processes = 0; + +BOOL DllProcessAttach(HINSTANCE HInstance) +{ + FarPlugin = CreateFarPlugin(HInstance); + + DebugAssert(!Processes); + Processes++; + InitExtensionModule(HInstance); + return TRUE; +} + +BOOL DllProcessDetach() +{ + DebugAssert(Processes); + Processes--; + if (!Processes) + { + DebugAssert(FarPlugin); + SAFE_DESTROY(FarPlugin); + TermExtensionModule(); + } + return TRUE; +} + +BOOL WINAPI DllMain(HINSTANCE HInstance, DWORD Reason, LPVOID /*ptr*/ ) +{ + BOOL Result = TRUE; + switch (Reason) + { + case DLL_PROCESS_ATTACH: + Result = DllProcessAttach(HInstance); + break; + + case DLL_PROCESS_DETACH: + Result = DllProcessDetach(); + break; + } + return Result; +} +#else +__attribute__((constructor)) void so_init(void) +{ + FarPlugin = CreateFarPlugin(0); +} +#endif + +} // extern "C" diff --git a/netbox/src/NetBox/NetBox.def b/netbox/src/NetBox/NetBox.def new file mode 100644 index 000000000..55f9f44ca --- /dev/null +++ b/netbox/src/NetBox/NetBox.def @@ -0,0 +1,22 @@ +EXPORTS + ClosePluginW + ConfigureW + DeleteFilesW + ExitFARW + FreeFindDataW + GetFilesW + GetFindDataW + GetMinFarVersionW + GetOpenPluginInfoW + GetPluginInfoW + MakeDirectoryW + OpenFilePluginW + OpenPluginW + ProcessKeyW + ProcessEventW + ProcessHostFileW + ProcessEditorEventW + ProcessEditorInputW + PutFilesW + SetDirectoryW + SetStartupInfoW diff --git a/netbox/src/NetBox/NetBox.rc b/netbox/src/NetBox/NetBox.rc new file mode 100644 index 000000000..3d4f23b50 --- /dev/null +++ b/netbox/src/NetBox/NetBox.rc @@ -0,0 +1,29 @@ +1 VERSIONINFO +FILEVERSION 2,3,0,436 +PRODUCTVERSION 2,3,0,436 +FILEOS 0x4 +FILETYPE 0x2 +{ + BLOCK "StringFileInfo" + { + BLOCK "000004E4" + { + VALUE "CompanyName", "Michael Lukashov\0" + VALUE "FileDescription", "NetBox: SFTP/FTP/FTPS/SCP/WebDAV client for FAR2\0" + VALUE "FileVersion", "2.3.0.436\0" + VALUE "InternalName", "FarNetBox\0" + VALUE "LegalCopyright", "(c) 2011, 2016 Michael Lukashov\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "NetBox.dll\0" + VALUE "ProductName", "NetBox\0" + VALUE "ProductVersion", "2.3.0.436\0" + VALUE "ReleaseType", "stable\0" + VALUE "WWW", "https://github.com/michaellukashov/Far-NetBox\0" + } + } + + BLOCK "VarFileInfo" + { + VALUE "Translation", 0, 0x04E4 + } +} \ No newline at end of file diff --git a/netbox/src/NetBox/NetBox.rc.template b/netbox/src/NetBox/NetBox.rc.template new file mode 100644 index 000000000..60b6511a6 --- /dev/null +++ b/netbox/src/NetBox/NetBox.rc.template @@ -0,0 +1,29 @@ +1 VERSIONINFO +FILEVERSION ${file.ver.major},${file.ver.minor},${file.ver.version_patch},${file.ver.build} +PRODUCTVERSION ${file.ver.major},${file.ver.minor},${file.ver.version_patch},${file.ver.build} +FILEOS 0x4 +FILETYPE 0x2 +{ + BLOCK "StringFileInfo" + { + BLOCK "000004E4" + { + VALUE "CompanyName", "Michael Lukashov\0" + VALUE "FileDescription", "NetBox: SFTP/FTP/FTPS/SCP/WebDAV client for FAR2\0" + VALUE "FileVersion", "${file.ver.major}.${file.ver.minor}.${file.ver.version_patch}.${file.ver.build}\0" + VALUE "InternalName", "FarNetBox\0" + VALUE "LegalCopyright", "(c) 2011, 2016 Michael Lukashov\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "NetBox.dll\0" + VALUE "ProductName", "NetBox\0" + VALUE "ProductVersion", "${file.ver.major}.${file.ver.minor}.${file.ver.version_patch}.${file.ver.build}\0" + VALUE "ReleaseType", "stable\0" + VALUE "WWW", "https://github.com/michaellukashov/Far-NetBox\0" + } + } + + BLOCK "VarFileInfo" + { + VALUE "Translation", 0, 0x04E4 + } +} \ No newline at end of file diff --git a/netbox/src/NetBox/NetBox.sln b/netbox/src/NetBox/NetBox.sln new file mode 100644 index 000000000..c2a85150a --- /dev/null +++ b/netbox/src/NetBox/NetBox.sln @@ -0,0 +1,192 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NetBox", "NetBox.vcxproj", "{61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}" + ProjectSection(ProjectDependencies) = postProject + {EC262402-898F-49D4-8599-850B0C19C1C6} = {EC262402-898F-49D4-8599-850B0C19C1C6} + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} = {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} + {77BA777C-4766-4CA4-AA41-7C089679A948} = {77BA777C-4766-4CA4-AA41-7C089679A948} + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} = {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} = {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\libs\zlib\zlib.vcxproj", "{E9F6071A-D2BC-471E-8899-5F49B024AA04}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libputty", "..\..\libs\Putty\libputty.vcxproj", "{456AD982-730D-46F8-8BE2-0E00D7433741}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtinyxml2", "..\..\libs\tinyxml2\libtinyxml2.vcxproj", "{593A4FB1-1787-4192-BF73-9C43BF8FC142}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libexpat", "..\..\libs\expat\lib\libexpat.vcxproj", "{27250AA6-FED6-F96F-E32B-E8E9719F8FEB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libneon", "..\..\libs\neon\libneon.vcxproj", "{90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}" + ProjectSection(ProjectDependencies) = postProject + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} = {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libapr", "..\..\libs\apr\build\libapr.vcxproj", "{74BFD665-A62B-4745-8A2A-EE1A2A63CD08}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mfc", "..\..\libs\atlmfc\mfc.vcxproj", "{EC262402-898F-49D4-8599-850B0C19C1C6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dlmalloc", "..\..\libs\dlmalloc\dlmalloc.vcxproj", "{5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rdestl", "..\..\libs\rdestl\rdestl.vcxproj", "{77BA777C-4766-4CA4-AA41-7C089679A948}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Intel_SSA|Win32 = Intel_SSA|Win32 + Intel_SSA|x64 = Intel_SSA|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + Template|Win32 = Template|Win32 + Template|x64 = Template|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|Win32.ActiveCfg = Debug|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|Win32.Build.0 = Debug|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|x64.ActiveCfg = Debug|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|x64.Build.0 = Debug|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|Win32.ActiveCfg = Intel_SSA|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|Win32.Build.0 = Intel_SSA|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|x64.ActiveCfg = Intel_SSA|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|x64.Build.0 = Intel_SSA|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|Win32.ActiveCfg = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|Win32.Build.0 = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|x64.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|x64.Build.0 = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|Win32.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|x64.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|Win32.ActiveCfg = Debug|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|Win32.Build.0 = Debug|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|x64.ActiveCfg = Debug|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|x64.Build.0 = Debug|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|Win32.ActiveCfg = Release|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|Win32.Build.0 = Release|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|Win32.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|Win32.ActiveCfg = Debug|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|Win32.Build.0 = Debug|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|x64.ActiveCfg = Debug|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|x64.Build.0 = Debug|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|Win32.ActiveCfg = Release|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|Win32.Build.0 = Release|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|Win32.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|Win32.ActiveCfg = Debug|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|Win32.Build.0 = Debug|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|x64.ActiveCfg = Debug|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|x64.Build.0 = Debug|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|Win32.ActiveCfg = Release|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|Win32.Build.0 = Release|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|Win32.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|Win32.ActiveCfg = Debug|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|Win32.Build.0 = Debug|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|x64.ActiveCfg = Debug|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|x64.Build.0 = Debug|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|x64.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|Win32.ActiveCfg = Release|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|Win32.Build.0 = Release|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|x64.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|Win32.ActiveCfg = Template|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|Win32.Build.0 = Template|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|x64.ActiveCfg = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|Win32.ActiveCfg = Debug|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|Win32.Build.0 = Debug|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|x64.ActiveCfg = Debug|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|x64.Build.0 = Debug|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|x64.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|x64.Build.0 = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|Win32.ActiveCfg = Release|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|Win32.Build.0 = Release|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|x64.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|x64.Build.0 = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|Win32.ActiveCfg = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|Win32.Build.0 = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|x64.ActiveCfg = Template|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|x64.Build.0 = Template|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|Win32.ActiveCfg = Debug|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|Win32.Build.0 = Debug|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|x64.ActiveCfg = Debug|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|x64.Build.0 = Debug|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|x64.Build.0 = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|Win32.ActiveCfg = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|Win32.Build.0 = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|x64.Build.0 = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|Win32.ActiveCfg = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|Win32.Build.0 = Debug|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|x64.ActiveCfg = Debug|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|x64.Build.0 = Debug|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|Win32.ActiveCfg = Release|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|Win32.Build.0 = Release|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|Win32.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|Win32.ActiveCfg = Debug|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|Win32.Build.0 = Debug|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|x64.ActiveCfg = Debug|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|x64.Build.0 = Debug|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|Win32.ActiveCfg = Release|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|Win32.Build.0 = Release|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|Win32.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|Win32.ActiveCfg = Debug|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|Win32.Build.0 = Debug|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|x64.ActiveCfg = Debug|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|x64.Build.0 = Debug|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|Win32.ActiveCfg = Release|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|Win32.Build.0 = Release|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|Win32.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/netbox/src/NetBox/NetBox.vcxproj b/netbox/src/NetBox/NetBox.vcxproj new file mode 100644 index 000000000..3e07ba466 --- /dev/null +++ b/netbox/src/NetBox/NetBox.vcxproj @@ -0,0 +1,344 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1} + Win32Proj + NetBox + + + + DynamicLibrary + false + Unicode + false + + + DynamicLibrary + true + Unicode + false + + + DynamicLibrary + false + false + Unicode + false + + + DynamicLibrary + false + false + Unicode + false + + + + + + + + + + + + + + + + + + + + false + ..\..\Far2_x86\Plugins\NetBox\ + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\ + false + AllRules.ruleset + + + false + ..\..\Far2_x64\Plugins\NetBox\ + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\ + + + false + ..\..\Far2_x86\Plugins\NetBox\ + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\ + false + + + false + ..\..\Far2_x64\Plugins\NetBox\ + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\ + false + + + + Level2 + Disabled + true + _SCL_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_WINDLL;_USRDLL;NETBOX_DEBUG;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\x86\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + false + false + false + true + MultiThreadedDebug + false + + + Windows + true + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\mfc;..\..\libs\openssl\x86;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libputty;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libtinyxml2;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libneon;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libapr;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libexpat;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\dlmalloc;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\rdestl; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmtd.lib;libcpmtd.lib;libtinyxml2.lib;libeay32.lib;ssleay32.lib;libputty.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.lib;rdestl.lib;%(AdditionalDependencies) + uafxcwd.lib;mfcs100ud.lib;msvcrtd.lib;msvcurtd.lib;msvcprtd.lib;libc.lib;libcmt.lib;msvcrt.lib;libcmtd.lib;mfc100ud.lib;Atl.lib;%(IgnoreSpecificDefaultLibraries) + NetBox.def + /verbose:lib /ignore:4099 /ignore:4204 %(AdditionalOptions) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Disabled + true + _SCL_SECURE_NO_WARNINGS;WIN32;WIN64;_DEBUG;_WINDOWS;_WINDLL;_USRDLL;NETBOX_DEBUG;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\x64\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreadedDebug + false + false + + + Windows + true + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\mfc;..\..\libs\openssl\x64;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libputty;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libtinyxml2;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libneon;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libapr;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libexpat;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\dlmalloc;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\rdestl; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmtd.lib;libcpmtd.lib;libtinyxml2.lib;libeay32.lib;ssleay32.lib;libputty.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.lib;rdestl.lib;%(AdditionalDependencies) + uafxcwd.lib;mfc100ud.lib;msvcrtd.lib;msvcurtd.lib;msvcprtd.lib;libc.lib;libcmt.lib;msvcrt.lib;libcmtd.lib;Atl.lib;mfcs100ud.lib;%(IgnoreSpecificDefaultLibraries) + NetBox.def + /verbose:lib /ignore:4099 /ignore:4204 %(AdditionalOptions) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Full + true + true + false + _SCL_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_WINDLL;_USRDLL;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\x86\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreaded + false + Size + OnlyExplicitInline + true + false + NotSet + true + true + false + Cdecl + false + + + Windows + true + true + true + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\mfc;..\..\libs\openssl\x86;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libputty;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libtinyxml2;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libneon;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libapr;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\libexpat;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\dlmalloc;..\..\build\vc10\$(SolutionName)\$(Configuration)\x86\rdestl; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmt.lib;libcpmt.lib;libtinyxml2.lib;libeay32.lib;ssleay32.lib;libputty.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.lib;rdestl.lib;%(AdditionalDependencies) + NetBox.def + uafxcw.lib;mfc100u.lib;Atl.lib;msvcrt.lib;msvcurt.lib;msvcprt.lib;libc.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + + + + + .;..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Full + true + true + false + _SCL_SECURE_NO_WARNINGS;WIN32;WIN64;NDEBUG;_WINDOWS;_WINDLL;_USRDLL;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\x64\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreaded + false + Size + OnlyExplicitInline + true + false + NotSet + true + false + true + Cdecl + false + + + Windows + true + true + true + ..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\mfc;..\..\libs\openssl\x64;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libputty;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libtinyxml2;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libneon;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libapr;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\libexpat;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\dlmalloc;..\..\build\vc10\$(SolutionName)\$(Configuration)\x64\rdestl; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmt.lib;libcpmt.lib;libtinyxml2.lib;libeay32.lib;ssleay32.lib;libputty.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.lib;rdestl.lib;%(AdditionalDependencies) + NetBox.def + uafxcw.lib;mfc100u.lib;Atl.lib;msvcrt.lib;msvcurt.lib;msvcprt.lib;libc.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + UseLinkTimeCodeGeneration + + + + + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Document + true + true + + + true + true + + + true + true + + + + + + + + + + + + + + + + + {5423dd5f-c5f6-40a0-ba13-4b0deb51d38e} + + + {593a4fb1-1787-4192-bf73-9c43bf8fc142} + + + {e9f6071a-d2bc-471e-8899-5f49b024aa04} + + + {456AD982-730D-46F8-8BE2-0E00D7433741} + + + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + + + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} + + + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} + + + + + + + \ No newline at end of file diff --git a/netbox/src/NetBox/NetBox.vs2015.sln b/netbox/src/NetBox/NetBox.vs2015.sln new file mode 100644 index 000000000..42aa8cda2 --- /dev/null +++ b/netbox/src/NetBox/NetBox.vs2015.sln @@ -0,0 +1,194 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NetBox.vs2015", "NetBox.vs2015.vcxproj", "{61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}" + ProjectSection(ProjectDependencies) = postProject + {EC262402-898F-49D4-8599-850B0C19C1C6} = {EC262402-898F-49D4-8599-850B0C19C1C6} + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} = {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} + {77BA777C-4766-4CA4-AA41-7C089679A948} = {77BA777C-4766-4CA4-AA41-7C089679A948} + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} = {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} = {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib.vs2015", "..\..\libs\zlib\zlib.vs2015.vcxproj", "{E9F6071A-D2BC-471E-8899-5F49B024AA04}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libputty.vs2015", "..\..\libs\Putty\libputty.vs2015.vcxproj", "{456AD982-730D-46F8-8BE2-0E00D7433741}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtinyxml2.vs2015", "..\..\libs\tinyxml2\libtinyxml2.vs2015.vcxproj", "{593A4FB1-1787-4192-BF73-9C43BF8FC142}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libexpat.vs2015", "..\..\libs\expat\lib\libexpat.vs2015.vcxproj", "{27250AA6-FED6-F96F-E32B-E8E9719F8FEB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libneon.vs2015", "..\..\libs\neon\libneon.vs2015.vcxproj", "{90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}" + ProjectSection(ProjectDependencies) = postProject + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} = {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libapr.vs2015", "..\..\libs\apr\build\libapr.vs2015.vcxproj", "{74BFD665-A62B-4745-8A2A-EE1A2A63CD08}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mfc.vs2015", "..\..\libs\atlmfc\mfc.vs2015.vcxproj", "{EC262402-898F-49D4-8599-850B0C19C1C6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dlmalloc.vs2015", "..\..\libs\dlmalloc\dlmalloc.vs2015.vcxproj", "{5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rdestl.vs2015", "..\..\libs\rdestl\rdestl.vs2015.vcxproj", "{77BA777C-4766-4CA4-AA41-7C089679A948}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Intel_SSA|Win32 = Intel_SSA|Win32 + Intel_SSA|x64 = Intel_SSA|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + Template|Win32 = Template|Win32 + Template|x64 = Template|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|Win32.ActiveCfg = Debug|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|Win32.Build.0 = Debug|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|x64.ActiveCfg = Debug|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Debug|x64.Build.0 = Debug|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|Win32.ActiveCfg = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|Win32.Build.0 = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|x64.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Intel_SSA|x64.Build.0 = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|Win32.ActiveCfg = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|Win32.Build.0 = Release|Win32 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|x64.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Release|x64.Build.0 = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|Win32.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|x64.ActiveCfg = Release|x64 + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1}.Template|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|Win32.ActiveCfg = Debug|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|Win32.Build.0 = Debug|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|x64.ActiveCfg = Debug|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Debug|x64.Build.0 = Debug|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Intel_SSA|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|Win32.ActiveCfg = Release|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|Win32.Build.0 = Release|Win32 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Release|x64.Build.0 = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|Win32.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|x64.ActiveCfg = Release|x64 + {E9F6071A-D2BC-471E-8899-5F49B024AA04}.Template|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|Win32.ActiveCfg = Debug|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|Win32.Build.0 = Debug|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|x64.ActiveCfg = Debug|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Debug|x64.Build.0 = Debug|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Intel_SSA|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|Win32.ActiveCfg = Release|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|Win32.Build.0 = Release|Win32 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Release|x64.Build.0 = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|Win32.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|x64.ActiveCfg = Release|x64 + {456AD982-730D-46F8-8BE2-0E00D7433741}.Template|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|Win32.ActiveCfg = Debug|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|Win32.Build.0 = Debug|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|x64.ActiveCfg = Debug|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Debug|x64.Build.0 = Debug|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Intel_SSA|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|Win32.ActiveCfg = Release|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|Win32.Build.0 = Release|Win32 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Release|x64.Build.0 = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|Win32.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|x64.ActiveCfg = Release|x64 + {593A4FB1-1787-4192-BF73-9C43BF8FC142}.Template|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|Win32.ActiveCfg = Debug|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|Win32.Build.0 = Debug|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|x64.ActiveCfg = Debug|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Debug|x64.Build.0 = Debug|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|x64.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Intel_SSA|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|Win32.ActiveCfg = Release|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|Win32.Build.0 = Release|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|x64.ActiveCfg = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Release|x64.Build.0 = Release|x64 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|Win32.ActiveCfg = Template|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|Win32.Build.0 = Template|Win32 + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB}.Template|x64.ActiveCfg = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|Win32.ActiveCfg = Debug|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|Win32.Build.0 = Debug|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|x64.ActiveCfg = Debug|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Debug|x64.Build.0 = Debug|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|x64.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Intel_SSA|x64.Build.0 = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|Win32.ActiveCfg = Release|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|Win32.Build.0 = Release|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|x64.ActiveCfg = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Release|x64.Build.0 = Release|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|Win32.ActiveCfg = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|Win32.Build.0 = Template|Win32 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|x64.ActiveCfg = Template|x64 + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A}.Template|x64.Build.0 = Template|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|Win32.ActiveCfg = Debug|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|Win32.Build.0 = Debug|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|x64.ActiveCfg = Debug|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Debug|x64.Build.0 = Debug|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Intel_SSA|x64.Build.0 = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|Win32.ActiveCfg = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|Win32.Build.0 = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Release|x64.Build.0 = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|Win32.ActiveCfg = Release|Win32 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|x64.ActiveCfg = Release|x64 + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08}.Template|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|Win32.Build.0 = Debug|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|x64.ActiveCfg = Debug|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Debug|x64.Build.0 = Debug|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Intel_SSA|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|Win32.ActiveCfg = Release|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|Win32.Build.0 = Release|Win32 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Release|x64.Build.0 = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|Win32.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|x64.ActiveCfg = Release|x64 + {EC262402-898F-49D4-8599-850B0C19C1C6}.Template|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|Win32.ActiveCfg = Debug|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|Win32.Build.0 = Debug|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|x64.ActiveCfg = Debug|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Debug|x64.Build.0 = Debug|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Intel_SSA|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|Win32.ActiveCfg = Release|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|Win32.Build.0 = Release|Win32 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Release|x64.Build.0 = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|Win32.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|x64.ActiveCfg = Release|x64 + {5423DD5F-C5F6-40A0-BA13-4B0DEB51D38E}.Template|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|Win32.ActiveCfg = Debug|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|Win32.Build.0 = Debug|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|x64.ActiveCfg = Debug|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Debug|x64.Build.0 = Debug|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|Win32.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Intel_SSA|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|Win32.ActiveCfg = Release|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|Win32.Build.0 = Release|Win32 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Release|x64.Build.0 = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|Win32.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|x64.ActiveCfg = Release|x64 + {77BA777C-4766-4CA4-AA41-7C089679A948}.Template|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/netbox/src/NetBox/NetBox.vs2015.vcxproj b/netbox/src/NetBox/NetBox.vs2015.vcxproj new file mode 100644 index 000000000..560273529 --- /dev/null +++ b/netbox/src/NetBox/NetBox.vs2015.vcxproj @@ -0,0 +1,353 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {61EA6DDD-19CD-481B-8FA4-D92CEBB516C1} + Win32Proj + NetBox + 10.0.10069.0 + + + + DynamicLibrary + false + Unicode + false + v140 + + + DynamicLibrary + true + Unicode + false + v140 + + + DynamicLibrary + false + false + Unicode + false + v140 + + + DynamicLibrary + false + false + Unicode + false + v140 + + + + + + + + + + + + + + + + + + + + false + ..\..\Far2_x86\Plugins\NetBox\ + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\ + false + AllRules.ruleset + NetBox + + + false + ..\..\Far2_x64\Plugins\NetBox\ + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\ + NetBox + + + false + ..\..\Far2_x86\Plugins\NetBox\ + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\ + false + NetBox + + + false + ..\..\Far2_x64\Plugins\NetBox\ + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\ + false + NetBox + + + + Level2 + Disabled + true + _SCL_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_WINDLL;_USRDLL;NETBOX_DEBUG;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\vs2015-x86\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + false + false + false + true + MultiThreadedDebug + false + + + Console + true + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\mfc.vs2015;..\..\libs\openssl\vs2015-x86;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libputty.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libtinyxml2.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libneon.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libapr.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libexpat.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\dlmalloc.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\rdestl.vs2015; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmtd.lib;libcpmtd.lib;libtinyxml2.vs2015.lib;libeay32.lib;ssleay32.lib;libputty.vs2015.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.vs2015.lib;rdestl.vs2015.lib;%(AdditionalDependencies) + uafxcwd.lib;mfcs100ud.lib;msvcrtd.lib;msvcurtd.lib;msvcprtd.lib;libc.lib;libcmt.lib;msvcrt.lib;libcmtd.lib;mfc100ud.lib;Atl.lib;%(IgnoreSpecificDefaultLibraries) + NetBox.def + /verbose:lib /ignore:4099 /ignore:4204 %(AdditionalOptions) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Disabled + true + _SCL_SECURE_NO_WARNINGS;WIN32;WIN64;_DEBUG;_WINDOWS;_WINDLL;_USRDLL;NETBOX_DEBUG;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\vs2015-x64\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreadedDebug + false + false + + + Console + true + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\mfc.vs2015;..\..\libs\openssl\vs2015-x64;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libputty.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libtinyxml2.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libneon.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libapr.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libexpat.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\dlmalloc.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\rdestl.vs2015; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmtd.lib;libcpmtd.lib;libtinyxml2.vs2015.lib;libeay32.lib;ssleay32.lib;libputty.vs2015.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.vs2015.lib;rdestl.vs2015.lib;%(AdditionalDependencies) + uafxcwd.lib;mfc100ud.lib;msvcrtd.lib;msvcurtd.lib;msvcprtd.lib;libc.lib;libcmt.lib;msvcrt.lib;libcmtd.lib;Atl.lib;mfcs100ud.lib;%(IgnoreSpecificDefaultLibraries) + NetBox.def + /verbose:lib /ignore:4099 /ignore:4204 %(AdditionalOptions) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Full + true + true + false + _SCL_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_WINDLL;_USRDLL;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\vs2015-x86\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreaded + false + Size + OnlyExplicitInline + true + false + NotSet + true + true + false + Cdecl + false + + + Console + true + true + true + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\mfc.vs2015;..\..\libs\openssl\vs2015-x86;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libputty.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libtinyxml2.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libneon.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libapr.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\libexpat.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\dlmalloc.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x86\rdestl.vs2015; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmt.lib;libcpmt.lib;libtinyxml2.vs2015.lib;libeay32.lib;ssleay32.lib;libputty.vs2015.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.vs2015.lib;rdestl.vs2015.lib;%(AdditionalDependencies) + NetBox.def + uafxcw.lib;mfc100u.lib;Atl.lib;msvcrt.lib;msvcurt.lib;msvcprt.lib;libc.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + + + + + + + .;..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + Level4 + Full + true + true + false + _SCL_SECURE_NO_WARNINGS;WIN32;WIN64;NDEBUG;_WINDOWS;_WINDLL;_USRDLL;MPEXT;NO_STRICT;NE_LFS;NE_HAVE_SSL;HAVE_OPENSSL;HAVE_EXPAT;HAVE_EXPAT_H;NE_HAVE_DAV;NE_HAVE_ZLIB;APR_DECLARE_STATIC;XML_STATIC;USE_DLMALLOC;USE_DL_PREFIX;_ATL_NO_COMMODULE;_ATL_NO_PERF_SUPPORT;_ATL_NO_DATETIME_RESOURCES_;%(PreprocessorDefinitions) + .;..\..\libs\atlmfc\include;..\core;..\base;..\resource;..\windows;..\..\libs\Putty;..\..\libs\Putty\windows;..\..\libs\Putty\charset;..\PluginSDK\Far2;..\..\libs;..\..\libs\openssl\vs2015-x64\inc32;..\filezilla;..\filezilla\misc;..\..\libs\tinyxml2;..\..\libs\neon\src;..\..\libs\expat\lib;..\..\libs\apr\include;..\..\libs\apr\include\arch;..\..\libs\apr\include\arch\win32;..\..\libs\apr\include\arch\unix;..\..\libs\dlmalloc;..\..\libs\rdestl; + MultiThreaded + false + Size + OnlyExplicitInline + true + false + NotSet + true + false + true + Cdecl + false + + + Console + true + true + true + ..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\mfc.vs2015;..\..\libs\openssl\vs2015-x64;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libputty.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libtinyxml2.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libneon.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libapr.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\libexpat.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\dlmalloc.vs2015;..\..\build\vc15\$(SolutionName)\$(Configuration)\x64\rdestl.vs2015; + mfc.lib;shell32.lib;shlwapi.lib;gdi32.lib;libcmt.lib;libcpmt.lib;libtinyxml2.vs2015.lib;libeay32.lib;ssleay32.lib;libputty.vs2015.lib;libexpat.lib;libneon.lib;libapr.lib;Ws2_32.lib;kernel32.lib;user32.lib;comdlg32.lib;advapi32.lib;Version.lib;winhttp.lib;winspool.lib;Crypt32.lib;dlmalloc.vs2015.lib;rdestl.vs2015.lib;%(AdditionalDependencies) + NetBox.def + uafxcw.lib;mfc100u.lib;Atl.lib;msvcrt.lib;msvcurt.lib;msvcprt.lib;libc.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries) + gdi32.dll;winhttp.dll;shell32.dll;shlwapi.dll;crypt32.dll;version.dll;ws2_32.dll;oleaut32.dll; + UseLinkTimeCodeGeneration + + + + + + + .\..\..\libs\atlmfc\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Document + true + true + + + true + true + + + true + true + + + + + + + + + + + + + + + + + {5423dd5f-c5f6-40a0-ba13-4b0deb51d38e} + + + {593a4fb1-1787-4192-bf73-9c43bf8fc142} + + + {e9f6071a-d2bc-471e-8899-5f49b024aa04} + + + {456AD982-730D-46F8-8BE2-0E00D7433741} + + + {27250AA6-FED6-F96F-E32B-E8E9719F8FEB} + + + {90DE9ADC-75C4-2AC0-72B1-5F2903110E0A} + + + {74BFD665-A62B-4745-8A2A-EE1A2A63CD08} + + + + + + + \ No newline at end of file diff --git a/netbox/src/NetBox/NetBoxEng.lng b/netbox/src/NetBox/NetBoxEng.lng new file mode 100644 index 000000000..524eebd35 --- /dev/null +++ b/netbox/src/NetBox/NetBoxEng.lng @@ -0,0 +1,1270 @@ +.Language=English,English + +"NetBox" +"NetBox" + +"Session name" +"NetBox: Stored sessions" + +"Connection terminated." +"Initialization..." +"Searching host..." +"Connecting to host..." +"Authenticating..." +"Authenticated." +"Starting the session..." +"Reading remote directory..." +"Session started." + +"Confirmation" +"Information" +"Error" +"Warning" + +"&Yes" +"&No" +"Ok" +"Cancel" +"A&bort" +"&Retry" +"&Ignore" +"A&ll" +"N&o to All" +"Yes to A&ll" +"&Help" +"&Skip" +"&Previous" +"&Next" +"A&ppend" + +"Close" +"Never ask me a&gain" +"Never show this message a&gain" +"%s (%d s)" + +"Group" +"Rights" +"Rights" +"Link target" + +"NewSes" +"Press Shift-F4 to add new stored session" +"Export" +"Duplicate" +"Rename" +"OpenFld" +"Duplicate" +"MoveTo" +"Rename" + +"%s\n \nWarning: Aborting this operation will close connection!" +"Can't create folder '%s'." +"Directory '%s' doesn't exist. Create?" +"**Cancel current operation?**" +"'%s' is invalid mask" +"View from find dialog is not supported" +"You are about to close WinSCP plugin, while having some queued background transfers. Do you want to show queue list?\n \nWarning: Pressing 'Cancel' will terminate all transfers immediately." +"MIT Kerberos 5 GSSAPI not found. You need to install it before using this feature." +"Error watching for changes." +"Error watching for changes in directory '%s'." +"Current %s session does not support command you request. Separate shell session may be opened to process the command. Do you want to open separate shell session?\n \nNote: The server must provide Unix-like shell and the shell must use same path syntax as current %s session." +"There are no files selected." +"Cannot create temporary directory '%s'." +"You are about to store session with filled password.\n \nSaved passwords are stored in a manner that they can easily be recovered. It is not possible to securely encrypt passwords in a way that still allows for automatic use. Do not use the save password feature if you are not absolutely sure of the physical and electronic security of the system on which you are storing passwords.\n \nIf you do not save password along with other session options, you will be prompted for it once you attempt to open the stored session.\n \nSave the password anyway?" +"Plugin requires FAR %s or later." +"You have overridden pre-selected synchronization direction. By default the direction is determined from file panel that was active before you run the synchronize function.\n \nDo you want to make direction you have selected the default?" +"Do you want to perform full synchronization of the remote directory first?\n \nFunction 'Keep remote directory up to date' works correctly only, if the remote directory is synchronized with the local one before it starts.\n" +"Synchronized browsing enabled." +"Synchronized browsing disabled." +"Synchronized browsing is supported only against regular panel." +"Cannot open corresponding directory in the opposite panel. Directory browsing synchronization failed. It has been turned off." +"Cannot open corresponding directory in the opposite panel.\n \nDo you want to try to create directory '%s'?" +"Error deleting file '%s'." +"More than %d directories and subdirectories found. Watching for changes in large number of directories can significantly degrade performance of the computer.\n \nDo you want to scan for another up to %d directories?" +"Operation not completed" +"Passive mode must be enabled when FTP connection through proxy is selected." +"%s\nalready loaded. How to open this file?" + +" Transfer settings " +"Transfer settin&gs..." + +"Make folder" +"New &folder name:" +" Attributes " +"Set pe&rmissions" +"Use &same settings next time" + +"Copy" +"&Copy \"%s\" to:" +"&Copy %d items to:" +"Move" +"Mo&ve \"%s\" to:" +"Mo&ve %d items to:" + +"Reading directory" +"Changing directory" + +"Do you wish to delete the file %s" +"Do you wish to delete %d items" +"Do you wish to move to the Recycle Bin the file %s" +"Do you wish to move to the Recycle Bin %d items" + +"Copying" +"Moving" +"Deleting" +"Setting properties" +"Calculating directory size" +"Moving" +"Copying" +"Getting properties" +"Calculating file checksum" + +"File: " +"Target: " +"Start time:" +"Elapsed:" +"Transferred:" +"CPS:" +"Left:" + +"Cancel file transfer?\n \nOperation can't be canceled in the middle of file transfer.\nPress 'Yes' to cancel file transfer and to close connection.\nPress 'No' to finish current file transfer.\nPress 'Cancel' to continue operation." +"Cancel current operation?" + +"Not supported" +"Current protocol %s does not support required operation." +"Cannot initialize session '%s'." + +"Session named '%s' already exists." +"Save session as" +"Save session as:" +"Do you wish to delete selected session(s)" +"Export" +"Export session \"%s\" to:" +"Export %d session(s) to:" +"Import session(s) from selected file(s)?" +"File \"%s\" does not contain any session data." +"Duplicate session" +"New session name:" +"Rename session" +"New session name:" + +"&Interface settings" +"&Confirmations" +"&Transfer settings" +"&Background transfers" +"En&durance settings" +"&Editor/viewer settings" +"&Logging settings" +"&About" +"I&ntegration settings" +"&Panel settings" + +"Add to &Disks menu" +"Add to &Plugins menu" +"Add &commands to Plugins menu (Alt-Shift-W)" +"Show &session name in window title" +"&Command line prefixes (separated by comma):" +" Custom \"Detailed\" panel mode " +"&Use custom settings for \"Detailed\" panel mode" +"Column &types" +"Column &widths" +"Status &line column types" +"Status l&ine column widths" +"&Full screen mode" +"Use standard Far column types, plus G (group)," +"R (rights), RO (octal format) and L (symlink target)" +"Auto&matically refresh directory after operation" + +"Version %s build %d" +"Based on %s version %s" +"&Homepage" +"&Support forum" +"http://winscp.net/" + +"Edit session" +"Add session" +"Connect session" +"Connect" +"Session" +"Environment" +"Directories" +"SCP/Shell" +"SFTP" +"FTP(S)" +"FTPS" +"SSH" +"Connection" +"Tunnel" +"Proxy" +"Bugs" +"Authentication" +"KEX|Key exchange" +"WebDAV" +" Session " +"&Host name:" +"Po&rt number:" +"&Login type:" +"Anonymous" +"Normal" +"&User name:" +"&Password:" +"Private &key file:" +" Protocol " +"&File protocol: " +"SCP" +"SFTP" +"FTP" +"WebDAV" +"Allow SCP &fallback" +"Insecure" +"Use buttons on top or Ctrl+PgUp and Ctrl+PgDn keys to " +"display additional session options." +" Directories " +"Re&member last used directory" +" Directory reading options " +"Cache &visited remote directories" +"Cache &directory changes" +"&Permanent cache" +"Resolve &symbolic links" +"&Remote directory:" +" Server environment " +"&End-of-line characters (if not known): " +" Daylight saving time " +"Adjust remote timestamp to local co&nventions (Unix)" +"Adjust remote timestamp with &DST (Windows)" +"Preser&ve remote timestamp (Unix)" +" SSH protocol options " +"Enable &compression" +" Preferred SSH protocol version " +"1 on&ly" +"&1" +"&2" +"2 o&nly" +" Encryption options " +"&Encryption cipher selection policy:" +"&Up " +"&Down" +"Enable legacy use of &single-DES in SSH-2" +"-- warn below here --" +"3DES" +"Blowfish" +"AES (SSH-2 only)" +"DES" +"Arcfour (SSH-2 only)" +"ChaCha20" +" Shell " +"S&hell: " +"Default" +"&Return code variable: " +"Autodetect" +" Directory listing " +"Ignore LS &warnings" +"Listing &command: " +"Try to get &full timestamp" +" Other options " +"Lookup &user groups" +"Clear &national variables" +"Clear a&liases " +"Use scp&2 with scp1 compat." +"Server time&zone offset: " +"hours" +"minutes" +" Timeouts " +"Server &response timeout:" +"seconds (0 - no timeout)" +" Keepalives " +"&Off" +"Sending of &null SSH packets" +"Executing &dummy protocol commands" +"Seconds &between keepalives:" +" Internet protocol version " +"A&uto" +"IPv&4" +"IPv&6" +"&Charset encoding for filenames:" +" Proxy " +"Proxy &type: " +"None" +"SOCKS4" +"SOCKS5" +"HTTP" +"Telnet" +"Local" +"System settings" + +"SITE %host" +"USER %proxyuser, USER %user@%host" +"OPEN %host" +"USER %proxyuser, USER %user" +"USER %user@%host" +"USER %proxyuser@%host" +"USER %user@%host %proxyuser" +"USER %user@%proxyuser@%host" + +"Pro&xy host name:" +"Po&rt number:" +"&User name:" +"&Password:" +" Proxy settings " +"Telnet co&mmand: " +"Local proxy co&mmand:" +"Co&nsider proxying local host connections" +"Do &DNS name lookup at proxy end:" +"No" +"Auto" +"Yes" +" Detection of known bugs in SSH servers " +"Chokes on SSH-1 &ignore messages: " +"Refuses all SSH-1 pass&word camouflage: " +"Chokes on SSH-1 &RSA authentication: " +"Miscomputes SSH-2 H&MAC keys: " +"Miscomputes SSH-2 &encryption keys: " +"Requires &padding on SSH-2 RSA signatures:" +"Misuses the sessio&n ID in SSH-2 PK auth: " +"Handles SSH-2 &key re-exchange badly: " +"Auto" +"Off" +"On" +"&Bypass authentication entirely (SSH-2)" +" Authentication options " +"Attempt authentication using &Pageant" +"Attempt &TIS or CryptoCard authentication (SSH-1)" +"Attempt 'keyboard-&interactive' authentication (SSH-2)" +"Respond with pass&word to the first prompt" +"Allow agent &forwarding" +" GSSAPI " +"Attempt &GSSAPI authentication (SSH-2)" +"Allow GSSAPI &credential delegation" +"Attempt &GSSAPI/SSPI authentication (SSH-2)" +"Ser&vice principal name (GSSAPI/SSPI):" +" Authentication parameters " +" Recycle bin " +"&Preserve deleted remote files to recycle bin" +"Preserve &overwritten remote files to bin (SFTP only)" +"&Remote recycle bin:" +" Protocol options " +"SFTP ser&ver: " +"Default" +"&Preferred SFTP protocol version: " +"&UTF-8 encoding for filenames: " +" Detection of known bugs in SFTP servers " +"&Reverses order of symlink command arguments: " +"&Misinterprets file timestamps prior to 1970: " +"M&in packet size:" +"M&ax packet size:" +" Key exchange algorithm options " +"Algorithm selection &policy:" +" Options controlling key re-exchange " +"Max &minutes before rekey (0 for no limit):" +"Ma&x data before rekey (0 for no limit): " +"-- warn below here --" +"Diffie-Hellman group 1" +"Diffie-Hellman group 14" +"Diffie-Hellman group exchange" +"RSA" +"ECDH" + +" Tunnel " +"&Connect through SSH tunnel" +" Host to setup tunnel on " +" Tunnel options " +"&Local tunnel port:" +"Autoselect" +"Uni&x" +"&Windows" +" Connection " +"&Passive mode" +"Optimize connection &buffer size" + +"&Allow empty password" +"Use &MLSD command for directory listing" +" Protocol options " +"Du&p FF in commands" +"&UnDup FF from PWD" +"&Session ID re-use" +"&Encryption:" +"No encryption" +"TLS/SSL Implicit encryption" +"TLS/SSL Explicit encryption" +"Post login &commands:" + +" WebDAV protocol options " + +"Attributes" +"Change file attributes for" +"selected objects" +"O&wner " +"Grou&p " +"Ot&hers" +"R" +"W" +"X" +" Permissions " +"Oct&al:" +"Add &X to directories" +"&Owner:" +"&Group:" +"Set attributes &recursively" +"&None" +"&Default" +"A&ll" +"Set UID " +"Set GID " +"Sticky bit" +"Link to:" + +"&Text (plain text, html, ...)" +"&Binary (archives, doc, ...)" +"Automat&ic" +"Transfer following files in t&ext mode" +"Transfer mode" +"Filename modification" +"&No change " +"&Upper case" +"L&ower case" +"&First upper " +"Lower case &8.3" +"&Replace \\:*\"?" +"&Set permissions" +"Preserve timesta&mp" +"Preserve rea&d-only" +"Use &same settings next time" +"Transfer on background (add to &queue)" +"No &confirmations" +"&New and updated file(s) only" +"Clear 'Archi&ve' attribute" +" Other " +"File mas&k (example: *|.svn/):" +"Exclude" +"Include" +"Upload options" +"Download options" +"Common options" +"&Ignore permission errors" +"&Calculate total size" +"Sp&eed (KB/s):" + +"Edit link" +"Add link" +"&Link file:" +"&Point link to:" +"Sy&mbolic link" + +"Move" +"Move file '%s' to remote directory:" +"Move %d files to remote directory:" + +"Duplicate" +"Duplicate file '%s' to remote directory:" +"Duplicate %d files to remote directory:" + +"Rename" +"Rename file '%s' to:" + +"&Hide typing" +"Note: This prompt is issued by the server. It is part of +"either keyboard-interactive, TIS or Cryptocard authentication." +"&Full prompt" +"&Change stored password to this one" + +"&Enable logging" +" Logging options " +"Logging &level:" +"Normal" +"Debug 1" +"Debug 2" +"Log to &file:" +"&Append" +"&Overwrite" +" In log viewer display (and keep in memory) " +"&Complete session" +"Only &last" +"lines" +"&&Y year; &&M month; &&D day; &&T time; &&H hostname" +"&&S session name; Example: C:\\&&S&&T.log" + +"&Overwriting of files (? = use FAR settings)" +"Continue on &error (advanced users)" +"&Transfer resuming" +"Opening separate &shell session" +"Synchronized &browsing" + +" Enable transfer resume/transfer to temporary filename for " +"A&ll files (not recommended)" +"Files abo&ve" +"KB" +"Di&sable" +" Automatic reconnect " +"&Reconnect after" +"seconds" +"Ma&ximum number of retries: " +"(0-99)" + +" When downloading to editor/viewer " +"Use default &download options" +"Display download &options dialog" + +" When uploading from editor " +"Use sa&me options as for download" +"Display &upload options dialog" +"Upload after every &save" +"&Allow more than one edited file" + +"Maximal &number of transfers" +"&Transfer on background by default" +"&Automatically popup prompts when idle" +"&Beep when queue empties or transfer requires attention" +"Reuse &password of the main session" + +"NetBox commands" +"Display session log &file" +"View/change file a&ttributes Ctrl-A" +"Create/edit &link Alt-F6" +"&Configure" +"Server and protocol &information" +"Open in &Putty" +"PuTTY&gen: key generator" +"Pagea&nt: authentication agent" +"&Open directory Alt-Shift-F12" +"Add &bookmark" +"&Home directory" +"Apply co&mmand Ctrl-G" +"Clea&r caches" +"&Synchronize" +"&Queue Alt-Shift-Q" +"&Keep Remote Directory Up To Date" +"Synchronize bro&wsing Ctrl-Alt-B" +"&Edit history" + +"Server and protocol information" +" Server information " +"SSH implementation:" +"Encryption algorithm:" +"Compression:" +"File transfer protocol:" +"Server host key fingerprint:" +" Protocol capabilities/information " +"Can change permissions:" +"Can change owner/group:" +"Can execute arbitrary command:" +"Can create symlink/hardlink:" +"Can lookup user groups:" +"Can duplicate remote files:" +"Can check available space:" +"Native text (ASCII) mode transfers:" +" Additional protocol information " +" Space available " +"Pa&th" +"C&heck space" +"Unknown" +"Total bytes on device:" +"Free bytes on device:" +"Total bytes for user:" +"Free bytes for user:" +"Bytes per allocation unit:" +"&Protocol" +"Capa&bilities" +"Space &available" +"&Copy to Clipboard" +"Remote system" +"Session protocol" +"Protocol commands only" +"Can calculate file checksum" + +"&Putty path" +"&Remember session password and pass it to PuTTY" +"Page&ant path" +"PuTTY&gen path" +"Open &Telnet sessions in PuTTY for FTP sessions" + +"Open directory" +"Manage bookmarks" +"&Remove" +"&Up " +"&Down " +"Del - remove, Ctrl-Enter - command line, Ctrl-Ins - clipboard" + +"Apply command" +"Enter &command to process selected files" +"&Apply to directories " +"&Execute recursively" +"Patterns: !! - exclamation mark; ! - file name" +"!&& - list of selected files (quoted, space-delimited)" +"!/ - current remote path; !@ - current hostname" +"!U - current username; !P - current password (if known)" +"!?prompt[\]?default! - prompts user for parameter value" +"Local command patterns: !^! - file name from local panel" +"Apply command parameter" +"Apply command parameter value:" +"&Remote command " +"&Local command" +"&Show results in console" +"Copy results to clip&board" +"To use entered command only one file must be selected in one panel to execute the command with the file for each selected file in an opposite panel. Alternatively same number of files can be selected in both panels to execute the command for file pairs." +"To use entered custom command only one file must be selected in local panel." +"Some of the selected remote files were not downloaded. The entered command must be executed for pairs of file, what is thus not possible." +"Local command can be executed only against regular panel." + +"Synchronization is supported only against regular panel." +"No differences found." +"Synchronize" +"Lo&cal directory:" +"Remo&te directory:" +" Direction/Target directory " +"&Local" +"&Remote " +"&Both " +" Mode " +" Synchronize options " +" Compare criteria " +"Synchronize &files" +"&Mirror files" +"Synchronize ×tamps" +"&Delete files " +"&No confirmations" +"&Existing files only " +"Use same &options next time" +"Pre&view changes " +"Synchronize on s&tart" +"&Modification time " +"File si&ze" +"Selected files o&nly" +"Same si&ze only" + +"Keep remote directory up to date" +"Keeping remote directory up to date ..." +"&Watch for changes in the local directory ..." +"... &and automatically reflect them on the remote directory" +"Update s&ubdirectories " +" Upload options " +"&Start" +"&Stop" + +"Synchronizing" +"Comparing" +"Local: " +"Remote: " +"Start time: " +"Time elapsed: " + +"Transfer settings" + +"Queue" +"Oper. Source/Destination/File Trans/Progr" +" &Show " +"&Execute" +"&Cancel" +"&Up" +"Do&wn" +"Close" +"&Suspend" +"&Resume " +"C" +"M" +"" +"" +"Connecting..." +"Query" +"Error" +"Prompt" +"Waiting..." +"There are still some queued background transfers. Do you want to wait for transfers to complete?\n \nWarning: Pressing 'Cancel' will terminate all transfers immediately." +"Calculating..." +"Suspended" + +"Authentication Banner - %s" +"&Never show this banner again" +"Continue" + +"Synchronization checklist" +"Name|Local dir.|Size|Changed||Remote dir.|Size|Changed" +"*|*| | |X|X" +" %d of %d " +"Check &all" +"Uncheck a&ll" +"&maximize" +"&restore" + +"&Current" +"&New instance" +"&Read-only" + +"Creating folder" + +"Edit history" + +"Imported %d sessions" + +"NetBox" + +"OK" +"Cancel" + +" Session " +" Proxy " + +//Edit link dialog +"Create session link" +"Edit session link" +"Session name:" +"&URL:" +"Code page:" +" Authentication " +"User name:" +"Password (user/private key):" +"Prompt for password upon connection" +"Show password" +"Public and private key file:" +"URL can not be empty" +"Invalid URL" +"The session name can not be empty" +"The session name contains restricted symbols (<>:\"/\\|?*)" + +//Configure menu +"NetBox - settings" +"Main settings" +"Proxy settings" +"Logging settings" +"About" + +//Main configure dialog +"Add to disk menu" +"Add to panels plugin menu" +"Use own encryption key" +"Prefix:" +"Handle prefixes ftp, http, ..." +"Default timeout (sec):" +"Saved sessions directory:" + +//Proxy configure dialog +"NetBox - Proxy settings" +"Proxy type:" +"None" +"SOCKS4" +"SOCKS5" +"HTTP" + +"Proxy host name:" +"Port number:" +"User name:" +"Password:" + +//Logging configure dialog +"NetBox - Logging settings" +"Enable logging" +" Logging options " +"Logging level:" +"Debug 1" +"Debug 2" +"Log to file:" +"C:\NetBox.log" + +//About menu +"NetBox - About" +"FTP/FTPS/SFTP/WebDAV client for Far 2.0" +"Version %s" +"Close" + +//Prompt to crypto key +"Enter password for read saved sessions:" + +//Create directory dialog +"Create directory" +"Directory name:" + +//Delete items dialog +"Delete" +"Do you wish to delete" +"the session link?" +"the folder?" +"the file?" +"selected items?" +"Delete" + +//Copy dialog +"Copy" +"Copy %s to:" +"selected items" +"Copy" + +//Move dialog +"Move" +"Move %s to:" +"selected items" +"Move" + +//Progress titles +"Establish connection with %s..." +"Changing directory to \"%s\"..." +"Get directory listing for %s..." +"Receiving file" +"Sending file" +"to" +"Delete..." + +//Error messages +"Unable to load private key file\n%s" +"Unable to establish connection with host\n%s" +"Unable to create directory\n%s" +"Unable to change directory to\n%s" +"Unable to get directory listing\n%s" +"Unable to copy file from\n%s\nto\n%s" +"Unable to rename/move\n%s\nto\n%s" +"Unable to delete file\n%s" +"Unable to delete directory\n%s" + +"Operation canceled by user" + +"Press Shift-F4 to create new session item" + +"Certificate verification error: %s\nContinue anyway?" + +// TextCore1.rc +"CORE_ERROR" +"Host key wasn't verified!" +"Connection failed." +"Terminated by user." +"Lost connection." +"Can't detect command return code." +"Command '%s'\nfailed with return code %d and error message\n%s." +"Command failed with return code %d." +"Command '%s' failed with invalid output '%s'." +"Error getting name of current remote directory." +"Error skipping startup message. Your shell is probably incompatible with the application (BASH is recommended)." +"Error changing directory to '%s'." +"Error listing directory '%s'." +"Unexpected directory listing line '%s'." +"Invalid rights description '%s'" +"Error cleaning up general configuration." +"Error cleaning up sites." +"Error cleaning up random seed file." +"Error cleaning up cached host keys." +"Error detecting variable containing return code of last command." +"Error looking up user groups." +"File or folder '%s' does not exist." +"Can't get attributes of file '%s'." +"Can't open file '%s'." +"Error reading file '%s'." +"Copying file '%s' fatally failed." +"Copying files to remote side failed." +"Copying files from remote side failed." +"SCP protocol error: Unexpected newline" +"SCP protocol error: Illegal time format" +"SCP protocol error: Invalid control record (%c; %s)" +"Copying file '%s' failed." +"SCP protocol error: Illegal file descriptor format" +"'%s' is not folder!" +"Error creating folder '%s'." +"Can't create file '%s'." +"Error writing to file '%s'" +"Can't set attributes of file '%s'." +"Received error message from remote side: '%s'" +"Error deleting file '%s'." +"Error occurred during logging. It's been turned off." +"Can't open log file '%s'." +"Error renaming file '%s' to '%s'." +"File with name '%s' already exists." +"Directory with name '%s' already exists." +"Error changing directory to home directory." +"Error clearing all aliases." +"Error clearing national user variables." +"Unexpected input from server: %s" +"Error cleaning up INI file." +"Authentication log (see session log for details):\n%s\n" +"Authentication failed." +"Connection has been unexpectedly closed." +"Error saving key to file '%s'." +"Server sent command exit status %d." +"SFTP protocol violation: Invalid response message type (%d)." +"Version of SFTP server (%d) is not supported. Supported versions are %d to %d." +"SFTP protocol violation: Invalid message number %d (expected %d)." +"Unexpected OK response." +"Unexpected EOF response." +"No such file or directory." +"Permission denied." +"General failure (server should provide error description)." +"Bad message (badly formatted packet or protocol incompatibility)." +"No connection." +"Connection lost." +"The server does not support the operation." +"%s\nError code: %d\nError message from server%s: %s" +"Unknown status code." +"Error reading symlink '%s'." +"Server returned empty listing for directory '%s'." +"Received SSH_FXP_NAME packet with zero or multiple records." +"Cannot get real path for '%s'." +"Cannot change properties of file '%s'." +"Cannot initialize SFTP protocol. Is the host running a SFTP server?" +"Cannot read timezone information" +"Cannot create remote file '%s'." +"Cannot open remote file '%s'." +"Cannot close remote file '%s'." +"'%s' is not file!" +"Transfer was successfully finished, but temporary transfer file '%s' could not be renamed to target file name '%s'. If the problem persists, you may try to turn off transfer resume support." +"Cannot create link '%s'." +"Invalid command '%s'." +"None" +"'%s' is not valid permission in octal format." +"Server requires unsupported end-of-line sequence (%s)." +"Unknown file type (%d)" +"Invalid handle." +"The file path does not exist or is invalid." +"File already exists." +"The file is on read-only media, or the media is write protected." +"There is no media available in the drive." +"Error decoding UTF-8 string." +"Error executing custom command '%s' on file '%s'." +"Cannot load locale %d." +"Received incomplete data packet before end of file." +"Error calculating size of directory '%s'." +"Received too large (%d B) SFTP packet. Max supported packet size is %d B." +"Cannot execute SCP to start transfer. Please make sure that SCP is installed on the server and path to it is included in PATH. You may also try SFTP instead of SCP." +"Location Profile with name '%s' already exists." +"Error moving file '%s' to '%s'." +"%s\n \nThe error is typically caused by message printed from startup script (like .profile). The message may start with %s." +"**Upload of file '%s' was successful, but error occurred while setting the permissions and/or timestamp.**\n\nIf the problem persists, turn off setting permissions or preserving timestamp. Alternatively you can turn on 'Ignore permission errors' option." +"Invalid access to memory." +"There is insufficient free space on the filesystem." +"Operation cannot be completed because it would exceed the users storage quota." +"Principal (%s) is unknown to the server." +"Error copying file '%s' to '%s'." +"Unterminated pattern '%c' starting at %d." +"Unknown pattern '%c' starting at %d." +"Cannot combine file name pattern (starting at %d) with file list pattern (starting at %d)." + +"Cannot determine status of socket (%d)." +"Error deleting file '%s'. After resumable file upload the existing destination file must be deleted. If you do not have permissions to delete file destination file, you need to disable resumable file transfers." +"Error decoding SFTP packet (%d, %d, %d)." +"Invalid name '%s'. Name cannot contain '%s'." +"The file could not be opened because it is locked by another process." +"The directory is not empty." +"The specified file is not a directory." +"The filename is not valid." +"Too many symbolic links encountered." +"The file cannot be deleted." +"One of the parameters was out of range, or the parameters specified cannot be used together." +"The specified file was a directory in a context where a directory cannot be used." +"Byte range lock conflict." +"Byte range lock refused." +"An operation was attempted on a file for which a delete operation is pending." +"The file is corrupt; an filesystem integrity check should be run." +"File '%s' does not contain private key in known format." +"**Private key file '%s' contains key in %s format. WinSCP supports only PuTTY format.**\n \nYou can use PuTTYgen tool to convert your private key file." +"Private key file '%s' contains key in %s format. It does not follow your preferred SSH protocol version." +"Cannot overwrite remote file '%s'.$$\n \nPress 'Delete' to delete the file and create new one instead of overwriting it.$$" +"&Delete" +"Error checking space available for path '%s'." +"Cannot find free local listening port number for tunnel in range %d to %d." +"Cannot setup network event (error %d)." +"Server unexpectedly closed network connection." +"Error while tunneling the connection.\n \n%s" +"Error calculating checksum for file '%s'." +"Internal error %s (%s)." +"Operation not supported." +"Access denied." +"Prompting for credentials..." +"Invalid response to %s command '%s'." +"This version does not support FTP protocol." + +"Error transferring file '%s'." +"Cannot execute '%s'." +"File '%s' not found." +"Error waiting for document to close." +"'%s' is not a valid speed limit." +"Self signed certificate." +"Format error in certificate's valid until field." +"Format error in certificate's valid from field." +"Invalid CA certificate." +"Unsupported certificate purpose." +"Key usage does not include certificate signing." +"Path length constraint exceeded." +"Self signed certificate in certificate chain." +"Unable to decode issuer public key." +"Unable to decrypt certificate signature." +"Unable to get issuer certificate." +"Unable to get local issuer certificate." +"Unable to verify the first certificate." +"Unknown certificate verification error." +"The error occurred at a depth of %d in the certificate chain." +"Mask is invalid near '%s'." +"The server cannot open connection in active mode. If you are behind a NAT router, you may need to specify an external IP address. Alternatively, consider switching to passive mode." +"Error deleting file '%s'." +"Invalid switch value '%s'. Valid values are 'on' and 'off'." +"Access without password denied." + +"Network error: No route to host '%HOST%'." +"Network error: Software caused connection abort" +"Host '%HOST%' does not exist." +"Incoming packet was garbled on decryption" +"%s\n\nPlease help us improving WinSCP by reporting the error on WinSCP support forum." +"Error decoding TLS/SSL certificate (%s)." +"Error retrieving file list for '%s'." +"Certificate was not issued for this server. You might be connecting to a server that is pretending to be '%s'." + +"Some certificates in certificate chain are invalid." +"Certificate is valid." +"Certificate chain too long." +"Certificate has expired." +"Certificate is not yet valid." +"Certificate rejected." +"Certificate signature failure." +"Certificate not trusted." +"WebDAV resource moved to '%s'." +"Too many redirects." +"Redirect loop detected." +"Invalid URL '%s'." +"Proxy authentication failed." +"Host key does not match configured key '%s'!" +"The name specified can not be assigned as an owner of a file." +"The name specified can not be assigned as the primary group of a file." +"The requested operation could not be completed because the specified byte range lock has not been granted." +"Private key file '%s' does not exist or cannot be opened." +"Checksum algorithm '%s' is not supported." +"Cipher %s was not verified!" +"Key-exchange algorithm %s was not verified!" +"Common reasons for the Error code 4 are:\n- Renaming a file to a name of already existing file.\n- Creating a directory that already exists.\n- Moving a remote file to a different filesystem (HDD).\n- Uploading a file to a full filesystem (HDD).\n- Exceeding a user disk quota." +"Cannot open certificate '%s'." +"Cannot read certificate '%s'." +"Error decoding certificate." +"Error decoding certificate '%s'." +"Certificate file '%s' does not contain a public key and no corresponding .crt/.cer file was found." +"Error locking file '%s'." +"Error unlocking file '%s'." +"File '%s' is not locked." +"Error saving key to file \"%s\"." +"Neon WebDAV library initialization failed, cannot open WebDAV session." +"Selecting files using a path ending with slash is ambiguous. Remove the slash to select the folder. Append * mask to select all files in the folder." +"When connecting using an IP address, it is not possible to verify if the certificate was issued for the server. Use a hostname instead of the IP address." +"Expected host key was not configured, use -hostkey switch." +"Redirected to an unencrypted URL." +"Received response %d '%s' from %s" +"FileZilla site manager file not found (%s)." +"No sites found in FileZilla site manager file (%s)." +"FileZilla site '%s' was not found." +"You cannot connect to an SFTP server using an FTP protocol. Please select the correct protocol." + +"CORE_CONFIRMATION" +"Host is not communicating for %d seconds.\n\nWait for another %d seconds?" +"&Passphrase for key '%s':" +"File '%s' already exists. Overwrite?" +"Directory '%s' already exists. Overwrite?" +"The first %s cipher supported by the server is %s, which is below the configured warning threshold.\n\nDo you want to continue with this connection?" +"" +"client-to-server " +"server-to-client " +"**Do you want to resume file transfer?**\n\nTarget directory contains partially transfered file '%s'.\n\nNote: Answering 'No' would delete partially transfered file and restart transfer." +"Target directory contains partially transfered file '%s', which is bigger than a source file. The file will be deleted." +"**Do you want to append file '%s' at the end of existing file?**\n\nPress 'No' to resume file transfer instead." +"%s\n \nNew: \t%s bytes, %s\nExisting: \t%s bytes, %s" +"File '%s' is read-only. Overwrite?" +"**Overwrite local file '%s'?**\n\nDestination directory already contains file '%s'.\nChoose, if you want to overwrite the file or skip this transfer and keep existing file." +"**Overwrite remote file '%s'?**\n\nDestination directory already contains file '%s'.\nChoose, if you want to overwrite the file or skip this transfer and keep existing file." +"Host is not communicating for more than %d seconds.\nStill waiting...\n\nNote: If the problem repeats, try turning off 'Optimize connection buffer size'." +"The first key-exchange algorithm supported by the server is %s, which is below the configured warning threshold.\n\nDo you want to continue with this connection?" +"&Reconnect" +"New na&me" +"Tunnel for %s" +"Password" +"Key passphrase" +"Server prompt" +"Username" +"&Username:" +"Server prompt: %s" +"New password" +"&Response:" +"Using TIS authentication.%s" +"Using CryptoCard authentication.%s" +"&Password: " +"Using keyboard-interactive authentication.%s" +"&Current password:" +"&New password:" +"Confirm new &password:" +"Tunnel session authentication" +"Transfer under different name" +"&New name:" +"**The server's certificate is not known. You have no guarantee that the server is the computer you think it is.**\n\nServer's certificate details follow:\n\n%s\n\nIf you trust this certificate, press Yes. To connect without storing certificate, press No. To abandon the connection press Cancel.\n\nContinue connecting and store the certificate?" +"- Organization: %s\n|- Location: %s\n|- Other: %s\n" +"%s, %s" +"Issuer:\n%s\nSubject:\n%s\nValid: %s - %s\n\nFingerprint (SHA1): %s\n\nSummary: %s" +"&Passphrase for client certificate:" +"Client certificate passphrase" +"**Do you want to convert this %s private key to PuTTY format?**\n\n%s" +"**Are you sure you want to transfer multiple files to a single file '%s' in a directory '%s'?**\n\nThe files will overwrite one another.\n\nIf you actually want to transfer all files to a directory '%s', keeping their name, make sure you terminate the path with a slash." + +"CORE_INFORMATION" +"Yes" +"No" +"Host: %s\nUser name: %s\nPrivate key file: %s\nTransfer protocol: %s" +"Version %s (Build %d)" +"Operation was successfully completed. Connection was closed." +"SFTP-%d" +"The version of SFTP protocol does not allow file renaming." +"The server does not support any SFTP extension." +"The server supports these SFTP extensions:" +"A&ppend" +"Ne&wer only" + +"Ski&p all" + +"&Text" +"&Binary" +"&Exclude temporaries" +"Transfer type: %s|Binary|Text|Automatic (%s)|Automatic" +"Filename modification: %s|No change|Upper case|Lower case|First upper case|Lower case 8.3" +"Set permissions: %s" +"Add X to directories" +"Preserve timestamp" +"File mask: %s" +"Clear 'Archive' attribute" +"Do not replace invalid characters" +"Do not preserve timestamp" +"Do not calculate transfer size" +"Default transfer settings" +"Hostname: %s" +"Username: %s" +"Remote directory: %s" +"Local directory: %s" +"Scanning '%s' for subdirectories..." +"Watching for changes in %d directories..." +"Change in '%s' detected." +"File '%s' uploaded." +"File '%s' deleted." +"%s configured transfer settings cannot be used in current context|Some|All" +"Ignore permission errors" +"Using username \"%s\"." +"Using keyboard-interactive authentication." +"Authenticating with public key \"%s\"." +"Wrong passphrase." +"Access denied." +"Authenticating with public key \"%s\" from agent." +"Trying public key authentication." +"Authenticating with pre-entered password." +"Opening tunnel..." +"Connection terminated." +"Searching for host..." +"Connecting to host..." +"Authenticating..." +"Authenticated." +"Starting the session..." +"Session started." +"Reading remote directory..." +"Connecting through tunnel..." +"Server refused our key." +"Administratively prohibited (%s)." +"Connect failed (%s)." +"Network error: Connection to '%HOST%' refused." +"Network error: Connection reset by peer." +"Network error: Connection to '%HOST%' timed out." +"Host: %s\nUser name: %s\nTransfer protocol: %s" +"&Resume" +"The server does not support any additional FTP feature." +"The server supports these FTP additional features:" +"Transfer speed limit: %u KB/s" +"&Copy Key" +"&Update" +"&Add" +"Preserve read-only" + +"Unlimited" +"TLS/SSL Implicit encryption" +"TLS/SSL Explicit encryption" + +"Host key fingerprint is %s." + +"&New and updated files only" +"The server rejected SFTP connection, but it listens for FTP connections.\n\nDid you want to use FTP protocol instead of SFTP? Prefer using encryption." + +"WARNING! Giving up security and accepting any host key as configured!" +"WARNING! Giving up security and accepting any certificate as configured!" + +"Remove EOF mark" +"Remove BOM" + +"Build" +"Dev Build" +"Debug Build" +"- Do NOT distribute" +"The server supports these WebDAV extensions:" +"Exclude &directories" +"Calculates checksum of remote file" +"Loading client certificate..." +"Server asks for authentication with a client certificate." +"Locked" +"Executable" +"Scripting does not use standalone parameters. The parameters you have specified on command-line will not be used. Your command-line syntax is probably wrong." +"In scripting, you should use a -hostkey switch to configure the expected host key." +"In scripting you should not rely on saved sites, use this command instead:" +"Setup session options" +"Connect" +"Your code" +"Load WinSCP .NET assembly" +"%s (including directories)" + +"CORE_VARIABLE" +"SSH and SCP code based on PuTTY %s" +"0.67" +"Copyright © 1997-2016 Simon Tatham" +"http://www.chiark.greenend.org.uk/~sgtatham/putty/" +"FTP code based on FileZilla" +"2.2.32" +"Copyright © Tim Kosse" +"http://filezilla-project.org/" +"This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit %s." +"Copyright © 1998-2016 The OpenSSL Project" +"1.0.2h" +"http://www.openssl.org/" +"WebDAV code based on neon library %s" +"Copyright В© 1999-2014 Joe Orton" +"http://www.webdav.org/neon/" +"eXpat library %s" +"Copyright В© 1998-2012 Expat maintainers" +"http://www.libexpat.org/" +"http://www.chiark.greenend.org.uk/~sgtatham/putty/licence.html" +"**" +"$$" + +"WinSCP" +"5.8.3" +"1.6.2" +"Based on WinSCP: SFTP/FTP/SCP client for FAR" +" Version %s" +"Copyright © 2000-2016 Martin Prikryl" + + +"**Continue connecting to an unknown server and add its host key to a cache?**\n\nThe server's host key was not found in the cache. You have no guarantee that the server is the computer you think it is.\n\nThe server's %s key fingerprint is:\n%s\n\nIf you trust this host, press Yes. To connect without adding host key to the cache, press No. To abandon the connection press Cancel." +"**WARNING - POTENTIAL SECURITY BREACH!**\n\nThe server's host key does not match the one WinSCP has in cache. This means that either the server administrator has changed the host key, the server presents different key under certain circumstance, or you have actually connected to another computer pretending to be the server.\n\nThe new %s key fingerprint is:\n%s\n\nIf you were expecting this change, trust the new key and want to continue connecting to the server, either press Update to update cache, or press Add to add the new key to the cache while keeping the old one(s). If you want to carry on connecting but without updating the cache, press Skip. If you want to abandon the connection completely, press Cancel. Pressing Cancel is the ONLY guaranteed safe choice." +"You are loading an SSH-2 private key which has an old version of the file format. This means your key file is not fully tamperproof. We recommend you convert your key to the new format.\n\nYou can perform this conversion by loading the key into PuTTYgen and then saving it again." + + +"String list does not allow duplicates" + +"List count out of bounds (%d)" +"List index out of bounds (%d)" + +"Out of memory while expanding memory stream" + +"Stream read error" + +"Operation not allowed on sorted list" + +"Invalid argument to time encode" + +"Stream write error" + +"Not implemented. Code: %d" +"System Error. Code: %d.\n%s" +"A call to an OS function failed" + +"Invalid argument to date encode" +"Cannot open clipboard" + +"Timeout detected." +"Disconnected from server" + +"Save converted private key" +"PuTTY Private Key Files (*.ppk)|*.ppk|All files (*.*)|*.*" +"Private key was converted and saved to '%s'." + +"Stack trace:" + +"No files selected" diff --git a/netbox/src/NetBox/NetBoxRus.lng b/netbox/src/NetBox/NetBoxRus.lng new file mode 100644 index 000000000..efd8f99cc --- /dev/null +++ b/netbox/src/NetBox/NetBoxRus.lng @@ -0,0 +1,1270 @@ +.Language=Russian,Russian (Русский) + +"NetBox" +"NetBox" + +"Название соединения" +"NetBox: Избранное" + +"Соединение прервано." +"Инициализация..." +"Поиск сервера..." +"Соединение с сервером..." +"Аутентификация..." +"Аутентификация пройдена." +"Старт сессии..." +"Чтение удалённого каталога..." +"Подключено." + +"Подтверждение" +"Информация" +"Ошибка" +"Предупреждение" + +"&Да" +"&Нет" +"Да" +"Отмена" +"П&рервать" +"&Повторить" +"&Ignore" +"В&се" +"Н&ет все" +"Да В&се" +"&Справка" +"&Пропуск" +"&Назад" +"&Далее" +"Д&полнить" + +"Закрыть" +"&Больше никогда не спрашивать" +"Б&ольше никогда не показывать это сообщение" +"%s (%d s)" + +"Group" +"Rights" +"Rights" +"Link target" + +"NewSes" +"Нажмите Shift-F4 для добавления сессии" +"Экспорт" +"Дублир." +"Переим." +"ОткрКат" +"Дублир." +"ПеремВ" +"Переим." + +"%s\n \nПредупреждение: Отмена этой операции закроет соединение!" +"Невозможно создать папку «%s»." +"Каталог «%s» не существует. Создать?" +"**Отменить текущую операцию?**" +"«%s» — недопустимая маска" +"Просмотр из диалога поиска не поддерживается" +"Вы собираетесь закрыть WinSCP плагин во время фоновой передачи данных очереди. Показать список очереди?\n \nВнимание: нажатие «Cancel» немедленно прервёт текущую передачу данных." +"Выполнение невозможно так как «MIT Kerberos 5 GSSAPI» не найдено. Вам необходимо установить данное ПО для использования этой функции." +"Ошибка отслеживания изменений." +"Ошибка отслеживания изменений в директории «%s»." +"Текущая "%s" сессия не поддерживает запрошенную Вами команду. Однако для обработки команды возможно открыть отдельную shell-сессию. Вы хотите открыть отдельную shell-сессию?\n \nПримечание: сервер должен поддерживать Unix-подобный shell, разрешающий синтаксис путей используемый в текущей сессии ("%s")" +"Нет выбранных файлов." +"Невозможно создать временную директорию «%s»." +"Вы собираетесь сохранить сессию с указанным паролем.\n \nСохраненные пароли хранятся незащищённым способом, и могут быть легко восстановлены. Невозможно надёжно защитить пароли если они используются автоматически. Не используйте функцию сохранения паролей, если вы не абсолютно уверены в физической и программной безопасности системы.\n \nЕсли вы не сохраните пароль вместе с другими опциями сессии, вам будет предложено ввести пароль при открытии сохранённой сессии. \n \nВы все-таки желаете сохранить пароль?" +"Плагину требуется версия FAR %s или более поздняя." +"Вы пытаетесь изменить предустановленное направление синхронизации директорий. Направление синхронизации по умолчанию: из активной панели в пассивную (на момент включения функции синхронизации)\n \nВы желаете, чтобы выбранное вами сейчас направлние, использовалось по умолчанию?" +"Выполнить вначале полную синхронизацию удалённой директории?\n \nЧтобы функция «Keep remote directory up to date» работала корректно, предварительно необходимо синхронизировать удалённую директорию с локальной директорией." +"Синхронный просмотр выключен." +"Синхронный просмотр выключен." +"Синхронный просмотр возможен только со стандартной панелью." +"Не удалось открыть соответствующий каталог в противоположной панели. Дальнейший синхронный просмотр невозможен и поэтому отключён." +"Не удалось открыть соответствующий каталог в противоположной панели\n \nСоздать каталог «%s»?" +"Ошибка удаления файла «%s»." +"Найдено более %d директорий и поддиректорий. Отслеживание изменений в большого количества директорий может значительно ухудшить производительность системы.\n \nВы настаиваете на сканировании еще %d директорий?" +"Операция не завершена" +"При FTP подключении через прокси должен быть включён пассивный режим." +"%s\nуже загружен. Как открыть этот файл?" + +" Параметры передачи " +"Параметры передачи..." + +"Создать папку" +"Новое имя &папки:" +" Атрибуты " +"Установить &разрешения" +"Использовать эти же настройки в &следующий раз" + +"Копирование" +"Копировать \"%s\" &в:" +"Копировать %d элемента &в:" +"Перенос" +"Перенести \"%s\" &в:" +"Перенести %d элемента &в:" + +"Чтение директории" +"Смена директории" + +"Вы хотите удалить файл %s" +"Вы хотите удалить %d элементов" +"Вы хотите удалить в корзину файл %s" +"Вы хотите удалить в корзину %d элементов" + +"Копирование" +"Перемещение" +"Удаление" +"Установка свойств" +"Вычисление размера директории" +"Перемещение" +"Копирование" +"Получение свойств" +"Вычисление контрольной суммы файла" + +"Файл: " +"Назначение: " +"Начато: " +"Прошло: " +"Передано: " +"CPS: " +"Осталось: " + +"Прервать передачу файлов?\n \nОперация не может быть корректно прервана во время передачи файла.\nНажмите «Yes» чтобы прервать передачу файла и закрыть соединение.\nНажмите «No» чтобы завершить передачу текущего файла и прервать операцию.\nНажмите «Cancel» чтобы продолжить операцию." +"Отменить текущую операцию?" + +"Не поддерживается" +"Текущий протокол (%s) не поддерживает запрошенную операцию." +"Невозможно инициализировать сессию «%s»." + +"Сессия с именем «%s» уже существует." +"Сохранить сессию как" +"Сохранить сессию как:" +"Удалить выбранную сессию/сессии?" +"Экспорт" +"Экспортировать сессию «%s» в: " +"Экспортировать %d сессий в: " +"Импортировать сессию/сессии из выбранного файла/файлов?" +"Файл «%s» не содержит данных о каких-либо сессиях." +"Дублировать сессию" +"Имя новой сессии:" +"Переименовать сессию" +"Новое имя сессии:" + +"Настройки &интерфейса" +"&Подтверждения" +"Настройки &передачи данных" +"&Фоновая передача данных" +"Настройки &аварийной стойкости" +"Настройки &редактора/просмотра" +"Настройки &журналирования" +"&О программе" +"&Интеграция" +"Настройки отображения пане&ли" + +"Добавить в меню &Дисков" +"Добавить в меню &Плагинов" +"Добавить &Команды в меню Плагинов (Alt-Shift-W)" +"Показывать имя &сессии в заголовке окна" +"Префиксы &командной строки (перечисленные через запятую):" +" Кастомный «Детальный» режим панели " +"&Использовать настройки для режима панели «Детальный»" +"&Типы колонок" +"&Ширина колонок" +"Типы кол. строки состо&яния" +"Ширин&а кол. строки сост." +"Полно&экранный режим" +"Использовать стандартные типы колонок Far, плюс G (group)," +"R (rights), RO (octal format) and L (symlink target)" +"Автомати&чески обновлять отображение директории после операций" + +"Version %s build %d" +"Based on %s version %s" +"&Homepage" +"&Support forum" +"http://winscp.net/" + +"Настройка подключения" +"Add session" +"Connect session" +"Подключиться" +"Подключение" +"Среда" +"Папки" +"SCP/Shell" +"SFTP" +"FTP(S)" +"FTPS" +"SSH" +"Соединение" +"Туннель" +"Прокси" +"Ошибки" +"Аутентификация" +"KEX|Key exchange" +"WebDAV" +" Сессия " +"Имя &сервера:" +"П&орт:" +"&Тип подключения:" +"Анонимное" +"Стандартное" +"&Имя пользователя:" +"&Пароль:" +"Файл с секретным &ключом:" +" Протокол " +"П&ротокол: " +"SCP" +"SFTP" +"FTP" +"WebDAV" +"Разрешить переход на &SCP" +"небезопасный" +"Используйте кнопки вверху или Ctrl+PgUp и Ctrl+PgDn" +"для доступа к дополнительным настройкам соединения." +" Каталоги " +"&Запоминать последний каталог" +" Настройки чтения каталога " +"&Кэшировать посещённые каталоги сервера" +"З&апоминать изменения каталогов" +"&Постоянный кэш" +"Разре&шать символьные ссылки" +"Ка&талог на сервере:" +" Среда сервера " +"&Символы конца строки (если не заданы сервером):" +" Летнее время " +"Приводить время для файлов к локальным нас&тройкам" +"Приводить &время для файлов с поправкой летнего времени" +"Не &менять время для файлов сервера" +" Настройки протокола SSH " +"Разрешить &сжатие" +" Предпочитаемая версия SSH " +"&только 1" +"&1" +"&2" +"то&лько 2" +" Настройки шифрования " +"&Метод выбора шифра:" +"&Выше" +"&Ниже" +"Enable legacy use of &single-DES in SSH-2" +"-- предупреждать о нижеследующих --" +"3DES" +"Blowfish" +"AES (SSH-2 only)" +"DES" +"Arcfour (SSH-2 only)" +"ChaCha20" +" Оболочка " +"О&болочка: " +"По умолчанию" +"&Возврат переменного кода:" +"Автоматическое обнаружение" +" Вывод каталога " +"Игнор. &сообщения" +"&Команда вывода: " +"Опред. полный &штамп времени" +" Прочие настройки " +"&Группы пользователей" +"&Очищать регион. переменные" +"Сбросить &алиасы " +"Совместимость scp&2 с scp1" +"Смещение часового по&яса сервера: " +"ч" +"мин" +" Тайм-ауты " +"Ожидание &ответа сервера:" +"секунд (0 - без тайм-аута)" +" Поддерживать активность " +"&нет" +"Посылать п&устые пакеты SSH" +"Посылать пуст&ые команды протокола" +"Секунд &между сообщениями:" +" Версия протокола IP " +"А&вто" +"IPv&4" +"IPv&6" +"&Кодировка для имён файлов:" +" Прокси " +"&Тип прокси: " +"Нет" +"SOCKS4" +"SOCKS5" +"HTTP" +"Telnet" +"Локальный" +"Настройки системы" + +"SITE %host" +"USER %proxyuser, USER %user@%host" +"OPEN %host" +"USER %proxyuser, USER %user" +"USER %user@%host" +"USER %proxyuser@%host" +"USER %user@%host %proxyuser" +"USER %user@%proxyuser@%host" + +"&Хост:" +"&Порт:" +"&Имя пользователя:" +"П&ароль:" +" Настройки прокси " +"Коман&да telnet: " +"Коман&да локального прокси:" +"Подключаться к &локальным хостам через прокси:" +"Выполнять &DNS-запросы на стороне прокси:" +"Нет" +"Авто" +"Да" +" Обработка известных ошибок SSH " +"Не &использовать игнорируемые сообщения с SSH-1:" +"Не пытаться скрыть длину п&ароля с SSH-1: " +"Не использовать &RSA-аутентификацию с SSH-1: " +"Неверное вычисление ключей SSH-2 H&MAC: " +"Н&еверное вычисление ключей сессии с SSH-2: " +"&Требование выравнивания подписей RSA с SSH-2: " +"&Неверный ID соединения при автор. ключом: " +"Отсутствие по&ддержки смены ключей SSH-2: " +"Авто" +"Выкл" +"Вкл" +"&Полностью игнорировать аутентификацию (SSH-2)" +" Настройки аутентификации " +"Пытаться аутентифицироваться с помощью &Pageant" +"Пробовать аутентификацию &TIS или CryptoCard (SSH-1)" +"Пытаться &аутентифицир. с помощью клавиатуры (SSH-2)" +"Отвечать с паролем на первый &запрос" +"Разрешить обращение к агенту через себя" +" GSSAPI " +"Пробовать аутентификацию &GSSAPI (SSH-2)" +"Разре&шить передачу мандата GSSAPI" +"Attempt &GSSAPI/SSPI authentication (SSH-2)" +"Ser&vice principal name (GSSAPI/SSPI):" +" Настройки аутентификации " +" Корзина " +"&Перемещать удаляемые файлы в корзину" +"Перемещать &удаляемые с сервера файлы в корзину (SFTP)" +"&Корзина на сервере:" +" Параметры протокола " +"&Сервер SFTP: " +"По умолчанию" +"&Предпочитаемая версия протокола SFTP: " +"Кодировка &UTF-8 для имён файлов: " +" Распознавание известных ошибок серверов SFTP " +"&Обратный порядок аргументов команды symlink: " +"О&шибочная интерпретация времени до 1970 года: " +"М&инимальный размер пакета: " +"М&аксимальный размер пакета:" +" Параметры обмена ключами " +"&Политика выбора протокола:" +" Параметры перевыбора ключей " +"&Менять не реже, мин. (0 - не менять): " +"М&енять после пересылки объема (0 - не менять):" +"-- предупреждать о нижеследующих --" +"Диффи-Хеллман, группа 1" +"Диффи-Хеллман, группа 14" +"Диффи-Хеллман group exchange" +"RSA" +"ECDH" + +" Tunnel " +"&Соединяться через SSH-туннель" +" Хост для туннеля " +" Настройки туннеля " +"&Локальный порт туннеля:" +"Автовыбор" +"Uni&x" +"&Windows" +" Соединение " +"&Пассивный режим" +"Оптимизировать размер &буфера соединения" +"&Разрешать пустой пароль" + +"Использовать команду &MLSD для просмотра каталога" +" Настройки протокола FTP " +"&Дублировать FF в командах" +"&Убирать двойные FF из PWD" +"&Повторное использование ID сессии" +"&Шифрование:" +"Без шифрования" +"Неявное шифрование TLS/SSL" +"Явное шифрование TLS/SSL" +"&Команды после подключения:" + +" Настройки протокола WebDAV " + +"Атрибуты" +"Изменить атрибуты для" +"выбранных объектов" +"Владеле&ц" +"&Группа " +"Про&чие " +"R" +"W" +"X" +" Права доступа " +"Oct&al:" +"Добавить X &для папок" +"&Owner:" +"&Group:" +"Устан. атрибуты &рекурсивно" +"&None" +"&Default" +"A&ll" +"Set UID " +"Set GID " +"Sticky bit" +"Ссылка на:" + +"&Текст (текст, html, ...)" +"&Двоичный (архивы, документы, ...)" +"Определять автоматическ&и" +"Передавать как те&кстовые файлы:" +"Режимы передачи" +"Изменение имён файлов" +"&Не изменять " +"&Верхний регистр" +"Ни&жний регистр" +"&Первая большая" +"Нижний для &8.3" +"&Заменять \\:*\"?" +"&Устанавливать права" +"Со&хранять время изменения" +"Сохран&ять атрибут Read-Only" +"Использовать эти настройки в следующий раз" +"Передача в фоне (добавить в очередь)" +"Без запросов" +"Только новые/измененные файлы" +"Снимать атрибут «&Архивный»" +" Другое " +"&Файлы по маскам (например: *|.svn/):" +"Exclude" +"Include" +"Загрузка на сервер" +"Загрузка с сервера" +"Общие настройки" +"Игнорироват&ь ошибки прав" +"В&ычислять общий объем" +"Ск&орость (Кб/с):" + +"Править линк" +"Создать линк" +"&Файл линка:" +"&Линк на:" +"Sy&mbolic link" + +"Пересылка" +"Переслать файл «%s» в каталог:" +"Пересылка %d файлов в каталог:" + +"Дублировать" +"Дублировать файлы «%s» в каталог:" +"Дублировать %d файлов в каталог:" + +"Переименовать" +"Переименовать «%s» в:" + +"&Hide typing" +"Note: This prompt is issued by the server. It is part of +"either keyboard-interactive, TIS or Cryptocard authentication." +"&Full prompt" +"&Change stored password to this one" + +"&Включить протоколирование" +" Настройки протоколирования " +"&Уровень:" +"Обычный" +"Отладка 1" +"Отладка 2" +"В &файл:" +"&Добавлять" +"&Перезаписывать" +" In log viewer display (and keep in memory) " +"&Complete session" +"Only &last" +"lines" +"&&Y год; &&M месяц; &&D день; &&T время; &&H хост" +"&&S имя соединения; Пример: C:\\&&S&&T.log" + +"&Перезаписывать файлы (? = использовать настройки FAR)" +"Continue on &error (advanced users)" +"&Возобновлять передачу данных" +"Открывать отдельную &сессию оболочки" +"Синхронный пр&осмотр" + +" Возобновлять передачу/передачу в временный файл для " +"Вс&ех файлов (не рекомендуется)" +"Файлов св&ыше" +"Кб" +"&Не возобновлять" +" Автоматический реконнект " +"&Реконнект после" +"секунд" +"&Максимальное количество попыток:" +"(0-99)" + +" Когда скачивается в редактор/просмотр " +"&Использовать настройки передачи по умолчанию" +"Показать &окно настройки параметров скачивания" + +" Когда загружается из редактора " +"Исп&ользовать настройки передачи как при скачивании" +"Показать окно &настройки параметров загрузки" +"Закачивать после каждого &сохранения" +"&Разрешать редактирование более одного файла" + +"Максимальное &число фоновых передач" +"По &умолчанию передавать в фоне" +"&Показывать запросы от фоновых задач при бездействии" +"&Сигналить когда очередь пустеет или передача требует внимания" +"Повторное использование парол&я основной сессии" + +"NetBox commands" +"Display session log &file" +"View/change file a&ttributes Ctrl-A" +"Create/edit &link Alt-F6" +"&Настройки" +"Server and protocol &information" +"Open in &Putty" +"PuTTY&gen: key generator" +"Pagea&nt: authentication agent" +"&Open directory Alt-Shift-F12" +"Add &bookmark" +"&Home directory" +"Apply co&mmand Ctrl-G" +"Clea&r caches" +"&Synchronize" +"&Queue Alt-Shift-Q" +"&Keep Remote Directory Up To Date" +"Synchronize bro&wsing Ctrl-Alt-B" +"&Edit history" + +"Server and protocol information" +" Server information " +"SSH implementation:" +"Encryption algorithm:" +"Compression:" +"File transfer protocol:" +"Server host key fingerprint:" +" Protocol capabilities/information " +"Can change permissions:" +"Can change owner/group:" +"Can execute arbitrary command:" +"Can create symlink/hardlink:" +"Can lookup user groups:" +"Can duplicate remote files:" +"Can check available space:" +"Native text (ASCII) mode transfers:" +" Additional protocol information " +" Space available " +"Pa&th" +"C&heck space" +"Unknown" +"Total bytes on device:" +"Free bytes on device:" +"Total bytes for user:" +"Free bytes for user:" +"Bytes per allocation unit:" +"&Protocol" +"Capa&bilities" +"Space &available" +"&Copy to Clipboard" +"Remote system" +"Session protocol" +"Protocol commands only" +"Can calculate file checksum" + +"Путь к &Putty" +"&Помнить пароль сессии и передавать его PuTTY" +"Путь к Page&ant" +"Путь к PuTTY&gen" +"Открывать &Telnet-сессии в PuTTY для сессий FTP" + +"Open directory" +"Manage bookmarks" +"&Remove" +"&Up " +"&Down " +"Del - remove, Ctrl-Enter - command line, Ctrl-Ins - clipboard" + +"Apply command" +"Enter &command to process selected files" +"&Apply to directories " +"&Execute recursively" +"Patterns: !! - exclamation mark; ! - file name" +"!&& - list of selected files (quoted, space-delimited)" +"!/ - current remote path; !@ - current hostname" +"!U - current username; !P - current password (if known)" +"!?prompt[\]?default! - prompts user for parameter value" +"Local command patterns: !^! - file name from local panel" +"Apply command parameter" +"Apply command parameter value:" +"&Remote command " +"&Local command" +"&Show results in console" +"Copy results to clip&board" +"To use entered command only one file must be selected in one panel to execute the command with the file for each selected file in an opposite panel. Alternatively same number of files can be selected in both panels to execute the command for file pairs." +"To use entered custom command only one file must be selected in local panel." +"Some of the selected remote files were not downloaded. The entered command must be executed for pairs of file, what is thus not possible." +"Local command can be executed only against regular panel." + +"Synchronization is supported only against regular panel." +"No differences found." +"Synchronize" +"Lo&cal directory:" +"Remo&te directory:" +" Direction/Target directory " +"&Local" +"&Remote " +"&Both " +" Mode " +" Synchronize options " +" Compare criteria " +"Synchronize &files" +"&Mirror files" +"Synchronize ×tamps" +"&Delete files " +"&No confirmations" +"&Existing files only " +"Use same &options next time" +"Pre&view changes " +"Synchronize on s&tart" +"&Modification time " +"File si&ze" +"Selected files o&nly" +"Same si&ze only" + +"Keep remote directory up to date" +"Keeping remote directory up to date ..." +"&Watch for changes in the local directory ..." +"... &and automatically reflect them on the remote directory" +"Update s&ubdirectories " +" Upload options " +"&Start" +"&Stop" + +"Synchronizing" +"Comparing" +"Local: " +"Remote: " +"Start time: " +"Time elapsed: " + +"Transfer settings" + +"Queue" +"Oper. Source/Destination/File Trans/Progr" +" &Show " +"&Execute" +"&Cancel" +"&Up" +"Do&wn" +"Close" +"&Suspend" +"&Resume " +"C" +"M" +"" +"" +"Connecting..." +"Query" +"Error" +"Prompt" +"Waiting..." +"There are still some queued background transfers. Do you want to wait for transfers to complete?\n \nWarning: Pressing «Cancel» will terminate all transfers immediately." +"Calculating..." +"Suspended" + +"Authentication Banner - %s" +"&Never show this banner again" +"Continue" + +"Synchronization checklist" +"Name|Local dir.|Size|Changed||Remote dir.|Size|Changed" +"*|*| | |X|X" +" %d of %d " +"Check &all" +"Uncheck a&ll" +"&Maximize" +"&Restore" + +"&Current" +"&New instance" +"&Read-only" + +"Creating folder" + +"Edit history" + +"Imported %d sessions" + +"NetBox (FTP/SCP/WebDAV Client)" + +"OK" +"Отмена" + +" Сессия " +" Прокси " + +//Edit link dialog +"Создание сессии" +"Редактирование сессии" +"Имя сессии:" +"&URL:" +"Кодировка:" +" Аутентификация " +"Имя пользователя:" +"Пароль (пользователя/ключа):" +"Спрашивать пароль при подключении" +"Показать пароль" +"Файл публичного и приватного ключа:" +"Не указан URL" +"Некорректный URL" +"Имя сессии не может быть пустым" +"Имя сессии содержит запрещенные символы (<>:\"/\\|?*)" + +//Configure menu +"NetBox - настройки" +"Основные настройки" +"Настройки прокси" +"Настройки логирования" +"О плагине" + +//Main configure dialog +"Добавлять в дисковое меню" +"Добавлять в меню плагинов панели" +"Использовать собственный ключ шифрования" +"Префикс:" +"Обрабатывать префиксы ftp, http, ..." +"Таймаут по умолчанию (сек):" +"Каталог сохранённых сессий:" + +//Proxy configure dialog +"NetBox - Настройки прокси" +"Тип прокси:" +"Нет" +"SOCKS4" +"SOCKS5" +"HTTP" + +"Адрес прокси сервера:" +"Порт:" +"Пользователь:" +"Пароль:" + +//Logging configure dialog +"NetBox - Настройки логирования" +"Разрешить логирование" +" Настройки логирования " +"Уровень логирования:" +"Debug 1" +"Debug 2" +"Писать лог в файл:" +"C:\NetBox.log" + +//About menu +"NetBox - О плагине" +"FTP/FTPS/SFTP/WebDAV клиент для Far 2.0" +"Версия %s" +"Закрыть" + +//Prompt to crypto key +"Введите ключ шифрования паролей хранимых сессий:" + +//Create directory dialog +"Создание каталога" +"Имя каталога:" + +//Delete items dialog +"Удаление" +"Хотите удалить" +"сессию?" +"папку?" +"файл?" +"выбранные элементы?" +"Удалить" + +//Copy dialog +"Копирование" +"Копировать %s в:" +"выбранные элементы" +"Копировать" + +//Move dialog +"Перемещение" +"Переместить %s в:" +"выбранные элементы" +"Переместить" + +//Progress titles +"Установка соединения с %s..." +"Смена каталога на «%s»..." +"Получение списка файлов каталога %s..." +"Получение файла" +"Отправка файла" +"в" +"Удаление..." + +//Error messages +"Невозможно загрузить приватный ключ из файла\n%s" +"Невозможно установить соединение с хостом\n%s" +"Невозможно создать каталог\n%s" +"Невозможно сменить каталог на\n%s" +"Невозможно получить список файлов каталога\n%s" +"Невозможно скопировать файл из\n%s\nв\n%s" +"Невозможно переименовать/перенести\n%s\nв\n%s" +"Невозможно удалить файл\n%s" +"Невозможно удалить каталог\n%s" + +"Операция прервана пользователем" + +"Нажмите Shift+F4 для ввода нового адреса" + +"Ошибка проверки сертификата: %s\nПродолжить?" + +// TextCore1.rc +"CORE_ERROR" +"Host key wasn’t verified!" +"Connection failed." +"Terminated by user." +"Lost connection." +"Can’t detect command return code." +"Command «%s»\nfailed with return code %d and error message\n%s." +"Command failed with return code %d." +"Command «%s» failed with invalid output «%s»." +"Error getting name of current remote directory." +"Error skipping startup message. Your shell is probably incompatible with the application (BASH is recommended)." +"Error changing directory to «%s»." +"Error listing directory «%s»." +"Unexpected directory listing line «%s»." +"Invalid rights description «%s»" +"Error cleaning up general configuration." +"Error cleaning up sites." +"Error cleaning up random seed file." +"Error cleaning up cached host keys." +"Error detecting variable containing return code of last command." +"Error looking up user groups." +"File or folder «%s» does not exist." +"Can’t get attributes of file «%s»." +"Can’t open file «%s»." +"Error reading file «%s»." +"Copying file «%s» fatally failed." +"Copying files to remote side failed." +"Copying files from remote side failed." +"SCP protocol error: Unexpected newline" +"SCP protocol error: Illegal time format" +"SCP protocol error: Invalid control record (%c; %s)" +"Copying file «%s» failed." +"SCP protocol error: Illegal file descriptor format" +"«%s» is not folder!" +"Error creating folder «%s»." +"Can’t create file «%s»." +"Error writing to file «%s»" +"Can’t set attributes of file «%s»." +"Received error message from remote side: «%s»" +"Error deleting file «%s»." +"Error occurred during logging. It’s been turned off." +"Can’t open log file «%s»." +"Error renaming file «%s» to «%s»." +"File with name «%s» already exists." +"Directory with name «%s» already exists." +"Error changing directory to home directory." +"Error clearing all aliases." +"Error clearing national user variables." +"Unexpected input from server: %s" +"Error cleaning up INI file." +"Authentication log (see session log for details):\n%s\n" +"Authentication failed." +"Connection has been unexpectedly closed." +"Error saving key to file «%s»." +"Server sent command exit status %d." +"SFTP protocol violation: Invalid response message type (%d)." +"Version of SFTP server (%d) is not supported. Supported versions are %d to %d." +"SFTP protocol violation: Invalid message number %d (expected %d)." +"Unexpected OK response." +"Unexpected EOF response." +"No such file or directory." +"Permission denied." +"General failure (server should provide error description)." +"Bad message (badly formatted packet or protocol incompatibility)." +"No connection." +"Connection lost." +"The server does not support the operation." +"%s\nError code: %d\nError message from server%s: %s" +"Unknown status code." +"Error reading symlink «%s»." +"Server returned empty listing for directory «%s»." +"Received SSH_FXP_NAME packet with zero or multiple records." +"Cannot get real path for «%s»." +"Cannot change properties of file «%s»." +"Cannot initialize SFTP protocol. Is the host running a SFTP server?" +"Cannot read timezone information" +"Cannot create remote file «%s»." +"Cannot open remote file «%s»." +"Cannot close remote file «%s»." +"«%s» is not file!" +"Transfer was successfully finished, but temporary transfer file «%s» could not be renamed to target file name «%s». If the problem persists, you may try to turn off transfer resume support." +"Cannot create link «%s»." +"Invalid command «%s»." +"None" +"«%s» is not valid permission in octal format." +"Server requires unsupported end-of-line sequence (%s)." +"Unknown file type (%d)" +"Invalid handle." +"The file path does not exist or is invalid." +"File already exists." +"The file is on read-only media, or the media is write protected." +"There is no media available in the drive." +"Error decoding UTF-8 string." +"Error executing custom command «%s» on file «%s»." +"Cannot load locale %d." +"Received incomplete data packet before end of file." +"Error calculating size of directory «%s»." +"Received too large (%d B) SFTP packet. Max supported packet size is %d B." +"Cannot execute SCP to start transfer. Please make sure that SCP is installed on the server and path to it is included in PATH. You may also try SFTP instead of SCP." +"Location Profile with name «%s» already exists." +"Error moving file «%s» to «%s»." +"%s\n \nThe error is typically caused by message printed from startup script (like .profile). The message may start with %s." +"**Upload of file «%s» was successful, but error occurred while setting the permissions and/or timestamp.**\n\nIf the problem persists, turn off setting permissions or preserving timestamp. Alternatively you can turn on «Ignore permission errors» option." +"Invalid access to memory." +"There is insufficient free space on the filesystem." +"Operation cannot be completed because it would exceed the users storage quota." +"Principal (%s) is unknown to the server." +"Error copying file «%s» to «%s»." +"Unterminated pattern «%c» starting at %d." +"Unknown pattern «%c» starting at %d." +"Cannot combine file name pattern (starting at %d) with file list pattern (starting at %d)." + +"Cannot determine status of socket (%d)." +"Error deleting file «%s». After resumable file upload the existing destination file must be deleted. If you do not have permissions to delete file destination file, you need to disable resumable file transfers." +"Error decoding SFTP packet (%d, %d, %d)." +"Invalid name «%s». Name cannot contain «%s»." +"The file could not be opened because it is locked by another process." +"The directory is not empty." +"The specified file is not a directory." +"The filename is not valid." +"Too many symbolic links encountered." +"The file cannot be deleted." +"One of the parameters was out of range, or the parameters specified cannot be used together." +"The specifed file was a directory in a context where a directory cannot be used." +"Byte range lock conflict." +"Byte range lock refused." +"An operation was attempted on a file for which a delete operation is pending." +"The file is corrupt; an filesystem integrity check should be run." +"File «%s» does not contain private key in known format." +"**Private key file «%s» contains key in %s format. WinSCP supports only PuTTY format.**\n \nYou can use PuTTYgen tool to convert your private key file." +"Private key file «%s» contains key in %s format. It does not follow your preferred SSH protocol version." +"Cannot overwrite remote file «%s».$$\n \nPress «Delete» to delete the file and create new one instead of overwriting it.$$" +"&Delete" +"Error checking space available for path «%s»." +"Cannot find free local listening port number for tunnel in range %d to %d." +"Cannot setup network event (error %d)." +"Server unexpectedly closed network connection." +"Error while tunneling the connection.\n \n%s" +"Error calculating checksum for file «%s»." +"Internal error %s (%s)." +"Operation not supported." +"Access denied." +"Prompting for credentials..." +"Invalid response to «%s» command «%s»." +"This version does not support FTP protocol." + +"Error transferring file «%s»." +"Cannot execute «%s»." +"File «%s» not found." +"Error waiting for document to close." +"«%s» is not a valid speed limit." +"Self signed certificate." +"Format error in certificate’s valid until field." +"Format error in certificate’s valid from field." +"Invalid CA certificate." +"Unsupported certificate purpose." +"Key usage does not include certificate signing." +"Path length constraint exceeded." +"Self signed certificate in certificate chain." +"Unable to decode issuer public key." +"Unable to decrypt certificate signature." +"Unable to get issuer certificate." +"Unable to get local issuer certificate." +"Unable to verify the first certificate." +"Unknown certificate verification error." +"The error occurred at a depth of %d in the certificate chain." +"Mask is invalid near «%s»." +"The server cannot open connection in active mode. If you are behind a NAT router, you may need to specify an external IP address. Alternatively, consider switching to passive mode." +"Error deleting file «%s»." +"Invalid switch value «%s». Valid values are «on» and «off»." +"Access without password denied." + +"Network error: No route to host «%HOST%»." +"Network error: Software caused connection abort" +"Host «%HOST%» does not exist." +"Incoming packet was garbled on decryption" +"%s\n\nPlease help us improving WinSCP by reporting the error on WinSCP support forum." +"Error decoding TLS/SSL certificate (%s)." +"Error retrieving file list for «%s»." +"Certificate was not issued for this server. You might be connecting to a server that is pretending to be «%s»." + +"Some certificates in certificate chain are invalid." +"Certificate is valid." +"Certificate chain too long." +"Certificate has expired." +"Certificate is not yet valid." +"Certificate rejected." +"Certificate signature failure." +"Certificate not trusted." +"WebDAV resource moved to «%s»." +"Too many redirects." +"Redirect loop detected." +"Invalid URL «%s»." +"Proxy authentication failed." +"Host key does not match configured key «%s»!" +"The name specified can not be assigned as an owner of a file." +"The name specified can not be assigned as the primary group of a file." +"The requested operation could not be completed because the specifed byte range lock has not been granted." +"Private key file «%s» does not exist or cannot be opened." +"Checksum algorithm «%s» is not supported." +"Cipher %s was not verified!" +"Key-exchange algorithm %s was not verified!" +"Common reasons for the Error code 4 are:\n- Renaming a file to a name of already existing file.\n- Creating a directory that already exists.\n- Moving a remote file to a different filesystem (HDD).\n- Uploading a file to a full filesystem (HDD).\n- Exceeding a user disk quota." +"Cannot open certificate '%s'." +"Cannot read certificate '%s'." +"Error decoding certificate." +"Error decoding certificate '%s'." +"Certificate file '%s' does not contain a public key and no corresponding .crt/.cer file was found." +"Error locking file '%s'." +"Error unlocking file '%s'." +"File '%s' is not locked." +"Error saving key to file \"%s\"." +"Neon WebDAV library initialization failed, cannot open WebDAV session." +"Selecting files using a path ending with slash is ambiguous. Remove the slash to select the folder. Append * mask to select all files in the folder." +"When connecting using an IP address, it is not possible to verify if the certificate was issued for the server. Use a hostname instead of the IP address." +"Expected host key was not configured, use -hostkey switch." +"Redirected to an unencrypted URL." +"Received response %d '%s' from %s" +"FileZilla site manager file not found (%s)." +"No sites found in FileZilla site manager file (%s)." +"FileZilla site '%s' was not found." +"You cannot connect to an SFTP server using an FTP protocol. Please select the correct protocol." + +"CORE_CONFIRMATION" +"Host is not communicating for %d seconds.\n\nWait for another %d seconds?" +"&Passphrase for key «%s»:" +"File «%s» already exists. Overwrite?" +"Directory «%s» already exists. Overwrite?" +"The first %scipher supported by the server is %s, which is below the configured warning threshold.\n\nDo you want to continue with this connection?" +"" +"client-to-server " +"server-to-client " +"**Do you want to resume file transfer?**\n\nTarget directory contains partially transfered file «%s».\n\nNote: Answering «No» would delete partially transfered file and restart transfer." +"Target directory contains partially transfered file «%s», which is bigger than a source file. The file will be deleted." +"**Do you want to append file «%s» at the end of existing file?**\n\nPress «No» to resume file transfer instead." +"%s\n \nNew: \t%s bytes, %s\nExisting: \t%s bytes, %s" +"File «%s» is read-only. Overwrite?" +"**Overwrite local file «%s»?**\n\nDestination directory already contains file «%s».\nChoose, if you want to overwrite the file or skip this transfer and keep existing file." +"**Overwrite remote file «%s»?**\n\nDestination directory already contains file «%s».\nChoose, if you want to overwrite the file or skip this transfer and keep existing file." +"Host is not communicating for more than %d seconds.\nStill waiting...\n\nNote: If the problem repeats, try turning off «Optimize connection buffer size»." +"The first key-exchange algorithm supported by the server is %s, which is below the configured warning threshold.\n\nDo you want to continue with this connection?" +"&Reconnect" +"New na&me" +"Tunnel for %s" +"Password" +"Key passphrase" +"Server prompt" +"Username" +"&Username:" +"Server prompt: %s" +"New password" +"&Response:" +"Using TIS authentication.%s" +"Using CryptoCard authentication.%s" +"&Password: " +"Using keyboard-interactive authentication.%s" +"&Current password:" +"&New password:" +"Confirm new &password:" +"Tunnel session authentication" +"Transfer under different name" +"&New name:" +"**The server’s certificate is not known. You have no guarantee that the server is the computer you think it is.**\n\nServer’s certificate details follow:\n\n%s\n\nIf you trust this certificate, press Yes. To connect without storing certificate, press No. To abandon the connection press Cancel.\n\nContinue connecting and store the certificate?" +"- Organization: %s\n|- Location: %s\n|- Other: %s\n" +"%s, %s" +"Issuer:\n%s\nSubject:\n%s\nValid: %s - %s\n\nFingerprint (SHA1): %s\n\nSummary: %s" +"&Passphrase for client certificate:" +"Client certificate passphrase" +"**Do you want to convert this %s private key to PuTTY format?**\n\n%s" +"**Are you sure you want to transfer multiple files to a single file '%s' in a directory '%s'?**\n\nThe files will overwrite one another.\n\nIf you actually want to transfer all files to a directory '%s', keeping their name, make sure you terminate the path with a slash." + +"CORE_INFORMATION" +"Yes" +"No" +"Host: %s\nUser name: %s\nPrivate key file: %s\nTransfer protocol: %s" +"Version %s (Build %d)" +"Operation was successfully completed. Connection was closed." +"SFTP-%d" +"The version of SFTP protocol does not allow file renaming." +"The server does not support any SFTP extension." +"The server supports these SFTP extensions:" +"A&ppend" +"Ne&wer only" + +"Ski&p all" + +"&Text" +"&Binary" +"&Exclude temporaries" +"Transfer type: %s|Binary|Text|Automatic (%s)|Automatic" +"Filename modification: %s|No change|Upper case|Lower case|First upper case|Lower case 8.3" +"Set permissions: %s" +"Add X to directories" +"Preserve timestamp" +"File mask: %s" +"Clear «Archive» attribute" +"Do not replace invalid characters" +"Do not preserve timestamp" +"Do not calculate transfer size" +"Default transfer settings" +"Hostname: %s" +"Username: %s" +"Remote directory: %s" +"Local directory: %s" +"Scanning «%s» for subdirectories..." +"Watching for changes in %d directories..." +"Change in «%s» detected." +"File «%s» uploaded." +"File «%s» deleted." +"%s configured transfer settings cannot be used in current context|Some|All" +"Ignore permission errors" +"Using username \"%s\"." +"Using keyboard-interactive authentication." +"Authenticating with public key \"%s\"." +"Wrong passphrase." +"Access denied." +"Authenticating with public key \"%s\" from agent." +"Trying public key authentication." +"Authenticating with pre-entered password." +"Opening tunnel..." +"Connection terminated." +"Searching for host..." +"Connecting to host..." +"Authenticating..." +"Authenticated." +"Starting the session..." +"Session started." +"Reading remote directory..." +"Connecting through tunnel..." +"Server refused our key." +"Administratively prohibited (%s)." +"Connect failed (%s)." +"Network error: Connection to «%HOST%» refused." +"Network error: Connection reset by peer." +"Network error: Connection to «%HOST%» timed out." +"Host: %s\nUser name: %s\nTransfer protocol: %s" +"&Resume" +"The server does not support any additional FTP feature." +"The server supports these FTP additional features:" +"Transfer speed limit: %u KB/s" +"&Copy Key" +"&Update" +"&Add" +"Preserve read-only" + +"Unlimited" +"TLS/SSL Implicit encryption" +"TLS/SSL Explicit encryption" + +"Host key fingerprint is %s." + +"&New and updated files only" +"The server rejected SFTP connection, but it listens for FTP connections.\n\nDid you want to use FTP protocol instead of SFTP? Prefer using encryption." + +"WARNING! Giving up security and accepting any host key as configured!" +"WARNING! Giving up security and accepting any certificate as configured!" + +"Remove EOF mark" +"Remove BOM" + +"Build" +"Dev Build" +"Debug Build" +"- Do NOT distribute" +"The server supports these WebDAV extensions:" +"Exclude &directories" +"Calculates checksum of remote file" +"Loading client certificate..." +"Server asks for authentication with a client certificate." +"Locked" +"Executable" +"Scripting does not use standalone parameters. The parameters you have specified on command-line will not be used. Your command-line syntax is probably wrong." +"In scripting, you should use a -hostkey switch to configure the expected host key." +"In scripting you should not rely on saved sites, use this command instead:" +"Setup session options" +"Connect" +"Your code" +"Load WinSCP .NET assembly" +"%s (including directories)" + +"CORE_VARIABLE" +"SSH and SCP code based on PuTTY %s" +"0.67" +"Copyright © 1997-2016 Simon Tatham" +"http://www.chiark.greenend.org.uk/~sgtatham/putty/" +"FTP code based on FileZilla" +"2.2.32" +"Copyright © Tim Kosse" +"http://filezilla-project.org/" +"This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit %s." +"Copyright © 1998-2016 The OpenSSL Project" +"1.0.2h" +"http://www.openssl.org/" +"WebDAV code based on neon library %s" +"Copyright В© 1999-2014 Joe Orton" +"http://www.webdav.org/neon/" +"eXpat library %s" +"Copyright В© 1998-2012 Expat maintainers" +"http://www.libexpat.org/" +"http://www.chiark.greenend.org.uk/~sgtatham/putty/licence.html" +"**" +"$$" + +"WinSCP" +"5.8.3" +"1.6.2" +"Based on WinSCP: SFTP/FTP/SCP client for FAR" +" Version %s" +"Copyright © 2000-2016 Martin Prikryl" + + +"**Continue connecting to an unknown server and add its host key to a cache?**\n\nThe server’s host key was not found in the cache. You have no guarantee that the server is the computer you think it is.\n\nThe server’s %s key fingerprint is:\n%s\n\nIf you trust this host, press Yes. To connect without adding host key to the cache, press No. To abandon the connection press Cancel." +"**WARNING - POTENTIAL SECURITY BREACH!**\n\nThe server’s host key does not match the one WinSCP has in cache. This means that either the server administrator has changed the host key, the server presents different key under certain circumstance, or you have actually connected to another computer pretending to be the server.\n\nThe new %s key fingerprint is:\n%s\n\nIf you were expecting this change, trust the new key and want to continue connecting to the server, either press Update to update cache, or press Add to add the new key to the cache while keeping the old one(s). If you want to carry on connecting but without updating the cache, press Skip. If you want to abandon the connection completely, press Cancel. Pressing Cancel is the ONLY guaranteed safe choice." +"You are loading an SSH-2 private key which has an old version of the file format. This means your key file is not fully tamperproof. We recommend you convert your key to the new format.\n\nYou can perform this conversion by loading the key into PuTTYgen and then saving it again." + + +"String list does not allow duplicates" + +"List count out of bounds (%d)" +"List index out of bounds (%d)" + +"Out of memory while expanding memory stream" + +"Stream read error" + +"Operation not allowed on sorted list" + +"Invalid argument to time encode" + +"Stream write error" + +"Not implemented. Code: %d" +"System Error. Code: %d.\n%s" +"A call to an OS function failed" + +"Invalid argument to date encode" +"Cannot open clipboard" + +"Timeout detected." +"Disconnected from server" + +"Save converted private key" +"PuTTY Private Key Files (*.ppk)|*.ppk|All files (*.*)|*.*" +"Private key was converted and saved to '%s'." + +"Stack trace:" + +"No files selected" diff --git a/netbox/src/NetBox/UnityBuildCore.cpp b/netbox/src/NetBox/UnityBuildCore.cpp new file mode 100644 index 000000000..9410be341 --- /dev/null +++ b/netbox/src/NetBox/UnityBuildCore.cpp @@ -0,0 +1,54 @@ +#define PUTTY_DO_GLOBALS +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NON_CONFORMING_SWPRINTFS + +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter + +#include "../base/UnicodeString.cpp" +#include "../base/Classes.cpp" +#include "../base/Masks.cpp" +#include "../base/Sysutils.cpp" +#include "../base/StrUtils.cpp" +#include "../base/local.cpp" +#include "../base/rtti.cpp" +#include "../base/WideStrUtils.cpp" +#include "../base/LibraryLoader.cpp" +#include "../base/Common.cpp" +#include "../base/Exceptions.cpp" +#include "../base/FileBuffer.cpp" +#include "../base/Global.cpp" + +#include "../core/RemoteFiles.cpp" +#include "../core/Terminal.cpp" +#include "../core/FileOperationProgress.cpp" +#include "../core/Queue.cpp" +#include "../core/SecureShell.cpp" +#include "../core/SessionInfo.cpp" +#include "../core/CoreMain.cpp" +#include "../core/FileMasks.cpp" +#include "../core/CopyParam.cpp" +#include "../core/SessionData.cpp" +#include "../core/Configuration.cpp" +#include "../core/ScpFileSystem.cpp" +#include "../core/FtpFileSystem.cpp" +#include "../core/SftpFileSystem.cpp" +#include "../core/WebDAVFileSystem.cpp" +#include "../core/PuttyIntf.cpp" +#include "../core/Cryptography.cpp" +#include "../core/NamedObjs.cpp" +#include "../core/HierarchicalStorage.cpp" +#include "../core/Option.cpp" +#include "../core/FileInfo.cpp" +#include "../core/FileSystems.cpp" +#include "../core/Bookmarks.cpp" +#include "../core/WinSCPSecurity.cpp" +#include "../core/Http.cpp" +#include "../core/NeonIntf.cpp" +#include "../windows/SynchronizeController.cpp" +#include "../windows/GUITools.cpp" +#include "../windows/GUIConfiguration.cpp" +#include "../windows/Tools.cpp" +#include "../windows/ProgParams.cpp" + +#pragma warning(pop) diff --git a/netbox/src/NetBox/UnityBuildFilezilla.cpp b/netbox/src/NetBox/UnityBuildFilezilla.cpp new file mode 100644 index 000000000..09699ff28 --- /dev/null +++ b/netbox/src/NetBox/UnityBuildFilezilla.cpp @@ -0,0 +1,27 @@ +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NON_CONFORMING_SWPRINTFS + +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter + +#include "../filezilla/stdafx.cpp" +#include "../filezilla/afxdll.cpp" +#include "../filezilla/FileZillaIntf.cpp" +#include "../filezilla/FileZillaIntern.cpp" +#include "../filezilla/ApiLog.cpp" +#include "../filezilla/ServerPath.cpp" +#include "../filezilla/AsyncSslSocketLayer.cpp" +#include "../filezilla/AsyncSocketExLayer.cpp" +#include "../filezilla/AsyncSocketEx.cpp" +#include "../filezilla/FileZillaApi.cpp" +#include "../filezilla/FzApiStructures.cpp" +#include "../filezilla/FtpControlSocket.cpp" +#include "../filezilla/MainThread.cpp" +#include "../filezilla/TransferSocket.cpp" +#include "../filezilla/FtpListResult.cpp" +#include "../filezilla/AsyncProxySocketLayer.cpp" +#include "../filezilla/structures.cpp" +#include "../filezilla/MFC64bitFix.cpp" +#include "../filezilla/misc/CBase64Coding.cpp" + +#pragma warning(pop) diff --git a/netbox/src/NetBox/UnityBuildMain.cpp b/netbox/src/NetBox/UnityBuildMain.cpp new file mode 100644 index 000000000..f21c30c5e --- /dev/null +++ b/netbox/src/NetBox/UnityBuildMain.cpp @@ -0,0 +1,20 @@ +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NON_CONFORMING_SWPRINTFS + +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter + +#include "NetBox.cpp" +#include "FarDialog.cpp" +#include "FarPlugin.cpp" +#include "FarUtil.cpp" +#include "WinSCPFileSystem.cpp" +#include "WinSCPDialogs.cpp" +#include "WinSCPPlugin.cpp" +#include "FarConfiguration.cpp" +#include "FarInterface.cpp" +#include "XmlStorage.cpp" +#include "FarPluginStrings.cpp" +#include "../windows/WinInterface.cpp" + +#pragma warning(pop) diff --git a/netbox/src/NetBox/WinSCPDialogs.cpp b/netbox/src/NetBox/WinSCPDialogs.cpp new file mode 100644 index 000000000..ddba0dece --- /dev/null +++ b/netbox/src/NetBox/WinSCPDialogs.cpp @@ -0,0 +1,8793 @@ +#include +#pragma hdrstop + +#include "WinSCPPlugin.h" +#include "WinSCPFileSystem.h" +#include "FarDialog.h" +#include "FarConfiguration.h" +#include "FarInterface.h" + +#ifndef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plugin_version.hpp" + +enum TButtonResult +{ + brCancel = -1, + brOK = 1, + brConnect +}; + +class TWinSCPDialog : public TFarDialog +{ +public: + explicit TWinSCPDialog(TCustomFarPlugin * AFarPlugin); + + void AddStandardButtons(int Shift = 0, bool ButtonsOnly = false); + + TFarSeparator * ButtonSeparator; + TFarButton * OkButton; + TFarButton * CancelButton; +}; + +TWinSCPDialog::TWinSCPDialog(TCustomFarPlugin * AFarPlugin) : + TFarDialog(AFarPlugin), + ButtonSeparator(nullptr), + OkButton(nullptr), + CancelButton(nullptr) +{ +} + +void TWinSCPDialog::AddStandardButtons(int Shift, bool ButtonsOnly) +{ + if (!ButtonsOnly) + { + SetNextItemPosition(ipNewLine); + + ButtonSeparator = new TFarSeparator(this); + if (Shift >= 0) + { + ButtonSeparator->Move(0, Shift); + } + else + { + ButtonSeparator->SetTop(Shift); + ButtonSeparator->SetBottom(Shift); + } + } + + DebugAssert(OkButton == nullptr); + OkButton = new TFarButton(this); + if (ButtonsOnly) + { + if (Shift >= 0) + { + OkButton->Move(0, Shift); + } + else + { + OkButton->SetTop(Shift); + OkButton->SetBottom(Shift); + } + } + OkButton->SetCaption(GetMsg(MSG_BUTTON_OK)); + OkButton->SetDefault(true); + OkButton->SetResult(brOK); + OkButton->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + DebugAssert(CancelButton == nullptr); + CancelButton = new TFarButton(this); + CancelButton->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + CancelButton->SetResult(brCancel); + CancelButton->SetCenterGroup(true); +} + +class TTabButton; +class TTabbedDialog : public TWinSCPDialog +{ + friend class TTabButton; + +public: + explicit TTabbedDialog(TCustomFarPlugin * AFarPlugin, int TabCount); + virtual ~TTabbedDialog() {} + + intptr_t GetTab() const { return FTab; } + +protected: + void HideTabs(); + virtual void SelectTab(intptr_t Tab); + void TabButtonClick(TFarButton * Sender, bool & Close); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + virtual UnicodeString GetTabName(intptr_t Tab) const; + TTabButton * GetTabButton(intptr_t Tab) const; + intptr_t GetTabCount() const { return FTabCount; } + +private: + UnicodeString FOrigCaption; + intptr_t FTab; + intptr_t FTabCount; +}; + +class TTabButton : public TFarButton +{ +NB_DECLARE_CLASS(TTabButton) +public: + explicit TTabButton(TTabbedDialog * Dialog); + + intptr_t GetTab() const { return FTab; } + void SetTab(intptr_t Value) { FTab = Value; } + UnicodeString GetTabName() const { return FTabName; } + void SetTabName(const UnicodeString & Value); + +private: + UnicodeString FTabName; + intptr_t FTab; +}; + +TTabbedDialog::TTabbedDialog(TCustomFarPlugin * AFarPlugin, int TabCount) : + TWinSCPDialog(AFarPlugin), + FTab(0), + FTabCount(TabCount) +{ + // FAR WORKAROUND + // (to avoid first control on dialog be a button, that would be "pressed" + // when listbox loses focus) + TFarText * Text = new TFarText(this); + // make next item be inserted to default position + Text->Move(0, -1); + // on FAR 1.70 alpha 6 and later, empty text control would overwrite the + // dialog box caption + Text->SetVisible(false); +} + +void TTabbedDialog::HideTabs() +{ + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TFarDialogItem * Item = GetItem(Index); + if (Item->GetGroup()) + { + Item->SetVisible(false); + } + } +} + +void TTabbedDialog::SelectTab(intptr_t Tab) +{ + /*for (intptr_t I = FTabCount - 1; I >= 1; I--) + { + TTabButton * Button = TabButton(I); + Button->SetBrackets(Button->GetTab() == Tab ? brTight : brNone); + }*/ + if (FTab != Tab) + { + if (FTab) + { + ShowGroup(FTab, false); + } + ShowGroup(Tab, true); + FTab = Tab; + } + + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TFarDialogItem * Item = GetItem(Index); + if ((Item->GetGroup() == Tab) && Item->CanFocus()) + { + Item->SetFocus(); + break; + } + } + + if (FOrigCaption.IsEmpty()) + { + FOrigCaption = GetCaption(); + } + SetCaption(FORMAT(L"%s - %s", GetTabName(Tab).c_str(), FOrigCaption.c_str())); +} + +TTabButton * TTabbedDialog::GetTabButton(intptr_t Tab) const +{ + TTabButton * Result = nullptr; + for (intptr_t Index = 0; Index < GetItemCount(); ++Index) + { + TTabButton * T = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index)); + if ((T != nullptr) && (T->GetTab() == Tab)) + { + Result = T; + break; + } + } + + if (!Result) + { + DEBUG_PRINTF("Tab = %d", Tab); + } + DebugAssert(Result != nullptr); + + return Result; +} + +UnicodeString TTabbedDialog::GetTabName(intptr_t Tab) const +{ + return GetTabButton(Tab)->GetTabName(); +} + +void TTabbedDialog::TabButtonClick(TFarButton * Sender, bool & Close) +{ + TTabButton * Tab = NB_STATIC_DOWNCAST(TTabButton, Sender); + DebugAssert(Tab != nullptr); + + // HideTabs(); + SelectTab(Tab->GetTab()); + + Close = false; +} + +bool TTabbedDialog::Key(TFarDialogItem * /*Item*/, LONG_PTR KeyCode) +{ + bool Result = false; + if ((KeyCode == KEY_CTRLPGDN) || (KeyCode == KEY_CTRLPGUP) || + (KeyCode == KEY_CTRLNUMPAD3) || (KeyCode == KEY_CTRLNUMPAD9)) + { + intptr_t NewTab = FTab; + do + { + if ((KeyCode == KEY_CTRLPGDN) || (KeyCode == KEY_CTRLNUMPAD3)) + { + NewTab = NewTab == FTabCount - 1 ? 1 : NewTab + 1; + } + else + { + NewTab = NewTab == 1 ? FTabCount - 1 : NewTab - 1; + } + } + while (!GetTabButton(NewTab)->GetEnabled()); + SelectTab(NewTab); + Result = true; + } + return Result; +} + +TTabButton::TTabButton(TTabbedDialog * Dialog) : + TFarButton(Dialog), + FTab(0) +{ + SetCenterGroup(true); + SetOnClick(MAKE_CALLBACK(TTabbedDialog::TabButtonClick, Dialog)); +} + +void TTabButton::SetTabName(const UnicodeString & Value) +{ + UnicodeString Val = Value; + if (FTabName != Val) + { + UnicodeString C; + intptr_t P = ::Pos(Val, L"|"); + if (P > 0) + { + C = Val.SubString(1, P - 1); + Val.Delete(1, P); + } + else + { + C = Val; + } + SetCaption(C); + FTabName = ::StripHotkey(Val); + } +} + +bool TWinSCPPlugin::ConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + TFarText * Text; + + Dialog->SetSize(TPoint(67, 22)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_INTERFACE)).c_str())); + + TFarCheckBox * DisksMenuCheck = new TFarCheckBox(Dialog); + DisksMenuCheck->SetCaption(GetMsg(CONFIG_DISKS_MENU)); + + Dialog->SetNextItemPosition(ipNewLine); + + TFarCheckBox * PluginsMenuCheck = new TFarCheckBox(Dialog); + PluginsMenuCheck->SetCaption(GetMsg(CONFIG_PLUGINS_MENU)); + + TFarCheckBox * PluginsMenuCommandsCheck = new TFarCheckBox(Dialog); + PluginsMenuCommandsCheck->SetCaption(GetMsg(CONFIG_PLUGINS_MENU_COMMANDS)); + + TFarCheckBox * HostNameInTitleCheck = new TFarCheckBox(Dialog); + HostNameInTitleCheck->SetCaption(GetMsg(CONFIG_HOST_NAME_IN_TITLE)); + + new TFarSeparator(Dialog); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(CONFIG_COMAND_PREFIXES)); + + TFarEdit * CommandPrefixesEdit = new TFarEdit(Dialog); + + new TFarSeparator(Dialog); + + TFarCheckBox * CustomPanelCheck = new TFarCheckBox(Dialog); + CustomPanelCheck->SetCaption(GetMsg(CONFIG_PANEL_MODE_CHECK)); + CustomPanelCheck->SetEnabled(true); + + Text = new TFarText(Dialog); + Text->SetLeft(Text->GetLeft() + 4); + Text->SetEnabledDependency(CustomPanelCheck); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_TYPES)); + + Dialog->SetNextItemPosition(ipBelow); + + TFarEdit * CustomPanelTypesEdit = new TFarEdit(Dialog); + CustomPanelTypesEdit->SetEnabledDependency(CustomPanelCheck); + CustomPanelTypesEdit->SetWidth(CustomPanelTypesEdit->GetWidth() / 2 - 1); + + Dialog->SetNextItemPosition(ipRight); + + Text = new TFarText(Dialog); + Text->SetEnabledDependency(CustomPanelCheck); + Text->Move(0, -1); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_STATUS_TYPES)); + + Dialog->SetNextItemPosition(ipBelow); + + TFarEdit * CustomPanelStatusTypesEdit = new TFarEdit(Dialog); + CustomPanelStatusTypesEdit->SetEnabledDependency(CustomPanelCheck); + + Dialog->SetNextItemPosition(ipNewLine); + + Text = new TFarText(Dialog); + Text->SetLeft(Text->GetLeft() + 4); + Text->SetEnabledDependency(CustomPanelCheck); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_WIDTHS)); + + Dialog->SetNextItemPosition(ipBelow); + + TFarEdit * CustomPanelWidthsEdit = new TFarEdit(Dialog); + CustomPanelWidthsEdit->SetEnabledDependency(CustomPanelCheck); + CustomPanelWidthsEdit->SetWidth(CustomPanelTypesEdit->GetWidth()); + + Dialog->SetNextItemPosition(ipRight); + + Text = new TFarText(Dialog); + Text->SetEnabledDependency(CustomPanelCheck); + Text->Move(0, -1); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_STATUS_WIDTHS)); + + Dialog->SetNextItemPosition(ipBelow); + + TFarEdit * CustomPanelStatusWidthsEdit = new TFarEdit(Dialog); + CustomPanelStatusWidthsEdit->SetEnabledDependency(CustomPanelCheck); + + Dialog->SetNextItemPosition(ipNewLine); + + TFarCheckBox * CustomPanelFullScreenCheck = new TFarCheckBox(Dialog); + CustomPanelFullScreenCheck->SetLeft(CustomPanelFullScreenCheck->GetLeft() + 4); + CustomPanelFullScreenCheck->SetEnabledDependency(CustomPanelCheck); + CustomPanelFullScreenCheck->SetCaption(GetMsg(CONFIG_PANEL_MODE_FULL_SCREEN)); + + Text = new TFarText(Dialog); + Text->SetLeft(Text->GetLeft()); + Text->SetEnabledDependency(CustomPanelCheck); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_HINT)); + Text = new TFarText(Dialog); + Text->SetLeft(Text->GetLeft()); + Text->SetEnabledDependency(CustomPanelCheck); + Text->SetCaption(GetMsg(CONFIG_PANEL_MODE_HINT2)); + + Dialog->AddStandardButtons(); + + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + DisksMenuCheck->SetChecked(FarConfiguration->GetDisksMenu()); + PluginsMenuCheck->SetChecked(FarConfiguration->GetPluginsMenu()); + PluginsMenuCommandsCheck->SetChecked(FarConfiguration->GetPluginsMenuCommands()); + HostNameInTitleCheck->SetChecked(FarConfiguration->GetHostNameInTitle()); + CommandPrefixesEdit->SetText(FarConfiguration->GetCommandPrefixes()); + + CustomPanelCheck->SetChecked(FarConfiguration->GetCustomPanelModeDetailed()); + CustomPanelTypesEdit->SetText(FarConfiguration->GetColumnTypesDetailed()); + CustomPanelWidthsEdit->SetText(FarConfiguration->GetColumnWidthsDetailed()); + CustomPanelStatusTypesEdit->SetText(FarConfiguration->GetStatusColumnTypesDetailed()); + CustomPanelStatusWidthsEdit->SetText(FarConfiguration->GetStatusColumnWidthsDetailed()); + CustomPanelFullScreenCheck->SetChecked(FarConfiguration->GetFullScreenDetailed()); + + bool Result = (Dialog->ShowModal() == brOK); + if (Result) + { + FarConfiguration->SetDisksMenu(DisksMenuCheck->GetChecked()); + FarConfiguration->SetPluginsMenu(PluginsMenuCheck->GetChecked()); + FarConfiguration->SetPluginsMenuCommands(PluginsMenuCommandsCheck->GetChecked()); + FarConfiguration->SetHostNameInTitle(HostNameInTitleCheck->GetChecked()); + + FarConfiguration->SetCommandPrefixes(CommandPrefixesEdit->GetText()); + + FarConfiguration->SetCustomPanelModeDetailed(CustomPanelCheck->GetChecked()); + FarConfiguration->SetColumnTypesDetailed(CustomPanelTypesEdit->GetText()); + FarConfiguration->SetColumnWidthsDetailed(CustomPanelWidthsEdit->GetText()); + FarConfiguration->SetStatusColumnTypesDetailed(CustomPanelStatusTypesEdit->GetText()); + FarConfiguration->SetStatusColumnWidthsDetailed(CustomPanelStatusWidthsEdit->GetText()); + FarConfiguration->SetFullScreenDetailed(CustomPanelFullScreenCheck->GetChecked()); + } + return Result; +} + +bool TWinSCPPlugin::PanelConfigurationDialog() +{ + std::unique_ptr Dialog(new TWinSCPDialog(this)); + Dialog->SetSize(TPoint(65, 7)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_PANEL)).c_str())); + + TFarCheckBox * AutoReadDirectoryAfterOpCheck = new TFarCheckBox(Dialog.get()); + AutoReadDirectoryAfterOpCheck->SetCaption(GetMsg(CONFIG_AUTO_READ_DIRECTORY_AFTER_OP)); + + Dialog->AddStandardButtons(); + + AutoReadDirectoryAfterOpCheck->SetChecked(GetConfiguration()->GetAutoReadDirectoryAfterOp()); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + GetConfiguration()->SetAutoReadDirectoryAfterOp(AutoReadDirectoryAfterOpCheck->GetChecked()); + } + return Result; +} + +bool TWinSCPPlugin::LoggingConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + TFarSeparator * Separator; + TFarText * Text; + + Dialog->SetSize(TPoint(65, 15)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_LOGGING)).c_str())); + + TFarCheckBox * LoggingCheck = new TFarCheckBox(Dialog); + LoggingCheck->SetCaption(GetMsg(LOGGING_ENABLE)); + + Separator = new TFarSeparator(Dialog); + Separator->SetCaption(GetMsg(LOGGING_OPTIONS_GROUP)); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(LOGGING_LOG_PROTOCOL)); + Text->SetEnabledDependency(LoggingCheck); + + Dialog->SetNextItemPosition(ipRight); + + TFarComboBox * LogProtocolCombo = new TFarComboBox(Dialog); + LogProtocolCombo->SetDropDownList(true); + LogProtocolCombo->SetWidth(10); + for (intptr_t Index = 0; Index <= 2; ++Index) + { + LogProtocolCombo->GetItems()->Add(GetMsg(LOGGING_LOG_PROTOCOL_0 + Index)); + } + LogProtocolCombo->SetEnabledDependency(LoggingCheck); + + Dialog->SetNextItemPosition(ipNewLine); + + new TFarSeparator(Dialog); + + TFarCheckBox * LogToFileCheck = new TFarCheckBox(Dialog); + LogToFileCheck->SetCaption(GetMsg(LOGGING_LOG_TO_FILE)); + LogToFileCheck->SetEnabledDependency(LoggingCheck); + + TFarEdit * LogFileNameEdit = new TFarEdit(Dialog); + LogFileNameEdit->SetLeft(LogFileNameEdit->GetLeft() + 4); + LogFileNameEdit->SetHistory(LOG_FILE_HISTORY); + LogFileNameEdit->SetEnabledDependency(LogToFileCheck); + + Dialog->SetNextItemPosition(ipBelow); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(LOGGING_LOG_FILE_HINT1)); + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(LOGGING_LOG_FILE_HINT2)); + + TFarRadioButton * LogFileAppendButton = new TFarRadioButton(Dialog); + LogFileAppendButton->SetCaption(GetMsg(LOGGING_LOG_FILE_APPEND)); + LogFileAppendButton->SetEnabledDependency(LogToFileCheck); + + Dialog->SetNextItemPosition(ipRight); + + TFarRadioButton * LogFileOverwriteButton = new TFarRadioButton(Dialog); + LogFileOverwriteButton->SetCaption(GetMsg(LOGGING_LOG_FILE_OVERWRITE)); + LogFileOverwriteButton->SetEnabledDependency(LogToFileCheck); + + Dialog->AddStandardButtons(); + + LoggingCheck->SetChecked(GetConfiguration()->GetLogging()); + LogProtocolCombo->SetItemIndex(GetConfiguration()->GetLogProtocol()); + LogToFileCheck->SetChecked(GetConfiguration()->GetLogToFile()); + LogFileNameEdit->SetText( + (!GetConfiguration()->GetLogToFile() && GetConfiguration()->GetLogFileName().IsEmpty()) ? + ::IncludeTrailingBackslash(GetSystemTemporaryDirectory()) + L"&s.log" : + GetConfiguration()->GetLogFileName()); + LogFileAppendButton->SetChecked(GetConfiguration()->GetLogFileAppend()); + LogFileOverwriteButton->SetChecked(!GetConfiguration()->GetLogFileAppend()); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + GetConfiguration()->SetLogging(LoggingCheck->GetChecked()); + GetConfiguration()->SetLogProtocol(LogProtocolCombo->GetItemIndex()); + GetConfiguration()->SetLogToFile(LogToFileCheck->GetChecked()); + if (LogToFileCheck->GetChecked()) + { + GetConfiguration()->SetLogFileName(LogFileNameEdit->GetText()); + } + GetConfiguration()->SetLogFileAppend(LogFileAppendButton->GetChecked()); + } + return Result; +} + +bool TWinSCPPlugin::TransferConfigurationDialog() +{ + UnicodeString Caption = FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_TRANSFER)).c_str()); + + TGUICopyParamType & CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + bool Result = CopyParamDialog(Caption, CopyParam, 0); + if (Result) + { + GetGUIConfiguration()->SetDefaultCopyParam(CopyParam); + } + + return Result; +} + +bool TWinSCPPlugin::EnduranceConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + TFarSeparator * Separator; + TFarText * Text; + + Dialog->SetSize(TPoint(76, 13)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_ENDURANCE)).c_str())); + + Separator = new TFarSeparator(Dialog); + Separator->SetCaption(GetMsg(TRANSFER_RESUME)); + + TFarRadioButton * ResumeOnButton = new TFarRadioButton(Dialog); + ResumeOnButton->SetCaption(GetMsg(TRANSFER_RESUME_ON)); + + TFarRadioButton * ResumeSmartButton = new TFarRadioButton(Dialog); + ResumeSmartButton->SetCaption(GetMsg(TRANSFER_RESUME_SMART)); + intptr_t ResumeThresholdLeft = ResumeSmartButton->GetRight(); + + TFarRadioButton * ResumeOffButton = new TFarRadioButton(Dialog); + ResumeOffButton->SetCaption(GetMsg(TRANSFER_RESUME_OFF)); + + TFarEdit * ResumeThresholdEdit = new TFarEdit(Dialog); + ResumeThresholdEdit->Move(0, -2); + ResumeThresholdEdit->SetLeft(ResumeThresholdLeft + 3); + ResumeThresholdEdit->SetFixed(true); + ResumeThresholdEdit->SetMask(L"9999999"); + ResumeThresholdEdit->SetWidth(9); + ResumeThresholdEdit->SetEnabledDependency(ResumeSmartButton); + + Dialog->SetNextItemPosition(ipRight); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(TRANSFER_RESUME_THRESHOLD_UNIT)); + Text->SetEnabledDependency(ResumeSmartButton); + + Dialog->SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(Dialog); + Separator->SetCaption(GetMsg(TRANSFER_SESSION_REOPEN_GROUP)); + Separator->Move(0, 1); + + TFarCheckBox * SessionReopenAutoCheck = new TFarCheckBox(Dialog); + SessionReopenAutoCheck->SetCaption(GetMsg(TRANSFER_SESSION_REOPEN_AUTO_LABEL)); + + Dialog->SetNextItemPosition(ipRight); + + TFarEdit * SessionReopenAutoEdit = new TFarEdit(Dialog); + SessionReopenAutoEdit->SetEnabledDependency(SessionReopenAutoCheck); + SessionReopenAutoEdit->SetFixed(true); + SessionReopenAutoEdit->SetMask(L"999"); + SessionReopenAutoEdit->SetWidth(5); + SessionReopenAutoEdit->Move(12, 0); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(TRANSFER_SESSION_REOPEN_AUTO_LABEL2)); + Text->SetEnabledDependency(SessionReopenAutoCheck); + + Dialog->SetNextItemPosition(ipNewLine); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(TRANSFER_SESSION_REOPEN_NUMBER_OF_RETRIES_LABEL)); + Text->SetEnabledDependency(SessionReopenAutoCheck); + Text->Move(4, 0); + + Dialog->SetNextItemPosition(ipRight); + + TFarEdit * SessionReopenNumberOfRetriesEdit = new TFarEdit(Dialog); + SessionReopenNumberOfRetriesEdit->SetEnabledDependency(SessionReopenAutoCheck); + SessionReopenNumberOfRetriesEdit->SetFixed(true); + SessionReopenNumberOfRetriesEdit->SetMask(L"999"); + SessionReopenNumberOfRetriesEdit->SetWidth(5); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(TRANSFER_SESSION_REOPEN_NUMBER_OF_RETRIES_LABEL2)); + Text->SetEnabledDependency(SessionReopenAutoCheck); + + Dialog->AddStandardButtons(); + + TGUICopyParamType & CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + ResumeOnButton->SetChecked(CopyParam.GetResumeSupport() == rsOn); + ResumeSmartButton->SetChecked(CopyParam.GetResumeSupport() == rsSmart); + ResumeOffButton->SetChecked(CopyParam.GetResumeSupport() == rsOff); + ResumeThresholdEdit->SetAsInteger( + static_cast(CopyParam.GetResumeThreshold() / 1024)); + + SessionReopenAutoCheck->SetChecked((GetConfiguration()->GetSessionReopenAuto() > 0)); + SessionReopenAutoEdit->SetAsInteger((GetConfiguration()->GetSessionReopenAuto() > 0 ? + (GetConfiguration()->GetSessionReopenAuto() / 1000) : 5)); + intptr_t Value = GetConfiguration()->GetSessionReopenAutoMaximumNumberOfRetries(); + SessionReopenNumberOfRetriesEdit->SetAsInteger(Value < 0 || Value > 99 ? + CONST_DEFAULT_NUMBER_OF_RETRIES : Value); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + { + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + if (ResumeOnButton->GetChecked()) + { + CopyParam.SetResumeSupport(rsOn); + } + if (ResumeSmartButton->GetChecked()) + { + CopyParam.SetResumeSupport(rsSmart); + } + if (ResumeOffButton->GetChecked()) + { + CopyParam.SetResumeSupport(rsOff); + } + CopyParam.SetResumeThreshold(ResumeThresholdEdit->GetAsInteger() * 1024); + + GetGUIConfiguration()->SetDefaultCopyParam(CopyParam); + + GetConfiguration()->SetSessionReopenAuto( + (SessionReopenAutoCheck->GetChecked() ? (SessionReopenAutoEdit->GetAsInteger() * 1000) : 0)); + GetConfiguration()->SetSessionReopenAutoMaximumNumberOfRetries( + (SessionReopenAutoCheck->GetChecked() ? SessionReopenNumberOfRetriesEdit->GetAsInteger() : CONST_DEFAULT_NUMBER_OF_RETRIES)); + } + } + return Result; +} + +bool TWinSCPPlugin::QueueConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + TFarText * Text; + + Dialog->SetSize(TPoint(76, 11)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_BACKGROUND)).c_str())); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(TRANSFER_QUEUE_LIMIT)); + + Dialog->SetNextItemPosition(ipRight); + + TFarEdit * QueueTransferLimitEdit = new TFarEdit(Dialog); + QueueTransferLimitEdit->SetFixed(true); + QueueTransferLimitEdit->SetMask(L"9"); + QueueTransferLimitEdit->SetWidth(3); + + Dialog->SetNextItemPosition(ipNewLine); + + TFarCheckBox * QueueCheck = new TFarCheckBox(Dialog); + QueueCheck->SetCaption(GetMsg(TRANSFER_QUEUE_DEFAULT)); + + TFarCheckBox * QueueAutoPopupCheck = new TFarCheckBox(Dialog); + QueueAutoPopupCheck->SetCaption(GetMsg(TRANSFER_AUTO_POPUP)); + + TFarCheckBox * RememberPasswordCheck = new TFarCheckBox(Dialog); + RememberPasswordCheck->SetCaption(GetMsg(TRANSFER_REMEMBER_PASSWORD)); + + TFarCheckBox * QueueBeepCheck = new TFarCheckBox(Dialog); + QueueBeepCheck->SetCaption(GetMsg(TRANSFER_QUEUE_BEEP)); + + Dialog->AddStandardButtons(); + + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + QueueTransferLimitEdit->SetAsInteger(FarConfiguration->GetQueueTransfersLimit()); + QueueCheck->SetChecked(FarConfiguration->GetDefaultCopyParam().GetQueue()); + QueueAutoPopupCheck->SetChecked(FarConfiguration->GetQueueAutoPopup()); + RememberPasswordCheck->SetChecked(GetGUIConfiguration()->GetSessionRememberPassword()); + QueueBeepCheck->SetChecked(FarConfiguration->GetQueueBeep()); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + TGUICopyParamType & CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + + FarConfiguration->SetQueueTransfersLimit(QueueTransferLimitEdit->GetAsInteger()); + CopyParam.SetQueue(QueueCheck->GetChecked()); + FarConfiguration->SetQueueAutoPopup(QueueAutoPopupCheck->GetChecked()); + GetGUIConfiguration()->SetSessionRememberPassword(RememberPasswordCheck->GetChecked()); + FarConfiguration->SetQueueBeep(QueueBeepCheck->GetChecked()); + + GetGUIConfiguration()->SetDefaultCopyParam(CopyParam); + } + return Result; +} + +class TTransferEditorConfigurationDialog : public TWinSCPDialog +{ +public: + explicit TTransferEditorConfigurationDialog(TCustomFarPlugin * AFarPlugin); + + bool Execute(); + +protected: + virtual void Change(); + +private: + virtual void UpdateControls(); + +private: + TFarCheckBox * EditorMultipleCheck; + TFarCheckBox * EditorUploadOnSaveCheck; + TFarRadioButton * EditorDownloadDefaultButton; + TFarRadioButton * EditorDownloadOptionsButton; + TFarRadioButton * EditorUploadSameButton; + TFarRadioButton * EditorUploadOptionsButton; +}; + +TTransferEditorConfigurationDialog::TTransferEditorConfigurationDialog( + TCustomFarPlugin * AFarPlugin) : + TWinSCPDialog(AFarPlugin) +{ + TFarSeparator * Separator; + + SetSize(TPoint(65, 14)); + SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_TRANSFER_EDITOR)).c_str())); + + EditorMultipleCheck = new TFarCheckBox(this); + EditorMultipleCheck->SetCaption(GetMsg(TRANSFER_EDITOR_MULTIPLE)); + + EditorUploadOnSaveCheck = new TFarCheckBox(this); + EditorUploadOnSaveCheck->SetCaption(GetMsg(TRANSFER_EDITOR_UPLOAD_ON_SAVE)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(TRANSFER_EDITOR_DOWNLOAD)); + + EditorDownloadDefaultButton = new TFarRadioButton(this); + EditorDownloadDefaultButton->SetCaption(GetMsg(TRANSFER_EDITOR_DOWNLOAD_DEFAULT)); + + EditorDownloadOptionsButton = new TFarRadioButton(this); + EditorDownloadOptionsButton->SetCaption(GetMsg(TRANSFER_EDITOR_DOWNLOAD_OPTIONS)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(TRANSFER_EDITOR_UPLOAD)); + + EditorUploadSameButton = new TFarRadioButton(this); + EditorUploadSameButton->SetCaption(GetMsg(TRANSFER_EDITOR_UPLOAD_SAME)); + + EditorUploadOptionsButton = new TFarRadioButton(this); + EditorUploadOptionsButton->SetCaption(GetMsg(TRANSFER_EDITOR_UPLOAD_OPTIONS)); + + AddStandardButtons(); +} + +bool TTransferEditorConfigurationDialog::Execute() +{ + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + EditorDownloadDefaultButton->SetChecked(FarConfiguration->GetEditorDownloadDefaultMode()); + EditorDownloadOptionsButton->SetChecked(!FarConfiguration->GetEditorDownloadDefaultMode()); + EditorUploadSameButton->SetChecked(FarConfiguration->GetEditorUploadSameOptions()); + EditorUploadOptionsButton->SetChecked(!FarConfiguration->GetEditorUploadSameOptions()); + EditorUploadOnSaveCheck->SetChecked(FarConfiguration->GetEditorUploadOnSave()); + EditorMultipleCheck->SetChecked(FarConfiguration->GetEditorMultiple()); + + bool Result = (ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + FarConfiguration->SetEditorDownloadDefaultMode(EditorDownloadDefaultButton->GetChecked()); + FarConfiguration->SetEditorUploadSameOptions(EditorUploadSameButton->GetChecked()); + FarConfiguration->SetEditorUploadOnSave(EditorUploadOnSaveCheck->GetChecked()); + FarConfiguration->SetEditorMultiple(EditorMultipleCheck->GetChecked()); + } + + return Result; +} + +void TTransferEditorConfigurationDialog::Change() +{ + TWinSCPDialog::Change(); + + if (GetHandle()) + { + LockChanges(); + SCOPE_EXIT + { + UnlockChanges(); + }; + UpdateControls(); + } +} + +void TTransferEditorConfigurationDialog::UpdateControls() +{ + EditorDownloadDefaultButton->SetEnabled(!EditorMultipleCheck->GetChecked()); + EditorDownloadOptionsButton->SetEnabled(EditorDownloadDefaultButton->GetEnabled()); + + EditorUploadSameButton->SetEnabled( + !EditorMultipleCheck->GetChecked() && !EditorUploadOnSaveCheck->GetChecked()); + EditorUploadOptionsButton->SetEnabled(EditorUploadSameButton->GetEnabled()); +} + +bool TWinSCPPlugin::TransferEditorConfigurationDialog() +{ + std::unique_ptr Dialog(new TTransferEditorConfigurationDialog(this)); + bool Result = Dialog->Execute(); + return Result; +} + +bool TWinSCPPlugin::ConfirmationsConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + Dialog->SetSize(TPoint(67, 10)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_CONFIRMATIONS)).c_str())); + + TFarCheckBox * ConfirmOverwritingCheck = new TFarCheckBox(Dialog); + ConfirmOverwritingCheck->SetAllowGrayed(true); + ConfirmOverwritingCheck->SetCaption(GetMsg(CONFIRMATIONS_CONFIRM_OVERWRITING)); + + TFarCheckBox * ConfirmCommandSessionCheck = new TFarCheckBox(Dialog); + ConfirmCommandSessionCheck->SetCaption(GetMsg(CONFIRMATIONS_OPEN_COMMAND_SESSION)); + + TFarCheckBox * ConfirmResumeCheck = new TFarCheckBox(Dialog); + ConfirmResumeCheck->SetCaption(GetMsg(CONFIRMATIONS_CONFIRM_RESUME)); + + TFarCheckBox * ConfirmSynchronizedBrowsingCheck = new TFarCheckBox(Dialog); + ConfirmSynchronizedBrowsingCheck->SetCaption(GetMsg(CONFIRMATIONS_SYNCHRONIZED_BROWSING)); + + Dialog->AddStandardButtons(); + + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + ConfirmOverwritingCheck->SetSelected(!FarConfiguration->GetConfirmOverwritingOverride() ? + BSTATE_3STATE : (GetConfiguration()->GetConfirmOverwriting() ? BSTATE_CHECKED : + BSTATE_UNCHECKED)); + ConfirmCommandSessionCheck->SetChecked(GetGUIConfiguration()->GetConfirmCommandSession()); + ConfirmResumeCheck->SetChecked(GetGUIConfiguration()->GetConfirmResume()); + ConfirmSynchronizedBrowsingCheck->SetChecked(FarConfiguration->GetConfirmSynchronizedBrowsing()); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + FarConfiguration->SetConfirmOverwritingOverride( + ConfirmOverwritingCheck->GetSelected() != BSTATE_3STATE); + GetGUIConfiguration()->SetConfirmCommandSession(ConfirmCommandSessionCheck->GetChecked()); + GetGUIConfiguration()->SetConfirmResume(ConfirmResumeCheck->GetChecked()); + if (FarConfiguration->GetConfirmOverwritingOverride()) + { + GetConfiguration()->SetConfirmOverwriting(ConfirmOverwritingCheck->GetChecked()); + } + FarConfiguration->SetConfirmSynchronizedBrowsing(ConfirmSynchronizedBrowsingCheck->GetChecked()); + } + return Result; +} + +bool TWinSCPPlugin::IntegrationConfigurationDialog() +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + TFarText * Text; + + Dialog->SetSize(TPoint(65, 14)); + Dialog->SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_INTEGRATION)).c_str())); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(INTEGRATION_PUTTY)); + + TFarEdit * PuttyPathEdit = new TFarEdit(Dialog); + + TFarCheckBox * PuttyPasswordCheck = new TFarCheckBox(Dialog); + PuttyPasswordCheck->SetCaption(GetMsg(INTEGRATION_PUTTY_PASSWORD)); + PuttyPasswordCheck->SetEnabledDependency(PuttyPathEdit); + + TFarCheckBox * TelnetForFtpInPuttyCheck = new TFarCheckBox(Dialog); + TelnetForFtpInPuttyCheck->SetCaption(GetMsg(INTEGRATION_TELNET_FOR_FTP_IN_PUTTY)); + TelnetForFtpInPuttyCheck->SetEnabledDependency(PuttyPathEdit); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(INTEGRATION_PAGEANT)); + + TFarEdit * PageantPathEdit = new TFarEdit(Dialog); + + Text = new TFarText(Dialog); + Text->SetCaption(GetMsg(INTEGRATION_PUTTYGEN)); + + TFarEdit * PuttygenPathEdit = new TFarEdit(Dialog); + + Dialog->AddStandardButtons(); + + PuttyPathEdit->SetText(GetGUIConfiguration()->GetPuttyPath()); + PuttyPasswordCheck->SetChecked(GetGUIConfiguration()->GetPuttyPassword()); + TelnetForFtpInPuttyCheck->SetChecked(GetGUIConfiguration()->GetTelnetForFtpInPutty()); + PageantPathEdit->SetText(GetFarConfiguration()->GetPageantPath()); + PuttygenPathEdit->SetText(GetFarConfiguration()->GetPuttygenPath()); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + GetGUIConfiguration()->SetPuttyPath(PuttyPathEdit->GetText()); + GetGUIConfiguration()->SetPuttyPassword(PuttyPasswordCheck->GetChecked()); + GetGUIConfiguration()->SetTelnetForFtpInPutty(TelnetForFtpInPuttyCheck->GetChecked()); + GetFarConfiguration()->SetPageantPath(PageantPathEdit->GetText()); + GetFarConfiguration()->SetPuttygenPath(PuttygenPathEdit->GetText()); + } + return Result; +} + +class TAboutDialog : public TFarDialog +{ +public: + explicit TAboutDialog(TCustomFarPlugin * AFarPlugin); + +private: + void UrlButtonClick(TFarButton * Sender, bool & Close); + void UrlTextClick(TFarDialogItem * Item, MOUSE_EVENT_RECORD * Event); +}; + +UnicodeString ReplaceCopyright(const UnicodeString & S) +{ + return ::StringReplaceAll(S, L"©", L"(c)"); +} + +TAboutDialog::TAboutDialog(TCustomFarPlugin * AFarPlugin) : + TFarDialog(AFarPlugin) +{ + TFarText * Text; + TFarButton * Button; + + // UnicodeString ProductName = GetConfiguration()->GetFileInfoString(L"ProductName"); + UnicodeString ProductName = LoadStr(WINSCPFAR_NAME); + UnicodeString Comments; + try + { + Comments = GetConfiguration()->GetFileInfoString(L"Comments"); + } + catch (...) + { + Comments.Clear(); + } + UnicodeString LegalCopyright; // = GetConfiguration()->GetFileInfoString(L"LegalCopyright"); + + int Height = 15; +#ifndef NO_FILEZILLA + Height += 2; +#endif + if (!ProductName.IsEmpty()) + { + Height++; + } + if (!Comments.IsEmpty()) + { + Height++; + } + if (!LegalCopyright.IsEmpty()) + { + Height++; + } + SetSize(TPoint(55, Height)); + + SetCaption(FORMAT(L"%s - %s", + GetMsg(PLUGIN_TITLE).c_str(), ::StripHotkey(GetMsg(CONFIG_ABOUT)).c_str())); + Text = new TFarText(this); + Text->SetCaption(GetConfiguration()->GetFileInfoString(L"FileDescription")); + Text->SetCenterGroup(true); + + Text = new TFarText(this); + Text->SetCaption(FORMAT(GetMsg(ABOUT_VERSION).c_str(), GetConfiguration()->GetProductVersion().c_str(), NETBOX_VERSION_BUILD)); + Text->SetCenterGroup(true); + + Text = new TFarText(this); + Text->Move(0, 1); + Text->SetCaption(LoadStr(WINSCPFAR_BASED_ON)); + Text->SetCenterGroup(true); + + Text = new TFarText(this); + Text->SetCaption(FMTLOAD(WINSCPFAR_BASED_VERSION, LoadStr(WINSCPFAR_VERSION).c_str())); + Text->SetCenterGroup(true); + + if (!ProductName.IsEmpty()) + { + Text = new TFarText(this); + Text->SetCaption(FORMAT(GetMsg(ABOUT_PRODUCT_VERSION).c_str(), + ProductName.c_str(), + LoadStr(WINSCP_VERSION).c_str())); + Text->SetCenterGroup(true); + } + + Text = new TFarText(this); + Text->SetCaption(LoadStr(WINSCPFAR_BASED_COPYRIGHT)); + Text->SetCenterGroup(true); + + if (!Comments.IsEmpty()) + { + Text = new TFarText(this); + if (ProductName.IsEmpty()) + { + Text->Move(0, 1); + } + Text->SetCaption(Comments); + Text->SetCenterGroup(true); + } +#if 0 + if (!LegalCopyright.IsEmpty()) + { + Text = new TFarText(this); + Text->Move(0, 1); + Text->SetCaption(GetConfiguration()->GetFileInfoString(L"LegalCopyright")); + Text->SetCenterGroup(true); + } + + Text = new TFarText(this); + if (LegalCopyright.IsEmpty()) + { + Text->Move(0, 1); + } + Text->SetCaption(GetMsg(ABOUT_URL)); + // FIXME Text->SetColor(static_cast((GetSystemColor(COL_DIALOGTEXT) & 0xF0) | 0x09)); + Text->SetCenterGroup(true); + Text->SetOnMouseClick(MAKE_CALLBACK(TAboutDialog::UrlTextClick, this)); + + Button = new TFarButton(this); + Button->Move(0, 1); + Button->SetCaption(GetMsg(ABOUT_HOMEPAGE)); + Button->SetOnClick(MAKE_CALLBACK(TAboutDialog::UrlButtonClick, this)); + Button->SetTag(1); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(ABOUT_FORUM)); + Button->SetOnClick(MAKE_CALLBACK(TAboutDialog::UrlButtonClick, this)); + Button->SetTag(2); + Button->SetCenterGroup(true); + SetNextItemPosition(ipNewLine); +#endif + + new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetCaption(FMTLOAD(PUTTY_BASED_ON, LoadStr(PUTTY_VERSION).c_str())); + Text->SetCenterGroup(true); + + Text = new TFarText(this); + Text->SetCaption(LoadStr(PUTTY_COPYRIGHT)); + Text->SetCenterGroup(true); + +#ifndef NO_FILEZILLA + Text = new TFarText(this); + Text->SetCaption(LoadStr(FILEZILLA_BASED_ON2)); + Text->SetCenterGroup(true); + + Text = new TFarText(this); + Text->SetCaption(LoadStr(FILEZILLA_COPYRIGHT2)); + Text->SetCenterGroup(true); +#endif + + new TFarSeparator(this); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_CLOSE)); + Button->SetDefault(true); + Button->SetResult(brOK); + Button->SetCenterGroup(true); + Button->SetFocus(); +} + +void TAboutDialog::UrlTextClick(TFarDialogItem * /*Item*/, + MOUSE_EVENT_RECORD * /*Event*/) +{ +#ifndef __linux__ + UnicodeString Address = GetMsg(ABOUT_URL); + ::ShellExecute(nullptr, L"open", const_cast(Address.c_str()), nullptr, nullptr, SW_SHOWNORMAL); +#endif +} + +void TAboutDialog::UrlButtonClick(TFarButton * Sender, bool & /*Close*/) +{ +#ifndef __linux__ + UnicodeString Address; + switch (Sender->GetTag()) + { + case 1: + Address = GetMsg(ABOUT_URL) + L"eng/docs/far"; + break; + case 2: + Address = GetMsg(ABOUT_URL) + L"forum/"; + break; + } + ::ShellExecute(nullptr, L"open", const_cast(Address.c_str()), nullptr, nullptr, SW_SHOWNORMAL); +#endif +} + +void TWinSCPPlugin::AboutDialog() +{ + std::unique_ptr Dialog(new TAboutDialog(this)); + Dialog->ShowModal(); +} + +class TPasswordDialog : public TFarDialog +{ +public: + explicit TPasswordDialog(TCustomFarPlugin * AFarPlugin, + const UnicodeString & SessionName, TPromptKind Kind, const UnicodeString & Name, + const UnicodeString & Instructions, const TStrings * Prompts, + bool StoredCredentialsTried); + virtual ~TPasswordDialog(); + bool Execute(TStrings * Results); + +private: + void ShowPromptClick(TFarButton * Sender, bool & Close); + void GenerateLabel(const UnicodeString & ACaption, bool & Truncated); + TFarEdit * GenerateEdit(bool Echo); + void GeneratePrompt(bool ShowSavePassword, + const UnicodeString & Instructions, const TStrings * Prompts, bool & Truncated); + +private: + TSessionData * FSessionData; + UnicodeString FPrompt; + TList * FEdits; + TFarCheckBox * SavePasswordCheck; +}; + +TPasswordDialog::TPasswordDialog(TCustomFarPlugin * AFarPlugin, + const UnicodeString & SessionName, TPromptKind Kind, const UnicodeString & Name, + const UnicodeString & Instructions, const TStrings * Prompts, + bool /*StoredCredentialsTried*/) : + TFarDialog(AFarPlugin), + FSessionData(nullptr), + FEdits(new TList()), + SavePasswordCheck(nullptr) +{ + TFarButton * Button; + + bool ShowSavePassword = false; + if (((Kind == pkPassword) || (Kind == pkTIS) || (Kind == pkCryptoCard) || + (Kind == pkKeybInteractive)) && + (Prompts->GetCount() == 1) && // FLAGSET((intptr_t)Prompts->GetObject(0), pupRemember) && + !SessionName.IsEmpty()) + // // StoredCredentialsTried) + { + FSessionData = NB_STATIC_DOWNCAST(TSessionData, StoredSessions->FindByName(SessionName)); + ShowSavePassword = (FSessionData != nullptr); + } + + bool Truncated = false; + GeneratePrompt(ShowSavePassword, Instructions, Prompts, Truncated); + + SetCaption(Name); + + if (ShowSavePassword) + { + SavePasswordCheck = new TFarCheckBox(this); + SavePasswordCheck->SetCaption(GetMsg(PASSWORD_SAVE)); + } + else + { + SavePasswordCheck = nullptr; + } + + new TFarSeparator(this); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_OK)); + Button->SetDefault(true); + Button->SetResult(brOK); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + if (Truncated) + { + Button = new TFarButton(this); + Button->SetCaption(GetMsg(PASSWORD_SHOW_PROMPT)); + Button->SetOnClick(MAKE_CALLBACK(TPasswordDialog::ShowPromptClick, this)); + Button->SetCenterGroup(true); + } + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + Button->SetResult(brCancel); + Button->SetCenterGroup(true); +} + +TPasswordDialog::~TPasswordDialog() +{ + SAFE_DESTROY(FEdits); +} + +void TPasswordDialog::GenerateLabel(const UnicodeString & ACaption, + bool & Truncated) +{ + UnicodeString Caption = ACaption; + TFarText * Result = new TFarText(this); + + if (!FPrompt.IsEmpty()) + { + FPrompt += L"\n\n"; + } + FPrompt += Caption; + + if (GetSize().x - 10 < static_cast(Caption.Length())) + { + Caption.SetLength(GetSize().x - 10 - 4); + Caption += L" ..."; + Truncated = true; + } + + Result->SetCaption(Caption); +} + +TFarEdit * TPasswordDialog::GenerateEdit(bool Echo) +{ + TFarEdit * Result = new TFarEdit(this); + Result->SetPassword(!Echo); + return Result; +} + +void TPasswordDialog::GeneratePrompt(bool ShowSavePassword, + const UnicodeString & Instructions, const TStrings * Prompts, bool & Truncated) +{ + FEdits->Clear(); + TPoint S = TPoint(40, ShowSavePassword ? 1 : 0); + + int x = static_cast(Instructions.Length()); + if (S.x < x) + { + S.x = x; + } + if (!Instructions.IsEmpty()) + { + S.y += 2; + } + + for (intptr_t Index = 0; Index < Prompts->GetCount(); ++Index) + { + int l = static_cast(Prompts->GetString(Index).Length()); + if (S.x < l) + { + S.x = l; + } + S.y += 2; + } + + if (S.x > 80 - 10) + { + S.x = 80 - 10; + } + + SetSize(TPoint(S.x + 10, S.y + 6)); + + if (!Instructions.IsEmpty()) + { + GenerateLabel(Instructions, Truncated); + // dumb way to add empty line + GenerateLabel(L"", Truncated); + } + + for (intptr_t Index = 0; Index < Prompts->GetCount(); ++Index) + { + GenerateLabel(Prompts->GetString(Index), Truncated); + + FEdits->Add(GenerateEdit(FLAGSET(reinterpret_cast(Prompts->GetObj(Index)), pupEcho))); + } +} + +void TPasswordDialog::ShowPromptClick(TFarButton * /*Sender*/, + bool & /*Close*/) +{ + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + + WinSCPPlugin->MoreMessageDialog(FPrompt, nullptr, qtInformation, qaOK); +} + +bool TPasswordDialog::Execute(TStrings * Results) +{ + for (intptr_t Index = 0; Index < FEdits->GetCount(); ++Index) + { + NB_STATIC_DOWNCAST(TFarEdit, FEdits->GetItem(Index))->SetText(Results->GetString(Index)); + } + + bool Result = (ShowModal() != brCancel); + if (Result) + { + for (intptr_t Index = 0; Index < FEdits->GetCount(); ++Index) + { + UnicodeString Text = NB_STATIC_DOWNCAST(TFarEdit, FEdits->GetItem(Index))->GetText(); + Results->SetString(Index, Text); + } + + if ((SavePasswordCheck != nullptr) && SavePasswordCheck->GetChecked()) + { + DebugAssert(FSessionData != nullptr); + FSessionData->SetPassword(Results->GetString(0)); + // modified only, explicit + StoredSessions->Save(false, true); + } + } + return Result; +} + +bool TWinSCPFileSystem::PasswordDialog(TSessionData * SessionData, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, + TStrings * Prompts, + TStrings * Results, bool StoredCredentialsTried) +{ + std::unique_ptr Dialog(new TPasswordDialog(FPlugin, SessionData->GetName(), + Kind, Name, Instructions, Prompts, StoredCredentialsTried)); + bool Result = Dialog->Execute(Results); + return Result; +} + +bool TWinSCPFileSystem::BannerDialog(const UnicodeString & SessionName, + const UnicodeString & Banner, bool & NeverShowAgain, intptr_t Options) +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(FPlugin)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + Dialog->SetSize(TPoint(70, 21)); + Dialog->SetCaption(FORMAT(GetMsg(BANNER_TITLE).c_str(), SessionName.c_str())); + + TFarLister * Lister = new TFarLister(Dialog); + FarWrapText(Banner, Lister->GetItems(), Dialog->GetBorderBox()->GetWidth() - 4); + Lister->SetHeight(15); + Lister->SetLeft(Dialog->GetBorderBox()->GetLeft() + 1); + Lister->SetRight(Dialog->GetBorderBox()->GetRight() - (Lister->GetScrollBar() ? 0 : 1)); + + new TFarSeparator(Dialog); + + TFarCheckBox * NeverShowAgainCheck = nullptr; + if (FLAGCLEAR(Options, boDisableNeverShowAgain)) + { + NeverShowAgainCheck = new TFarCheckBox(Dialog); + NeverShowAgainCheck->SetCaption(GetMsg(BANNER_NEVER_SHOW_AGAIN)); + NeverShowAgainCheck->SetVisible(FLAGCLEAR(Options, boDisableNeverShowAgain)); + NeverShowAgainCheck->SetChecked(NeverShowAgain); + + Dialog->SetNextItemPosition(ipRight); + } + + TFarButton * Button = new TFarButton(Dialog); + Button->SetCaption(GetMsg(BANNER_CONTINUE)); + Button->SetDefault(true); + Button->SetResult(brOK); + if (NeverShowAgainCheck != nullptr) + { + Button->SetLeft(Dialog->GetBorderBox()->GetRight() - Button->GetWidth() - 1); + } + else + { + Button->SetCenterGroup(true); + } + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + if (NeverShowAgainCheck != nullptr) + { + NeverShowAgain = NeverShowAgainCheck->GetChecked(); + } + } + return Result; +} + +class TSessionDialog : public TTabbedDialog +{ +public: + enum TSessionTab + { + tabSession = 1, tabEnvironment, tabDirectories, tabSFTP, tabSCP, tabFTP, + tabConnection, tabTunnel, tabProxy, tabSsh, tabKex, tabAuthentication, tabBugs, tabWebDAV, tabCount + }; + + explicit TSessionDialog(TCustomFarPlugin * AFarPlugin, TSessionActionEnum Action); + virtual ~TSessionDialog(); + + bool Execute(TSessionData * Data, TSessionActionEnum & Action); + +protected: + virtual void Change(); + virtual void Init(); + virtual bool CloseQuery(); + virtual void SelectTab(intptr_t Tab); + +private: + void LoadPing(TSessionData * SessionData); + void SavePing(TSessionData * SessionData); + intptr_t ProxyMethodToIndex(TProxyMethod ProxyMethod, TFarList * Items) const; + TProxyMethod IndexToProxyMethod(intptr_t Index, TFarList * Items) const; + TFarComboBox * GetProxyMethodCombo() const; + TFarComboBox * GetOtherProxyMethodCombo() const; + intptr_t FSProtocolToIndex(TFSProtocol FSProtocol, bool & AllowScpFallback) const; + TFSProtocol IndexToFSProtocol(intptr_t Index, bool AllowScpFallback) const; + TFSProtocol GetFSProtocol() const; + inline intptr_t GetLastSupportedFtpProxyMethod() const; + bool GetSupportedFtpProxyMethod(intptr_t Method) const; + TProxyMethod GetProxyMethod() const; + intptr_t GetFtpProxyLogonType() const; + TFtps IndexToFtps(intptr_t Index) const; + TFtps GetFtps() const; + TLoginType IndexToLoginType(intptr_t Index) const; + bool VerifyKey(const UnicodeString & AFileName, bool TypeOnly); + void PrevTabClick(TFarButton * /*Sender*/, bool & Close); + void NextTabClick(TFarButton * /*Sender*/, bool & Close); + void CipherButtonClick(TFarButton * Sender, bool & Close); + void KexButtonClick(TFarButton * Sender, bool & Close); + void AuthGSSAPICheckAllowChange(TFarDialogItem * Sender, intptr_t NewState, bool & Allow); + void UnixEnvironmentButtonClick(TFarButton * Sender, bool & Close); + void WindowsEnvironmentButtonClick(TFarButton * Sender, bool & Close); + void UpdateControls(); + void TransferProtocolComboChange(); + void LoginTypeComboChange(); + void FillCodePageEdit(); + void CodePageEditAdd(uint32_t Cp); + void FtpProxyMethodComboAddNewItem(int ProxyTypeId, TProxyMethod ProxyType); + void SshProxyMethodComboAddNewItem(int ProxyTypeId, TProxyMethod ProxyType); + bool IsSshProtocol(TFSProtocol FSProtocol) const; + bool IsWebDAVProtocol(TFSProtocol FSProtocol) const; + bool IsSshOrWebDAVProtocol(TFSProtocol FSProtocol) const; + + void ChangeTabs(intptr_t FirstVisibleTabIndex); + intptr_t GetVisibleTabsCount(intptr_t TabIndex, bool Forward) const; + intptr_t AddTab(intptr_t TabID, const wchar_t * TabCaption); + +private: + TSessionActionEnum FAction; + TSessionData * FSessionData; + intptr_t FTransferProtocolIndex; + intptr_t FFtpEncryptionComboIndex; + + TTabButton * SshTab; + TTabButton * AuthenticationTab; + TTabButton * KexTab; + TTabButton * BugsTab; + TTabButton * WebDAVTab; + TTabButton * ScpTab; + TTabButton * SftpTab; + TTabButton * FtpTab; + TTabButton * TunnelTab; + TTabButton * PrevTab; + TTabButton * NextTab; + TFarButton * ConnectButton; + TFarEdit * HostNameEdit; + TFarEdit * PortNumberEdit; + TFarEdit * UserNameEdit; + TFarEdit * PasswordEdit; + TFarEdit * PrivateKeyEdit; + TFarComboBox * TransferProtocolCombo; + TFarCheckBox * AllowScpFallbackCheck; + TFarText * HostNameLabel; + TFarText * InsecureLabel; + TFarText * FtpEncryptionLabel; + TFarComboBox * FtpEncryptionCombo; + TFarCheckBox * UpdateDirectoriesCheck; + TFarCheckBox * CacheDirectoriesCheck; + TFarCheckBox * CacheDirectoryChangesCheck; + TFarCheckBox * PreserveDirectoryChangesCheck; + TFarCheckBox * ResolveSymlinksCheck; + TFarEdit * RemoteDirectoryEdit; + TFarComboBox * EOLTypeCombo; + TFarRadioButton * DSTModeWinCheck; + TFarRadioButton * DSTModeKeepCheck; + TFarRadioButton * DSTModeUnixCheck; + TFarCheckBox * CompressionCheck; + TFarRadioButton * SshProt1onlyButton; + TFarRadioButton * SshProt1Button; + TFarRadioButton * SshProt2Button; + TFarRadioButton * SshProt2onlyButton; + TFarListBox * CipherListBox; + TFarButton * CipherUpButton; + TFarButton * CipherDownButton; + TFarCheckBox * Ssh2DESCheck; + TFarComboBox * ShellEdit; + TFarComboBox * ReturnVarEdit; + TFarCheckBox * LookupUserGroupsCheck; + TFarCheckBox * ClearAliasesCheck; + TFarCheckBox * UnsetNationalVarsCheck; + TFarComboBox * ListingCommandEdit; + TFarCheckBox * IgnoreLsWarningsCheck; + TFarCheckBox * SCPLsFullTimeAutoCheck; + TFarCheckBox * Scp1CompatibilityCheck; + TFarEdit * PostLoginCommandsEdits[3]; + TFarEdit * TimeDifferenceEdit; + TFarEdit * TimeDifferenceMinutesEdit; + TFarEdit * TimeoutEdit; + TFarRadioButton * PingOffButton; + TFarRadioButton * PingNullPacketButton; + TFarRadioButton * PingDummyCommandButton; + TFarEdit * PingIntervalSecEdit; + TFarComboBox * CodePageEdit; + TFarComboBox * SshProxyMethodCombo; + TFarComboBox * FtpProxyMethodCombo; + TFarEdit * ProxyHostEdit; + TFarEdit * ProxyPortEdit; + TFarEdit * ProxyUsernameEdit; + TFarEdit * ProxyPasswordEdit; + TFarText * ProxyLocalCommandLabel; + TFarEdit * ProxyLocalCommandEdit; + TFarText * ProxyTelnetCommandLabel; + TFarEdit * ProxyTelnetCommandEdit; + TFarCheckBox * ProxyLocalhostCheck; + TFarRadioButton * ProxyDNSOffButton; + TFarRadioButton * ProxyDNSAutoButton; + TFarRadioButton * ProxyDNSOnButton; + TFarCheckBox * TunnelCheck; + TFarEdit * TunnelHostNameEdit; + TFarEdit * TunnelPortNumberEdit; + TFarEdit * TunnelUserNameEdit; + TFarEdit * TunnelPasswordEdit; + TFarEdit * TunnelPrivateKeyEdit; + TFarComboBox * TunnelLocalPortNumberEdit; + TFarComboBox * BugIgnore1Combo; + TFarComboBox * BugPlainPW1Combo; + TFarComboBox * BugRSA1Combo; + TFarComboBox * BugHMAC2Combo; + TFarComboBox * BugDeriveKey2Combo; + TFarComboBox * BugRSAPad2Combo; + TFarComboBox * BugPKSessID2Combo; + TFarComboBox * BugRekey2Combo; + TFarCheckBox * SshNoUserAuthCheck; + TFarCheckBox * AuthTISCheck; + TFarCheckBox * TryAgentCheck; + TFarCheckBox * AuthKICheck; + TFarCheckBox * AuthKIPasswordCheck; + TFarCheckBox * AgentFwdCheck; + TFarCheckBox * AuthGSSAPICheck3; + TFarCheckBox * GSSAPIFwdTGTCheck; + TFarCheckBox * DeleteToRecycleBinCheck; + TFarCheckBox * OverwrittenToRecycleBinCheck; + TFarEdit * RecycleBinPathEdit; + TFarComboBox * SFTPMaxVersionCombo; + TFarComboBox * SftpServerEdit; + TFarComboBox * SFTPBugSymlinkCombo; + TFarComboBox * SFTPBugSignedTSCombo; + TFarListBox * KexListBox; + TFarButton * KexUpButton; + TFarButton * KexDownButton; + TFarEdit * SFTPMinPacketSizeEdit; + TFarEdit * SFTPMaxPacketSizeEdit; + TFarEdit * RekeyTimeEdit; + TFarEdit * RekeyDataEdit; + TFarRadioButton * IPAutoButton; + TFarRadioButton * IPv4Button; + TFarRadioButton * IPv6Button; + TFarCheckBox * SshBufferSizeCheck; + TFarComboBox * FtpUseMlsdCombo; + TFarCheckBox * FtpPasvModeCheck; + TFarCheckBox * FtpAllowEmptyPasswordCheck; + TFarCheckBox * FtpDupFFCheck; + TFarCheckBox * FtpUndupFFCheck; + TFarCheckBox * SslSessionReuseCheck; + TFarCheckBox * WebDAVCompressionCheck; + TObjectList * FTabs; + intptr_t FFirstVisibleTabIndex; +}; + +#define BUG(BUGID, MSG, PREFIX) \ + TRISTATE(PREFIX ## Bug ## BUGID ## Combo, PREFIX ## Bug(sb ## BUGID), MSG) +#define BUGS() \ + BUG(Ignore1, LOGIN_BUGS_IGNORE1, ); \ + BUG(PlainPW1, LOGIN_BUGS_PLAIN_PW1, ); \ + BUG(RSA1, LOGIN_BUGS_RSA1, ); \ + BUG(HMAC2, LOGIN_BUGS_HMAC2, ); \ + BUG(DeriveKey2, LOGIN_BUGS_DERIVE_KEY2, ); \ + BUG(RSAPad2, LOGIN_BUGS_RSA_PAD2, ); \ + BUG(PKSessID2, LOGIN_BUGS_PKSESSID2, ); \ + BUG(Rekey2, LOGIN_BUGS_REKEY2, ); +#define SFTP_BUGS() \ + BUG(Symlink, LOGIN_SFTP_BUGS_SYMLINK, SFTP); \ + BUG(SignedTS, LOGIN_SFTP_BUGS_SIGNED_TS, SFTP); + +static const TFSProtocol FSOrder[] = { fsSFTPonly, fsSCPonly, fsFTP, fsWebDAV }; + +TSessionDialog::TSessionDialog(TCustomFarPlugin * AFarPlugin, TSessionActionEnum Action) : + TTabbedDialog(AFarPlugin, tabCount), + FAction(Action), + FSessionData(nullptr), + FTransferProtocolIndex(0), + FFtpEncryptionComboIndex(0), + FTabs(new TObjectList()), + FFirstVisibleTabIndex(0) +{ + TPoint S = TPoint(67, 23); + bool Limited = (S.y > GetMaxSize().y); + if (Limited) + { + S.y = GetMaxSize().y; + } + SetSize(S); + + FTabs->SetOwnsObjects(false); + FFirstVisibleTabIndex = 0; + +#define TRISTATE(COMBO, PROP, MSG) \ + Text = new TFarText(this); \ + Text->SetCaption(GetMsg(MSG)); \ + SetNextItemPosition(ipRight); \ + COMBO = new TFarComboBox(this); \ + COMBO->SetDropDownList(true); \ + COMBO->SetWidth(7); \ + COMBO->GetItems()->BeginUpdate(); \ + { \ + SCOPE_EXIT \ + { \ + COMBO->GetItems()->EndUpdate(); \ + }; \ + COMBO->GetItems()->Add(GetMsg(LOGIN_BUGS_AUTO)); \ + COMBO->GetItems()->Add(GetMsg(LOGIN_BUGS_OFF)); \ + COMBO->GetItems()->Add(GetMsg(LOGIN_BUGS_ON)); \ + } \ + Text->SetEnabledFollow(COMBO); \ + SetNextItemPosition(ipNewLine); + + TRect CRect = GetClientRect(); + + TFarButton * Button; + // TTabButton * Tab; + TFarSeparator * Separator; + TFarText * Text; + intptr_t GroupTop; + intptr_t Pos; + intptr_t Index1; + + Index1 = AddTab(tabSession, GetMsg(LOGIN_TAB_SESSION).c_str()); + // Tab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index)); + + SetNextItemPosition(ipRight); + + Index1 = AddTab(tabEnvironment, GetMsg(LOGIN_TAB_ENVIRONMENT).c_str()); + + Index1 = AddTab(tabDirectories, GetMsg(LOGIN_TAB_DIRECTORIES).c_str()); + + Index1 = AddTab(tabSFTP, GetMsg(LOGIN_TAB_SFTP).c_str()); + SftpTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabSCP, GetMsg(LOGIN_TAB_SCP).c_str()); + ScpTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + PrevTab = new TTabButton(this); + PrevTab->SetTabName(UnicodeString('\x11')); + PrevTab->SetBrackets(brNone); + PrevTab->SetCenterGroup(false); + PrevTab->SetOnClick(MAKE_CALLBACK(TSessionDialog::PrevTabClick, this)); + + NextTab = new TTabButton(this); + NextTab->SetTabName(UnicodeString('\x10')); + NextTab->SetBrackets(brNone); + NextTab->SetCenterGroup(false); + NextTab->SetOnClick(MAKE_CALLBACK(TSessionDialog::NextTabClick, this)); + + intptr_t PWidth = PrevTab->GetWidth(); + intptr_t NWidth = NextTab->GetWidth(); + int R = S.x - 4; + PrevTab->SetLeft(R - PWidth - NWidth - 2); + PrevTab->SetWidth(PWidth); + NextTab->SetLeft(R - NWidth - 1); + NextTab->SetWidth(PWidth); + + Index1 = AddTab(tabFTP, GetMsg(LOGIN_TAB_FTP).c_str()); + FtpTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabConnection, GetMsg(LOGIN_TAB_CONNECTION).c_str()); + + Index1 = AddTab(tabProxy, GetMsg(LOGIN_TAB_PROXY).c_str()); + + Index1 = AddTab(tabTunnel, GetMsg(LOGIN_TAB_TUNNEL).c_str()); + TunnelTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabSsh, GetMsg(LOGIN_TAB_SSH).c_str()); + SshTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabKex, GetMsg(LOGIN_TAB_KEX).c_str()); + KexTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabAuthentication, GetMsg(LOGIN_TAB_AUTH).c_str()); + AuthenticationTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabBugs, GetMsg(LOGIN_TAB_BUGS).c_str()); + BugsTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + Index1 = AddTab(tabWebDAV, GetMsg(LOGIN_TAB_WEBDAV).c_str()); + WebDAVTab = NB_STATIC_DOWNCAST(TTabButton, GetItem(Index1)); + + // Session tab + + SetNextItemPosition(ipNewLine); + SetDefaultGroup(tabSession); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_GROUP_SESSION)); + GroupTop = Separator->GetTop(); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TRANSFER_PROTOCOL)); + + SetNextItemPosition(ipRight); + + TransferProtocolCombo = new TFarComboBox(this); + TransferProtocolCombo->SetDropDownList(true); + TransferProtocolCombo->SetWidth(10); + TransferProtocolCombo->GetItems()->Add(GetMsg(LOGIN_SFTP)); + TransferProtocolCombo->GetItems()->Add(GetMsg(LOGIN_SCP)); +#ifndef NO_FILEZILLA + TransferProtocolCombo->GetItems()->Add(GetMsg(LOGIN_FTP)); +#endif + TransferProtocolCombo->GetItems()->Add(GetMsg(LOGIN_WEBDAV)); + + AllowScpFallbackCheck = new TFarCheckBox(this); + AllowScpFallbackCheck->SetCaption(GetMsg(LOGIN_ALLOW_SCP_FALLBACK)); + + InsecureLabel = new TFarText(this); + InsecureLabel->SetCaption(GetMsg(LOGIN_INSECURE)); + InsecureLabel->MoveAt(AllowScpFallbackCheck->GetLeft(), AllowScpFallbackCheck->GetTop()); + + SetNextItemPosition(ipNewLine); + + FtpEncryptionLabel = new TFarText(this); + FtpEncryptionLabel->SetCaption(GetMsg(LOGIN_FTP_ENCRYPTION)); + FtpEncryptionLabel->SetWidth(15); + + SetNextItemPosition(ipRight); + + FtpEncryptionCombo = new TFarComboBox(this); + FtpEncryptionCombo->SetDropDownList(true); + FtpEncryptionCombo->GetItems()->Add(GetMsg(LOGIN_FTP_USE_PLAIN_FTP)); + FtpEncryptionCombo->GetItems()->Add(GetMsg(LOGIN_FTP_REQUIRE_IMPLICIT_FTP)); + FtpEncryptionCombo->GetItems()->Add(GetMsg(LOGIN_FTP_REQUIRE_EXPLICIT_FTP)); + FtpEncryptionCombo->SetRight(CRect.Right); + FtpEncryptionCombo->SetWidth(30); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + HostNameLabel = new TFarText(this); + HostNameLabel->SetCaption(GetMsg(LOGIN_HOST_NAME)); + + HostNameEdit = new TFarEdit(this); + HostNameEdit->SetRight(CRect.Right - 12 - 2); + + SetNextItemPosition(ipRight); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PORT_NUMBER)); + Text->Move(0, -1); + + SetNextItemPosition(ipBelow); + + PortNumberEdit = new TFarEdit(this); + PortNumberEdit->SetFixed(true); + PortNumberEdit->SetMask(L"999999"); + + SetNextItemPosition(ipNewLine); + Text = new TFarText(this); + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_USER_NAME)); + Text->SetWidth(20); + + SetNextItemPosition(ipRight); + + UserNameEdit = new TFarEdit(this); + UserNameEdit->SetWidth(20); + UserNameEdit->SetRight(CRect.Right - 12 - 2); + + SetNextItemPosition(ipNewLine); + Text = new TFarText(this); + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PASSWORD)); + Text->SetWidth(20); + + SetNextItemPosition(ipRight); + + PasswordEdit = new TFarEdit(this); + PasswordEdit->SetPassword(true); + PasswordEdit->SetWidth(20); + PasswordEdit->SetRight(CRect.Right - 12 - 2); + + SetNextItemPosition(ipNewLine); + Text = new TFarText(this); + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PRIVATE_KEY)); + + PrivateKeyEdit = new TFarEdit(this); + Text->SetEnabledFollow(PrivateKeyEdit); + + Separator = new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetTop(CRect.Bottom - 3); + Text->SetBottom(Text->GetTop()); + Text->SetCaption(GetMsg(LOGIN_TAB_HINT1)); + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TAB_HINT2)); + + // Environment tab + + SetDefaultGroup(tabEnvironment); + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_ENVIRONMENT_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_EOL_TYPE)); + + SetNextItemPosition(ipRight); + + EOLTypeCombo = new TFarComboBox(this); + EOLTypeCombo->SetDropDownList(true); + EOLTypeCombo->SetWidth(7); + EOLTypeCombo->SetRight(CRect.Right); + EOLTypeCombo->GetItems()->Add(L"LF"); + EOLTypeCombo->GetItems()->Add(L"CR/LF"); + + SetNextItemPosition(ipNewLine); + + // UTF_TRISTATE(); + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_CODE_PAGE)); + + SetNextItemPosition(ipRight); + + CodePageEdit = new TFarComboBox(this); + CodePageEdit->SetWidth(30); + CodePageEdit->SetRight(CRect.Right); + FillCodePageEdit(); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TIME_DIFFERENCE)); + + SetNextItemPosition(ipRight); + + TimeDifferenceEdit = new TFarEdit(this); + TimeDifferenceEdit->SetFixed(true); + TimeDifferenceEdit->SetMask(L"###"); + TimeDifferenceEdit->SetWidth(4); + Text->SetEnabledFollow(TimeDifferenceEdit); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TIME_DIFFERENCE_HOURS)); + Text->SetEnabledFollow(TimeDifferenceEdit); + + TimeDifferenceMinutesEdit = new TFarEdit(this); + TimeDifferenceMinutesEdit->SetFixed(true); + TimeDifferenceMinutesEdit->SetMask(L"###"); + TimeDifferenceMinutesEdit->SetWidth(4); + TimeDifferenceMinutesEdit->SetEnabledFollow(TimeDifferenceEdit); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TIME_DIFFERENCE_MINUTES)); + Text->SetEnabledFollow(TimeDifferenceEdit); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_DST_MODE_GROUP)); + + DSTModeUnixCheck = new TFarRadioButton(this); + DSTModeUnixCheck->SetCaption(GetMsg(LOGIN_DST_MODE_UNIX)); + + DSTModeWinCheck = new TFarRadioButton(this); + DSTModeWinCheck->SetCaption(GetMsg(LOGIN_DST_MODE_WIN)); + DSTModeWinCheck->SetEnabledFollow(DSTModeUnixCheck); + + DSTModeKeepCheck = new TFarRadioButton(this); + DSTModeKeepCheck->SetCaption(GetMsg(LOGIN_DST_MODE_KEEP)); + DSTModeKeepCheck->SetEnabledFollow(DSTModeUnixCheck); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(LOGIN_ENVIRONMENT_UNIX)); + Button->SetOnClick(MAKE_CALLBACK(TSessionDialog::UnixEnvironmentButtonClick, this)); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(LOGIN_ENVIRONMENT_WINDOWS)); + Button->SetOnClick(MAKE_CALLBACK(TSessionDialog::WindowsEnvironmentButtonClick, this)); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_RECYCLE_BIN_GROUP)); + + DeleteToRecycleBinCheck = new TFarCheckBox(this); + DeleteToRecycleBinCheck->SetCaption(GetMsg(LOGIN_RECYCLE_BIN_DELETE)); + + OverwrittenToRecycleBinCheck = new TFarCheckBox(this); + OverwrittenToRecycleBinCheck->SetCaption(GetMsg(LOGIN_RECYCLE_BIN_OVERWRITE)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_RECYCLE_BIN_LABEL)); + + RecycleBinPathEdit = new TFarEdit(this); + Text->SetEnabledFollow(RecycleBinPathEdit); + + SetNextItemPosition(ipNewLine); + + // Directories tab + + SetDefaultGroup(tabDirectories); + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_DIRECTORIES_GROUP)); + + UpdateDirectoriesCheck = new TFarCheckBox(this); + UpdateDirectoriesCheck->SetCaption(GetMsg(LOGIN_UPDATE_DIRECTORIES)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_REMOTE_DIRECTORY)); + + RemoteDirectoryEdit = new TFarEdit(this); + RemoteDirectoryEdit->SetHistory(REMOTE_DIR_HISTORY); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_DIRECTORY_OPTIONS_GROUP)); + + CacheDirectoriesCheck = new TFarCheckBox(this); + CacheDirectoriesCheck->SetCaption(GetMsg(LOGIN_CACHE_DIRECTORIES)); + + CacheDirectoryChangesCheck = new TFarCheckBox(this); + CacheDirectoryChangesCheck->SetCaption(GetMsg(LOGIN_CACHE_DIRECTORY_CHANGES)); + + PreserveDirectoryChangesCheck = new TFarCheckBox(this); + PreserveDirectoryChangesCheck->SetCaption(GetMsg(LOGIN_PRESERVE_DIRECTORY_CHANGES)); + PreserveDirectoryChangesCheck->SetLeft(PreserveDirectoryChangesCheck->GetLeft() + 4); + + ResolveSymlinksCheck = new TFarCheckBox(this); + ResolveSymlinksCheck->SetCaption(GetMsg(LOGIN_RESOLVE_SYMLINKS)); + + new TFarSeparator(this); + + // SCP Tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabSCP); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_SHELL_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SHELL_SHELL)); + + SetNextItemPosition(ipRight); + + ShellEdit = new TFarComboBox(this); + ShellEdit->GetItems()->Add(GetMsg(LOGIN_SHELL_SHELL_DEFAULT)); + ShellEdit->GetItems()->Add(L"/bin/bash"); + ShellEdit->GetItems()->Add(L"/bin/ksh"); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SHELL_RETURN_VAR)); + + SetNextItemPosition(ipRight); + + ReturnVarEdit = new TFarComboBox(this); + ReturnVarEdit->GetItems()->Add(GetMsg(LOGIN_SHELL_RETURN_VAR_AUTODETECT)); + ReturnVarEdit->GetItems()->Add(L"?"); + ReturnVarEdit->GetItems()->Add(L"status"); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_SCP_LS_OPTIONS_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_LISTING_COMMAND)); + + SetNextItemPosition(ipRight); + + ListingCommandEdit = new TFarComboBox(this); + ListingCommandEdit->GetItems()->Add(L"ls -la"); + ListingCommandEdit->GetItems()->Add(L"ls -gla"); + Text->SetEnabledFollow(ListingCommandEdit); + + SetNextItemPosition(ipNewLine); + + IgnoreLsWarningsCheck = new TFarCheckBox(this); + IgnoreLsWarningsCheck->SetCaption(GetMsg(LOGIN_IGNORE_LS_WARNINGS)); + + SetNextItemPosition(ipRight); + + SCPLsFullTimeAutoCheck = new TFarCheckBox(this); + SCPLsFullTimeAutoCheck->SetCaption(GetMsg(LOGIN_SCP_LS_FULL_TIME_AUTO)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_SCP_OPTIONS)); + + LookupUserGroupsCheck = new TFarCheckBox(this); + LookupUserGroupsCheck->SetCaption(GetMsg(LOGIN_LOOKUP_USER_GROUPS)); + + SetNextItemPosition(ipRight); + + UnsetNationalVarsCheck = new TFarCheckBox(this); + UnsetNationalVarsCheck->SetCaption(GetMsg(LOGIN_CLEAR_NATIONAL_VARS)); + + SetNextItemPosition(ipNewLine); + + ClearAliasesCheck = new TFarCheckBox(this); + ClearAliasesCheck->SetCaption(GetMsg(LOGIN_CLEAR_ALIASES)); + + SetNextItemPosition(ipRight); + + Scp1CompatibilityCheck = new TFarCheckBox(this); + Scp1CompatibilityCheck->SetCaption(GetMsg(LOGIN_SCP1_COMPATIBILITY)); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + // SFTP Tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabSFTP); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_SFTP_PROTOCOL_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SFTP_SERVER)); + SetNextItemPosition(ipRight); + SftpServerEdit = new TFarComboBox(this); + SftpServerEdit->GetItems()->Add(GetMsg(LOGIN_SFTP_SERVER_DEFAULT)); + SftpServerEdit->GetItems()->Add(L"/bin/sftp-server"); + SftpServerEdit->GetItems()->Add(L"sudo su -c /bin/sftp-server"); + Text->SetEnabledFollow(SftpServerEdit); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SFTP_MAX_VERSION)); + SetNextItemPosition(ipRight); + SFTPMaxVersionCombo = new TFarComboBox(this); + SFTPMaxVersionCombo->SetDropDownList(true); + SFTPMaxVersionCombo->SetWidth(7); + for (intptr_t Index2 = 0; Index2 <= 5; ++Index2) + { + SFTPMaxVersionCombo->GetItems()->Add(::IntToStr(Index2)); + } + Text->SetEnabledFollow(SFTPMaxVersionCombo); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_SFTP_BUGS_GROUP)); + + SFTP_BUGS(); + + new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SFTP_MIN_PACKET_SIZE)); + SetNextItemPosition(ipRight); + + SFTPMinPacketSizeEdit = new TFarEdit(this); + SFTPMinPacketSizeEdit->SetFixed(true); + SFTPMinPacketSizeEdit->SetMask(L"99999999"); + SFTPMinPacketSizeEdit->SetWidth(8); + // SFTPMinPacketSizeEdit->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_SFTP_MAX_PACKET_SIZE)); + SetNextItemPosition(ipRight); + + SFTPMaxPacketSizeEdit = new TFarEdit(this); + SFTPMaxPacketSizeEdit->SetFixed(true); + SFTPMaxPacketSizeEdit->SetMask(L"99999999"); + SFTPMaxPacketSizeEdit->SetWidth(8); + // SFTPMaxPacketSizeEdit->SetEnabledDependencyNegative(SshProt1onlyButton); + + // FTP tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabFTP); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_FTP_GROUP)); + + TRISTATE(FtpUseMlsdCombo, FtpUseMlsd, LOGIN_FTP_USE_MLSD); + + FtpPasvModeCheck = new TFarCheckBox(this); + FtpPasvModeCheck->SetCaption(GetMsg(LOGIN_FTP_PASV_MODE)); + + FtpAllowEmptyPasswordCheck = new TFarCheckBox(this); + FtpAllowEmptyPasswordCheck->SetCaption(GetMsg(LOGIN_FTP_ALLOW_EMPTY_PASSWORD)); + + SetNextItemPosition(ipNewLine); + + FtpDupFFCheck = new TFarCheckBox(this); + FtpDupFFCheck->SetCaption(GetMsg(LOGIN_FTP_DUPFF)); + + SetNextItemPosition(ipNewLine); + + FtpUndupFFCheck = new TFarCheckBox(this); + FtpUndupFFCheck->SetCaption(GetMsg(LOGIN_FTP_UNDUPFF)); + + SetNextItemPosition(ipNewLine); + + SslSessionReuseCheck = new TFarCheckBox(this); + SslSessionReuseCheck->SetCaption(GetMsg(LOGIN_FTP_SSLSESSIONREUSE)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_FTP_POST_LOGIN_COMMANDS)); + + for (intptr_t Index3 = 0; Index3 < static_cast(_countof(PostLoginCommandsEdits)); ++Index3) + { + TFarEdit * Edit = new TFarEdit(this); + PostLoginCommandsEdits[Index3] = Edit; + } + + new TFarSeparator(this); + + // Connection tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabConnection); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_CONNECTION_GROUP)); + + Text = new TFarText(this); + SetNextItemPosition(ipNewLine); + + SshBufferSizeCheck = new TFarCheckBox(this); + SshBufferSizeCheck->SetCaption(GetMsg(LOGIN_SSH_OPTIMIZE_BUFFER_SIZE)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_TIMEOUTS_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TIMEOUT)); + + SetNextItemPosition(ipRight); + + TimeoutEdit = new TFarEdit(this); + TimeoutEdit->SetFixed(true); + TimeoutEdit->SetMask(L"####"); + TimeoutEdit->SetWidth(5); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TIMEOUT_SECONDS)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_PING_GROUP)); + + PingOffButton = new TFarRadioButton(this); + PingOffButton->SetCaption(GetMsg(LOGIN_PING_OFF)); + + PingNullPacketButton = new TFarRadioButton(this); + PingNullPacketButton->SetCaption(GetMsg(LOGIN_PING_NULL_PACKET)); + + PingDummyCommandButton = new TFarRadioButton(this); + PingDummyCommandButton->SetCaption(GetMsg(LOGIN_PING_DUMMY_COMMAND)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PING_INTERVAL)); + Text->SetEnabledDependencyNegative(PingOffButton); + + SetNextItemPosition(ipRight); + + PingIntervalSecEdit = new TFarEdit(this); + PingIntervalSecEdit->SetFixed(true); + PingIntervalSecEdit->SetMask(L"####"); + PingIntervalSecEdit->SetWidth(6); + PingIntervalSecEdit->SetEnabledDependencyNegative(PingOffButton); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_IP_GROUP)); + + IPAutoButton = new TFarRadioButton(this); + IPAutoButton->SetCaption(GetMsg(LOGIN_IP_AUTO)); + + SetNextItemPosition(ipRight); + + IPv4Button = new TFarRadioButton(this); + IPv4Button->SetCaption(GetMsg(LOGIN_IP_V4)); + + IPv6Button = new TFarRadioButton(this); + IPv6Button->SetCaption(GetMsg(LOGIN_IP_V6)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + + SetNextItemPosition(ipNewLine); + + // Proxy tab + + SetDefaultGroup(tabProxy); + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_PROXY_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_METHOD)); + + SetNextItemPosition(ipRight); + + FtpProxyMethodCombo = new TFarComboBox(this); + FtpProxyMethodCombo->SetDropDownList(true); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_NONE, pmNone); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_SOCKS4, pmSocks4); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_SOCKS5, pmSocks5); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_HTTP, pmHTTP); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_SYSTEM, pmSystem); + TProxyMethod FtpProxyMethod = static_cast(GetLastSupportedFtpProxyMethod()); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_SITE, static_cast(FtpProxyMethod + 1)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_PROXYUSER_USERHOST, static_cast(FtpProxyMethod + 2)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_OPEN_HOST, static_cast(FtpProxyMethod + 3)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_PROXYUSER_USERUSER, static_cast(FtpProxyMethod + 4)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_USER_USERHOST, static_cast(FtpProxyMethod + 5)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_PROXYUSER_HOST, static_cast(FtpProxyMethod + 6)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_USERHOST_PROXYUSER, static_cast(FtpProxyMethod + 7)); + FtpProxyMethodComboAddNewItem(LOGIN_PROXY_FTP_USER_USERPROXYUSERHOST, static_cast(FtpProxyMethod + 8)); + FtpProxyMethodCombo->SetWidth(40); + + SshProxyMethodCombo = new TFarComboBox(this); + SshProxyMethodCombo->SetLeft(FtpProxyMethodCombo->GetLeft()); + SshProxyMethodCombo->SetWidth(FtpProxyMethodCombo->GetWidth()); + SshProxyMethodCombo->SetRight(FtpProxyMethodCombo->GetRight()); + SshProxyMethodCombo->SetDropDownList(true); + + SshProxyMethodComboAddNewItem(LOGIN_PROXY_NONE, pmNone); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_SOCKS4, pmSocks4); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_SOCKS5, pmSocks5); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_HTTP, pmHTTP); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_TELNET, pmTelnet); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_LOCAL, pmCmd); + SshProxyMethodComboAddNewItem(LOGIN_PROXY_SYSTEM, pmSystem); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_HOST)); + + SetNextItemPosition(ipNewLine); + + ProxyHostEdit = new TFarEdit(this); + ProxyHostEdit->SetWidth(42); + Text->SetEnabledFollow(ProxyHostEdit); + + SetNextItemPosition(ipRight); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_PORT)); + Text->Move(0, -1); + + SetNextItemPosition(ipBelow); + + ProxyPortEdit = new TFarEdit(this); + ProxyPortEdit->SetFixed(true); + ProxyPortEdit->SetMask(L"999999"); + // ProxyPortEdit->SetWidth(12); + Text->SetEnabledFollow(ProxyPortEdit); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_USERNAME)); + + ProxyUsernameEdit = new TFarEdit(this); + ProxyUsernameEdit->SetWidth(ProxyUsernameEdit->GetWidth() / 2 - 1); + Text->SetEnabledFollow(ProxyUsernameEdit); + + SetNextItemPosition(ipRight); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_PASSWORD)); + Text->Move(0, -1); + + SetNextItemPosition(ipBelow); + + ProxyPasswordEdit = new TFarEdit(this); + ProxyPasswordEdit->SetPassword(true); + Text->SetEnabledFollow(ProxyPasswordEdit); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_PROXY_SETTINGS_GROUP)); + + ProxyTelnetCommandLabel = new TFarText(this); + ProxyTelnetCommandLabel->SetCaption(GetMsg(LOGIN_PROXY_TELNET_COMMAND)); + + SetNextItemPosition(ipRight); + + ProxyTelnetCommandEdit = new TFarEdit(this); + ProxyTelnetCommandLabel->SetEnabledFollow(ProxyTelnetCommandEdit); + + SetNextItemPosition(ipNewLine); + + ProxyLocalCommandLabel = new TFarText(this); + ProxyLocalCommandLabel->SetCaption(GetMsg(LOGIN_PROXY_LOCAL_COMMAND)); + ProxyLocalCommandLabel->Move(0, -1); + + SetNextItemPosition(ipRight); + + ProxyLocalCommandEdit = new TFarEdit(this); + ProxyLocalCommandLabel->SetEnabledFollow(ProxyLocalCommandEdit); + + SetNextItemPosition(ipNewLine); + + ProxyLocalhostCheck = new TFarCheckBox(this); + ProxyLocalhostCheck->SetCaption(GetMsg(LOGIN_PROXY_LOCALHOST)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PROXY_DNS)); + + ProxyDNSOffButton = new TFarRadioButton(this); + ProxyDNSOffButton->SetCaption(GetMsg(LOGIN_PROXY_DNS_NO)); + Text->SetEnabledFollow(ProxyDNSOffButton); + + SetNextItemPosition(ipRight); + + ProxyDNSAutoButton = new TFarRadioButton(this); + ProxyDNSAutoButton->SetCaption(GetMsg(LOGIN_PROXY_DNS_AUTO)); + ProxyDNSAutoButton->SetEnabledFollow(ProxyDNSOffButton); + + ProxyDNSOnButton = new TFarRadioButton(this); + ProxyDNSOnButton->SetCaption(GetMsg(LOGIN_PROXY_DNS_YES)); + ProxyDNSOnButton->SetEnabledFollow(ProxyDNSOffButton); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + // Tunnel tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabTunnel); + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_TUNNEL_GROUP)); + + TunnelCheck = new TFarCheckBox(this); + TunnelCheck->SetCaption(GetMsg(LOGIN_TUNNEL_TUNNEL)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_TUNNEL_SESSION_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_HOST_NAME)); + Text->SetEnabledDependency(TunnelCheck); + + TunnelHostNameEdit = new TFarEdit(this); + TunnelHostNameEdit->SetRight(CRect.Right - 12 - 2); + TunnelHostNameEdit->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipRight); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PORT_NUMBER)); + Text->Move(0, -1); + Text->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipBelow); + + TunnelPortNumberEdit = new TFarEdit(this); + TunnelPortNumberEdit->SetFixed(true); + TunnelPortNumberEdit->SetMask(L"999999"); + TunnelPortNumberEdit->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_USER_NAME)); + Text->SetEnabledDependency(TunnelCheck); + + TunnelUserNameEdit = new TFarEdit(this); + TunnelUserNameEdit->SetWidth(TunnelUserNameEdit->GetWidth() / 2 - 1); + TunnelUserNameEdit->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipRight); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PASSWORD)); + Text->Move(0, -1); + Text->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipBelow); + + TunnelPasswordEdit = new TFarEdit(this); + TunnelPasswordEdit->SetPassword(true); + TunnelPasswordEdit->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_PRIVATE_KEY)); + Text->SetEnabledDependency(TunnelCheck); + + TunnelPrivateKeyEdit = new TFarEdit(this); + TunnelPrivateKeyEdit->SetEnabledDependency(TunnelCheck); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_TUNNEL_OPTIONS_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_TUNNEL_LOCAL_PORT_NUMBER)); + Text->SetEnabledDependency(TunnelCheck); + + SetNextItemPosition(ipRight); + + TunnelLocalPortNumberEdit = new TFarComboBox(this); + TunnelLocalPortNumberEdit->SetLeft(TunnelPortNumberEdit->GetLeft()); + TunnelLocalPortNumberEdit->SetEnabledDependency(TunnelCheck); + TunnelLocalPortNumberEdit->GetItems()->BeginUpdate(); + { + SCOPE_EXIT + { + TunnelLocalPortNumberEdit->GetItems()->EndUpdate(); + }; + TunnelLocalPortNumberEdit->GetItems()->Add(GetMsg(LOGIN_TUNNEL_LOCAL_PORT_NUMBER_AUTOASSIGN)); + for (intptr_t Index4 = GetConfiguration()->GetTunnelLocalPortNumberLow(); + Index4 <= GetConfiguration()->GetTunnelLocalPortNumberHigh(); ++Index4) + { + TunnelLocalPortNumberEdit->GetItems()->Add(::IntToStr(Index1)); + } + } + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + // SSH tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabSsh); + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_SSH_GROUP)); + + CompressionCheck = new TFarCheckBox(this); + CompressionCheck->SetCaption(GetMsg(LOGIN_COMPRESSION)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_SSH_PROTOCOL_GROUP)); + + SshProt1onlyButton = new TFarRadioButton(this); + SshProt1onlyButton->SetCaption(GetMsg(LOGIN_SSH1_ONLY)); + + SetNextItemPosition(ipRight); + + SshProt1Button = new TFarRadioButton(this); + SshProt1Button->SetCaption(GetMsg(LOGIN_SSH1)); + + SshProt2Button = new TFarRadioButton(this); + SshProt2Button->SetCaption(GetMsg(LOGIN_SSH2)); + + SshProt2onlyButton = new TFarRadioButton(this); + SshProt2onlyButton->SetCaption(GetMsg(LOGIN_SSH2_ONLY)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_ENCRYPTION_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_CIPHER)); + + CipherListBox = new TFarListBox(this); + CipherListBox->SetRight(CipherListBox->GetRight() - 15); + CipherListBox->SetHeight(1 + CIPHER_COUNT + 1); + Pos = CipherListBox->GetBottom(); + + SetNextItemPosition(ipRight); + + CipherUpButton = new TFarButton(this); + CipherUpButton->SetCaption(GetMsg(LOGIN_UP)); + CipherUpButton->Move(0, 1); + CipherUpButton->SetResult(-1); + CipherUpButton->SetOnClick(MAKE_CALLBACK(TSessionDialog::CipherButtonClick, this)); + + SetNextItemPosition(ipBelow); + + CipherDownButton = new TFarButton(this); + CipherDownButton->SetCaption(GetMsg(LOGIN_DOWN)); + CipherDownButton->SetResult(1); + CipherDownButton->SetOnClick(MAKE_CALLBACK(TSessionDialog::CipherButtonClick, this)); + + SetNextItemPosition(ipNewLine); + + if (!Limited) + { + Ssh2DESCheck = new TFarCheckBox(this); + Ssh2DESCheck->Move(0, Pos - Ssh2DESCheck->GetTop() + 1); + Ssh2DESCheck->SetCaption(GetMsg(LOGIN_SSH2DES)); + Ssh2DESCheck->SetEnabledDependencyNegative(SshProt1onlyButton); + } + else + { + Ssh2DESCheck = nullptr; + } + + // KEX tab + + SetDefaultGroup(tabKex); + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_KEX_REEXCHANGE_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_KEX_REKEY_TIME)); + Text->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipRight); + + RekeyTimeEdit = new TFarEdit(this); + RekeyTimeEdit->SetFixed(true); + RekeyTimeEdit->SetMask(L"####"); + RekeyTimeEdit->SetWidth(6); + RekeyTimeEdit->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_KEX_REKEY_DATA)); + Text->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipRight); + + RekeyDataEdit = new TFarEdit(this); + RekeyDataEdit->SetWidth(6); + RekeyDataEdit->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_KEX_OPTIONS_GROUP)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(LOGIN_KEX_LIST)); + Text->SetEnabledDependencyNegative(SshProt1onlyButton); + + KexListBox = new TFarListBox(this); + KexListBox->SetRight(KexListBox->GetRight() - 15); + KexListBox->SetHeight(1 + KEX_COUNT + 1); + KexListBox->SetEnabledDependencyNegative(SshProt1onlyButton); + + SetNextItemPosition(ipRight); + + KexUpButton = new TFarButton(this); + KexUpButton->SetCaption(GetMsg(LOGIN_UP)); + KexUpButton->Move(0, 1); + KexUpButton->SetResult(-1); + KexUpButton->SetOnClick(MAKE_CALLBACK(TSessionDialog::KexButtonClick, this)); + + SetNextItemPosition(ipBelow); + + KexDownButton = new TFarButton(this); + KexDownButton->SetCaption(GetMsg(LOGIN_DOWN)); + KexDownButton->SetResult(1); + KexDownButton->SetOnClick(MAKE_CALLBACK(TSessionDialog::KexButtonClick, this)); + + SetNextItemPosition(ipNewLine); + + // Authentication tab + + SetDefaultGroup(tabAuthentication); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + + SshNoUserAuthCheck = new TFarCheckBox(this); + SshNoUserAuthCheck->SetCaption(GetMsg(LOGIN_AUTH_SSH_NO_USER_AUTH)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_AUTH_GROUP)); + + TryAgentCheck = new TFarCheckBox(this); + TryAgentCheck->SetCaption(GetMsg(LOGIN_AUTH_TRY_AGENT)); + + AuthTISCheck = new TFarCheckBox(this); + AuthTISCheck->SetCaption(GetMsg(LOGIN_AUTH_TIS)); + + AuthKICheck = new TFarCheckBox(this); + AuthKICheck->SetCaption(GetMsg(LOGIN_AUTH_KI)); + + AuthKIPasswordCheck = new TFarCheckBox(this); + AuthKIPasswordCheck->SetCaption(GetMsg(LOGIN_AUTH_KI_PASSWORD)); + AuthKIPasswordCheck->Move(4, 0); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_AUTH_PARAMS_GROUP)); + + AgentFwdCheck = new TFarCheckBox(this); + AgentFwdCheck->SetCaption(GetMsg(LOGIN_AUTH_AGENT_FWD)); + + // GSSAPI + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(LOGIN_AUTH_GSSAPI_PARAMS_GROUP)); + + AuthGSSAPICheck3 = new TFarCheckBox(this); + AuthGSSAPICheck3->SetCaption(GetMsg(LOGIN_AUTH_ATTEMPT_GSSAPI_AUTHENTICATION)); + AuthGSSAPICheck3->SetOnAllowChange(MAKE_CALLBACK(TSessionDialog::AuthGSSAPICheckAllowChange, this)); + + GSSAPIFwdTGTCheck = new TFarCheckBox(this); + GSSAPIFwdTGTCheck->SetCaption(GetMsg(LOGIN_AUTH_ALLOW_GSSAPI_CREDENTIAL_DELEGATION)); + + new TFarSeparator(this); + + // Bugs tab + + SetDefaultGroup(tabBugs); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_BUGS_GROUP)); + + BUGS(); + + BugIgnore1Combo->SetEnabledDependencyNegative(SshProt2onlyButton); + BugPlainPW1Combo->SetEnabledDependencyNegative(SshProt2onlyButton); + BugRSA1Combo->SetEnabledDependencyNegative(SshProt2onlyButton); + BugHMAC2Combo->SetEnabledDependencyNegative(SshProt1onlyButton); + BugDeriveKey2Combo->SetEnabledDependencyNegative(SshProt1onlyButton); + BugRSAPad2Combo->SetEnabledDependencyNegative(SshProt1onlyButton); + BugPKSessID2Combo->SetEnabledDependencyNegative(SshProt1onlyButton); + BugRekey2Combo->SetEnabledDependencyNegative(SshProt1onlyButton); + + // WebDAV tab + + SetNextItemPosition(ipNewLine); + + SetDefaultGroup(tabWebDAV); + Separator = new TFarSeparator(this); + Separator->SetPosition(GroupTop); + Separator->SetCaption(GetMsg(LOGIN_WEBDAV_GROUP)); + + WebDAVCompressionCheck = new TFarCheckBox(this); + WebDAVCompressionCheck->SetCaption(GetMsg(LOGIN_COMPRESSION)); + +#undef TRISTATE + + new TFarSeparator(this); + + // Buttons + + SetNextItemPosition(ipNewLine); + SetDefaultGroup(0); + + Separator = new TFarSeparator(this); + Separator->SetPosition(CRect.Bottom - 1); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_OK)); + Button->SetDefault(Action != saConnect); + Button->SetResult(brOK); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + ConnectButton = new TFarButton(this); + ConnectButton->SetCaption(GetMsg(LOGIN_CONNECT_BUTTON)); + ConnectButton->SetDefault(Action == saConnect); + ConnectButton->SetResult(brConnect); + ConnectButton->SetCenterGroup(true); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + Button->SetResult(brCancel); + Button->SetCenterGroup(true); +} + +void TSessionDialog::FtpProxyMethodComboAddNewItem(int ProxyTypeId, TProxyMethod ProxyType) +{ + FtpProxyMethodCombo->GetItems()->AddObject(GetMsg(ProxyTypeId), + static_cast(ToPtr(ProxyType))); +} + +void TSessionDialog::SshProxyMethodComboAddNewItem(int ProxyTypeId, TProxyMethod ProxyType) +{ + SshProxyMethodCombo->GetItems()->AddObject(GetMsg(ProxyTypeId), + static_cast(ToPtr(ProxyType))); +} + +TSessionDialog::~TSessionDialog() +{ + SAFE_DESTROY(FTabs); +} + +void TSessionDialog::Change() +{ + TTabbedDialog::Change(); + + if (GetHandle() && !ChangesLocked()) + { + if ((FTransferProtocolIndex != TransferProtocolCombo->GetItemIndex()) || + (FFtpEncryptionComboIndex != FtpEncryptionCombo->GetItemIndex())) + { + TransferProtocolComboChange(); + } + + LockChanges(); + SCOPE_EXIT + { + UnlockChanges(); + }; + UpdateControls(); + } +} + +void TSessionDialog::Init() +{ + TTabbedDialog::Init(); + +} + +static void AdjustRemoteDir( + UnicodeString & HostName, + TFarEdit * PortNumberEdit, + TFarEdit * RemoteDirectoryEdit) +{ + UnicodeString Dir; + intptr_t P = HostName.Pos(L'/'); + if (P > 0) + { + Dir = HostName.SubString(P, HostName.Length() - P + 1); + intptr_t P2 = Dir.Pos(L':'); + if (P2 > 0) + { + UnicodeString Port = Dir.SubString(P2 + 1, Dir.Length() - P2); + Dir.SetLength(P2 - 1); + if (Port.ToInt()) + PortNumberEdit->SetAsInteger(Port.ToInt()); + } + HostName.SetLength(P - 1); + } + UnicodeString RemoteDir = RemoteDirectoryEdit->GetText(); + if (RemoteDir.IsEmpty() && !Dir.IsEmpty()) + { + RemoteDirectoryEdit->SetText(Dir); + } +} + +void TSessionDialog::TransferProtocolComboChange() +{ + TFtps Ftps = GetFtps(); + // note that this modifies the session for good, + // even if user cancels the dialog + SavePing(FSessionData); + + FTransferProtocolIndex = TransferProtocolCombo->GetItemIndex(); + FFtpEncryptionComboIndex = FtpEncryptionCombo->GetItemIndex(); + intptr_t Port = PortNumberEdit->GetAsInteger(); + + LoadPing(FSessionData); + TFSProtocol FSProtocol = GetFSProtocol(); + if (FSProtocol == fsSFTPonly || FSProtocol == fsSCPonly) + { + if (Port == FtpPortNumber) + { + PortNumberEdit->SetAsInteger(SshPortNumber); + } + } + else if ((FSProtocol == fsFTP) && ((Ftps == ftpsNone) || (Ftps == ftpsExplicitSsl) || (Ftps == ftpsExplicitTls))) + { + if ((Port == SshPortNumber) || (Port == FtpsImplicitPortNumber) || (Port == HTTPPortNumber) || (Port == HTTPSPortNumber)) + { + PortNumberEdit->SetAsInteger(FtpPortNumber); + } + } + else if ((FSProtocol == fsFTP) && (Ftps == ftpsImplicit)) + { + if ((Port == SshPortNumber) || (Port == FtpPortNumber) || (Port == HTTPPortNumber) || (Port == HTTPSPortNumber)) + { + PortNumberEdit->SetAsInteger(FtpsImplicitPortNumber); + } + } + else if ((FSProtocol == fsWebDAV) && (Ftps == ftpsNone)) + { + if ((Port == FtpPortNumber) || (Port == FtpsImplicitPortNumber) || (Port == HTTPSPortNumber)) + { + PortNumberEdit->SetAsInteger(HTTPPortNumber); + UnicodeString HostName = HostNameEdit->GetText(); + ::AdjustRemoteDir(HostName, PortNumberEdit, RemoteDirectoryEdit); + HostNameEdit->SetText(HostName); + } + } + else if ((FSProtocol == fsWebDAV) && (Ftps != ftpsNone)) + { + if ((Port == FtpPortNumber) || (Port == FtpsImplicitPortNumber) || (Port == HTTPPortNumber)) + { + PortNumberEdit->SetAsInteger(HTTPSPortNumber); + UnicodeString HostName = HostNameEdit->GetText(); + ::AdjustRemoteDir(HostName, PortNumberEdit, RemoteDirectoryEdit); + HostNameEdit->SetText(HostName); + } + } +} + +bool TSessionDialog::IsSshProtocol(TFSProtocol FSProtocol) const +{ + bool Result = + (FSProtocol == fsSFTPonly) || (FSProtocol == fsSFTP) || (FSProtocol == fsSCPonly); + + return Result; +} + +bool TSessionDialog::IsWebDAVProtocol(TFSProtocol FSProtocol) const +{ + return FSProtocol == fsWebDAV; +} + +bool TSessionDialog::IsSshOrWebDAVProtocol(TFSProtocol FSProtocol) const +{ + return IsSshProtocol(FSProtocol) || IsWebDAVProtocol(FSProtocol); +} + +void TSessionDialog::UpdateControls() +{ + TFSProtocol FSProtocol = GetFSProtocol(); + TFtps Ftps = GetFtps(); + bool InternalSshProtocol = IsSshProtocol(FSProtocol); + bool InternalWebDAVProtocol = IsWebDAVProtocol(FSProtocol); + bool HTTPSProtocol = (FSProtocol == fsWebDAV) && (Ftps != ftpsNone); + bool SshProtocol = InternalSshProtocol; + bool SftpProtocol = (FSProtocol == fsSFTPonly) || (FSProtocol == fsSFTP); + bool ScpOnlyProtocol = (FSProtocol == fsSCPonly); + bool FtpProtocol = (FSProtocol == fsFTP) && (Ftps == ftpsNone); + bool FtpsProtocol = (FSProtocol == fsFTP) && (Ftps != ftpsNone); + bool LoginAnonymous = false; + + ConnectButton->SetEnabled(!HostNameEdit->GetIsEmpty()); + + // Basic tab + AllowScpFallbackCheck->SetVisible( + TransferProtocolCombo->GetVisible() && + (IndexToFSProtocol(TransferProtocolCombo->GetItemIndex(), false) == fsSFTPonly)); + InsecureLabel->SetVisible(TransferProtocolCombo->GetVisible() && !SshProtocol && !FtpsProtocol && !HTTPSProtocol); + bool FtpEncryptionVisible = (GetTab() == FtpEncryptionCombo->GetGroup()) && + (FtpProtocol || FtpsProtocol || InternalWebDAVProtocol || HTTPSProtocol); + FtpEncryptionLabel->SetVisible(FtpEncryptionVisible); + FtpEncryptionCombo->SetVisible(FtpEncryptionVisible); + PrivateKeyEdit->SetEnabled(SshProtocol || FtpsProtocol || HTTPSProtocol); + HostNameLabel->SetCaption(GetMsg(LOGIN_HOST_NAME)); + + UserNameEdit->SetEnabled(!LoginAnonymous); + PasswordEdit->SetEnabled(!LoginAnonymous); + + // Connection sheet + FtpPasvModeCheck->SetEnabled(FtpProtocol); + if (FtpProtocol && (FtpProxyMethodCombo->GetItemIndex() != pmNone) && !FtpPasvModeCheck->GetChecked()) + { + FtpPasvModeCheck->SetChecked(true); + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + WinSCPPlugin->MoreMessageDialog(GetMsg(FTP_PASV_MODE_REQUIRED), + nullptr, qtInformation, qaOK); + } + SshBufferSizeCheck->SetEnabled(SshProtocol); + PingNullPacketButton->SetEnabled(SshProtocol); + IPAutoButton->SetEnabled(SshProtocol); + + // SFTP tab + SftpTab->SetEnabled(SftpProtocol); + + // FTP tab + FtpTab->SetEnabled(FtpProtocol || FtpsProtocol); + FtpAllowEmptyPasswordCheck->SetEnabled(FtpProtocol || FtpsProtocol); + SslSessionReuseCheck->SetEnabled(FtpsProtocol); + + // SSH tab + SshTab->SetEnabled(SshProtocol); + CipherUpButton->SetEnabled(CipherListBox->GetItems()->GetSelected() != 0); + CipherDownButton->SetEnabled( + CipherListBox->GetItems()->GetSelected() < CipherListBox->GetItems()->GetCount() - 1); + + // Authentication tab + AuthenticationTab->SetEnabled(SshProtocol); + SshNoUserAuthCheck->SetEnabled(!SshProt1onlyButton->GetChecked()); + bool Authentication = !SshNoUserAuthCheck->GetEnabled() || !SshNoUserAuthCheck->GetChecked(); + TryAgentCheck->SetEnabled(Authentication); + AuthTISCheck->SetEnabled(Authentication && !SshProt2onlyButton->GetChecked()); + AuthKICheck->SetEnabled(Authentication && !SshProt1onlyButton->GetChecked()); + AuthKIPasswordCheck->SetEnabled( + Authentication && + ((AuthTISCheck->GetEnabled() && AuthTISCheck->GetChecked()) || + (AuthKICheck->GetEnabled() && AuthKICheck->GetChecked()))); + AuthGSSAPICheck3->SetEnabled( + Authentication && !SshProt1onlyButton->GetChecked()); + GSSAPIFwdTGTCheck->SetEnabled( + Authentication && !SshProt1onlyButton->GetChecked()); + + // Directories tab + CacheDirectoryChangesCheck->SetEnabled( + (FSProtocol != fsSCPonly) || CacheDirectoriesCheck->GetChecked()); + PreserveDirectoryChangesCheck->SetEnabled( + CacheDirectoryChangesCheck->GetIsEnabled() && CacheDirectoryChangesCheck->GetChecked()); + ResolveSymlinksCheck->SetEnabled(!InternalWebDAVProtocol); + + // Environment tab + DSTModeUnixCheck->SetEnabled(!FtpProtocol); + TimeDifferenceEdit->SetEnabled((FtpProtocol || (FSProtocol == fsSCPonly))); + + // Recycle bin tab + OverwrittenToRecycleBinCheck->SetEnabled((FSProtocol != fsSCPonly) && + !FtpProtocol); + RecycleBinPathEdit->SetEnabled( + (DeleteToRecycleBinCheck->GetIsEnabled() && DeleteToRecycleBinCheck->GetChecked()) || + (OverwrittenToRecycleBinCheck->GetIsEnabled() && OverwrittenToRecycleBinCheck->GetChecked())); + + // Kex tab + KexTab->SetEnabled(SshProtocol && !SshProt1onlyButton->GetChecked() && + (BugRekey2Combo->GetItemIndex() != 2)); + KexUpButton->SetEnabled((KexListBox->GetItems()->GetSelected() > 0)); + KexDownButton->SetEnabled( + (KexListBox->GetItems()->GetSelected() < KexListBox->GetItems()->GetCount() - 1)); + + // Bugs tab + BugsTab->SetEnabled(SshProtocol); + + // WebDAV tab + WebDAVTab->SetEnabled(InternalWebDAVProtocol); + + // Scp/Shell tab + ScpTab->SetEnabled(InternalSshProtocol); + // disable also for SFTP with SCP fallback, as if someone wants to configure + // these he/she probably intends to use SCP and should explicitly select it. + // (note that these are not used for secondary shell session) + ListingCommandEdit->SetEnabled(ScpOnlyProtocol); + IgnoreLsWarningsCheck->SetEnabled(ScpOnlyProtocol); + SCPLsFullTimeAutoCheck->SetEnabled(ScpOnlyProtocol); + LookupUserGroupsCheck->SetEnabled(ScpOnlyProtocol); + UnsetNationalVarsCheck->SetEnabled(ScpOnlyProtocol); + ClearAliasesCheck->SetEnabled(ScpOnlyProtocol); + Scp1CompatibilityCheck->SetEnabled(ScpOnlyProtocol); + + // Connection/Proxy tab + TFarComboBox * ProxyMethodCombo = GetProxyMethodCombo(); + TProxyMethod ProxyMethod = IndexToProxyMethod(ProxyMethodCombo->GetItemIndex(), ProxyMethodCombo->GetItems()); + ProxyMethodCombo->SetVisible(GetTab() == ProxyMethodCombo->GetGroup()); + TFarComboBox * OtherProxyMethodCombo = GetOtherProxyMethodCombo(); + OtherProxyMethodCombo->SetVisible(false); + + bool Proxy = (ProxyMethod != pmNone); + UnicodeString ProxyCommand = + (ProxyMethod == pmCmd) ? + ProxyLocalCommandEdit->GetText() : ProxyTelnetCommandEdit->GetText(); + ProxyHostEdit->SetEnabled(Proxy && (ProxyMethod != pmSystem) && + ((ProxyMethod != pmCmd) || + ::AnsiContainsText(ProxyCommand, L"%proxyhost"))); + ProxyPortEdit->SetEnabled(Proxy && (ProxyMethod != pmSystem) && + ((ProxyMethod != pmCmd) || + ::AnsiContainsText(ProxyCommand, L"%proxyport"))); + ProxyUsernameEdit->SetEnabled(Proxy && + // FZAPI does not support username for SOCKS4 + (((ProxyMethod == pmSocks4) && SshProtocol) || + (ProxyMethod == pmSocks5) || + (ProxyMethod == pmHTTP) || + (((ProxyMethod == pmTelnet) || + (ProxyMethod == pmCmd)) && + ::AnsiContainsText(ProxyCommand, L"%user")) || + (ProxyMethod == pmSystem))); + ProxyPasswordEdit->SetEnabled(Proxy && + ((ProxyMethod == pmSocks5) || + (ProxyMethod == pmHTTP) || + (((ProxyMethod == pmTelnet) || + (ProxyMethod == pmCmd)) && + ::AnsiContainsText(ProxyCommand, L"%pass")) || + (ProxyMethod == pmSystem))); + bool ProxySettings = Proxy && SshProtocol; + ProxyTelnetCommandEdit->SetEnabled(ProxySettings && (ProxyMethod == pmTelnet)); + ProxyLocalCommandEdit->SetVisible((GetTab() == ProxyMethodCombo->GetGroup()) && (ProxyMethod == pmCmd)); + ProxyLocalCommandLabel->SetVisible(ProxyLocalCommandEdit->GetVisible()); + ProxyTelnetCommandEdit->SetVisible((GetTab() == ProxyMethodCombo->GetGroup()) && (ProxyMethod != pmCmd)); + ProxyTelnetCommandLabel->SetVisible(ProxyTelnetCommandEdit->GetVisible()); + ProxyLocalhostCheck->SetEnabled(ProxySettings); + ProxyDNSOffButton->SetEnabled(ProxySettings); + + // Tunnel tab + TunnelTab->SetEnabled(InternalSshProtocol); +} + +bool TSessionDialog::Execute(TSessionData * SessionData, TSessionActionEnum & Action) +{ + int Captions[] = + { + LOGIN_ADD, + LOGIN_EDIT, + LOGIN_CONNECT + }; + SetCaption(GetMsg(Captions[Action])); + + FSessionData = SessionData; + FTransferProtocolIndex = TransferProtocolCombo->GetItemIndex(); + + FFtpEncryptionComboIndex = FtpEncryptionCombo->GetItemIndex(); + + HideTabs(); + SelectTab(tabSession); + + // load session data + + // Basic tab + HostNameEdit->SetText(SessionData->GetHostName()); + PortNumberEdit->SetAsInteger(SessionData->GetPortNumber()); + + UserNameEdit->SetText(SessionData->SessionGetUserName()); + PasswordEdit->SetText(SessionData->GetPassword()); + PrivateKeyEdit->SetText(SessionData->GetPublicKeyFile()); + + bool AllowScpFallback; + TransferProtocolCombo->SetItemIndex( + static_cast(FSProtocolToIndex(SessionData->GetFSProtocol(), AllowScpFallback))); + AllowScpFallbackCheck->SetChecked(AllowScpFallback); + + // Directories tab + RemoteDirectoryEdit->SetText(SessionData->GetRemoteDirectory()); + UpdateDirectoriesCheck->SetChecked(SessionData->GetUpdateDirectories()); + CacheDirectoriesCheck->SetChecked(SessionData->GetCacheDirectories()); + CacheDirectoryChangesCheck->SetChecked(SessionData->GetCacheDirectoryChanges()); + PreserveDirectoryChangesCheck->SetChecked(SessionData->GetPreserveDirectoryChanges()); + ResolveSymlinksCheck->SetChecked(SessionData->GetResolveSymlinks()); + + // Environment tab + if (SessionData->GetEOLType() == eolLF) + { + EOLTypeCombo->SetItemIndex(0); + } + else + { + EOLTypeCombo->SetItemIndex(1); + } + + switch (SessionData->GetDSTMode()) + { + case dstmWin: + DSTModeWinCheck->SetChecked(true); + break; + + case dstmKeep: + DSTModeKeepCheck->SetChecked(true); + break; + + default: + case dstmUnix: + DSTModeUnixCheck->SetChecked(true); + break; + } + + DeleteToRecycleBinCheck->SetChecked(SessionData->GetDeleteToRecycleBin()); + OverwrittenToRecycleBinCheck->SetChecked(SessionData->GetOverwrittenToRecycleBin()); + RecycleBinPathEdit->SetText(SessionData->GetRecycleBinPath()); + + // Shell tab + if (SessionData->GetDefaultShell()) + { + ShellEdit->SetText(ShellEdit->GetItems()->GetString(0)); + } + else + { + ShellEdit->SetText(SessionData->GetShell()); + } + if (SessionData->GetDetectReturnVar()) + { + ReturnVarEdit->SetText(ReturnVarEdit->GetItems()->GetString(0)); + } + else + { + ReturnVarEdit->SetText(SessionData->GetReturnVar()); + } + LookupUserGroupsCheck->SetChecked(SessionData->GetLookupUserGroups() != asOff); + ClearAliasesCheck->SetChecked(SessionData->GetClearAliases()); + IgnoreLsWarningsCheck->SetChecked(SessionData->GetIgnoreLsWarnings()); + Scp1CompatibilityCheck->SetChecked(SessionData->GetScp1Compatibility()); + UnsetNationalVarsCheck->SetChecked(SessionData->GetUnsetNationalVars()); + ListingCommandEdit->SetText(SessionData->GetListingCommand()); + SCPLsFullTimeAutoCheck->SetChecked((SessionData->GetSCPLsFullTime() != asOff)); + intptr_t TimeDifferenceMin = ::DateTimeToTimeStamp(SessionData->GetTimeDifference()).Time / 60000; + if (SessionData->GetTimeDifference().GetValue() < 0) + { + TimeDifferenceMin = -TimeDifferenceMin; + } + TimeDifferenceEdit->SetAsInteger(TimeDifferenceMin / 60); + TimeDifferenceMinutesEdit->SetAsInteger(TimeDifferenceMin % 60); + + // SFTP tab + +#define TRISTATE(COMBO, PROP, MSG) \ + COMBO->SetItemIndex(static_cast(2 - SessionData->Get ## PROP)) + SFTP_BUGS(); + + if (SessionData->GetSftpServer().IsEmpty()) + { + SftpServerEdit->SetText(SftpServerEdit->GetItems()->GetString(0)); + } + else + { + SftpServerEdit->SetText(SessionData->GetSftpServer()); + } + SFTPMaxVersionCombo->SetItemIndex(static_cast(SessionData->GetSFTPMaxVersion())); + SFTPMinPacketSizeEdit->SetAsInteger(SessionData->GetSFTPMinPacketSize()); + SFTPMaxPacketSizeEdit->SetAsInteger(SessionData->GetSFTPMaxPacketSize()); + + // FTP tab + FtpUseMlsdCombo->SetItemIndex(static_cast(2 - SessionData->GetFtpUseMlsd())); + FtpAllowEmptyPasswordCheck->SetChecked(SessionData->GetFtpAllowEmptyPassword()); + std::unique_ptr PostLoginCommands(new TStringList()); + PostLoginCommands->SetText(SessionData->GetPostLoginCommands()); + for (intptr_t Index = 0; (Index < PostLoginCommands->GetCount()) && + (Index < static_cast(_countof(PostLoginCommandsEdits))); ++Index) + { + PostLoginCommandsEdits[Index]->SetText(PostLoginCommands->GetString(Index)); + } + + FtpDupFFCheck->SetChecked(SessionData->GetFtpDupFF()); + FtpUndupFFCheck->SetChecked(SessionData->GetFtpUndupFF()); + SslSessionReuseCheck->SetChecked(SessionData->GetSslSessionReuse()); + + TFtps Ftps = SessionData->GetFtps(); + switch (Ftps) + { + case ftpsNone: + FtpEncryptionCombo->SetItemIndex(0); + break; + + case ftpsImplicit: + FtpEncryptionCombo->SetItemIndex(1); + break; + + case ftpsExplicitSsl: + FtpEncryptionCombo->SetItemIndex(2); + break; + + case ftpsExplicitTls: + FtpEncryptionCombo->SetItemIndex(3); + break; + + default: + FtpEncryptionCombo->SetItemIndex(0); + break; + } + + // Connection tab + FtpPasvModeCheck->SetChecked(SessionData->GetFtpPasvMode()); + SshBufferSizeCheck->SetChecked((FSessionData->GetSendBuf() > 0) && FSessionData->GetSshSimple()); + LoadPing(SessionData); + TimeoutEdit->SetAsInteger(SessionData->GetTimeout()); + + switch (SessionData->GetAddressFamily()) + { + case afIPv4: + IPv4Button->SetChecked(true); + break; + + case afIPv6: + IPv6Button->SetChecked(true); + break; + + case afAuto: + default: + IPAutoButton->SetChecked(true); + break; + } + + if (SessionData->GetCodePage().IsEmpty()) + { + CodePageEdit->SetText(CodePageEdit->GetItems()->GetString(0)); + } + else + { + CodePageEdit->SetText(SessionData->GetCodePage()); + } + + // Proxy tab + TFarComboBox * ProxyMethodCombo = GetProxyMethodCombo(); + intptr_t Index = ProxyMethodToIndex(SessionData->GetProxyMethod(), ProxyMethodCombo->GetItems()); + ProxyMethodCombo->SetItemIndex(Index); + if (SessionData->GetProxyMethod() != pmSystem) + { + ProxyHostEdit->SetText(SessionData->GetProxyHost()); + ProxyPortEdit->SetAsInteger(SessionData->GetProxyPort()); + } + ProxyUsernameEdit->SetText(SessionData->GetProxyUsername()); + ProxyPasswordEdit->SetText(SessionData->GetProxyPassword()); + ProxyTelnetCommandEdit->SetText(SessionData->GetProxyTelnetCommand()); + ProxyLocalCommandEdit->SetText(SessionData->GetProxyLocalCommand()); + ProxyLocalhostCheck->SetChecked(SessionData->GetProxyLocalhost()); + switch (SessionData->GetProxyDNS()) + { + case asOn: + ProxyDNSOnButton->SetChecked(true); + break; + case asOff: + ProxyDNSOffButton->SetChecked(true); + break; + default: + ProxyDNSAutoButton->SetChecked(true); + break; + } + + // Tunnel tab + TunnelCheck->SetChecked(SessionData->GetTunnel()); + TunnelUserNameEdit->SetText(SessionData->GetTunnelUserName()); + TunnelPortNumberEdit->SetAsInteger(SessionData->GetTunnelPortNumber()); + TunnelHostNameEdit->SetText(SessionData->GetTunnelHostName()); + TunnelPasswordEdit->SetText(SessionData->GetTunnelPassword()); + TunnelPrivateKeyEdit->SetText(SessionData->GetTunnelPublicKeyFile()); + if (SessionData->GetTunnelAutoassignLocalPortNumber()) + { + TunnelLocalPortNumberEdit->SetText(TunnelLocalPortNumberEdit->GetItems()->GetString(0)); + } + else + { + TunnelLocalPortNumberEdit->SetText(::IntToStr(SessionData->GetTunnelLocalPortNumber())); + } + + // SSH tab + CompressionCheck->SetChecked(SessionData->GetCompression()); + if (Ssh2DESCheck != nullptr) + { + Ssh2DESCheck->SetChecked(SessionData->GetSsh2DES()); + } + + switch (SessionData->GetSshProt()) + { + case ssh1only: + SshProt1onlyButton->SetChecked(true); + break; + case ssh1deprecated: + SshProt1Button->SetChecked(true); + break; + case ssh2deprecated: + SshProt2Button->SetChecked(true); + break; + case ssh2only: + SshProt2onlyButton->SetChecked(true); + break; + } + + CipherListBox->GetItems()->BeginUpdate(); + { + SCOPE_EXIT + { + CipherListBox->GetItems()->EndUpdate(); + }; + CipherListBox->GetItems()->Clear(); + DebugAssert(CIPHER_NAME_WARN + CIPHER_COUNT - 1 == CIPHER_NAME_CHACHA20); + for (intptr_t Index2 = 0; Index2 < CIPHER_COUNT; ++Index2) + { + TObject * Obj = static_cast(ToPtr(SessionData->GetCipher(Index2))); + CipherListBox->GetItems()->AddObject( + GetMsg(CIPHER_NAME_WARN + static_cast(SessionData->GetCipher(Index2))), + Obj); + } + } + + // KEX tab + + RekeyTimeEdit->SetAsInteger(SessionData->GetRekeyTime()); + RekeyDataEdit->SetText(SessionData->GetRekeyData()); + + KexListBox->GetItems()->BeginUpdate(); + { + SCOPE_EXIT + { + KexListBox->GetItems()->EndUpdate(); + }; + KexListBox->GetItems()->Clear(); + DebugAssert(KEX_NAME_WARN + KEX_COUNT - 1 == KEX_NAME_ECDH); + for (intptr_t Index3 = 0; Index3 < KEX_COUNT; ++Index3) + { + KexListBox->GetItems()->AddObject( + GetMsg(KEX_NAME_WARN + static_cast(SessionData->GetKex(Index3))), + static_cast(ToPtr(SessionData->GetKex(Index3)))); + } + } + + // Authentication tab + SshNoUserAuthCheck->SetChecked(SessionData->GetSshNoUserAuth()); + TryAgentCheck->SetChecked(SessionData->GetTryAgent()); + AuthTISCheck->SetChecked(SessionData->GetAuthTIS()); + AuthKICheck->SetChecked(SessionData->GetAuthKI()); + AuthKIPasswordCheck->SetChecked(SessionData->GetAuthKIPassword()); + AgentFwdCheck->SetChecked(SessionData->GetAgentFwd()); + AuthGSSAPICheck3->SetChecked(SessionData->GetAuthGSSAPI()); + GSSAPIFwdTGTCheck->SetChecked(SessionData->GetGSSAPIFwdTGT()); + + // Bugs tab + + BUGS(); + + // WebDAV tab + WebDAVCompressionCheck->SetChecked(SessionData->GetCompression()); + +#undef TRISTATE + + intptr_t Button = ShowModal(); + bool Result = (Button == brOK || Button == brConnect); + if (Result) + { + if (Button == brConnect) + { + Action = saConnect; + } + else if (Action == saConnect) + { + Action = saEdit; + } + + UnicodeString HostName = HostNameEdit->GetText(); + UnicodeString UserName = UserNameEdit->GetText(); + UnicodeString Password = PasswordEdit->GetText(); + SessionData->RemoveProtocolPrefix(HostName); + // parse username, password and directory, if any + { + intptr_t Pos = HostName.RPos(L'@'); + if (Pos > 0) + { + UnicodeString UserNameAndPassword = HostName.SubString(1, Pos - 1); + Pos = UserNameAndPassword.RPos(L':'); + if (Pos > 0) + { + UserName = UserNameAndPassword.SubString(1, Pos - 1); + Password = UserNameAndPassword.SubString(Pos + 1, - 1); + } + else + { + UserName = UserNameAndPassword; + } + } + } + // if (GetFSProtocol() == fsWebDAV) + { + ::AdjustRemoteDir(HostName, PortNumberEdit, RemoteDirectoryEdit); + } + + // save session data + + // Basic tab + SessionData->SetFSProtocol(GetFSProtocol()); + + SessionData->SetHostName(HostName); + SessionData->SetPortNumber(PortNumberEdit->GetAsInteger()); + SessionData->SetUserName(UserName); + SessionData->SetPassword(Password); + SessionData->SetLoginType(ltNormal); + SessionData->SetPublicKeyFile(PrivateKeyEdit->GetText()); + + // Directories tab + SessionData->SetRemoteDirectory(RemoteDirectoryEdit->GetText()); + SessionData->SetUpdateDirectories(UpdateDirectoriesCheck->GetChecked()); + SessionData->SetCacheDirectories(CacheDirectoriesCheck->GetChecked()); + SessionData->SetCacheDirectoryChanges(CacheDirectoryChangesCheck->GetChecked()); + SessionData->SetPreserveDirectoryChanges(PreserveDirectoryChangesCheck->GetChecked()); + SessionData->SetResolveSymlinks(ResolveSymlinksCheck->GetChecked()); + + // Environment tab + if (DSTModeUnixCheck->GetChecked()) + { + SessionData->SetDSTMode(dstmUnix); + } + else if (DSTModeKeepCheck->GetChecked()) + { + SessionData->SetDSTMode(dstmKeep); + } + else + { + SessionData->SetDSTMode(dstmWin); + } + if (EOLTypeCombo->GetItemIndex() == 0) + { + SessionData->SetEOLType(eolLF); + } + else + { + SessionData->SetEOLType(eolCRLF); + } + + SessionData->SetDeleteToRecycleBin(DeleteToRecycleBinCheck->GetChecked()); + SessionData->SetOverwrittenToRecycleBin(OverwrittenToRecycleBinCheck->GetChecked()); + SessionData->SetRecycleBinPath(RecycleBinPathEdit->GetText()); + + // SCP tab + SessionData->SetDefaultShell(ShellEdit->GetText() == ShellEdit->GetItems()->GetString(0)); + SessionData->SetShell((SessionData->GetDefaultShell() ? UnicodeString() : ShellEdit->GetText())); + SessionData->SetDetectReturnVar(ReturnVarEdit->GetText() == ReturnVarEdit->GetItems()->GetString(0)); + SessionData->SetReturnVar((SessionData->GetDetectReturnVar() ? UnicodeString() : ReturnVarEdit->GetText())); + SessionData->SetLookupUserGroups(LookupUserGroupsCheck->GetChecked() ? asOn : asOff); + SessionData->SetClearAliases(ClearAliasesCheck->GetChecked()); + SessionData->SetIgnoreLsWarnings(IgnoreLsWarningsCheck->GetChecked()); + SessionData->SetScp1Compatibility(Scp1CompatibilityCheck->GetChecked()); + SessionData->SetUnsetNationalVars(UnsetNationalVarsCheck->GetChecked()); + SessionData->SetListingCommand(ListingCommandEdit->GetText()); + SessionData->SetSCPLsFullTime(SCPLsFullTimeAutoCheck->GetChecked() ? asAuto : asOff); + SessionData->SetTimeDifference(TDateTime( + (ToDouble(TimeDifferenceEdit->GetAsInteger()) / 24) + + (ToDouble(TimeDifferenceMinutesEdit->GetAsInteger()) / 24 / 60))); + + // SFTP tab + +#define TRISTATE(COMBO, PROP, MSG) \ + SessionData->Set##PROP(sb##PROP, static_cast(2 - COMBO->GetItemIndex())); + // SFTP_BUGS(); + SessionData->SetSFTPBug(sbSymlink, static_cast(2 - SFTPBugSymlinkCombo->GetItemIndex())); + SessionData->SetSFTPBug(sbSignedTS, static_cast(2 - SFTPBugSignedTSCombo->GetItemIndex())); + + SessionData->SetSftpServer( + (SftpServerEdit->GetText() == SftpServerEdit->GetItems()->GetString(0)) ? + UnicodeString() : SftpServerEdit->GetText()); + SessionData->SetSFTPMaxVersion(SFTPMaxVersionCombo->GetItemIndex()); + SessionData->SetSFTPMinPacketSize(SFTPMinPacketSizeEdit->GetAsInteger()); + SessionData->SetSFTPMaxPacketSize(SFTPMaxPacketSizeEdit->GetAsInteger()); + + // FTP tab + SessionData->SetFtpUseMlsd(static_cast(2 - FtpUseMlsdCombo->GetItemIndex())); + SessionData->SetFtpAllowEmptyPassword(FtpAllowEmptyPasswordCheck->GetChecked()); + SessionData->SetFtpDupFF(FtpDupFFCheck->GetChecked()); + SessionData->SetFtpUndupFF(FtpUndupFFCheck->GetChecked()); + SessionData->SetSslSessionReuse(SslSessionReuseCheck->GetChecked()); + TODO("TlsCertificateFileEdit->GetText()"); + SessionData->SetTlsCertificateFile(PrivateKeyEdit->GetText()); + std::unique_ptr PostLoginCommands2(new TStringList()); + for (intptr_t Index4 = 0; Index4 < static_cast(_countof(PostLoginCommandsEdits)); ++Index4) + { + UnicodeString Text = PostLoginCommandsEdits[Index4]->GetText(); + if (!Text.IsEmpty()) + { + PostLoginCommands2->Add(PostLoginCommandsEdits[Index4]->GetText()); + } + } + + SessionData->SetPostLoginCommands(PostLoginCommands2->GetText()); + if ((GetFSProtocol() == fsFTP) && (GetFtps() != ftpsNone)) + { + SessionData->SetFtps(GetFtps()); + } + else + { + SessionData->SetFtps(ftpsNone); + } + + switch (FtpEncryptionCombo->GetItemIndex()) + { + case 0: + SessionData->SetFtps(ftpsNone); + break; + case 1: + SessionData->SetFtps(ftpsImplicit); + break; + case 2: + SessionData->SetFtps(ftpsExplicitSsl); + break; + case 3: + SessionData->SetFtps(ftpsExplicitTls); + break; + default: + SessionData->SetFtps(ftpsNone); + break; + } + + // Connection tab + SessionData->SetFtpPasvMode(FtpPasvModeCheck->GetChecked()); + SessionData->SetSendBuf(SshBufferSizeCheck->GetChecked() ? DefaultSendBuf : 0); + SessionData->SetSshSimple(SshBufferSizeCheck->GetChecked()); + if (PingOffButton->GetChecked()) + { + SessionData->SetPingType(ptOff); + } + else if (PingNullPacketButton->GetChecked()) + { + SessionData->SetPingType(ptNullPacket); + } + else if (PingDummyCommandButton->GetChecked()) + { + SessionData->SetPingType(ptDummyCommand); + } + else + { + SessionData->SetPingType(ptOff); + } + if (GetFSProtocol() == fsFTP) + { + if (PingOffButton->GetChecked()) + { + SessionData->SetFtpPingType(ptOff); + } + else if (PingNullPacketButton->GetChecked()) + { + SessionData->SetFtpPingType(ptNullPacket); + } + else if (PingDummyCommandButton->GetChecked()) + { + SessionData->SetFtpPingType(ptDummyCommand); + } + else + { + SessionData->SetFtpPingType(ptOff); + } + SessionData->SetFtpPingInterval(PingIntervalSecEdit->GetAsInteger()); + } + else + { + SessionData->SetPingInterval(PingIntervalSecEdit->GetAsInteger()); + } + SessionData->SetTimeout(TimeoutEdit->GetAsInteger()); + + if (IPv4Button->GetChecked()) + { + SessionData->SetAddressFamily(afIPv4); + } + else if (IPv6Button->GetChecked()) + { + SessionData->SetAddressFamily(afIPv6); + } + else + { + SessionData->SetAddressFamily(afAuto); + } + SessionData->SetCodePage( + (CodePageEdit->GetText() == CodePageEdit->GetItems()->GetString(0)) ? + UnicodeString() : CodePageEdit->GetText()); + + // Proxy tab + SessionData->SetProxyMethod(GetProxyMethod()); + SessionData->SetFtpProxyLogonType(GetFtpProxyLogonType()); + SessionData->SetProxyHost(ProxyHostEdit->GetText()); + SessionData->SetProxyPort(ProxyPortEdit->GetAsInteger()); + SessionData->SetProxyUsername(ProxyUsernameEdit->GetText()); + SessionData->SetProxyPassword(ProxyPasswordEdit->GetText()); + SessionData->SetProxyTelnetCommand(ProxyTelnetCommandEdit->GetText()); + SessionData->SetProxyLocalCommand(ProxyLocalCommandEdit->GetText()); + SessionData->SetProxyLocalhost(ProxyLocalhostCheck->GetChecked()); + + if (ProxyDNSOnButton->GetChecked()) + { + SessionData->SetProxyDNS(asOn); + } + else if (ProxyDNSOffButton->GetChecked()) + { + SessionData->SetProxyDNS(asOff); + } + else + { + SessionData->SetProxyDNS(asAuto); + } + + // Tunnel tab + SessionData->SetTunnel(TunnelCheck->GetChecked()); + SessionData->SetTunnelUserName(TunnelUserNameEdit->GetText()); + SessionData->SetTunnelPortNumber(TunnelPortNumberEdit->GetAsInteger()); + SessionData->SetTunnelHostName(TunnelHostNameEdit->GetText()); + SessionData->SetTunnelPassword(TunnelPasswordEdit->GetText()); + SessionData->SetTunnelPublicKeyFile(TunnelPrivateKeyEdit->GetText()); + if (TunnelLocalPortNumberEdit->GetText() == TunnelLocalPortNumberEdit->GetItems()->GetString(0)) + { + SessionData->SetTunnelLocalPortNumber(0); + } + else + { + SessionData->SetTunnelLocalPortNumber(::StrToIntDef(TunnelLocalPortNumberEdit->GetText(), 0)); + } + + // SSH tab + SessionData->SetCompression(CompressionCheck->GetChecked()); + if (Ssh2DESCheck != nullptr) + { + SessionData->SetSsh2DES(Ssh2DESCheck->GetChecked()); + } + + if (SshProt1onlyButton->GetChecked()) + { + SessionData->SetSshProt(ssh1only); + } + else if (SshProt1Button->GetChecked()) + { + SessionData->SetSshProt(ssh1deprecated); + } + else if (SshProt2Button->GetChecked()) + { + SessionData->SetSshProt(ssh2deprecated); + } + else + { + SessionData->SetSshProt(ssh2only); + } + + for (intptr_t Index5 = 0; Index5 < CIPHER_COUNT; ++Index5) + { + TObject * Obj = static_cast(CipherListBox->GetItems()->GetObj(Index5)); + SessionData->SetCipher(Index5, static_cast(reinterpret_cast(Obj))); + } + + // KEX tab + + SessionData->SetRekeyTime(RekeyTimeEdit->GetAsInteger()); + SessionData->SetRekeyData(RekeyDataEdit->GetText()); + + for (intptr_t Index6 = 0; Index6 < KEX_COUNT; ++Index6) + { + SessionData->SetKex(Index6, static_cast(reinterpret_cast(KexListBox->GetItems()->GetObj(Index)))); + } + + // Authentication tab + SessionData->SetSshNoUserAuth(SshNoUserAuthCheck->GetChecked()); + SessionData->SetTryAgent(TryAgentCheck->GetChecked()); + SessionData->SetAuthTIS(AuthTISCheck->GetChecked()); + SessionData->SetAuthKI(AuthKICheck->GetChecked()); + SessionData->SetAuthKIPassword(AuthKIPasswordCheck->GetChecked()); + SessionData->SetAgentFwd(AgentFwdCheck->GetChecked()); + SessionData->SetAuthGSSAPI(AuthGSSAPICheck3->GetChecked()); + SessionData->SetGSSAPIFwdTGT(GSSAPIFwdTGTCheck->GetChecked()); + + // Bugs tab + // BUGS(); + + // WebDAV tab + if (GetFSProtocol() == fsWebDAV) + SessionData->SetCompression(WebDAVCompressionCheck->GetChecked()); + +#undef TRISTATE + SessionData->SetBug(sbIgnore1, static_cast(2 - BugIgnore1Combo->GetItemIndex())); + SessionData->SetBug(sbPlainPW1, static_cast(2 - BugPlainPW1Combo->GetItemIndex())); + SessionData->SetBug(sbRSA1, static_cast(2 - BugRSA1Combo->GetItemIndex())); + SessionData->SetBug(sbHMAC2, static_cast(2 - BugHMAC2Combo->GetItemIndex())); + SessionData->SetBug(sbDeriveKey2, static_cast(2 - BugDeriveKey2Combo->GetItemIndex())); + SessionData->SetBug(sbRSAPad2, static_cast(2 - BugRSAPad2Combo->GetItemIndex())); + SessionData->SetBug(sbPKSessID2, static_cast(2 - BugPKSessID2Combo->GetItemIndex())); + SessionData->SetBug(sbRekey2, static_cast(2 - BugRekey2Combo->GetItemIndex())); + } + + return Result; +} + +void TSessionDialog::LoadPing(TSessionData * SessionData) +{ + TFSProtocol FSProtocol = IndexToFSProtocol(FTransferProtocolIndex, + AllowScpFallbackCheck->GetChecked()); + + switch ((FSProtocol == fsFTP) ? SessionData->GetFtpPingType() : SessionData->GetPingType()) + { + case ptOff: + PingOffButton->SetChecked(true); + break; + case ptNullPacket: + PingNullPacketButton->SetChecked(true); + break; + + case ptDummyCommand: + PingDummyCommandButton->SetChecked(true); + break; + + default: + PingOffButton->SetChecked(true); + break; + } + PingIntervalSecEdit->SetAsInteger( + (GetFSProtocol() == fsFTP) ? + SessionData->GetFtpPingInterval() : SessionData->GetPingInterval()); +} + +void TSessionDialog::SavePing(TSessionData * SessionData) +{ + TPingType PingType; + if (PingOffButton->GetChecked()) + { + PingType = ptOff; + } + else if (PingNullPacketButton->GetChecked()) + { + PingType = ptNullPacket; + } + else if (PingDummyCommandButton->GetChecked()) + { + PingType = ptDummyCommand; + } + else + { + PingType = ptOff; + } + TFSProtocol FSProtocol = IndexToFSProtocol(FTransferProtocolIndex, + AllowScpFallbackCheck->GetChecked()); + if (FSProtocol == fsFTP) + { + SessionData->SetFtpPingType(PingType); + SessionData->SetFtpPingInterval(PingIntervalSecEdit->GetAsInteger()); + } + else + { + SessionData->SetPingType(PingType); + SessionData->SetPingInterval(PingIntervalSecEdit->GetAsInteger()); + } +} + +intptr_t TSessionDialog::FSProtocolToIndex(TFSProtocol FSProtocol, + bool & AllowScpFallback) const +{ + if (FSProtocol == fsSFTP) + { + AllowScpFallback = true; + bool Dummy; + return FSProtocolToIndex(fsSFTPonly, Dummy); + } + else + { + AllowScpFallback = false; + for (intptr_t Index = 0; Index < TransferProtocolCombo->GetItems()->GetCount(); ++Index) + { + if (FSOrder[Index] == FSProtocol) + { + return Index; + } + } + // SFTP is always present + return FSProtocolToIndex(fsSFTP, AllowScpFallback); + } +} + +intptr_t TSessionDialog::ProxyMethodToIndex(TProxyMethod ProxyMethod, TFarList * Items) const +{ + for (intptr_t Index = 0; Index < Items->GetCount(); ++Index) + { + TObject * Obj = static_cast(Items->GetObj(Index)); + TProxyMethod Method = static_cast(reinterpret_cast(Obj)); + if (Method == ProxyMethod) + return Index; + } + return -1; +} + +TProxyMethod TSessionDialog::IndexToProxyMethod(intptr_t Index, TFarList * Items) const +{ + TProxyMethod Result = pmNone; + if (Index >= 0 && Index < Items->GetCount()) + { + TObject * Obj = static_cast(Items->GetObj(Index)); + Result = static_cast(reinterpret_cast(Obj)); + } + return Result; +} + +TFarComboBox * TSessionDialog::GetProxyMethodCombo() const +{ + return IsSshOrWebDAVProtocol(GetFSProtocol()) ? SshProxyMethodCombo : FtpProxyMethodCombo; +} + +TFarComboBox * TSessionDialog::GetOtherProxyMethodCombo() const +{ + return IsSshOrWebDAVProtocol(GetFSProtocol()) ? FtpProxyMethodCombo : SshProxyMethodCombo; +} + +TFSProtocol TSessionDialog::GetFSProtocol() const +{ + return IndexToFSProtocol(TransferProtocolCombo->GetItemIndex(), + AllowScpFallbackCheck->GetChecked()); +} + +inline intptr_t TSessionDialog::GetLastSupportedFtpProxyMethod() const +{ + return pmSystem; +} + +bool TSessionDialog::GetSupportedFtpProxyMethod(intptr_t Method) const +{ + return (Method >= 0) && (Method <= GetLastSupportedFtpProxyMethod()); +} + +TProxyMethod TSessionDialog::GetProxyMethod() const +{ + TFarComboBox * ProxyMethodCombo = GetProxyMethodCombo(); + TProxyMethod Result = IndexToProxyMethod(ProxyMethodCombo->GetItemIndex(), ProxyMethodCombo->GetItems()); + return Result; +} + +intptr_t TSessionDialog::GetFtpProxyLogonType() const +{ + intptr_t Result = GetProxyMethod(); + if (Result > GetLastSupportedFtpProxyMethod()) + Result -= GetLastSupportedFtpProxyMethod(); + else + Result = 0; + return Result; +} + +TFtps TSessionDialog::IndexToFtps(intptr_t Index) const +{ + bool InBounds = (Index != NPOS) && (Index < FtpEncryptionCombo->GetItems()->GetCount()); + DebugAssert(InBounds); + TFtps Result = ftpsNone; + if (InBounds) + { + switch (Index) + { + case 0: + Result = ftpsNone; + break; + + case 1: + Result = ftpsImplicit; + break; + + case 2: + Result = ftpsExplicitSsl; + break; + + case 3: + Result = ftpsExplicitTls; + break; + + default: + break; + } + } + return Result; +} + +TFtps TSessionDialog::GetFtps() const +{ + return static_cast(IndexToFtps(FtpEncryptionCombo->GetItemIndex())); +} + +TFSProtocol TSessionDialog::IndexToFSProtocol(intptr_t Index, bool AllowScpFallback) const +{ + bool InBounds = (Index >= 0) && (Index < static_cast(_countof(FSOrder))); + DebugAssert(InBounds || (Index == -1)); + TFSProtocol Result = fsSFTP; + if (InBounds) + { + Result = FSOrder[Index]; + if ((Result == fsSFTPonly) && AllowScpFallback) + { + Result = fsSFTP; + } + } + return Result; +} + +TLoginType TSessionDialog::IndexToLoginType(intptr_t Index) const +{ + bool InBounds = (Index != NPOS) && (Index <= ltNormal); + DebugAssert(InBounds); + TLoginType Result = ltAnonymous; + if (InBounds) + { + Result = static_cast(Index); + } + return Result; +} + +bool TSessionDialog::VerifyKey(const UnicodeString & AFileName, bool TypeOnly) +{ + bool Result = true; + +// Result = ::VerifyKey(AFileName, TypeOnly); + Result = ::VerifyAndConvertKey(AFileName, TypeOnly); +#if 0 + if (!::Trim(AFileName).IsEmpty()) + { + TKeyType KeyType = GetKeyType(AFileName); + UnicodeString Message; + switch (KeyType) + { + case ktOpenSSHAuto: + Message = FMTLOAD(KEY_TYPE_UNSUPPORTED2, AFileName.c_str(), L"OpenSSH SSH-2"); + break; + + case ktOpenSSHPEM: + case ktOpenSSHNew: + case ktSSHCom: + Message = FMTLOAD(KEY_TYPE_UNSUPPORTED2, AFileName.c_str(), L"ssh.com SSH-2"); + break; + + case ktSSH1Public: + case ktSSH2PublicRFC4716: + case ktSSH2PublicOpenSSH: + // noop + // Do not even bother checking SSH protocol version + break; + + case ktSSH1: + case ktSSH2: + if (!TypeOnly) + { + if ((KeyType == ktSSH1) != + (SshProt1onlyButton->GetChecked() || SshProt1Button->GetChecked())) + { + Message = FMTLOAD(KEY_TYPE_DIFFERENT_SSH, + AFileName.c_str(), (KeyType == ktSSH1 ? L"SSH-1" : L"PuTTY SSH-2")); + } + } + break; + + default: + DebugAssert(false); + // fallthru + case ktUnopenable: + case ktUnknown: + Message = FMTLOAD(KEY_TYPE_UNKNOWN2, AFileName.c_str()); + break; + } + + if (!Message.IsEmpty()) + { + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + Result = (WinSCPPlugin->MoreMessageDialog(Message, nullptr, qtWarning, + qaIgnore | qaAbort) != qaAbort); + } + } +#endif + return Result; +} + +bool TSessionDialog::CloseQuery() +{ + bool CanClose = TTabbedDialog::CloseQuery(); + + if (CanClose && (GetResult() != brCancel)) + { + CanClose = + VerifyKey(PrivateKeyEdit->GetText(), false) && + // for tunnel key do not check SSH version as it is not configurable + VerifyKey(TunnelPrivateKeyEdit->GetText(), true); + } + + if (CanClose && !PasswordEdit->GetText().IsEmpty() && + !GetConfiguration()->GetDisablePasswordStoring() && + (PasswordEdit->GetText() != FSessionData->GetPassword()) && + (((GetResult() == brOK)) || + ((GetResult() == brConnect) && (FAction == saEdit)))) + { + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + CanClose = (WinSCPPlugin->MoreMessageDialog(GetMsg(SAVE_PASSWORD), nullptr, + qtWarning, qaOK | qaCancel) == qaOK); + } + + return CanClose; +} + +void TSessionDialog::SelectTab(intptr_t Tab) +{ + TTabbedDialog::SelectTab(Tab); + TTabButton * SelectedTabBtn = GetTabButton(Tab); + intptr_t Index; + /*for (Index = 0; Index < FTabs->Count; ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetItem(Index)); + // Button->SetBrackets(Button->GetTab() == Tab ? brTight : brNone); + if (TabBtn == SelectedTabBtn) + TabBtn->SetColor(0, static_cast((GetSystemColor(COL_DIALOGTEXT) & 0xF0) | 0x09)); + else + TabBtn->SetColor(0, static_cast((GetSystemColor(COL_DIALOGTEXT) & 0xF0))); + }*/ + for (Index = 0; Index < FTabs->GetCount(); ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + if (TabBtn == SelectedTabBtn) + { + break; + } + } + intptr_t SelectedTabIndex = Index; + intptr_t VisibleTabsCount = GetVisibleTabsCount(SelectedTabIndex, false); + if ((FFirstVisibleTabIndex < SelectedTabIndex - VisibleTabsCount) || + (SelectedTabIndex - VisibleTabsCount == 0)) + { + FFirstVisibleTabIndex = SelectedTabIndex - VisibleTabsCount; + ChangeTabs(FFirstVisibleTabIndex); + } +} + +void TSessionDialog::PrevTabClick(TFarButton * /*Sender*/, bool & Close) +{ + Key(nullptr, KEY_CTRLPGUP); + Close = false; +} + +void TSessionDialog::NextTabClick(TFarButton * /*Sender*/, bool & Close) +{ + Key(nullptr, KEY_CTRLPGDN); + Close = false; +} + +void TSessionDialog::ChangeTabs(intptr_t FirstVisibleTabIndex) +{ + // Calculate which tabs are visible + intptr_t VisibleTabsCount = GetVisibleTabsCount(FirstVisibleTabIndex, true); + intptr_t LastVisibleTabIndex = FirstVisibleTabIndex + VisibleTabsCount; + // Change visibility + for (intptr_t Index = 0; Index < FirstVisibleTabIndex; ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + TabBtn->SetVisible(false); + } + intptr_t LeftPos = GetBorderBox()->GetLeft() + 2; + for (intptr_t Index = FirstVisibleTabIndex; Index <= LastVisibleTabIndex; ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + intptr_t Width = TabBtn->GetWidth(); + TabBtn->SetLeft(LeftPos); + TabBtn->SetWidth(Width); + LeftPos += Width + 1; + TabBtn->SetVisible(true); + } + for (intptr_t Index = LastVisibleTabIndex + 1; Index < FTabs->GetCount(); ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + TabBtn->SetVisible(false); + } +} + +intptr_t TSessionDialog::GetVisibleTabsCount(intptr_t TabIndex, bool Forward) const +{ + intptr_t Result = 0; + intptr_t PWidth = PrevTab->GetWidth(); + intptr_t NWidth = NextTab->GetWidth(); + intptr_t DialogWidth = GetBorderBox()->GetWidth() - 2 - PWidth - NWidth - 2; + intptr_t TabsWidth = 0; + if (Forward) + { + for (intptr_t Index = TabIndex; Index < FTabs->GetCount() - 1; ++Index) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + TabsWidth += TabBtn->GetWidth() + 1; + TTabButton * NextTabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index + 1)); + intptr_t NextTabWidth = NextTabBtn->GetWidth() + 1; + if (TabsWidth + NextTabWidth >= DialogWidth) + break; + Result++; + } + } + else + { + for (intptr_t Index = TabIndex; Index >= 1; Index--) + { + TTabButton * TabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index)); + TabsWidth += TabBtn->GetWidth() + 1; + TTabButton * PrevTabBtn = NB_STATIC_DOWNCAST(TTabButton, FTabs->GetObj(Index - 1)); + intptr_t PrevTabWidth = PrevTabBtn->GetWidth() + 1; + if (TabsWidth + PrevTabWidth >= DialogWidth) + break; + Result++; + } + } + return Result; +} + +void TSessionDialog::CipherButtonClick(TFarButton * Sender, bool & Close) +{ + if (Sender->GetEnabled()) + { + intptr_t Source = CipherListBox->GetItems()->GetSelected(); + intptr_t Dest = Source + Sender->GetResult(); + + CipherListBox->GetItems()->Move(Source, Dest); + CipherListBox->GetItems()->SetSelected(Dest); + } + + Close = false; +} + +void TSessionDialog::KexButtonClick(TFarButton * Sender, bool & Close) +{ + if (Sender->GetEnabled()) + { + intptr_t Source = KexListBox->GetItems()->GetSelected(); + intptr_t Dest = Source + Sender->GetResult(); + + KexListBox->GetItems()->Move(Source, Dest); + KexListBox->GetItems()->SetSelected(Dest); + } + + Close = false; +} + +void TSessionDialog::AuthGSSAPICheckAllowChange(TFarDialogItem * /*Sender*/, + intptr_t NewState, bool & Allow) +{ + if ((NewState == BSTATE_CHECKED) && !HasGSSAPI(L"")) + { + Allow = false; + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + + WinSCPPlugin->MoreMessageDialog(GetMsg(GSSAPI_NOT_INSTALLED), + nullptr, qtError, qaOK); + } +} + +void TSessionDialog::UnixEnvironmentButtonClick( + TFarButton * /*Sender*/, bool & /*Close*/) +{ + EOLTypeCombo->SetItemIndex(0); + DSTModeUnixCheck->SetChecked(true); +} + +void TSessionDialog::WindowsEnvironmentButtonClick( + TFarButton * /*Sender*/, bool & /*Close*/) +{ + EOLTypeCombo->SetItemIndex(1); + DSTModeWinCheck->SetChecked(true); +} + +void TSessionDialog::FillCodePageEdit() +{ + // CodePageEditAdd(CP_UTF8); + CodePageEdit->GetItems()->AddObject(L"65001 (UTF-8)", + static_cast(ToPtr(65001))); + CodePageEditAdd(CP_ACP); + CodePageEditAdd(CP_OEMCP); + CodePageEditAdd(20866); // KOI8-r +} + +void TSessionDialog::CodePageEditAdd(uint32_t Cp) +{ + CPINFOEX cpInfoEx; + ::ClearStruct(cpInfoEx); + if (::GetCodePageInfo(Cp, cpInfoEx)) + { + CodePageEdit->GetItems()->AddObject(cpInfoEx.CodePageName, + static_cast(ToPtr(cpInfoEx.CodePage))); + } +} + +intptr_t TSessionDialog::AddTab(intptr_t TabID, const wchar_t * TabCaption) +{ + TFarButtonBrackets TabBrackets = brNone; // brSpace; // + TTabButton * Tab = new TTabButton(this); + Tab->SetTabName(UnicodeString(TabCaption)); + Tab->SetTab(TabID); + Tab->SetBrackets(TabBrackets); + // SetTabCount(GetTabCount() + 1); + Tab->SetCenterGroup(false); + FTabs->Add(Tab); + return GetItem(Tab); +} + +bool TWinSCPFileSystem::SessionDialog(TSessionData * SessionData, + TSessionActionEnum & Action) +{ + std::unique_ptr Dialog(new TSessionDialog(FPlugin, Action)); + bool Result = Dialog->Execute(SessionData, Action); + return Result; +} + +class TRightsContainer : public TFarDialogContainer +{ +NB_DISABLE_COPY(TRightsContainer) +public: + explicit TRightsContainer(TFarDialog * ADialog, bool AAnyDirectories, + bool ShowButtons, bool ShowSpecials, + TFarDialogItem * EnabledDependency); +protected: + bool FAnyDirectories; + TFarCheckBox * FCheckBoxes[12]; + TRights::TState FFixedStates[12]; + TFarEdit * FOctalEdit; + TFarCheckBox * FDirectoriesXCheck; + + virtual void Change(); + void UpdateControls(); + +public: + TRights GetRights(); + void SetRights(const TRights & Value); + void SetAddXToDirectories(bool Value); + bool GetAddXToDirectories(); + TFarCheckBox * GetChecks(TRights::TRight Right); + TRights::TState GetStates(TRights::TRight Right); + bool GetAllowUndef(); + void SetAllowUndef(bool Value); + void SetStates(TRights::TRight Flag, TRights::TState Value); + void OctalEditExit(TObject * Sender); + void RightsButtonClick(TFarButton * Sender, bool & Close); +}; + +TRightsContainer::TRightsContainer(TFarDialog * ADialog, + bool AAnyDirectories, bool ShowButtons, + bool ShowSpecials, TFarDialogItem * EnabledDependency) : + TFarDialogContainer(ADialog), + FAnyDirectories(AAnyDirectories), + FOctalEdit(nullptr), + FDirectoriesXCheck(nullptr) +{ + GetDialog()->SetNextItemPosition(ipNewLine); + + static int RowLabels[] = + { + PROPERTIES_OWNER_RIGHTS, + PROPERTIES_GROUP_RIGHTS, + PROPERTIES_OTHERS_RIGHTS + }; + static int ColLabels[] = + { + PROPERTIES_READ_RIGHTS, + PROPERTIES_WRITE_RIGHTS, + PROPERTIES_EXECUTE_RIGHTS + }; + static int SpecialLabels[] = + { + PROPERTIES_SETUID_RIGHTS, + PROPERTIES_SETGID_RIGHTS, + PROPERTIES_STICKY_BIT_RIGHTS + }; + + for (intptr_t RowIndex = 0; RowIndex < 3; ++RowIndex) + { + GetDialog()->SetNextItemPosition(ipNewLine); + TFarText * Text = new TFarText(GetDialog()); + if (RowIndex == 0) + { + Text->SetTop(0); + } + Text->SetLeft(0); + Add(Text); + Text->SetEnabledDependency(EnabledDependency); + Text->SetCaption(GetMsg(RowLabels[RowIndex])); + + GetDialog()->SetNextItemPosition(ipRight); + + for (intptr_t ColIndex = 0; ColIndex < 3; ++ColIndex) + { + TFarCheckBox * CheckBox = new TFarCheckBox(GetDialog()); + FCheckBoxes[(RowIndex + 1) * 3 + ColIndex] = CheckBox; + Add(CheckBox); + CheckBox->SetEnabledDependency(EnabledDependency); + CheckBox->SetCaption(GetMsg(ColLabels[ColIndex])); + } + + if (ShowSpecials) + { + TFarCheckBox * CheckBox = new TFarCheckBox(GetDialog()); + Add(CheckBox); + CheckBox->SetVisible(ShowSpecials); + CheckBox->SetEnabledDependency(EnabledDependency); + CheckBox->SetCaption(GetMsg(SpecialLabels[RowIndex])); + FCheckBoxes[RowIndex] = CheckBox; + } + else + { + FCheckBoxes[RowIndex] = nullptr; + FFixedStates[RowIndex] = TRights::rsNo; + } + } + + GetDialog()->SetNextItemPosition(ipNewLine); + + TFarText * Text = new TFarText(GetDialog()); + Add(Text); + Text->SetEnabledDependency(EnabledDependency); + Text->SetLeft(0); + Text->SetCaption(GetMsg(PROPERTIES_OCTAL)); + + GetDialog()->SetNextItemPosition(ipRight); + + FOctalEdit = new TFarEdit(GetDialog()); + Add(FOctalEdit); + FOctalEdit->SetEnabledDependency(EnabledDependency); + FOctalEdit->SetWidth(5); + FOctalEdit->SetMask(L"9999"); + FOctalEdit->SetOnExit(MAKE_CALLBACK(TRightsContainer::OctalEditExit, this)); + + if (ShowButtons) + { + GetDialog()->SetNextItemPosition(ipRight); + + TFarButton * Button = new TFarButton(GetDialog()); + Add(Button); + Button->SetEnabledDependency(EnabledDependency); + Button->SetCaption(GetMsg(PROPERTIES_NONE_RIGHTS)); + Button->SetTag(TRights::rfNo); + Button->SetOnClick(MAKE_CALLBACK(TRightsContainer::RightsButtonClick, this)); + + Button = new TFarButton(GetDialog()); + Add(Button); + Button->SetEnabledDependency(EnabledDependency); + Button->SetCaption(GetMsg(PROPERTIES_DEFAULT_RIGHTS)); + Button->SetTag(TRights::rfDefault); + Button->SetOnClick(MAKE_CALLBACK(TRightsContainer::RightsButtonClick, this)); + + Button = new TFarButton(GetDialog()); + Add(Button); + Button->SetEnabledDependency(EnabledDependency); + Button->SetCaption(GetMsg(PROPERTIES_ALL_RIGHTS)); + Button->SetTag(TRights::rfAll); + Button->SetOnClick(MAKE_CALLBACK(TRightsContainer::RightsButtonClick, this)); + } + + GetDialog()->SetNextItemPosition(ipNewLine); + + if (FAnyDirectories) + { + FDirectoriesXCheck = new TFarCheckBox(GetDialog()); + Add(FDirectoriesXCheck); + FDirectoriesXCheck->SetEnabledDependency(EnabledDependency); + FDirectoriesXCheck->SetLeft(0); + FDirectoriesXCheck->SetCaption(GetMsg(PROPERTIES_DIRECTORIES_X)); + } + else + { + FDirectoriesXCheck = nullptr; + } + ClearArray(FFixedStates); +} + +void TRightsContainer::RightsButtonClick(TFarButton * Sender, + bool & /*Close*/) +{ + TRights R = GetRights(); + R.SetNumber(static_cast(Sender->GetTag())); + SetRights(R); +} + +void TRightsContainer::OctalEditExit(TObject * /*Sender*/) +{ + if (!::Trim(FOctalEdit->GetText()).IsEmpty()) + { + TRights R = GetRights(); + R.SetOctal(::Trim(FOctalEdit->GetText())); + SetRights(R); + } +} + +void TRightsContainer::UpdateControls() +{ + if (GetDialog()->GetHandle()) + { + TRights R = GetRights(); + + if (FDirectoriesXCheck) + { + FDirectoriesXCheck->SetEnabled( + !((R.GetNumberSet() & TRights::rfExec) == TRights::rfExec)); + } + + if (!FOctalEdit->Focused()) + { + FOctalEdit->SetText(R.GetIsUndef() ? UnicodeString() : R.GetOctal()); + } + else if (::Trim(FOctalEdit->GetText()).Length() >= 3) + { + try + { + OctalEditExit(nullptr); + } + catch (...) + { + } + } + } +} + +void TRightsContainer::Change() +{ + TFarDialogContainer::Change(); + + if (GetDialog()->GetHandle()) + { + UpdateControls(); + } +} + +TFarCheckBox * TRightsContainer::GetChecks(TRights::TRight Right) +{ + DebugAssert((Right >= 0) && (static_cast(Right) < _countof(FCheckBoxes))); + return FCheckBoxes[Right]; +} + +TRights::TState TRightsContainer::GetStates(TRights::TRight Right) +{ + TFarCheckBox * CheckBox = GetChecks(Right); + if (CheckBox != nullptr) + { + switch (CheckBox->GetSelected()) + { + case BSTATE_UNCHECKED: return TRights::rsNo; + case BSTATE_CHECKED: return TRights::rsYes; + case BSTATE_3STATE: + default: return TRights::rsUndef; + } + } + else + { + return FFixedStates[Right]; + } +} + +void TRightsContainer::SetStates(TRights::TRight Right, + TRights::TState Value) +{ + TFarCheckBox * CheckBox = GetChecks(Right); + if (CheckBox != nullptr) + { + switch (Value) + { + case TRights::rsNo: + CheckBox->SetSelected(BSTATE_UNCHECKED); + break; + case TRights::rsYes: + CheckBox->SetSelected(BSTATE_CHECKED); + break; + case TRights::rsUndef: + CheckBox->SetSelected(BSTATE_3STATE); + break; + } + } + else + { + FFixedStates[Right] = Value; + } +} + +TRights TRightsContainer::GetRights() +{ + TRights Result; + Result.SetAllowUndef(GetAllowUndef()); + for (size_t Right = 0; Right < _countof(FCheckBoxes); Right++) + { + Result.SetRightUndef(static_cast(Right), + GetStates(static_cast(Right))); + } + return Result; +} + +void TRightsContainer::SetRights(const TRights & Value) +{ + if (GetRights() != Value) + { + GetDialog()->LockChanges(); + SCOPE_EXIT + { + GetDialog()->UnlockChanges(); + }; + SetAllowUndef(true); // temporarily + for (size_t Right = 0; Right < _countof(FCheckBoxes); Right++) + { + SetStates(static_cast(Right), + Value.GetRightUndef(static_cast(Right))); + } + SetAllowUndef(Value.GetAllowUndef()); + } +} + +bool TRightsContainer::GetAddXToDirectories() +{ + return FDirectoriesXCheck ? FDirectoriesXCheck->GetChecked() : false; +} + +void TRightsContainer::SetAddXToDirectories(bool Value) +{ + if (FDirectoriesXCheck) + { + FDirectoriesXCheck->SetChecked(Value); + } +} + +bool TRightsContainer::GetAllowUndef() +{ + DebugAssert(FCheckBoxes[_countof(FCheckBoxes) - 1] != nullptr); + return FCheckBoxes[_countof(FCheckBoxes) - 1]->GetAllowGrayed(); +} + +void TRightsContainer::SetAllowUndef(bool Value) +{ + for (size_t Right = 0; Right < _countof(FCheckBoxes); Right++) + { + if (FCheckBoxes[Right] != nullptr) + { + FCheckBoxes[Right]->SetAllowGrayed(Value); + } + } +} + +class TPropertiesDialog : public TFarDialog +{ +NB_DISABLE_COPY(TPropertiesDialog) +public: + explicit TPropertiesDialog(TCustomFarPlugin * AFarPlugin, TStrings * AFileList, + const UnicodeString & Directory, + // TStrings * GroupList, TStrings * UserList, + const TRemoteTokenList * GroupList, const TRemoteTokenList * UserList, + intptr_t AllowedChanges); + + bool Execute(TRemoteProperties * Properties); + +protected: + virtual void Change(); + void UpdateProperties(TRemoteProperties & Properties); + +private: + bool FAnyDirectories; + intptr_t FAllowedChanges; + TRemoteProperties FOrigProperties; + bool FMultiple; + + TRightsContainer * RightsContainer; + TFarComboBox * OwnerComboBox; + TFarComboBox * GroupComboBox; + TFarCheckBox * RecursiveCheck; + TFarButton * OkButton; +}; + +TPropertiesDialog::TPropertiesDialog(TCustomFarPlugin * AFarPlugin, + TStrings * AFileList, const UnicodeString & /*Directory*/, + const TRemoteTokenList * GroupList, const TRemoteTokenList * UserList, + intptr_t AAllowedChanges) : + TFarDialog(AFarPlugin), + FAnyDirectories(false), + FAllowedChanges(AAllowedChanges), + RightsContainer(nullptr), + OwnerComboBox(nullptr), + GroupComboBox(nullptr), + RecursiveCheck(nullptr), + OkButton(nullptr) +{ + DebugAssert(AFileList->GetCount() > 0); + TRemoteFile * OnlyFile = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(0)); + DebugUsedParam(OnlyFile); + DebugAssert(OnlyFile); + FMultiple = (AFileList->GetCount() > 1); + + { + std::unique_ptr UsedGroupList; + std::unique_ptr UsedUserList; + if ((GroupList == nullptr) || (GroupList->GetCount() == 0)) + { + UsedGroupList.reset(new TStringList()); + UsedGroupList->SetDuplicates(dupIgnore); + UsedGroupList->SetSorted(true); + } + if ((UserList == nullptr) || (UserList->GetCount() == 0)) + { + UsedUserList.reset(new TStringList()); + UsedUserList->SetDuplicates(dupIgnore); + UsedUserList->SetSorted(true); + } + + intptr_t Directories = 0; + for (intptr_t Index = 0; Index < AFileList->GetCount(); ++Index) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(Index)); + DebugAssert(File); + if (UsedGroupList.get() && !File->GetFileGroup().GetName().IsEmpty()) + { + UsedGroupList->Add(File->GetFileGroup().GetName()); + } + if (UsedUserList.get() && !File->GetFileOwner().GetName().IsEmpty()) + { + UsedUserList->Add(File->GetFileOwner().GetName()); + } + if (File->GetIsDirectory()) + { + Directories++; + } + } + FAnyDirectories = (Directories > 0); + + SetCaption(GetMsg(PROPERTIES_CAPTION)); + + SetSize(TPoint(56, 19)); + + TFarButton * Button; + TFarSeparator * Separator; + TFarText * Text; + TRect CRect = GetClientRect(); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(PROPERTIES_PROMPT)); + Text->SetCenterGroup(true); + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCenterGroup(true); + if (AFileList->GetCount() > 1) + { + Text->SetCaption(FORMAT(GetMsg(PROPERTIES_PROMPT_FILES).c_str(), AFileList->GetCount())); + } + else + { + Text->SetCaption(core::MinimizeName(AFileList->GetString(0), static_cast(GetClientSize().x), true)); + } + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(0)); + if (!File->GetLinkTo().IsEmpty()) + { + Text = new TFarText(this); + Text->SetCaption(GetMsg(PROPERTIES_LINKTO)); + + SetNextItemPosition(ipRight); + + TFarEdit* Edit = new TFarEdit(this); + Edit->SetText(File->GetLinkTo()); + Edit->SetReadOnly(true); + + SetNextItemPosition(ipNewLine); + } + + new TFarSeparator(this); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(PROPERTIES_OWNER)); + Text->SetEnabled((FAllowedChanges & cpOwner) != 0); + + SetNextItemPosition(ipRight); + + OwnerComboBox = new TFarComboBox(this); + OwnerComboBox->SetWidth(20); + OwnerComboBox->SetEnabled((FAllowedChanges & cpOwner) != 0); + if (UsedUserList.get()) + { + OwnerComboBox->GetItems()->Assign(UsedUserList.get()); + } + else if (UserList) + { + for (intptr_t Index = 0; Index < UserList->GetCount(); ++Index) + { + OwnerComboBox->GetItems()->Add(UserList->Token(Index)->GetName()); + } + } + + SetNextItemPosition(ipNewLine); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(PROPERTIES_GROUP)); + Text->SetEnabled((FAllowedChanges & cpGroup) != 0); + + SetNextItemPosition(ipRight); + + GroupComboBox = new TFarComboBox(this); + GroupComboBox->SetWidth(OwnerComboBox->GetWidth()); + GroupComboBox->SetEnabled((FAllowedChanges & cpGroup) != 0); + if (UsedGroupList.get()) + { + GroupComboBox->GetItems()->Assign(UsedGroupList.get()); + } + else if (GroupList) + { + for (intptr_t Index = 0; Index < GroupList->GetCount(); ++Index) + { + GroupComboBox->GetItems()->Add(GroupList->Token(Index)->GetName()); + } + } + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(PROPERTIES_RIGHTS)); + + RightsContainer = new TRightsContainer(this, FAnyDirectories, + true, true, nullptr); + RightsContainer->SetEnabled(FAllowedChanges & cpMode); + + if (FAnyDirectories) + { + Separator = new TFarSeparator(this); + Separator->SetPosition(Separator->GetPosition() + RightsContainer->GetTop()); + + RecursiveCheck = new TFarCheckBox(this); + RecursiveCheck->SetCaption(GetMsg(PROPERTIES_RECURSIVE)); + } + else + { + RecursiveCheck = nullptr; + } + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetPosition(CRect.Bottom - 1); + + OkButton = new TFarButton(this); + OkButton->SetCaption(GetMsg(MSG_BUTTON_OK)); + OkButton->SetDefault(true); + OkButton->SetResult(brOK); + OkButton->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + Button->SetResult(brCancel); + Button->SetCenterGroup(true); + } +} + +void TPropertiesDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle()) + { + TRemoteProperties FileProperties; + UpdateProperties(FileProperties); + + if (!FMultiple) + { + // when setting properties for one file only, allow undef state + // only when the input right explicitly requires it or + // when "recursive" is on (possible for directory only). + bool AllowUndef = + (FOrigProperties.Valid.Contains(vpRights) && + FOrigProperties.Rights.GetAllowUndef()) || + ((RecursiveCheck != nullptr) && (RecursiveCheck->GetChecked())); + if (!AllowUndef) + { + // when disallowing undef state, make sure, all undef are turned into unset + RightsContainer->SetRights(TRights(RightsContainer->GetRights().GetNumberSet())); + } + RightsContainer->SetAllowUndef(AllowUndef); + } + + OkButton->SetEnabled( + // group name is specified or we set multiple-file properties and + // no valid group was specified (there are at least two different groups) + (!GroupComboBox->GetText().IsEmpty() || + (FMultiple && !FOrigProperties.Valid.Contains(vpGroup)) || + (FOrigProperties.Group.GetName() == GroupComboBox->GetText())) && + // same but with owner + (!OwnerComboBox->GetText().IsEmpty() || + (FMultiple && !FOrigProperties.Valid.Contains(vpOwner)) || + (FOrigProperties.Owner.GetName() == OwnerComboBox->GetText())) && + ((FileProperties != FOrigProperties) || (RecursiveCheck && RecursiveCheck->GetChecked()))); + } +} + +void TPropertiesDialog::UpdateProperties(TRemoteProperties & Properties) +{ + if (FAllowedChanges & cpMode) + { + Properties.Valid << vpRights; + Properties.Rights = RightsContainer->GetRights(); + Properties.AddXToDirectories = RightsContainer->GetAddXToDirectories(); + } + +#define STORE_NAME(PROPERTY) \ + if (!PROPERTY ## ComboBox->GetText().IsEmpty() && \ + FAllowedChanges & cp ## PROPERTY) \ + { \ + Properties.Valid << vp ## PROPERTY; \ + Properties.PROPERTY.SetName(::Trim(PROPERTY ## ComboBox->GetText())); \ + } + STORE_NAME(Group); + STORE_NAME(Owner); +#undef STORE_NAME + + Properties.Recursive = RecursiveCheck != nullptr && RecursiveCheck->GetChecked(); +} + +bool TPropertiesDialog::Execute(TRemoteProperties * Properties) +{ + TValidProperties Valid; + if (Properties->Valid.Contains(vpRights) && FAllowedChanges & cpMode) + { + Valid << vpRights; + } + if (Properties->Valid.Contains(vpOwner) && FAllowedChanges & cpOwner) + { + Valid << vpOwner; + } + if (Properties->Valid.Contains(vpGroup) && FAllowedChanges & cpGroup) + { + Valid << vpGroup; + } + FOrigProperties = *Properties; + FOrigProperties.Valid = Valid; + FOrigProperties.Recursive = false; + + if (Properties->Valid.Contains(vpRights)) + { + RightsContainer->SetRights(Properties->Rights); + RightsContainer->SetAddXToDirectories(Properties->AddXToDirectories); + } + else + { + RightsContainer->SetRights(TRights()); + RightsContainer->SetAddXToDirectories(false); + } + OwnerComboBox->SetText(Properties->Valid.Contains(vpOwner) ? + Properties->Owner.GetName() : UnicodeString()); + GroupComboBox->SetText(Properties->Valid.Contains(vpGroup) ? + Properties->Group.GetName() : UnicodeString()); + if (RecursiveCheck) + { + RecursiveCheck->SetChecked(Properties->Recursive); + } + + bool Result = ShowModal() != brCancel; + if (Result) + { + *Properties = TRemoteProperties(); + UpdateProperties(*Properties); + } + return Result; +} + +bool TWinSCPFileSystem::PropertiesDialog(TStrings * AFileList, + const UnicodeString & Directory, + const TRemoteTokenList * GroupList, const TRemoteTokenList * UserList, + TRemoteProperties * Properties, intptr_t AllowedChanges) +{ + std::unique_ptr Dialog(new TPropertiesDialog(FPlugin, AFileList, + Directory, GroupList, UserList, AllowedChanges)); + bool Result = Dialog->Execute(Properties); + return Result; +} + +class TCopyParamsContainer : public TFarDialogContainer +{ +public: + explicit TCopyParamsContainer(TFarDialog * ADialog, + intptr_t Options, intptr_t CopyParamAttrs); + + void SetParams(const TCopyParamType & Value); + TCopyParamType GetParams(); + int GetHeight(); + +protected: + TFarRadioButton * TMTextButton; + TFarRadioButton * TMBinaryButton; + TFarRadioButton * TMAutomaticButton; + TFarEdit * AsciiFileMaskEdit; + TRightsContainer * RightsContainer; + TFarRadioButton * CCNoChangeButton; + TFarRadioButton * CCUpperCaseButton; + TFarRadioButton * CCLowerCaseButton; + TFarRadioButton * CCFirstUpperCaseButton; + TFarRadioButton * CCLowerCaseShortButton; + TFarCheckBox * ReplaceInvalidCharsCheck; + TFarCheckBox * PreserveRightsCheck; + TFarCheckBox * PreserveTimeCheck; + TFarCheckBox * PreserveReadOnlyCheck; + TFarCheckBox * IgnorePermErrorsCheck; + TFarCheckBox * ClearArchiveCheck; + TFarCheckBox * CalculateSizeCheck; + TFarText * FileMaskText; + TFarEdit * FileMaskEdit; + TFarComboBox * SpeedCombo; + + void ValidateMaskComboExit(TObject * Sender); + void ValidateSpeedComboExit(TObject * Sender); + virtual void Change(); + void UpdateControls(); + +private: + intptr_t FOptions; + intptr_t FCopyParamAttrs; + TCopyParamType FParams; +}; + +TCopyParamsContainer::TCopyParamsContainer(TFarDialog * ADialog, + intptr_t Options, intptr_t CopyParamAttrs) : + TFarDialogContainer(ADialog), + TMTextButton(nullptr), + TMBinaryButton(nullptr), + TMAutomaticButton(nullptr), + AsciiFileMaskEdit(nullptr), + RightsContainer(nullptr), + CCNoChangeButton(nullptr), + CCUpperCaseButton(nullptr), + CCLowerCaseButton(nullptr), + CCFirstUpperCaseButton(nullptr), + CCLowerCaseShortButton(nullptr), + ReplaceInvalidCharsCheck(nullptr), + PreserveRightsCheck(nullptr), + PreserveTimeCheck(nullptr), + PreserveReadOnlyCheck(nullptr), + IgnorePermErrorsCheck(nullptr), + ClearArchiveCheck(nullptr), + CalculateSizeCheck(nullptr), + FileMaskText(nullptr), + FileMaskEdit(nullptr), + SpeedCombo(nullptr), + FOptions(Options), FCopyParamAttrs(CopyParamAttrs) +{ + TFarBox * Box; + TFarSeparator * Separator; + TFarText * Text; + + intptr_t TMWidth = 37; + intptr_t TMTop; + intptr_t TMBottom; + + SetLeft(GetLeft() - 1); + + Box = new TFarBox(GetDialog()); + Box->SetLeft(0); + Box->SetTop(0); + Box->SetHeight(1); + Add(Box); + Box->SetWidth(TMWidth + 2); + Box->SetCaption(GetMsg(TRANSFER_MODE)); + + GetDialog()->SetNextItemPosition(ipRight); + + Box = new TFarBox(GetDialog()); + Add(Box); + Box->SetLeft(Box->GetLeft() - 2); + Box->SetRight(Box->GetRight() + 1); + Box->SetCaption(GetMsg(TRANSFER_UPLOAD_OPTIONS)); + + GetDialog()->SetNextItemPosition(ipNewLine); + + TMTextButton = new TFarRadioButton(GetDialog()); + TMTextButton->SetLeft(1); + Add(TMTextButton); + TMTop = TMTextButton->GetTop(); + TMTextButton->SetCaption(GetMsg(TRANSFER_MODE_TEXT)); + TMTextButton->SetEnabled( + FLAGCLEAR(CopyParamAttrs, cpaNoTransferMode)); + + TMBinaryButton = new TFarRadioButton(GetDialog()); + TMBinaryButton->SetLeft(1); + Add(TMBinaryButton); + TMBinaryButton->SetCaption(GetMsg(TRANSFER_MODE_BINARY)); + TMBinaryButton->SetEnabled(TMTextButton->GetEnabled()); + + TMAutomaticButton = new TFarRadioButton(GetDialog()); + TMAutomaticButton->SetLeft(1); + Add(TMAutomaticButton); + TMAutomaticButton->SetCaption(GetMsg(TRANSFER_MODE_AUTOMATIC)); + TMAutomaticButton->SetEnabled(TMTextButton->GetEnabled()); + + Text = new TFarText(GetDialog()); + Text->SetLeft(1); + Add(Text); + Text->SetCaption(GetMsg(TRANSFER_MODE_MASK)); + Text->SetEnabledDependency(TMAutomaticButton); + + AsciiFileMaskEdit = new TFarEdit(GetDialog()); + AsciiFileMaskEdit->SetLeft(1); + Add(AsciiFileMaskEdit); + AsciiFileMaskEdit->SetEnabledDependency(TMAutomaticButton); + AsciiFileMaskEdit->SetWidth(TMWidth); + AsciiFileMaskEdit->SetHistory(ASCII_MASK_HISTORY); + AsciiFileMaskEdit->SetOnExit(MAKE_CALLBACK(TCopyParamsContainer::ValidateMaskComboExit, this)); + + Box = new TFarBox(GetDialog()); + Box->SetLeft(0); + Add(Box); + Box->SetWidth(TMWidth + 2); + Box->SetCaption(GetMsg(TRANSFER_FILENAME_MODIFICATION)); + + CCNoChangeButton = new TFarRadioButton(GetDialog()); + CCNoChangeButton->SetLeft(1); + Add(CCNoChangeButton); + CCNoChangeButton->SetCaption(GetMsg(TRANSFER_FILENAME_NOCHANGE)); + CCNoChangeButton->SetEnabled(true); + + GetDialog()->SetNextItemPosition(ipRight); + + CCUpperCaseButton = new TFarRadioButton(GetDialog()); + Add(CCUpperCaseButton); + CCUpperCaseButton->SetCaption(GetMsg(TRANSFER_FILENAME_UPPERCASE)); + CCUpperCaseButton->SetEnabled(CCNoChangeButton->GetEnabled()); + + GetDialog()->SetNextItemPosition(ipNewLine); + + CCFirstUpperCaseButton = new TFarRadioButton(GetDialog()); + CCFirstUpperCaseButton->SetLeft(1); + Add(CCFirstUpperCaseButton); + CCFirstUpperCaseButton->SetCaption(GetMsg(TRANSFER_FILENAME_FIRSTUPPERCASE)); + CCFirstUpperCaseButton->SetEnabled(CCNoChangeButton->GetEnabled()); + + GetDialog()->SetNextItemPosition(ipRight); + + CCLowerCaseButton = new TFarRadioButton(GetDialog()); + Add(CCLowerCaseButton); + CCLowerCaseButton->SetCaption(GetMsg(TRANSFER_FILENAME_LOWERCASE)); + CCLowerCaseButton->SetEnabled(CCNoChangeButton->GetEnabled()); + + GetDialog()->SetNextItemPosition(ipNewLine); + + CCLowerCaseShortButton = new TFarRadioButton(GetDialog()); + CCLowerCaseShortButton->SetLeft(1); + Add(CCLowerCaseShortButton); + CCLowerCaseShortButton->SetCaption(GetMsg(TRANSFER_FILENAME_LOWERCASESHORT)); + CCLowerCaseShortButton->SetEnabled(CCNoChangeButton->GetEnabled()); + + GetDialog()->SetNextItemPosition(ipRight); + + ReplaceInvalidCharsCheck = new TFarCheckBox(GetDialog()); + Add(ReplaceInvalidCharsCheck); + ReplaceInvalidCharsCheck->SetCaption(GetMsg(TRANSFER_FILENAME_REPLACE_INVALID)); + ReplaceInvalidCharsCheck->SetEnabled(CCNoChangeButton->GetEnabled()); + + GetDialog()->SetNextItemPosition(ipNewLine); + + Box = new TFarBox(GetDialog()); + Box->SetLeft(0); + Add(Box); + Box->SetWidth(TMWidth + 2); + Box->SetCaption(GetMsg(TRANSFER_DOWNLOAD_OPTIONS)); + + PreserveReadOnlyCheck = new TFarCheckBox(GetDialog()); + Add(PreserveReadOnlyCheck); + PreserveReadOnlyCheck->SetLeft(1); + PreserveReadOnlyCheck->SetCaption(GetMsg(TRANSFER_PRESERVE_READONLY)); + PreserveReadOnlyCheck->SetEnabled( + FLAGCLEAR(CopyParamAttrs, cpaNoPreserveReadOnly)); + TMBottom = PreserveReadOnlyCheck->GetTop(); + + PreserveRightsCheck = new TFarCheckBox(GetDialog()); + Add(PreserveRightsCheck); + PreserveRightsCheck->SetLeft(TMWidth + 3); + PreserveRightsCheck->SetTop(TMTop); + PreserveRightsCheck->SetBottom(TMTop); + PreserveRightsCheck->SetCaption(GetMsg(TRANSFER_PRESERVE_RIGHTS)); + PreserveRightsCheck->SetEnabled( + FLAGCLEAR(CopyParamAttrs, cpaNoRights)); + + GetDialog()->SetNextItemPosition(ipBelow); + + RightsContainer = new TRightsContainer(GetDialog(), true, false, + false, PreserveRightsCheck); + RightsContainer->SetLeft(PreserveRightsCheck->GetActualBounds().Left); + RightsContainer->SetTop(PreserveRightsCheck->GetActualBounds().Top + 1); + + IgnorePermErrorsCheck = new TFarCheckBox(GetDialog()); + Add(IgnorePermErrorsCheck); + IgnorePermErrorsCheck->SetLeft(PreserveRightsCheck->GetLeft()); + IgnorePermErrorsCheck->SetTop(TMTop + 6); + IgnorePermErrorsCheck->SetCaption(GetMsg(TRANSFER_PRESERVE_PERM_ERRORS)); + + ClearArchiveCheck = new TFarCheckBox(GetDialog()); + ClearArchiveCheck->SetLeft(IgnorePermErrorsCheck->GetLeft()); + Add(ClearArchiveCheck); + ClearArchiveCheck->SetTop(TMTop + 7); + ClearArchiveCheck->SetCaption(GetMsg(TRANSFER_CLEAR_ARCHIVE)); + ClearArchiveCheck->SetEnabled( + FLAGCLEAR(FOptions, coTempTransfer) && + FLAGCLEAR(CopyParamAttrs, cpaNoClearArchive)); + + Box = new TFarBox(GetDialog()); + Box->SetTop(TMTop + 8); + Add(Box); + Box->SetBottom(Box->GetTop()); + Box->SetLeft(TMWidth + 3 - 1); + Box->SetCaption(GetMsg(TRANSFER_COMMON_OPTIONS)); + + PreserveTimeCheck = new TFarCheckBox(GetDialog()); + Add(PreserveTimeCheck); + PreserveTimeCheck->SetLeft(TMWidth + 3); + PreserveTimeCheck->SetCaption(GetMsg(TRANSFER_PRESERVE_TIMESTAMP)); + PreserveTimeCheck->SetEnabled( + FLAGCLEAR(CopyParamAttrs, cpaNoPreserveTime)); + + CalculateSizeCheck = new TFarCheckBox(GetDialog()); + CalculateSizeCheck->SetCaption(GetMsg(TRANSFER_CALCULATE_SIZE)); + Add(CalculateSizeCheck); + CalculateSizeCheck->SetLeft(TMWidth + 3); + + GetDialog()->SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(GetDialog()); + Add(Separator); + Separator->SetPosition(TMBottom + 1); + Separator->SetCaption(GetMsg(TRANSFER_OTHER)); + + FileMaskText = new TFarText(GetDialog()); + FileMaskText->SetLeft(1); + Add(FileMaskText); + FileMaskText->SetCaption(GetMsg(TRANSFER_FILE_MASK)); + + GetDialog()->SetNextItemPosition(ipNewLine); + + FileMaskEdit = new TFarEdit(GetDialog()); + FileMaskEdit->SetLeft(1); + Add(FileMaskEdit); + FileMaskEdit->SetWidth(TMWidth); + FileMaskEdit->SetHistory(WINSCP_FILE_MASK_HISTORY); + FileMaskEdit->SetOnExit(MAKE_CALLBACK(TCopyParamsContainer::ValidateMaskComboExit, this)); + FileMaskEdit->SetEnabled(true); + + GetDialog()->SetNextItemPosition(ipNewLine); + + Text = new TFarText(GetDialog()); + Add(Text); + Text->SetCaption(GetMsg(TRANSFER_SPEED)); + Text->MoveAt(TMWidth + 3, FileMaskText->GetTop()); + + GetDialog()->SetNextItemPosition(ipRight); + + SpeedCombo = new TFarComboBox(GetDialog()); + Add(SpeedCombo); + SpeedCombo->GetItems()->Add(LoadStr(SPEED_UNLIMITED)); + intptr_t Speed = 1024; + while (Speed >= 8) + { + SpeedCombo->GetItems()->Add(::IntToStr(Speed)); + Speed = Speed / 2; + } + SpeedCombo->SetOnExit(MAKE_CALLBACK(TCopyParamsContainer::ValidateSpeedComboExit, this)); + + GetDialog()->SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(GetDialog()); + Separator->SetPosition(FileMaskEdit->GetBottom() + 1); + Separator->SetLeft(0); + Add(Separator); +} + +void TCopyParamsContainer::UpdateControls() +{ + if (IgnorePermErrorsCheck != nullptr) + { + IgnorePermErrorsCheck->SetEnabled( + ((PreserveRightsCheck->GetEnabled() && PreserveRightsCheck->GetChecked()) || + (PreserveTimeCheck->GetEnabled() && PreserveTimeCheck->GetChecked())) && + FLAGCLEAR(FCopyParamAttrs, cpaNoIgnorePermErrors)); + } +} + +void TCopyParamsContainer::Change() +{ + TFarDialogContainer::Change(); + + if (GetDialog()->GetHandle()) + { + UpdateControls(); + } +} + +void TCopyParamsContainer::SetParams(const TCopyParamType & Value) +{ + if (TMBinaryButton->GetEnabled()) + { + switch (Value.GetTransferMode()) + { + case tmAscii: + TMTextButton->SetChecked(true); + break; + + case tmBinary: + TMBinaryButton->SetChecked(true); + break; + + default: + TMAutomaticButton->SetChecked(true); + break; + } + } + else + { + TMBinaryButton->SetChecked(true); + } + + AsciiFileMaskEdit->SetText(Value.GetAsciiFileMask().GetMasks()); + + switch (Value.GetFileNameCase()) + { + case ncLowerCase: + CCLowerCaseButton->SetChecked(true); + break; + + case ncUpperCase: + CCUpperCaseButton->SetChecked(true); + break; + + case ncFirstUpperCase: + CCFirstUpperCaseButton->SetChecked(true); + break; + + case ncLowerCaseShort: + CCLowerCaseShortButton->SetChecked(true); + break; + + default: + case ncNoChange: + CCNoChangeButton->SetChecked(true); + break; + } + + RightsContainer->SetAddXToDirectories(Value.GetAddXToDirectories()); + RightsContainer->SetRights(Value.GetRights()); + PreserveRightsCheck->SetChecked(Value.GetPreserveRights()); + IgnorePermErrorsCheck->SetChecked(Value.GetIgnorePermErrors()); + + PreserveReadOnlyCheck->SetChecked(Value.GetPreserveReadOnly()); + ReplaceInvalidCharsCheck->SetChecked( + Value.GetInvalidCharsReplacement() != TCopyParamType::NoReplacement); + + ClearArchiveCheck->SetChecked(Value.GetClearArchive()); + + FileMaskEdit->SetText(Value.GetIncludeFileMask().GetMasks()); + PreserveTimeCheck->SetChecked(Value.GetPreserveTime()); + CalculateSizeCheck->SetChecked(Value.GetCalculateSize()); + + SpeedCombo->SetText(SetSpeedLimit(Value.GetCPSLimit())); + + FParams = Value; +} + +TCopyParamType TCopyParamsContainer::GetParams() +{ + TCopyParamType Result = FParams; + + DebugAssert(TMTextButton->GetChecked() || TMBinaryButton->GetChecked() || TMAutomaticButton->GetChecked()); + if (TMTextButton->GetChecked()) + { + Result.SetTransferMode(tmAscii); + } + else if (TMAutomaticButton->GetChecked()) + { + Result.SetTransferMode(tmAutomatic); + } + else + { + Result.SetTransferMode(tmBinary); + } + + if (Result.GetTransferMode() == tmAutomatic) + { + Result.GetAsciiFileMask().SetMasks(AsciiFileMaskEdit->GetText()); + } + + if (CCLowerCaseButton->GetChecked()) + { + Result.SetFileNameCase(ncLowerCase); + } + else if (CCUpperCaseButton->GetChecked()) + { + Result.SetFileNameCase(ncUpperCase); + } + else if (CCFirstUpperCaseButton->GetChecked()) + { + Result.SetFileNameCase(ncFirstUpperCase); + } + else if (CCLowerCaseShortButton->GetChecked()) + { + Result.SetFileNameCase(ncLowerCaseShort); + } + else + { + Result.SetFileNameCase(ncNoChange); + } + + Result.SetAddXToDirectories(RightsContainer->GetAddXToDirectories()); + Result.SetRights(RightsContainer->GetRights()); + Result.SetPreserveRights(PreserveRightsCheck->GetChecked()); + Result.SetIgnorePermErrors(IgnorePermErrorsCheck->GetChecked()); + + Result.SetReplaceInvalidChars(ReplaceInvalidCharsCheck->GetChecked()); + Result.SetPreserveReadOnly(PreserveReadOnlyCheck->GetChecked()); + + Result.SetClearArchive(ClearArchiveCheck->GetChecked()); + + Result.GetIncludeFileMask().SetMasks(FileMaskEdit->GetText()); + Result.SetPreserveTime(PreserveTimeCheck->GetChecked()); + Result.SetCalculateSize(CalculateSizeCheck->GetChecked()); + + Result.SetCPSLimit(GetSpeedLimit(SpeedCombo->GetText())); + + return Result; +} + +void TCopyParamsContainer::ValidateMaskComboExit(TObject * Sender) +{ + ValidateMaskEdit(NB_STATIC_DOWNCAST(TFarEdit, Sender)); +} + +void TCopyParamsContainer::ValidateSpeedComboExit(TObject * /*Sender*/) +{ + try + { + GetSpeedLimit(SpeedCombo->GetText()); + } + catch (...) + { + SpeedCombo->SetFocus(); + throw; + } +} + +int TCopyParamsContainer::GetHeight() +{ + return 16; +} + +class TCopyDialog : TFarDialog +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + explicit TCopyDialog(TCustomFarPlugin * AFarPlugin, + bool ToRemote, bool Move, const TStrings * AFileList, intptr_t Options, intptr_t CopyParamAttrs); + + bool Execute(OUT UnicodeString & TargetDirectory, OUT TGUICopyParamType * Params); + +protected: + virtual bool CloseQuery(); + virtual void Change(); + void CustomCopyParam(); + + void CopyParamListerClick(TFarDialogItem * Item, MOUSE_EVENT_RECORD * Event); + void TransferSettingsButtonClick(TFarButton * Sender, bool & Close); + +private: + TFarEdit * DirectoryEdit; + TFarLister * CopyParamLister; + TFarCheckBox * NewerOnlyCheck; + TFarCheckBox * SaveSettingsCheck; + TFarCheckBox * QueueCheck; + TFarCheckBox * QueueNoConfirmationCheck; + + const TStrings * FFileList; + intptr_t FOptions; + intptr_t FCopyParamAttrs; + TGUICopyParamType FCopyParams; + bool FToRemote; +}; + +TCopyDialog::TCopyDialog(TCustomFarPlugin * AFarPlugin, + bool ToRemote, bool Move, const TStrings * AFileList, + intptr_t Options, intptr_t CopyParamAttrs) : + TFarDialog(AFarPlugin), + DirectoryEdit(nullptr), + NewerOnlyCheck(nullptr), + QueueCheck(nullptr), + QueueNoConfirmationCheck(nullptr), + FFileList(AFileList), + FOptions(Options), + FCopyParamAttrs(CopyParamAttrs), + FToRemote(ToRemote) +{ + DebugAssert(FFileList); + const intptr_t DlgLength = 78; + SetSize(TPoint(DlgLength, 12 + (FLAGCLEAR(FOptions, coTempTransfer) ? 4 : 0))); + + SetCaption(GetMsg(Move ? MOVE_TITLE : COPY_TITLE)); + + if (FLAGCLEAR(FOptions, coTempTransfer)) + { + UnicodeString Prompt; + if (FFileList->GetCount() > 1) + { + Prompt = FORMAT(GetMsg(Move ? MOVE_FILES_PROMPT : COPY_FILES_PROMPT).c_str(), FFileList->GetCount()); + } + else + { + UnicodeString PromptMsg = GetMsg(Move ? MOVE_FILE_PROMPT : COPY_FILE_PROMPT); + UnicodeString FileName = FFileList->GetString(0); + UnicodeString OnlyFileName = ToRemote ? + base::ExtractFileName(FileName, false) : + base::UnixExtractFileName(FileName); + UnicodeString MinimizedName = core::MinimizeName(OnlyFileName, DlgLength - PromptMsg.Length() - 6, false); + Prompt = FORMAT(PromptMsg.c_str(), MinimizedName.c_str()); + } + + TFarText * Text = new TFarText(this); + Text->SetCaption(Prompt); + + DirectoryEdit = new TFarEdit(this); + DirectoryEdit->SetHistory(ToRemote ? REMOTE_DIR_HISTORY : L"Copy"); + } + + TFarSeparator * Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(COPY_PARAM_GROUP)); + + CopyParamLister = new TFarLister(this); + CopyParamLister->SetHeight(3); + CopyParamLister->SetLeft(GetBorderBox()->GetLeft() + 1); + CopyParamLister->SetTabStop(false); + CopyParamLister->SetOnMouseClick(MAKE_CALLBACK(TCopyDialog::CopyParamListerClick, this)); + + new TFarSeparator(this); + + if (FLAGCLEAR(FOptions, coTempTransfer)) + { + NewerOnlyCheck = new TFarCheckBox(this); + NewerOnlyCheck->SetCaption(GetMsg(TRANSFER_NEWER_ONLY)); + NewerOnlyCheck->SetEnabled(FLAGCLEAR(FOptions, coDisableNewerOnly)); + + QueueCheck = new TFarCheckBox(this); + QueueCheck->SetCaption(GetMsg(TRANSFER_QUEUE)); + + SetNextItemPosition(ipRight); + + QueueNoConfirmationCheck = new TFarCheckBox(this); + QueueNoConfirmationCheck->SetCaption(GetMsg(TRANSFER_QUEUE_NO_CONFIRMATION)); + QueueNoConfirmationCheck->SetEnabledDependency(QueueCheck); + + SetNextItemPosition(ipNewLine); + } + else + { + DebugAssert(FLAGSET(FOptions, coDisableNewerOnly)); + } + + SaveSettingsCheck = new TFarCheckBox(this); + SaveSettingsCheck->SetCaption(GetMsg(TRANSFER_REUSE_SETTINGS)); + + new TFarSeparator(this); + + TFarButton * Button = new TFarButton(this); + Button->SetCaption(GetMsg(TRANSFER_SETTINGS_BUTTON)); + Button->SetResult(-1); + Button->SetCenterGroup(true); + Button->SetOnClick(MAKE_CALLBACK(TCopyDialog::TransferSettingsButtonClick, this)); + + SetNextItemPosition(ipRight); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_OK)); + Button->SetDefault(true); + Button->SetResult(brOK); + Button->SetCenterGroup(true); + Button->SetEnabledDependency( + ((Options & coTempTransfer) == 0) ? DirectoryEdit : nullptr); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + Button->SetResult(brCancel); + Button->SetCenterGroup(true); +} + +bool TCopyDialog::Execute(OUT UnicodeString & TargetDirectory, + OUT TGUICopyParamType * Params) +{ + FCopyParams.Assign(Params); + + if (FLAGCLEAR(FOptions, coTempTransfer)) + { + NewerOnlyCheck->SetChecked(FLAGCLEAR(FOptions, coDisableNewerOnly) && Params->GetNewerOnly()); + + UnicodeString FileMask = Params->GetFileMask(); + UnicodeString Directory = FToRemote ? + core::UnixIncludeTrailingBackslash(TargetDirectory) : + ::IncludeTrailingBackslash(TargetDirectory); + if (FFileList->GetCount() == 1) + { + UnicodeString DestFileName = FFileList->GetString(0); + DestFileName = FToRemote ? DestFileName : FCopyParams.ChangeFileName(DestFileName, osRemote, true); + FileMask = base::ExtractFileName(DestFileName, false); + } + DirectoryEdit->SetText(Directory + FileMask); + QueueCheck->SetChecked(Params->GetQueue()); + QueueNoConfirmationCheck->SetChecked(Params->GetQueueNoConfirmation()); + } + + bool Result = ShowModal() != brCancel; + + if (Result) + { + Params->Assign(&FCopyParams); + + if (FLAGCLEAR(FOptions, coTempTransfer)) + { + UnicodeString NewTargetDirectory; + if (FToRemote) + { + Params->SetFileMask(base::UnixExtractFileName(DirectoryEdit->GetText())); + NewTargetDirectory = core::UnixExtractFilePath(DirectoryEdit->GetText()); + if (!NewTargetDirectory.IsEmpty()) + TargetDirectory = NewTargetDirectory; + } + else + { + Params->SetFileMask(base::ExtractFileName(DirectoryEdit->GetText(), false)); + NewTargetDirectory = ::ExtractFilePath(DirectoryEdit->GetText()); + if (!NewTargetDirectory.IsEmpty()) + TargetDirectory = NewTargetDirectory; + } + + Params->SetNewerOnly(FLAGCLEAR(FOptions, coDisableNewerOnly) && NewerOnlyCheck->GetChecked()); + + Params->SetQueue(QueueCheck->GetChecked()); + Params->SetQueueNoConfirmation(QueueNoConfirmationCheck->GetChecked()); + } + + GetConfiguration()->BeginUpdate(); + SCOPE_EXIT + { + GetConfiguration()->EndUpdate(); + }; + if (SaveSettingsCheck->GetChecked()) + { + GetGUIConfiguration()->SetDefaultCopyParam(*Params); + } + } + return Result; +} + +bool TCopyDialog::CloseQuery() +{ + bool CanClose = TFarDialog::CloseQuery(); + + if (CanClose && GetResult() >= 0) + { + if (!FToRemote && ((FOptions & coTempTransfer) == 0)) + { + UnicodeString Directory = ::ExtractFilePath(DirectoryEdit->GetText()); + if (!Directory.IsEmpty() && !::DirectoryExists(Directory)) + { + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + + if (WinSCPPlugin->MoreMessageDialog(FORMAT(GetMsg(CREATE_LOCAL_DIRECTORY).c_str(), Directory.c_str()), + nullptr, qtConfirmation, qaOK | qaCancel) != qaCancel) + { + if (!::ForceDirectories(ApiPath(Directory))) + { + DirectoryEdit->SetFocus(); + throw ExtException(FORMAT(GetMsg(CREATE_LOCAL_DIR_ERROR).c_str(), Directory.c_str())); + } + } + else + { + DirectoryEdit->SetFocus(); + Abort(); + } + } + } + } + return CanClose; +} + +void TCopyDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle()) + { + UnicodeString InfoStr = FCopyParams.GetInfoStr(L"; ", FCopyParamAttrs); + std::unique_ptr InfoStrLines(new TStringList()); + FarWrapText(InfoStr, InfoStrLines.get(), GetBorderBox()->GetWidth() - 4); + CopyParamLister->SetItems(InfoStrLines.get()); + CopyParamLister->SetRight(GetBorderBox()->GetRight() - (CopyParamLister->GetScrollBar() ? 0 : 1)); + } +} + +void TCopyDialog::TransferSettingsButtonClick( + TFarButton * /*Sender*/, bool & Close) +{ + CustomCopyParam(); + Close = false; +} + +void TCopyDialog::CopyParamListerClick( + TFarDialogItem * /*Item*/, MOUSE_EVENT_RECORD * Event) +{ + if (FLAGSET(Event->dwEventFlags, DOUBLE_CLICK)) + { + CustomCopyParam(); + } +} + +void TCopyDialog::CustomCopyParam() +{ + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + if (WinSCPPlugin->CopyParamCustomDialog(FCopyParams, FCopyParamAttrs)) + { + Change(); + } +} + +bool TWinSCPFileSystem::CopyDialog(bool ToRemote, + bool Move, const TStrings * AFileList, + intptr_t Options, + intptr_t CopyParamAttrs, + OUT UnicodeString & TargetDirectory, + OUT TGUICopyParamType * Params) +{ + std::unique_ptr Dialog(new TCopyDialog(FPlugin, ToRemote, + Move, AFileList, Options, CopyParamAttrs)); + bool Result = Dialog->Execute(TargetDirectory, Params); + return Result; +} + +bool TWinSCPPlugin::CopyParamDialog(const UnicodeString & Caption, + TCopyParamType & CopyParam, intptr_t CopyParamAttrs) +{ + std::unique_ptr DialogPtr(new TWinSCPDialog(this)); + TWinSCPDialog * Dialog = DialogPtr.get(); + + Dialog->SetCaption(Caption); + + // temporary + Dialog->SetSize(TPoint(78, 10)); + + TCopyParamsContainer * CopyParamsContainer = new TCopyParamsContainer( + Dialog, 0, CopyParamAttrs); + + Dialog->SetSize(TPoint(78, 2 + CopyParamsContainer->GetHeight() + 3)); + + Dialog->SetNextItemPosition(ipNewLine); + + Dialog->AddStandardButtons(2, true); + + CopyParamsContainer->SetParams(CopyParam); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + CopyParam = CopyParamsContainer->GetParams(); + } + return Result; +} + +bool TWinSCPPlugin::CopyParamCustomDialog(TCopyParamType & CopyParam, + intptr_t CopyParamAttrs) +{ + return CopyParamDialog(GetMsg(COPY_PARAM_CUSTOM_TITLE), CopyParam, CopyParamAttrs); +} + +class TLinkDialog : TFarDialog +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + explicit TLinkDialog(TCustomFarPlugin * AFarPlugin, + bool Edit, bool AllowSymbolic); + + bool Execute(UnicodeString & AFileName, UnicodeString & PointTo, + bool & Symbolic); + +protected: + virtual void Change(); + +private: + TFarEdit * FileNameEdit; + TFarEdit * PointToEdit; + TFarCheckBox * SymbolicCheck; + TFarButton * OkButton; +}; + +TLinkDialog::TLinkDialog(TCustomFarPlugin * AFarPlugin, + bool Edit, bool AllowSymbolic) : TFarDialog(AFarPlugin) +{ + TFarButton * Button; + TFarSeparator * Separator; + TFarText * Text; + + SetSize(TPoint(76, 12)); + TRect CRect = GetClientRect(); + + SetCaption(GetMsg(Edit ? STRING_LINK_EDIT_CAPTION : STRING_LINK_ADD_CAPTION)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(STRING_LINK_FILE)); + Text->SetEnabled(!Edit); + + FileNameEdit = new TFarEdit(this); + FileNameEdit->SetEnabled(!Edit); + FileNameEdit->SetHistory(LINK_FILENAME_HISTORY); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(STRING_LINK_POINT_TO)); + + PointToEdit = new TFarEdit(this); + PointToEdit->SetHistory(LINK_POINT_TO_HISTORY); + + new TFarSeparator(this); + + SymbolicCheck = new TFarCheckBox(this); + SymbolicCheck->SetCaption(GetMsg(STRING_LINK_SYMLINK)); + SymbolicCheck->SetEnabled(AllowSymbolic && !Edit); + + Separator = new TFarSeparator(this); + Separator->SetPosition(CRect.Bottom - 1); + + OkButton = new TFarButton(this); + OkButton->SetCaption(GetMsg(MSG_BUTTON_OK)); + OkButton->SetDefault(true); + OkButton->SetResult(brOK); + OkButton->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(MSG_BUTTON_Cancel)); + Button->SetResult(brCancel); + Button->SetCenterGroup(true); +} + +void TLinkDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle()) + { + OkButton->SetEnabled(!FileNameEdit->GetText().IsEmpty() && + !PointToEdit->GetText().IsEmpty()); + } +} + +bool TLinkDialog::Execute(UnicodeString & AFileName, UnicodeString & PointTo, + bool & Symbolic) +{ + FileNameEdit->SetText(AFileName); + PointToEdit->SetText(PointTo); + SymbolicCheck->SetChecked(Symbolic); + + bool Result = ShowModal() != brCancel; + if (Result) + { + AFileName = FileNameEdit->GetText(); + PointTo = PointToEdit->GetText(); + Symbolic = SymbolicCheck->GetChecked(); + } + return Result; +} + +bool TWinSCPFileSystem::LinkDialog(UnicodeString & AFileName, + UnicodeString & PointTo, bool & Symbolic, bool Edit, bool AllowSymbolic) +{ + std::unique_ptr Dialog(new TLinkDialog(FPlugin, Edit, AllowSymbolic)); + bool Result = Dialog->Execute(AFileName, PointTo, Symbolic); + return Result; +} + +DEFINE_CALLBACK_TYPE3(TFeedFileSystemDataEvent, void, + TObject * /*Control*/, int /*Label*/, const UnicodeString & /*Value*/); + +class TLabelList; +class TFileSystemInfoDialog : TTabbedDialog +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + enum + { + tabProtocol = 1, + tabCapabilities, + tabSpaceAvailable, + tabCount + }; + + explicit TFileSystemInfoDialog(TCustomFarPlugin * AFarPlugin, + TGetSpaceAvailableEvent OnGetSpaceAvailable); + virtual ~TFileSystemInfoDialog(); + void Execute(const TSessionInfo & SessionInfo, + const TFileSystemInfo & FileSystemInfo, const UnicodeString & SpaceAvailablePath); + +protected: + void Feed(TFeedFileSystemDataEvent AddItem); + UnicodeString CapabilityStr(TFSCapability Capability); + UnicodeString CapabilityStr(TFSCapability Capability1, + TFSCapability Capability2); + UnicodeString SpaceStr(int64_t Bytes); + void ControlsAddItem(TObject * Control, int Label, const UnicodeString & Value); + void CalculateMaxLenAddItem(TObject * Control, int Label, const UnicodeString & Value); + void ClipboardAddItem(TObject * Control, int Label, const UnicodeString & Value); + void FeedControls(); + void UpdateControls(); + TLabelList * CreateLabelArray(intptr_t Count); + virtual void SelectTab(intptr_t Tab); + virtual void Change(); + void SpaceAvailableButtonClick(TFarButton * Sender, bool & Close); + void ClipboardButtonClick(TFarButton * Sender, bool & Close); + void CheckSpaceAvailable(); + void NeedSpaceAvailable(); + bool SpaceAvailableSupported(); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + +private: + TGetSpaceAvailableEvent FOnGetSpaceAvailable; + TFileSystemInfo FFileSystemInfo; + TSessionInfo FSessionInfo; + bool FSpaceAvailableLoaded; + TSpaceAvailable FSpaceAvailable; + TObject * FLastFeededControl; + intptr_t FLastListItem; + UnicodeString FClipboard; + + TLabelList * ServerLabels; + TLabelList * ProtocolLabels; + TLabelList * SpaceAvailableLabels; + TTabButton * SpaceAvailableTab; + TFarText * HostKeyFingerprintLabel; + TFarEdit * HostKeyFingerprintEdit; + TFarText * InfoLabel; + TFarSeparator * InfoSeparator; + TFarLister * InfoLister; + TFarEdit * SpaceAvailablePathEdit; + TFarButton * OkButton; +}; + +class TLabelList : public TList +{ +NB_DECLARE_CLASS(TLabelList) +public: + explicit TLabelList() : + TList(), MaxLen(0) + { + } + + intptr_t MaxLen; +}; + +TFileSystemInfoDialog::TFileSystemInfoDialog(TCustomFarPlugin * AFarPlugin, + TGetSpaceAvailableEvent OnGetSpaceAvailable) : TTabbedDialog(AFarPlugin, tabCount), + FSpaceAvailableLoaded(false), + FLastFeededControl(nullptr), + FLastListItem(0), + InfoLabel(nullptr) +{ + FOnGetSpaceAvailable = OnGetSpaceAvailable; + TFarText * Text; + TFarSeparator * Separator; + TFarButton * Button; + TTabButton * Tab; + intptr_t GroupTop; + + SetSize(TPoint(73, 22)); + SetCaption(GetMsg(SERVER_PROTOCOL_INFORMATION)); + + Tab = new TTabButton(this); + Tab->SetTabName(GetMsg(SERVER_PROTOCOL_TAB_PROTOCOL)); + Tab->SetTab(tabProtocol); + + SetNextItemPosition(ipRight); + + Tab = new TTabButton(this); + Tab->SetTabName(GetMsg(SERVER_PROTOCOL_TAB_CAPABILITIES)); + Tab->SetTab(tabCapabilities); + + SpaceAvailableTab = new TTabButton(this); + SpaceAvailableTab->SetTabName(GetMsg(SERVER_PROTOCOL_TAB_SPACE_AVAILABLE)); + SpaceAvailableTab->SetTab(tabSpaceAvailable); + + // Server tab + + SetNextItemPosition(ipNewLine); + SetDefaultGroup(tabProtocol); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(SERVER_INFORMATION_GROUP)); + GroupTop = Separator->GetTop(); + + ServerLabels = CreateLabelArray(5); + + new TFarSeparator(this); + + HostKeyFingerprintLabel = new TFarText(this); + HostKeyFingerprintLabel->SetCaption(GetMsg(SERVER_HOST_KEY)); + HostKeyFingerprintEdit = new TFarEdit(this); + HostKeyFingerprintEdit->SetReadOnly(true); + + // Protocol tab + + SetDefaultGroup(tabCapabilities); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(PROTOCOL_INFORMATION_GROUP)); + Separator->SetPosition(GroupTop); + + ProtocolLabels = CreateLabelArray(9); + + InfoSeparator = new TFarSeparator(this); + InfoSeparator->SetCaption(GetMsg(PROTOCOL_INFO_GROUP)); + + InfoLister = new TFarLister(this); + InfoLister->SetHeight(4); + InfoLister->SetLeft(GetBorderBox()->GetLeft() + 1); + // Right edge is adjusted in FeedControls + + // Space available tab + + SetDefaultGroup(tabSpaceAvailable); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(SPACE_AVAILABLE_GROUP)); + Separator->SetPosition(GroupTop); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(SPACE_AVAILABLE_PATH)); + + SetNextItemPosition(ipRight); + + SpaceAvailablePathEdit = new TFarEdit(this); + SpaceAvailablePathEdit->SetRight( + - (static_cast(GetMsg(SPACE_AVAILABLE_CHECK_SPACE).Length() + 11))); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(SPACE_AVAILABLE_CHECK_SPACE)); + Button->SetEnabledDependency(SpaceAvailablePathEdit); + Button->SetOnClick(MAKE_CALLBACK(TFileSystemInfoDialog::SpaceAvailableButtonClick, this)); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + SpaceAvailableLabels = CreateLabelArray(5); + + // Buttons + + SetDefaultGroup(0); + + Separator = new TFarSeparator(this); + Separator->SetPosition(GetClientRect().Bottom - 1); + + Button = new TFarButton(this); + Button->SetCaption(GetMsg(SERVER_PROTOCOL_COPY_CLIPBOARD)); + Button->SetOnClick(MAKE_CALLBACK(TFileSystemInfoDialog::ClipboardButtonClick, this)); + Button->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + OkButton = new TFarButton(this); + OkButton->SetCaption(GetMsg(MSG_BUTTON_OK)); + OkButton->SetDefault(true); + OkButton->SetResult(brOK); + OkButton->SetCenterGroup(true); +} + +TFileSystemInfoDialog::~TFileSystemInfoDialog() +{ + SAFE_DESTROY(ServerLabels); + SAFE_DESTROY(ProtocolLabels); + SAFE_DESTROY(SpaceAvailableLabels); +} + +TLabelList * TFileSystemInfoDialog::CreateLabelArray(intptr_t Count) +{ + std::unique_ptr List(new TLabelList()); + for (intptr_t Index = 0; Index < Count; ++Index) + { + List->Add(new TFarText(this)); + } + return List.release(); +} + +UnicodeString TFileSystemInfoDialog::CapabilityStr(TFSCapability Capability) +{ + return BooleanToStr(FFileSystemInfo.IsCapable[Capability]); +} + +UnicodeString TFileSystemInfoDialog::CapabilityStr(TFSCapability Capability1, + TFSCapability Capability2) +{ + return FORMAT(L"%s/%s", CapabilityStr(Capability1).c_str(), CapabilityStr(Capability2).c_str()); +} + +UnicodeString TFileSystemInfoDialog::SpaceStr(int64_t Bytes) +{ + UnicodeString Result; + if (Bytes == 0) + { + Result = GetMsg(SPACE_AVAILABLE_BYTES_UNKNOWN); + } + else + { + Result = FormatBytes(Bytes); + UnicodeString SizeUnorderedStr = FormatBytes(Bytes, false); + if (Result != SizeUnorderedStr) + { + Result = FORMAT(L"%s (%s)", Result.c_str(), SizeUnorderedStr.c_str()); + } + } + return Result; +} + +void TFileSystemInfoDialog::Feed(TFeedFileSystemDataEvent AddItem) +{ + AddItem(ServerLabels, SERVER_REMOTE_SYSTEM, FFileSystemInfo.RemoteSystem); + AddItem(ServerLabels, SERVER_SESSION_PROTOCOL, FSessionInfo.ProtocolName); + AddItem(ServerLabels, SERVER_SSH_IMPLEMENTATION, FSessionInfo.SshImplementation); + + UnicodeString Str = FSessionInfo.CSCipher; + if (FSessionInfo.CSCipher != FSessionInfo.SCCipher) + { + Str += FORMAT(L"/%s", FSessionInfo.SCCipher.c_str()); + } + AddItem(ServerLabels, SERVER_CIPHER, Str); + + Str = DefaultStr(FSessionInfo.CSCompression, LoadStr(NO_STR)); + if (FSessionInfo.CSCompression != FSessionInfo.SCCompression) + { + Str += FORMAT(L"/%s", DefaultStr(FSessionInfo.SCCompression, LoadStr(NO_STR)).c_str()); + } + AddItem(ServerLabels, SERVER_COMPRESSION, Str); + if (FSessionInfo.ProtocolName != FFileSystemInfo.ProtocolName) + { + AddItem(ServerLabels, SERVER_FS_PROTOCOL, FFileSystemInfo.ProtocolName); + } + + AddItem(HostKeyFingerprintEdit, 0, FSessionInfo.HostKeyFingerprint); + + AddItem(ProtocolLabels, PROTOCOL_MODE_CHANGING, CapabilityStr(fcModeChanging)); + AddItem(ProtocolLabels, PROTOCOL_OWNER_GROUP_CHANGING, CapabilityStr(fcGroupChanging)); + UnicodeString AnyCommand; + if (!FFileSystemInfo.IsCapable[fcShellAnyCommand] && + FFileSystemInfo.IsCapable[fcAnyCommand]) + { + AnyCommand = GetMsg(PROTOCOL_PROTOCOL_ANY_COMMAND); + } + else + { + AnyCommand = CapabilityStr(fcAnyCommand); + } + AddItem(ProtocolLabels, PROTOCOL_ANY_COMMAND, AnyCommand); + AddItem(ProtocolLabels, PROTOCOL_SYMBOLIC_HARD_LINK, CapabilityStr(fcSymbolicLink, fcHardLink)); + AddItem(ProtocolLabels, PROTOCOL_USER_GROUP_LISTING, CapabilityStr(fcUserGroupListing)); + AddItem(ProtocolLabels, PROTOCOL_REMOTE_COPY, CapabilityStr(fcRemoteCopy)); + AddItem(ProtocolLabels, PROTOCOL_CHECKING_SPACE_AVAILABLE, CapabilityStr(fcCheckingSpaceAvailable)); + AddItem(ProtocolLabels, PROTOCOL_CALCULATING_CHECKSUM, CapabilityStr(fcCalculatingChecksum)); + AddItem(ProtocolLabels, PROTOCOL_NATIVE_TEXT_MODE, CapabilityStr(fcNativeTextMode)); + + AddItem(InfoLister, 0, FFileSystemInfo.AdditionalInfo); + + AddItem(SpaceAvailableLabels, SPACE_AVAILABLE_BYTES_ON_DEVICE, SpaceStr(FSpaceAvailable.BytesOnDevice)); + AddItem(SpaceAvailableLabels, SPACE_AVAILABLE_UNUSED_BYTES_ON_DEVICE, SpaceStr(FSpaceAvailable.UnusedBytesOnDevice)); + AddItem(SpaceAvailableLabels, SPACE_AVAILABLE_BYTES_AVAILABLE_TO_USER, SpaceStr(FSpaceAvailable.BytesAvailableToUser)); + AddItem(SpaceAvailableLabels, SPACE_AVAILABLE_UNUSED_BYTES_AVAILABLE_TO_USER, SpaceStr(FSpaceAvailable.UnusedBytesAvailableToUser)); + AddItem(SpaceAvailableLabels, SPACE_AVAILABLE_BYTES_PER_ALLOCATION_UNIT, SpaceStr(FSpaceAvailable.BytesPerAllocationUnit)); +} + +void TFileSystemInfoDialog::ControlsAddItem(TObject * Control, + int Label, const UnicodeString & Value) +{ + if (FLastFeededControl != Control) + { + FLastFeededControl = Control; + FLastListItem = 0; + } + + if (Control == HostKeyFingerprintEdit) + { + HostKeyFingerprintEdit->SetText(Value); + HostKeyFingerprintEdit->SetEnabled(!Value.IsEmpty()); + if (!HostKeyFingerprintEdit->GetEnabled()) + { + HostKeyFingerprintEdit->SetVisible(false); + HostKeyFingerprintEdit->SetGroup(0); + HostKeyFingerprintLabel->SetVisible(false); + HostKeyFingerprintLabel->SetGroup(0); + } + } + else if (Control == InfoLister) + { + InfoLister->GetItems()->SetText(Value); + InfoLister->SetEnabled(!Value.IsEmpty()); + if (!InfoLister->GetEnabled()) + { + InfoLister->SetVisible(false); + InfoLister->SetGroup(0); + InfoSeparator->SetVisible(false); + InfoSeparator->SetGroup(0); + } + } + else + { + TLabelList * List = NB_STATIC_DOWNCAST(TLabelList, Control); + DebugAssert(List != nullptr); + if (!Value.IsEmpty()) + { + TFarText * Text = NB_STATIC_DOWNCAST(TFarText, List->GetItem(FLastListItem)); + FLastListItem++; + + Text->SetCaption(FORMAT(L"%d-%s %s", List->MaxLen, GetMsg(Label).c_str(), Value.c_str())); + } + } +} + +void TFileSystemInfoDialog::CalculateMaxLenAddItem(TObject * Control, + int Label, const UnicodeString & ) +{ + TLabelList * List = NB_STATIC_DOWNCAST(TLabelList, Control); + if (List != nullptr) + { + UnicodeString S = GetMsg(Label); + if (List->MaxLen < S.Length()) + { + List->MaxLen = S.Length(); + } + } +} + +void TFileSystemInfoDialog::ClipboardAddItem(TObject * AControl, + int Label, const UnicodeString & Value) +{ + TFarDialogItem * Control = NB_STATIC_DOWNCAST(TFarDialogItem, AControl); + // check for Enabled instead of Visible, as Visible is false + // when control is on non-active tab + if ((!Value.IsEmpty() && + ((Control == nullptr) || Control->GetEnabled()) && + (AControl != SpaceAvailableLabels)) || + SpaceAvailableSupported()) + { + if (FLastFeededControl != AControl) + { + if (FLastFeededControl != nullptr) + { + FClipboard += ::StringOfChar('-', 60) + L"\r\n"; + } + FLastFeededControl = AControl; + } + + if (NB_STATIC_DOWNCAST(TLabelList, AControl) == nullptr) + { + UnicodeString LabelStr; + if (Control == HostKeyFingerprintEdit) + { + LabelStr = GetMsg(SERVER_HOST_KEY); + } + else if (Control == InfoLister) + { + LabelStr = ::Trim(GetMsg(PROTOCOL_INFO_GROUP)); + } + else + { + DebugAssert(false); + } + + if (!LabelStr.IsEmpty() && (LabelStr[LabelStr.Length()] == L':')) + { + LabelStr.SetLength(LabelStr.Length() - 1); + } + + UnicodeString Value2 = Value; + if ((Value2.Length() >= 2) && (Value2.SubString(Value2.Length() - 1, 2) == L"\r\n")) + { + Value2.SetLength(Value2.Length() - 2); + } + + FClipboard += FORMAT(L"%s\r\n%s\r\n", LabelStr.c_str(), Value2.c_str()); + } + else + { + DebugAssert(NB_STATIC_DOWNCAST(TLabelList, AControl) != nullptr); + UnicodeString LabelStr = GetMsg(Label); + if (!LabelStr.IsEmpty() && (LabelStr[LabelStr.Length()] == L':')) + { + LabelStr.SetLength(LabelStr.Length() - 1); + } + FClipboard += FORMAT(L"%s = %s\r\n", LabelStr.c_str(), Value.c_str()); + } + } +} + +void TFileSystemInfoDialog::FeedControls() +{ + FLastFeededControl = nullptr; + Feed(MAKE_CALLBACK(TFileSystemInfoDialog::ControlsAddItem, this)); + InfoLister->SetRight(GetBorderBox()->GetRight() - (InfoLister->GetScrollBar() ? 0 : 1)); +} + +void TFileSystemInfoDialog::SelectTab(intptr_t Tab) +{ + TTabbedDialog::SelectTab(Tab); + if (InfoLister->GetVisible()) + { + // At first the dialog border box hides the eventual scrollbar of infolister, + // so redraw to reshow it. + Redraw(); + } + + if (Tab == tabSpaceAvailable) + { + NeedSpaceAvailable(); + } +} + +void TFileSystemInfoDialog::Execute( + const TSessionInfo & SessionInfo, const TFileSystemInfo & FileSystemInfo, + const UnicodeString & SpaceAvailablePath) +{ + FFileSystemInfo = FileSystemInfo; + FSessionInfo = SessionInfo; + SpaceAvailablePathEdit->SetText(SpaceAvailablePath); + UpdateControls(); + + Feed(MAKE_CALLBACK(TFileSystemInfoDialog::CalculateMaxLenAddItem, this)); + FeedControls(); + HideTabs(); + SelectTab(tabProtocol); + + ShowModal(); +} + +bool TFileSystemInfoDialog::Key(TFarDialogItem * Item, LONG_PTR KeyCode) +{ + bool Result = false; + if ((Item == SpaceAvailablePathEdit) && (KeyCode == KEY_ENTER)) + { + CheckSpaceAvailable(); + Result = true; + } + else + { + Result = TTabbedDialog::Key(Item, KeyCode); + } + return Result; +} + +void TFileSystemInfoDialog::Change() +{ + TTabbedDialog::Change(); + + if (GetHandle()) + { + UpdateControls(); + } +} + +void TFileSystemInfoDialog::UpdateControls() +{ + SpaceAvailableTab->SetEnabled(SpaceAvailableSupported()); +} + +void TFileSystemInfoDialog::ClipboardButtonClick(TFarButton * /*Sender*/, + bool & Close) +{ + NeedSpaceAvailable(); + FLastFeededControl = nullptr; + FClipboard.Clear(); + Feed(MAKE_CALLBACK(TFileSystemInfoDialog::ClipboardAddItem, this)); + FarPlugin->FarCopyToClipboard(FClipboard); + Close = false; +} + +void TFileSystemInfoDialog::SpaceAvailableButtonClick( + TFarButton * /*Sender*/, bool & Close) +{ + CheckSpaceAvailable(); + Close = false; +} + +void TFileSystemInfoDialog::CheckSpaceAvailable() +{ + DebugAssert(FOnGetSpaceAvailable); + DebugAssert(!SpaceAvailablePathEdit->GetText().IsEmpty()); + + FSpaceAvailableLoaded = true; + + bool DoClose = false; + + FOnGetSpaceAvailable(SpaceAvailablePathEdit->GetText(), FSpaceAvailable, DoClose); + + FeedControls(); + if (DoClose) + { + Close(OkButton); + } +} + +void TFileSystemInfoDialog::NeedSpaceAvailable() +{ + if (!FSpaceAvailableLoaded && SpaceAvailableSupported()) + { + CheckSpaceAvailable(); + } +} + +bool TFileSystemInfoDialog::SpaceAvailableSupported() +{ + return (FOnGetSpaceAvailable); +} + +void TWinSCPFileSystem::FileSystemInfoDialog( + const TSessionInfo & SessionInfo, const TFileSystemInfo & FileSystemInfo, + const UnicodeString & SpaceAvailablePath, TGetSpaceAvailableEvent OnGetSpaceAvailable) +{ + std::unique_ptr Dialog(new TFileSystemInfoDialog(FPlugin, OnGetSpaceAvailable)); + Dialog->Execute(SessionInfo, FileSystemInfo, SpaceAvailablePath); +} + +bool TWinSCPFileSystem::OpenDirectoryDialog( + bool Add, UnicodeString & Directory, TBookmarkList * BookmarkList) +{ + bool Result = false; + bool Repeat = false; + + intptr_t ItemFocused = -1; + + do + { + std::unique_ptr BookmarkPaths(new TStringList()); + std::unique_ptr BookmarkItems(new TFarMenuItems()); + std::unique_ptr Bookmarks(new TList()); + intptr_t BookmarksOffset = -1; + + intptr_t MaxLength = FPlugin->MaxMenuItemLength(); + intptr_t MaxHistory = 40; + intptr_t FirstHistory = 0; + + if (FPathHistory->GetCount() > MaxHistory) + { + FirstHistory = FPathHistory->GetCount() - MaxHistory + 1; + } + + for (intptr_t Index = FirstHistory; Index < FPathHistory->GetCount(); ++Index) + { + UnicodeString Path = FPathHistory->GetString(Index); + BookmarkPaths->Add(Path); + BookmarkItems->Add(core::MinimizeName(Path, MaxLength, true)); + } + + intptr_t FirstItemFocused = -1; + std::unique_ptr BookmarkDirectories(new TStringList()); + BookmarkDirectories->SetSorted(true); + for (intptr_t Index = 0; Index < BookmarkList->GetCount(); ++Index) + { + TBookmark * Bookmark = BookmarkList->GetBookmarks(Index); + UnicodeString RemoteDirectory = Bookmark->GetRemote(); + if (!RemoteDirectory.IsEmpty() && (BookmarkDirectories->IndexOf(RemoteDirectory.c_str()) == NPOS)) + { + intptr_t Pos = BookmarkDirectories->Add(RemoteDirectory); + if (RemoteDirectory == Directory) + { + FirstItemFocused = Pos; + } + else if ((FirstItemFocused >= 0) && (FirstItemFocused >= Pos)) + { + FirstItemFocused++; + } + Bookmarks->Insert(Pos, Bookmark); + } + } + + if (BookmarkDirectories->GetCount() == 0) + { + FirstItemFocused = BookmarkItems->Add(L""); + BookmarkPaths->Add(L""); + BookmarksOffset = BookmarkItems->GetCount(); + } + else + { + if (BookmarkItems->GetCount() > 0) + { + BookmarkItems->AddSeparator(); + BookmarkPaths->Add(L""); + } + + BookmarksOffset = BookmarkItems->GetCount(); + + if (FirstItemFocused >= 0) + { + FirstItemFocused += BookmarkItems->GetCount(); + } + else + { + FirstItemFocused = BookmarkItems->GetCount(); + } + + for (intptr_t II = 0; II < BookmarkDirectories->GetCount(); II++) + { + UnicodeString Path = BookmarkDirectories->GetString(II); + BookmarkItems->Add(Path); + BookmarkPaths->Add(core::MinimizeName(Path, MaxLength, true)); + } + } + + if (ItemFocused < 0) + { + BookmarkItems->SetItemFocused(FirstItemFocused); + } + else if (ItemFocused < BookmarkItems->GetCount()) + { + BookmarkItems->SetItemFocused(ItemFocused); + } + else + { + BookmarkItems->SetItemFocused(BookmarkItems->GetCount() - 1); + } + + int BreakCode; + + Repeat = false; + UnicodeString Caption = GetMsg(Add ? OPEN_DIRECTORY_ADD_BOOMARK_ACTION : + OPEN_DIRECTORY_BROWSE_CAPTION); + const int BreakKeys[] = + { + VK_DELETE, + VK_F8, + VK_RETURN + (PKF_CONTROL << 16), + 'C' + (PKF_CONTROL << 16), + VK_INSERT + (PKF_CONTROL << 16), + 0 + }; + + ItemFocused = FPlugin->Menu(FMENU_REVERSEAUTOHIGHLIGHT | FMENU_SHOWAMPERSAND | FMENU_WRAPMODE, + Caption, GetMsg(OPEN_DIRECTORY_HELP), BookmarkItems.get(), BreakKeys, BreakCode); + if (BreakCode >= 0) + { + DebugAssert(BreakCode >= 0 && BreakCode <= 4); + if ((BreakCode == 0) || (BreakCode == 1)) + { + DebugAssert(ItemFocused >= 0); + if (ItemFocused >= BookmarksOffset) + { + TBookmark * Bookmark = NB_STATIC_DOWNCAST(TBookmark, Bookmarks->GetItem(ItemFocused - BookmarksOffset)); + BookmarkList->Delete(Bookmark); + } + else + { + FPathHistory->Clear(); + ItemFocused = -1; + } + Repeat = true; + } + else if (BreakCode == 2) + { + FarControl(FCTL_INSERTCMDLINE, 0, reinterpret_cast(BookmarkPaths->GetString(ItemFocused).c_str())); + } + else if (BreakCode == 3 || BreakCode == 4) + { + FPlugin->FarCopyToClipboard(BookmarkPaths->GetString(ItemFocused)); + Repeat = true; + } + } + else if (ItemFocused >= 0) + { + Directory = BookmarkPaths->GetString(ItemFocused); + if (Directory.IsEmpty()) + { + // empty trailing line in no-bookmark mode selected + ItemFocused = -1; + } + } + + Result = (BreakCode < 0) && (ItemFocused >= 0); + } + while (Repeat); + + return Result; +} + +class TApplyCommandDialog : public TWinSCPDialog +{ +public: + explicit TApplyCommandDialog(TCustomFarPlugin * AFarPlugin); + + bool Execute(UnicodeString & Command, intptr_t & Params); + +protected: + virtual void Change(); + +private: + intptr_t FParams; + + TFarEdit * CommandEdit; + TFarText * LocalHintText; + TFarRadioButton * RemoteCommandButton; + TFarRadioButton * LocalCommandButton; + TFarCheckBox * ApplyToDirectoriesCheck; + TFarCheckBox * RecursiveCheck; + TFarCheckBox * ShowResultsCheck; + TFarCheckBox * CopyResultsCheck; + + UnicodeString FPrompt; + TFarEdit * PasswordEdit; + TFarEdit * NormalEdit; + TFarCheckBox * HideTypingCheck; +}; + +TApplyCommandDialog::TApplyCommandDialog(TCustomFarPlugin * AFarPlugin) : + TWinSCPDialog(AFarPlugin), + FParams(0), + PasswordEdit(nullptr), + NormalEdit(nullptr), + HideTypingCheck(nullptr) +{ + TFarText * Text; + + SetSize(TPoint(76, 18)); + SetCaption(GetMsg(APPLY_COMMAND_TITLE)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_PROMPT)); + + CommandEdit = new TFarEdit(this); + CommandEdit->SetHistory(APPLY_COMMAND_HISTORY); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_HINT1)); + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_HINT2)); + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_HINT3)); + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_HINT4)); + Text = new TFarText(this); + Text->SetCaption(GetMsg(APPLY_COMMAND_HINT5)); + LocalHintText = new TFarText(this); + LocalHintText->SetCaption(GetMsg(APPLY_COMMAND_HINT_LOCAL)); + + new TFarSeparator(this); + + RemoteCommandButton = new TFarRadioButton(this); + RemoteCommandButton->SetCaption(GetMsg(APPLY_COMMAND_REMOTE_COMMAND)); + + SetNextItemPosition(ipRight); + + LocalCommandButton = new TFarRadioButton(this); + LocalCommandButton->SetCaption(GetMsg(APPLY_COMMAND_LOCAL_COMMAND)); + + LocalHintText->SetEnabledDependency(LocalCommandButton); + + SetNextItemPosition(ipNewLine); + + ApplyToDirectoriesCheck = new TFarCheckBox(this); + ApplyToDirectoriesCheck->SetCaption( + GetMsg(APPLY_COMMAND_APPLY_TO_DIRECTORIES)); + + SetNextItemPosition(ipRight); + + RecursiveCheck = new TFarCheckBox(this); + RecursiveCheck->SetCaption(GetMsg(APPLY_COMMAND_RECURSIVE)); + + SetNextItemPosition(ipNewLine); + + ShowResultsCheck = new TFarCheckBox(this); + ShowResultsCheck->SetCaption(GetMsg(APPLY_COMMAND_SHOW_RESULTS)); + ShowResultsCheck->SetEnabledDependency(RemoteCommandButton); + + SetNextItemPosition(ipRight); + + CopyResultsCheck = new TFarCheckBox(this); + CopyResultsCheck->SetCaption(GetMsg(APPLY_COMMAND_COPY_RESULTS)); + CopyResultsCheck->SetEnabledDependency(RemoteCommandButton); + + AddStandardButtons(); + + OkButton->SetEnabledDependency(CommandEdit); +} + +void TApplyCommandDialog::Change() +{ + TWinSCPDialog::Change(); + + if (GetHandle()) + { + bool RemoteCommand = RemoteCommandButton->GetChecked(); + bool AllowRecursive = true; + bool AllowApplyToDirectories = true; + try + { + TRemoteCustomCommand RemoteCustomCommand; + TLocalCustomCommand LocalCustomCommand; + TFileCustomCommand * FileCustomCommand = + (RemoteCommand ? &RemoteCustomCommand : &LocalCustomCommand); + + TInteractiveCustomCommand InteractiveCustomCommand(FileCustomCommand); + UnicodeString Cmd = InteractiveCustomCommand.Complete(CommandEdit->GetText(), false); + bool FileCommand = FileCustomCommand->IsFileCommand(Cmd); + AllowRecursive = FileCommand && !FileCustomCommand->IsFileListCommand(Cmd); + if (AllowRecursive && !RemoteCommand) + { + AllowRecursive = !LocalCustomCommand.HasLocalFileName(Cmd); + } + AllowApplyToDirectories = FileCommand; + } + catch (...) + { + } + + RecursiveCheck->SetEnabled(AllowRecursive); + ApplyToDirectoriesCheck->SetEnabled(AllowApplyToDirectories); + } +} + +bool TApplyCommandDialog::Execute(UnicodeString & Command, intptr_t & Params) +{ + CommandEdit->SetText(Command); + FParams = Params; + RemoteCommandButton->SetChecked(FLAGCLEAR(Params, ccLocal)); + LocalCommandButton->SetChecked(FLAGSET(Params, ccLocal)); + ApplyToDirectoriesCheck->SetChecked(FLAGSET(Params, ccApplyToDirectories)); + RecursiveCheck->SetChecked(FLAGSET(Params, ccRecursive)); + ShowResultsCheck->SetChecked(FLAGSET(Params, ccShowResults)); + CopyResultsCheck->SetChecked(FLAGSET(Params, ccCopyResults)); + + bool Result = (ShowModal() != brCancel); + if (Result) + { + Command = CommandEdit->GetText(); + Params &= ~(ccLocal | ccApplyToDirectories | ccRecursive | ccShowResults | ccCopyResults); + Params |= + FLAGMASK(!RemoteCommandButton->GetChecked(), ccLocal) | + FLAGMASK(ApplyToDirectoriesCheck->GetChecked(), ccApplyToDirectories) | + FLAGMASK(RecursiveCheck->GetChecked() && RecursiveCheck->GetEnabled(), ccRecursive) | + FLAGMASK(ShowResultsCheck->GetChecked() && ShowResultsCheck->GetEnabled(), ccShowResults) | + FLAGMASK(CopyResultsCheck->GetChecked() && CopyResultsCheck->GetEnabled(), ccCopyResults); + } + return Result; +} + +bool TWinSCPFileSystem::ApplyCommandDialog(UnicodeString & Command, + intptr_t & Params) +{ + std::unique_ptr Dialog(new TApplyCommandDialog(FPlugin)); + bool Result = Dialog->Execute(Command, Params); + return Result; +} + +class TFullSynchronizeDialog : public TWinSCPDialog +{ +public: + explicit TFullSynchronizeDialog(TCustomFarPlugin * AFarPlugin, intptr_t Options, + const TUsableCopyParamAttrs & CopyParamAttrs); + + bool Execute(TTerminal::TSynchronizeMode & Mode, + intptr_t & Params, UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TCopyParamType * CopyParams, bool & SaveSettings, bool & SaveMode); + +protected: + virtual bool CloseQuery(); + virtual void Change(); + virtual LONG_PTR DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + + void TransferSettingsButtonClick(TFarButton * Sender, bool & Close); + void CopyParamListerClick(TFarDialogItem * Item, MOUSE_EVENT_RECORD * Event); + + intptr_t ActualCopyParamAttrs(); + void CustomCopyParam(); + void AdaptSize(); + +private: + TFarEdit * LocalDirectoryEdit; + TFarEdit * RemoteDirectoryEdit; + TFarRadioButton * SynchronizeBothButton; + TFarRadioButton * SynchronizeRemoteButton; + TFarRadioButton * SynchronizeLocalButton; + TFarRadioButton * SynchronizeFilesButton; + TFarRadioButton * MirrorFilesButton; + TFarRadioButton * SynchronizeTimestampsButton; + TFarCheckBox * SynchronizeDeleteCheck; + TFarCheckBox * SynchronizeExistingOnlyCheck; + TFarCheckBox * SynchronizeSelectedOnlyCheck; + TFarCheckBox * SynchronizePreviewChangesCheck; + TFarCheckBox * SynchronizeByTimeCheck; + TFarCheckBox * SynchronizeBySizeCheck; + TFarCheckBox * SaveSettingsCheck; + TFarLister * CopyParamLister; + + bool FSaveMode; + intptr_t FOptions; + intptr_t FFullHeight; + TTerminal::TSynchronizeMode FOrigMode; + TUsableCopyParamAttrs FCopyParamAttrs; + TCopyParamType FCopyParams; + + TTerminal::TSynchronizeMode GetMode() const; +}; + +TFullSynchronizeDialog::TFullSynchronizeDialog( + TCustomFarPlugin * AFarPlugin, intptr_t Options, + const TUsableCopyParamAttrs & CopyParamAttrs) : + TWinSCPDialog(AFarPlugin), + FOptions(Options), + FCopyParamAttrs(CopyParamAttrs) +{ + TFarText * Text; + TFarSeparator * Separator; + + SetSize(TPoint(78, 25)); + SetCaption(GetMsg(FULL_SYNCHRONIZE_TITLE)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(FULL_SYNCHRONIZE_LOCAL_LABEL)); + + LocalDirectoryEdit = new TFarEdit(this); + LocalDirectoryEdit->SetHistory(LOCAL_SYNC_HISTORY); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(FULL_SYNCHRONIZE_REMOTE_LABEL)); + + RemoteDirectoryEdit = new TFarEdit(this); + RemoteDirectoryEdit->SetHistory(REMOTE_SYNC_HISTORY); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(FULL_SYNCHRONIZE_DIRECTION_GROUP)); + + SynchronizeBothButton = new TFarRadioButton(this); + SynchronizeBothButton->SetCaption(GetMsg(FULL_SYNCHRONIZE_BOTH)); + + SetNextItemPosition(ipRight); + + SynchronizeRemoteButton = new TFarRadioButton(this); + SynchronizeRemoteButton->SetCaption(GetMsg(FULL_SYNCHRONIZE_REMOTE)); + + SynchronizeLocalButton = new TFarRadioButton(this); + SynchronizeLocalButton->SetCaption(GetMsg(FULL_SYNCHRONIZE_LOCAL)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(FULL_SYNCHRONIZE_MODE_GROUP)); + + SynchronizeFilesButton = new TFarRadioButton(this); + SynchronizeFilesButton->SetCaption(GetMsg(SYNCHRONIZE_SYNCHRONIZE_FILES)); + + SetNextItemPosition(ipRight); + + MirrorFilesButton = new TFarRadioButton(this); + MirrorFilesButton->SetCaption(GetMsg(SYNCHRONIZE_MIRROR_FILES)); + + SynchronizeTimestampsButton = new TFarRadioButton(this); + SynchronizeTimestampsButton->SetCaption(GetMsg(SYNCHRONIZE_SYNCHRONIZE_TIMESTAMPS)); + SynchronizeTimestampsButton->SetEnabled(FLAGCLEAR(Options, fsoDisableTimestamp)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(FULL_SYNCHRONIZE_GROUP)); + + SynchronizeDeleteCheck = new TFarCheckBox(this); + SynchronizeDeleteCheck->SetCaption(GetMsg(SYNCHRONIZE_DELETE)); + + SetNextItemPosition(ipRight); + + SynchronizeExistingOnlyCheck = new TFarCheckBox(this); + SynchronizeExistingOnlyCheck->SetCaption(GetMsg(SYNCHRONIZE_EXISTING_ONLY)); + SynchronizeExistingOnlyCheck->SetEnabledDependencyNegative(SynchronizeTimestampsButton); + + SetNextItemPosition(ipNewLine); + + SynchronizePreviewChangesCheck = new TFarCheckBox(this); + SynchronizePreviewChangesCheck->SetCaption(GetMsg(SYNCHRONIZE_PREVIEW_CHANGES)); + + SetNextItemPosition(ipRight); + + SynchronizeSelectedOnlyCheck = new TFarCheckBox(this); + SynchronizeSelectedOnlyCheck->SetCaption(GetMsg(SYNCHRONIZE_SELECTED_ONLY)); + SynchronizeSelectedOnlyCheck->SetEnabled(FLAGSET(FOptions, fsoAllowSelectedOnly)); + + SetNextItemPosition(ipNewLine); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(FULL_SYNCHRONIZE_CRITERIONS_GROUP)); + + SynchronizeByTimeCheck = new TFarCheckBox(this); + SynchronizeByTimeCheck->SetCaption(GetMsg(SYNCHRONIZE_BY_TIME)); + + SetNextItemPosition(ipRight); + + SynchronizeBySizeCheck = new TFarCheckBox(this); + SynchronizeBySizeCheck->SetCaption(GetMsg(SYNCHRONIZE_BY_SIZE)); + SynchronizeBySizeCheck->SetEnabledDependencyNegative(SynchronizeBothButton); + + SetNextItemPosition(ipNewLine); + + new TFarSeparator(this); + + SaveSettingsCheck = new TFarCheckBox(this); + SaveSettingsCheck->SetCaption(GetMsg(SYNCHRONIZE_REUSE_SETTINGS)); + + Separator = new TFarSeparator(this); + Separator->SetGroup(1); + Separator->SetCaption(GetMsg(COPY_PARAM_GROUP)); + + CopyParamLister = new TFarLister(this); + CopyParamLister->SetHeight(3); + CopyParamLister->SetLeft(GetBorderBox()->GetLeft() + 1); + CopyParamLister->SetTabStop(false); + CopyParamLister->SetOnMouseClick(MAKE_CALLBACK(TFullSynchronizeDialog::CopyParamListerClick, this)); + CopyParamLister->SetGroup(1); + // Right edge is adjusted in Change + + // Align buttons with bottom of the window + Separator = new TFarSeparator(this); + Separator->SetPosition(-4); + + TFarButton * Button = new TFarButton(this); + Button->SetCaption(GetMsg(TRANSFER_SETTINGS_BUTTON)); + Button->SetResult(-1); + Button->SetCenterGroup(true); + Button->SetOnClick(MAKE_CALLBACK(TFullSynchronizeDialog::TransferSettingsButtonClick, this)); + + SetNextItemPosition(ipRight); + + AddStandardButtons(0, true); + + FFullHeight = GetSize().y; + AdaptSize(); +} + +void TFullSynchronizeDialog::AdaptSize() +{ + bool ShowCopyParam = (FFullHeight <= GetMaxSize().y); + if (ShowCopyParam != CopyParamLister->GetVisible()) + { + ShowGroup(1, ShowCopyParam); + SetHeight(FFullHeight - (ShowCopyParam ? 0 : CopyParamLister->GetHeight() + 1)); + } +} + +TTerminal::TSynchronizeMode TFullSynchronizeDialog::GetMode() const +{ + TTerminal::TSynchronizeMode Mode; + + if (SynchronizeRemoteButton->GetChecked()) + { + Mode = TTerminal::smRemote; + } + else if (SynchronizeLocalButton->GetChecked()) + { + Mode = TTerminal::smLocal; + } + else + { + Mode = TTerminal::smBoth; + } + + return Mode; +} + +void TFullSynchronizeDialog::TransferSettingsButtonClick( + TFarButton * /*Sender*/, bool & Close) +{ + CustomCopyParam(); + Close = false; +} + +void TFullSynchronizeDialog::CopyParamListerClick( + TFarDialogItem * /*Item*/, MOUSE_EVENT_RECORD * Event) +{ + if (FLAGSET(Event->dwEventFlags, DOUBLE_CLICK)) + { + CustomCopyParam(); + } +} + +void TFullSynchronizeDialog::CustomCopyParam() +{ + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + if (WinSCPPlugin->CopyParamCustomDialog(FCopyParams, ActualCopyParamAttrs())) + { + Change(); + } +} + +void TFullSynchronizeDialog::Change() +{ + TWinSCPDialog::Change(); + + if (GetHandle()) + { + if (SynchronizeTimestampsButton->GetChecked()) + { + SynchronizeExistingOnlyCheck->SetChecked(true); + SynchronizeDeleteCheck->SetChecked(false); + SynchronizeByTimeCheck->SetChecked(true); + } + if (SynchronizeBothButton->GetChecked()) + { + SynchronizeBySizeCheck->SetChecked(false); + if (MirrorFilesButton->GetChecked()) + { + SynchronizeFilesButton->SetChecked(true); + } + } + if (MirrorFilesButton->GetChecked()) + { + SynchronizeByTimeCheck->SetChecked(true); + } + MirrorFilesButton->SetEnabled(!SynchronizeBothButton->GetChecked()); + SynchronizeDeleteCheck->SetEnabled(!SynchronizeBothButton->GetChecked() && + !SynchronizeTimestampsButton->GetChecked()); + SynchronizeByTimeCheck->SetEnabled(!SynchronizeBothButton->GetChecked() && + !SynchronizeTimestampsButton->GetChecked() && !MirrorFilesButton->GetChecked()); + SynchronizeBySizeCheck->SetCaption(SynchronizeTimestampsButton->GetChecked() ? + GetMsg(SYNCHRONIZE_SAME_SIZE) : GetMsg(SYNCHRONIZE_BY_SIZE)); + + if (!SynchronizeBySizeCheck->GetChecked() && !SynchronizeByTimeCheck->GetChecked()) + { + // suppose that in FAR the checkbox cannot be unchecked unless focused + if (SynchronizeByTimeCheck->Focused()) + { + SynchronizeBySizeCheck->SetChecked(true); + } + else + { + SynchronizeByTimeCheck->SetChecked(true); + } + } + + UnicodeString InfoStr = FCopyParams.GetInfoStr(L"; ", ActualCopyParamAttrs()); + std::unique_ptr InfoStrLines(new TStringList()); + FarWrapText(InfoStr, InfoStrLines.get(), GetBorderBox()->GetWidth() - 4); + CopyParamLister->SetItems(InfoStrLines.get()); + CopyParamLister->SetRight(GetBorderBox()->GetRight() - (CopyParamLister->GetScrollBar() ? 0 : 1)); + } +} + +intptr_t TFullSynchronizeDialog::ActualCopyParamAttrs() +{ + intptr_t Result = -1; + if (SynchronizeTimestampsButton->GetChecked()) + { + Result = cpaIncludeMaskOnly; + } + else + { + switch (GetMode()) + { + case TTerminal::smRemote: + Result = FCopyParamAttrs.Upload; + break; + + case TTerminal::smLocal: + Result = FCopyParamAttrs.Download; + break; + + default: + DebugAssert(false); + //fallthru + case TTerminal::smBoth: + Result = FCopyParamAttrs.General; + break; + } + } + return Result | cpaNoPreserveTime; +} + +bool TFullSynchronizeDialog::CloseQuery() +{ + bool CanClose = TWinSCPDialog::CloseQuery(); + + if (CanClose && (GetResult() == brOK) && + SaveSettingsCheck->GetChecked() && (FOrigMode != GetMode()) && !FSaveMode) + { + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + + switch (WinSCPPlugin->MoreMessageDialog(GetMsg(SAVE_SYNCHRONIZE_MODE), nullptr, + qtConfirmation, qaYes | qaNo | qaCancel, 0)) + { + case qaYes: + FSaveMode = true; + break; + + case qaCancel: + CanClose = false; + break; + } + } + + return CanClose; +} + +LONG_PTR TFullSynchronizeDialog::DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (Msg == DN_RESIZECONSOLE) + { + AdaptSize(); + } + + return TFarDialog::DialogProc(Msg, Param1, Param2); +} + +bool TFullSynchronizeDialog::Execute(TTerminal::TSynchronizeMode & Mode, + intptr_t & Params, UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TCopyParamType * CopyParams, bool & SaveSettings, bool & SaveMode) +{ + LocalDirectoryEdit->SetText(LocalDirectory); + RemoteDirectoryEdit->SetText(RemoteDirectory); + SynchronizeRemoteButton->SetChecked((Mode == TTerminal::smRemote)); + SynchronizeLocalButton->SetChecked((Mode == TTerminal::smLocal)); + SynchronizeBothButton->SetChecked((Mode == TTerminal::smBoth)); + SynchronizeDeleteCheck->SetChecked(FLAGSET(Params, TTerminal::spDelete)); + SynchronizeExistingOnlyCheck->SetChecked(FLAGSET(Params, TTerminal::spExistingOnly)); + SynchronizePreviewChangesCheck->SetChecked(FLAGSET(Params, TTerminal::spPreviewChanges)); + SynchronizeSelectedOnlyCheck->SetChecked(FLAGSET(Params, spSelectedOnly)); + if (FLAGSET(Params, TTerminal::spTimestamp) && FLAGCLEAR(FOptions, fsoDisableTimestamp)) + { + SynchronizeTimestampsButton->SetChecked(true); + } + else if (FLAGSET(Params, TTerminal::spMirror)) + { + MirrorFilesButton->SetChecked(true); + } + else + { + SynchronizeFilesButton->SetChecked(true); + } + SynchronizeByTimeCheck->SetChecked(FLAGCLEAR(Params, TTerminal::spNotByTime)); + SynchronizeBySizeCheck->SetChecked(FLAGSET(Params, TTerminal::spBySize)); + SaveSettingsCheck->SetChecked(SaveSettings); + FSaveMode = SaveMode; + FOrigMode = Mode; + FCopyParams = *CopyParams; + + bool Result = (ShowModal() == brOK); + + if (Result) + { + RemoteDirectory = RemoteDirectoryEdit->GetText(); + LocalDirectory = LocalDirectoryEdit->GetText(); + + Mode = GetMode(); + + Params &= ~(TTerminal::spDelete | TTerminal::spNoConfirmation | + TTerminal::spExistingOnly | TTerminal::spPreviewChanges | + TTerminal::spTimestamp | TTerminal::spNotByTime | TTerminal::spBySize | + spSelectedOnly | TTerminal::spMirror); + Params |= + FLAGMASK(SynchronizeDeleteCheck->GetChecked(), TTerminal::spDelete) | + FLAGMASK(SynchronizeExistingOnlyCheck->GetChecked(), TTerminal::spExistingOnly) | + FLAGMASK(SynchronizePreviewChangesCheck->GetChecked(), TTerminal::spPreviewChanges) | + FLAGMASK(SynchronizeSelectedOnlyCheck->GetChecked(), spSelectedOnly) | + FLAGMASK(SynchronizeTimestampsButton->GetChecked() && FLAGCLEAR(FOptions, fsoDisableTimestamp), + TTerminal::spTimestamp) | + FLAGMASK(MirrorFilesButton->GetChecked(), TTerminal::spMirror) | + FLAGMASK(!SynchronizeByTimeCheck->GetChecked(), TTerminal::spNotByTime) | + FLAGMASK(SynchronizeBySizeCheck->GetChecked(), TTerminal::spBySize); + + SaveSettings = SaveSettingsCheck->GetChecked(); + SaveMode = FSaveMode; + *CopyParams = FCopyParams; + } + + return Result; +} + +bool TWinSCPFileSystem::FullSynchronizeDialog(TTerminal::TSynchronizeMode & Mode, + intptr_t & Params, UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TCopyParamType * CopyParams, bool & SaveSettings, bool & SaveMode, intptr_t Options, + const TUsableCopyParamAttrs & CopyParamAttrs) +{ + std::unique_ptr Dialog(new TFullSynchronizeDialog( + FPlugin, Options, CopyParamAttrs)); + bool Result = Dialog->Execute(Mode, Params, LocalDirectory, RemoteDirectory, + CopyParams, SaveSettings, SaveMode); + return Result; +} + +class TSynchronizeChecklistDialog : public TWinSCPDialog +{ +public: + explicit TSynchronizeChecklistDialog( + TCustomFarPlugin * AFarPlugin, TTerminal::TSynchronizeMode Mode, intptr_t Params, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory); + + virtual bool Execute(TSynchronizeChecklist * Checklist); + +protected: + virtual LONG_PTR DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + void CheckAllButtonClick(TFarButton * Sender, bool & Close); + void VideoModeButtonClick(TFarButton * Sender, bool & Close); + void ListBoxClick(TFarDialogItem * Item, MOUSE_EVENT_RECORD * Event); + +private: + TFarText * Header; + TFarListBox * ListBox; + TFarButton * CheckAllButton; + TFarButton * UncheckAllButton; + TFarButton * VideoModeButton; + + TSynchronizeChecklist * FChecklist; + UnicodeString FLocalDirectory; + UnicodeString FRemoteDirectory; + static const int FColumns = 8; + int FWidths[FColumns]; + UnicodeString FActions[TSynchronizeChecklist::ActionCount]; + intptr_t FScroll; + bool FCanScrollRight; + intptr_t FChecked; + + void AdaptSize(); + int ColumnWidth(intptr_t Index); + void LoadChecklist(); + void RefreshChecklist(bool Scroll); + void UpdateControls(); + void CheckAll(bool Check); + UnicodeString ItemLine(const TChecklistItem * ChecklistItem); + void AddColumn(UnicodeString & List, const UnicodeString & Value, size_t Column, + bool AHeader = false); + UnicodeString FormatSize(int64_t Size, int Column); +}; + +TSynchronizeChecklistDialog::TSynchronizeChecklistDialog( + TCustomFarPlugin * AFarPlugin, TTerminal::TSynchronizeMode /*Mode*/, intptr_t /*Params*/, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory) : + TWinSCPDialog(AFarPlugin), + FChecklist(nullptr), + FLocalDirectory(LocalDirectory), + FRemoteDirectory(RemoteDirectory), + FScroll(0), + FCanScrollRight(false) +{ + SetCaption(GetMsg(CHECKLIST_TITLE)); + + Header = new TFarText(this); + + ListBox = new TFarListBox(this); + ListBox->SetNoBox(true); + // align list with bottom of the window + ListBox->SetBottom(-5); + ListBox->SetOnMouseClick(MAKE_CALLBACK(TSynchronizeChecklistDialog::ListBoxClick, this)); + + UnicodeString Actions = GetMsg(CHECKLIST_ACTIONS); + size_t Action = 0; + while (!Actions.IsEmpty() && (Action < _countof(FActions))) + { + FActions[Action] = CutToChar(Actions, '|', false); + Action++; + } + + // align buttons with bottom of the window + ButtonSeparator = new TFarSeparator(this); + ButtonSeparator->SetTop(-4); + ButtonSeparator->SetBottom(ButtonSeparator->GetTop()); + + CheckAllButton = new TFarButton(this); + CheckAllButton->SetCaption(GetMsg(CHECKLIST_CHECK_ALL)); + CheckAllButton->SetCenterGroup(true); + CheckAllButton->SetOnClick(MAKE_CALLBACK(TSynchronizeChecklistDialog::CheckAllButtonClick, this)); + + SetNextItemPosition(ipRight); + + UncheckAllButton = new TFarButton(this); + UncheckAllButton->SetCaption(GetMsg(CHECKLIST_UNCHECK_ALL)); + UncheckAllButton->SetCenterGroup(true); + UncheckAllButton->SetOnClick(MAKE_CALLBACK(TSynchronizeChecklistDialog::CheckAllButtonClick, this)); + + VideoModeButton = new TFarButton(this); + VideoModeButton->SetCenterGroup(true); + VideoModeButton->SetOnClick(MAKE_CALLBACK(TSynchronizeChecklistDialog::VideoModeButtonClick, this)); + + AddStandardButtons(0, true); + + AdaptSize(); + UpdateControls(); + ListBox->SetFocus(); +} + +void TSynchronizeChecklistDialog::AddColumn(UnicodeString & List, + const UnicodeString & Value, size_t Column, bool AHeader) +{ + wchar_t Separator = L'|'; // '\xB3'; + intptr_t Len = Value.Length(); + intptr_t Width = static_cast(FWidths[Column]); + bool Right = (Column == 2) || (Column == 3) || (Column == 6) || (Column == 7); + bool LastCol = (Column == FColumns - 1); + if (Len <= Width) + { + intptr_t Added = 0; + if (AHeader && (Len < Width)) + { + Added += (Width - Len) / 2; + } + else if (Right && (Len < Width)) + { + Added += Width - Len; + } + List += ::StringOfChar(L' ', Added) + Value; + Added += Value.Length(); + if (Width > Added) + { + List += ::StringOfChar(' ', Width - Added); + } + if (!LastCol) + { + List += Separator; + } + } + else + { + intptr_t Scroll = FScroll; + if ((Scroll > 0) && !AHeader) + { + if (List.IsEmpty()) + { + List += L'{'; + Width--; + Scroll++; + } + else + { + List[List.Length()] = L'{'; + } + } + if (Scroll > Len - Width) + { + Scroll = Len - Width; + } + else if (!AHeader && LastCol && (Scroll < Len - Width)) + { + Width--; + } + List += Value.SubString(Scroll + 1, Width); + if (!Header && (Len - Scroll > Width)) + { + List += L'}'; + FCanScrollRight = true; + } + else if (!LastCol) + { + List += Separator; + } + } +} + +void TSynchronizeChecklistDialog::AdaptSize() +{ + FScroll = 0; + SetSize(GetMaxSize()); +#ifndef __linux__ + VideoModeButton->SetCaption(GetMsg( + FarPlugin->ConsoleWindowState() == SW_SHOWMAXIMIZED ? + CHECKLIST_RESTORE : CHECKLIST_MAXIMIZE)); +#endif + static const int Ratio[FColumns] = { 140, 100, 80, 150, -2, 100, 80, 150 }; + + intptr_t Width = ListBox->GetWidth() - 2 /*checkbox*/ - 1 /*scrollbar*/ - FColumns; + double Temp[FColumns]; + + int TotalRatio = 0; + int FixedRatio = 0; + for (intptr_t Index = 0; Index < FColumns; ++Index) + { + if (Ratio[Index] >= 0) + { + TotalRatio += Ratio[Index]; + } + else + { + FixedRatio += -Ratio[Index]; + } + } + + intptr_t TotalAssigned = 0; + for (intptr_t Index = 0; Index < FColumns; ++Index) + { + if (Ratio[Index] >= 0) + { + double W = ToDouble(Ratio[Index]) * (Width - FixedRatio) / TotalRatio; + FWidths[Index] = static_cast(floor(W)); + Temp[Index] = W - FWidths[Index]; + } + else + { + FWidths[Index] = -Ratio[Index]; + Temp[Index] = 0; + } + TotalAssigned += FWidths[Index]; + } + + while (TotalAssigned < Width) + { + size_t GrowIndex = 0; + double MaxMissing = 0.0; + for (intptr_t Index = 0; Index < FColumns; ++Index) + { + if (MaxMissing < Temp[Index]) + { + MaxMissing = Temp[Index]; + GrowIndex = Index; + } + } + + DebugAssert(MaxMissing > 0.0); + + FWidths[GrowIndex]++; + Temp[GrowIndex] = 0.0; + TotalAssigned++; + } + + RefreshChecklist(false); +} + +UnicodeString TSynchronizeChecklistDialog::FormatSize( + int64_t Size, int Column) +{ + intptr_t Width = static_cast(FWidths[Column]); + UnicodeString Result = FORMAT(L"%lu", Size); + + if (Result.Length() > Width) + { + Result = FORMAT(L"%.2f 'K'", Size / 1024.0); + if (Result.Length() > Width) + { + Result = FORMAT(L"%.2f 'M'", Size / (1024.0 * 1024)); + if (Result.Length() > Width) + { + Result = FORMAT(L"%.2f 'G'", Size / (1024.0 * 1024 * 1024)); + if (Result.Length() > Width) + { + // back to default + Result = FORMAT(L"%lu", Size); + } + } + } + } + + return Result; +} + +UnicodeString TSynchronizeChecklistDialog::ItemLine(const TChecklistItem * ChecklistItem) +{ + UnicodeString Line; + UnicodeString S; + + S = ChecklistItem->GetFileName(); + if (ChecklistItem->IsDirectory) + { + S = ::IncludeTrailingBackslash(S); + } + AddColumn(Line, S, 0); + + if (ChecklistItem->Action == saDeleteRemote) + { + AddColumn(Line, L"", 1); + AddColumn(Line, L"", 2); + AddColumn(Line, L"", 3); + } + else + { + S = ChecklistItem->Local.Directory; + if (::AnsiSameText(FLocalDirectory, S.SubString(1, FLocalDirectory.Length()))) + { + S[1] = L'.'; + S.Delete(2, FLocalDirectory.Length() - 1); + } + else + { + DebugAssert(false); + } + AddColumn(Line, S, 1); + if (ChecklistItem->Action == saDownloadNew) + { + AddColumn(Line, L"", 2); + AddColumn(Line, L"", 3); + } + else + { + if (ChecklistItem->IsDirectory) + { + AddColumn(Line, L"", 2); + } + else + { + AddColumn(Line, FormatSize(ChecklistItem->Local.Size, 2), 2); + } + AddColumn(Line, core::UserModificationStr(ChecklistItem->Local.Modification, + ChecklistItem->Local.ModificationFmt), 3); + } + } + + intptr_t Action = static_cast(ChecklistItem->Action - 1); + DebugAssert((Action != NPOS) && (Action < static_cast(_countof(FActions)))); + AddColumn(Line, FActions[Action], 4); + + if (ChecklistItem->Action == saDeleteLocal) + { + AddColumn(Line, L"", 5); + AddColumn(Line, L"", 6); + AddColumn(Line, L"", 7); + } + else + { + S = ChecklistItem->Remote.Directory; + if (::AnsiSameText(FRemoteDirectory, S.SubString(1, FRemoteDirectory.Length()))) + { + S[1] = L'.'; + S.Delete(2, FRemoteDirectory.Length() - 1); + } + else + { + DebugAssert(false); + } + AddColumn(Line, S, 5); + if (ChecklistItem->Action == saUploadNew) + { + AddColumn(Line, L"", 6); + AddColumn(Line, L"", 7); + } + else + { + if (ChecklistItem->IsDirectory) + { + AddColumn(Line, L"", 6); + } + else + { + AddColumn(Line, FormatSize(ChecklistItem->Remote.Size, 6), 6); + } + AddColumn(Line, core::UserModificationStr(ChecklistItem->Remote.Modification, + ChecklistItem->Remote.ModificationFmt), 7); + } + } + + return Line; +} + +void TSynchronizeChecklistDialog::LoadChecklist() +{ + FChecked = 0; + std::unique_ptr List(new TFarList()); + List->BeginUpdate(); + for (intptr_t Index = 0; Index < FChecklist->GetCount(); ++Index) + { + const TChecklistItem * ChecklistItem = FChecklist->GetItem(Index); + + List->AddObject(ItemLine(ChecklistItem), + const_cast(reinterpret_cast(ChecklistItem))); + } + List->EndUpdate(); + + // items must be checked in second pass once the internal array is allocated + for (intptr_t Index = 0; Index < FChecklist->GetCount(); ++Index) + { + const TChecklistItem * ChecklistItem = FChecklist->GetItem(Index); + + List->SetChecked(Index, ChecklistItem->Checked); + if (ChecklistItem->Checked) + { + FChecked++; + } + } + + ListBox->SetItems(List.get()); + + UpdateControls(); +} + +void TSynchronizeChecklistDialog::RefreshChecklist(bool Scroll) +{ + UnicodeString HeaderStr = GetMsg(CHECKLIST_HEADER); + UnicodeString HeaderCaption(::StringOfChar(' ', 2)); + + for (intptr_t Index = 0; Index < FColumns; ++Index) + { + AddColumn(HeaderCaption, CutToChar(HeaderStr, '|', false), Index, true); + } + Header->SetCaption(HeaderCaption); + + FCanScrollRight = false; + TFarList * List = ListBox->GetItems(); + List->BeginUpdate(); + { + SCOPE_EXIT + { + List->EndUpdate(); + }; + for (intptr_t Index = 0; Index < List->GetCount(); ++Index) + { + if (!Scroll || (List->GetString(Index).LastDelimiter(L"{}") > 0)) + { + const TChecklistItem * ChecklistItem = + NB_STATIC_DOWNCAST(TChecklistItem, List->GetObj(Index)); + + List->SetString(Index, ItemLine(ChecklistItem)); + } + } + } +} + +void TSynchronizeChecklistDialog::UpdateControls() +{ + ButtonSeparator->SetCaption( + FORMAT(GetMsg(CHECKLIST_CHECKED).c_str(), FChecked, ListBox->GetItems()->GetCount())); + CheckAllButton->SetEnabled(FChecked < ListBox->GetItems()->GetCount()); + UncheckAllButton->SetEnabled(FChecked > 0); +} + +LONG_PTR TSynchronizeChecklistDialog::DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (Msg == DN_RESIZECONSOLE) + { + AdaptSize(); + } + + return TFarDialog::DialogProc(Msg, Param1, Param2); +} + +void TSynchronizeChecklistDialog::CheckAll(bool Check) +{ + TFarList * List = ListBox->GetItems(); + List->BeginUpdate(); + { + SCOPE_EXIT + { + List->EndUpdate(); + }; + intptr_t Count = List->GetCount(); + for (intptr_t Index = 0; Index < Count; ++Index) + { + List->SetChecked(Index, Check); + } + + FChecked = Check ? Count : 0; + } + + UpdateControls(); +} + +void TSynchronizeChecklistDialog::CheckAllButtonClick( + TFarButton * Sender, bool & Close) +{ + CheckAll(Sender == CheckAllButton); + ListBox->SetFocus(); + + Close = false; +} + +void TSynchronizeChecklistDialog::VideoModeButtonClick( + TFarButton * /*Sender*/, bool & Close) +{ + FarPlugin->ToggleVideoMode(); + + Close = false; +} + +void TSynchronizeChecklistDialog::ListBoxClick( + TFarDialogItem * /*Item*/, MOUSE_EVENT_RECORD * /*Event*/) +{ + intptr_t Index = ListBox->GetItems()->GetSelected(); + if (Index >= 0) + { + if (ListBox->GetItems()->GetChecked(Index)) + { + ListBox->GetItems()->SetChecked(Index, false); + FChecked--; + } + else if (!ListBox->GetItems()->GetChecked(Index)) + { + ListBox->GetItems()->SetChecked(Index, true); + FChecked++; + } + + UpdateControls(); + } +} + +bool TSynchronizeChecklistDialog::Key(TFarDialogItem * Item, LONG_PTR KeyCode) +{ + bool Result = false; + if (ListBox->Focused()) + { + if ((KeyCode == KEY_SHIFTADD) || (KeyCode == KEY_SHIFTSUBTRACT)) + { + CheckAll(KeyCode == KEY_SHIFTADD); + Result = true; + } + else if ((KeyCode == KEY_SPACE) || (KeyCode == KEY_INS) || + (KeyCode == KEY_ADD) || (KeyCode == KEY_SUBTRACT)) + { + intptr_t Index = ListBox->GetItems()->GetSelected(); + if (Index >= 0) + { + if (ListBox->GetItems()->GetChecked(Index) && (KeyCode != KEY_ADD)) + { + ListBox->GetItems()->SetChecked(Index, false); + FChecked--; + } + else if (!ListBox->GetItems()->GetChecked(Index) && (KeyCode != KEY_SUBTRACT)) + { + ListBox->GetItems()->SetChecked(Index, true); + FChecked++; + } + + // FAR WORKAROUND + // Changing "checked" state is not always drawn. + Redraw(); + UpdateControls(); + if ((KeyCode == KEY_INS) && + (Index < ListBox->GetItems()->GetCount() - 1)) + { + ListBox->GetItems()->SetSelected(Index + 1); + } + else + { + ListBox->GetItems()->SetSelected(Index); + } + } + Result = true; + } + else if (KeyCode == KEY_ALTLEFT) + { + if (FScroll > 0) + { + FScroll--; + RefreshChecklist(true); + } + Result = true; + } + else if (KeyCode == KEY_ALTRIGHT) + { + if (FCanScrollRight) + { + FScroll++; + RefreshChecklist(true); + } + Result = true; + } + } + + if (!Result) + { + Result = TWinSCPDialog::Key(Item, KeyCode); + } + + return Result; +} + +bool TSynchronizeChecklistDialog::Execute(TSynchronizeChecklist * Checklist) +{ + FChecklist = Checklist; + LoadChecklist(); + bool Result = (ShowModal() == brOK); + + if (Result) + { + TFarList * List = ListBox->GetItems(); + intptr_t Count = List->GetCount(); + for (intptr_t Index = 0; Index < Count; ++Index) + { + TChecklistItem * ChecklistItem = + NB_STATIC_DOWNCAST(TChecklistItem, List->GetObj(Index)); + ChecklistItem->Checked = List->GetChecked(Index); + } + } + + return Result; +} + +bool TWinSCPFileSystem::SynchronizeChecklistDialog( + TSynchronizeChecklist * Checklist, TTerminal::TSynchronizeMode Mode, intptr_t Params, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory) +{ + std::unique_ptr Dialog(new TSynchronizeChecklistDialog( + FPlugin, Mode, Params, LocalDirectory, RemoteDirectory)); + bool Result = Dialog->Execute(Checklist); + return Result; +} + +class TSynchronizeDialog : TFarDialog +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + explicit TSynchronizeDialog(TCustomFarPlugin * AFarPlugin, + TSynchronizeStartStopEvent OnStartStop, + intptr_t Options, intptr_t CopyParamAttrs, TGetSynchronizeOptionsEvent OnGetOptions); + virtual ~TSynchronizeDialog(); + + bool Execute(TSynchronizeParamType & Params, + const TCopyParamType * CopyParams, bool & SaveSettings); + +protected: + virtual void Change(); + void UpdateControls(); + void StartButtonClick(TFarButton * Sender, bool & Close); + void StopButtonClick(TFarButton * Sender, bool & Close); + void TransferSettingsButtonClick(TFarButton * Sender, bool & Close); + void CopyParamListerClick(TFarDialogItem * Item, MOUSE_EVENT_RECORD * Event); + void Stop(); + void DoStartStop(bool Start, bool Synchronize); + TSynchronizeParamType GetParams(); + void DoAbort(TObject * Sender, bool Close); + void DoLog(TSynchronizeController * Controller, + TSynchronizeLogEntry Entry, const UnicodeString & Message); + void DoSynchronizeThreads(TObject * Sender, TThreadMethod Slot); + virtual LONG_PTR DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2); + virtual bool CloseQuery(); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + TCopyParamType GetCopyParams(); + intptr_t ActualCopyParamAttrs(); + void CustomCopyParam(); + +private: + bool FSynchronizing; + bool FStarted; + bool FAbort; + bool FClose; + TSynchronizeParamType FParams; + TSynchronizeStartStopEvent FOnStartStop; + intptr_t FOptions; + TSynchronizeOptions * FSynchronizeOptions; + TCopyParamType FCopyParams; + TGetSynchronizeOptionsEvent FOnGetOptions; + intptr_t FCopyParamAttrs; + + TFarEdit * LocalDirectoryEdit; + TFarEdit * RemoteDirectoryEdit; + TFarCheckBox * SynchronizeDeleteCheck; + TFarCheckBox * SynchronizeExistingOnlyCheck; + TFarCheckBox * SynchronizeSelectedOnlyCheck; + TFarCheckBox * SynchronizeRecursiveCheck; + TFarCheckBox * SynchronizeSynchronizeCheck; + TFarCheckBox * SaveSettingsCheck; + TFarButton * StartButton; + TFarButton * StopButton; + TFarButton * CloseButton; + TFarLister * CopyParamLister; +}; + +TSynchronizeDialog::TSynchronizeDialog(TCustomFarPlugin * AFarPlugin, + TSynchronizeStartStopEvent OnStartStop, + intptr_t Options, intptr_t CopyParamAttrs, TGetSynchronizeOptionsEvent OnGetOptions) : + TFarDialog(AFarPlugin) +{ + TFarText * Text; + TFarSeparator * Separator; + + FSynchronizing = false; + FStarted = false; + FOnStartStop = OnStartStop; + FAbort = false; + FClose = false; + FOptions = Options; + FOnGetOptions = OnGetOptions; + FSynchronizeOptions = nullptr; + FCopyParamAttrs = CopyParamAttrs; + + SetSize(TPoint(76, 20)); + + SetDefaultGroup(1); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(SYNCHRONIZE_LOCAL_LABEL)); + + LocalDirectoryEdit = new TFarEdit(this); + LocalDirectoryEdit->SetHistory(LOCAL_SYNC_HISTORY); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(SYNCHRONIZE_REMOTE_LABEL)); + + RemoteDirectoryEdit = new TFarEdit(this); + RemoteDirectoryEdit->SetHistory(REMOTE_SYNC_HISTORY); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(SYNCHRONIZE_GROUP)); + Separator->SetGroup(0); + + SynchronizeDeleteCheck = new TFarCheckBox(this); + SynchronizeDeleteCheck->SetCaption(GetMsg(SYNCHRONIZE_DELETE)); + + SetNextItemPosition(ipRight); + + SynchronizeExistingOnlyCheck = new TFarCheckBox(this); + SynchronizeExistingOnlyCheck->SetCaption(GetMsg(SYNCHRONIZE_EXISTING_ONLY)); + + SetNextItemPosition(ipNewLine); + + SynchronizeRecursiveCheck = new TFarCheckBox(this); + SynchronizeRecursiveCheck->SetCaption(GetMsg(SYNCHRONIZE_RECURSIVE)); + + SetNextItemPosition(ipRight); + + SynchronizeSelectedOnlyCheck = new TFarCheckBox(this); + SynchronizeSelectedOnlyCheck->SetCaption(GetMsg(SYNCHRONIZE_SELECTED_ONLY)); + // have more complex enable rules + SynchronizeSelectedOnlyCheck->SetGroup(0); + + SetNextItemPosition(ipNewLine); + + SynchronizeSynchronizeCheck = new TFarCheckBox(this); + SynchronizeSynchronizeCheck->SetCaption(GetMsg(SYNCHRONIZE_SYNCHRONIZE)); + SynchronizeSynchronizeCheck->SetAllowGrayed(true); + + Separator = new TFarSeparator(this); + Separator->SetGroup(0); + + SaveSettingsCheck = new TFarCheckBox(this); + SaveSettingsCheck->SetCaption(GetMsg(SYNCHRONIZE_REUSE_SETTINGS)); + + Separator = new TFarSeparator(this); + Separator->SetCaption(GetMsg(COPY_PARAM_GROUP)); + + CopyParamLister = new TFarLister(this); + CopyParamLister->SetHeight(3); + CopyParamLister->SetLeft(GetBorderBox()->GetLeft() + 1); + CopyParamLister->SetTabStop(false); + CopyParamLister->SetOnMouseClick(MAKE_CALLBACK(TSynchronizeDialog::CopyParamListerClick, this)); + // Right edge is adjusted in Change + + SetDefaultGroup(0); + + // align buttons with bottom of the window + Separator = new TFarSeparator(this); + Separator->SetPosition(-4); + + TFarButton * Button = new TFarButton(this); + Button->SetCaption(GetMsg(TRANSFER_SETTINGS_BUTTON)); + Button->SetResult(-1); + Button->SetCenterGroup(true); + Button->SetOnClick(MAKE_CALLBACK(TSynchronizeDialog::TransferSettingsButtonClick, this)); + + SetNextItemPosition(ipRight); + + StartButton = new TFarButton(this); + StartButton->SetCaption(GetMsg(SYNCHRONIZE_START_BUTTON)); + StartButton->SetDefault(true); + StartButton->SetCenterGroup(true); + StartButton->SetOnClick(MAKE_CALLBACK(TSynchronizeDialog::StartButtonClick, this)); + + StopButton = new TFarButton(this); + StopButton->SetCaption(GetMsg(SYNCHRONIZE_STOP_BUTTON)); + StopButton->SetCenterGroup(true); + StopButton->SetOnClick(MAKE_CALLBACK(TSynchronizeDialog::StopButtonClick, this)); + + SetNextItemPosition(ipRight); + + CloseButton = new TFarButton(this); + CloseButton->SetCaption(GetMsg(MSG_BUTTON_CLOSE)); + CloseButton->SetResult(brCancel); + CloseButton->SetCenterGroup(true); +} + +TSynchronizeDialog::~TSynchronizeDialog() +{ + SAFE_DESTROY(FSynchronizeOptions); +} + +void TSynchronizeDialog::TransferSettingsButtonClick( + TFarButton * /*Sender*/, bool & Close) +{ + CustomCopyParam(); + Close = false; +} + +void TSynchronizeDialog::CopyParamListerClick( + TFarDialogItem * /*Item*/, MOUSE_EVENT_RECORD * Event) +{ + if (FLAGSET(Event->dwEventFlags, DOUBLE_CLICK)) + { + CustomCopyParam(); + } +} + +void TSynchronizeDialog::CustomCopyParam() +{ + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + // PreserveTime is forced for some settings, but avoid hard-setting it until + // user really confirms it on custom dialog + TCopyParamType ACopyParams = GetCopyParams(); + if (WinSCPPlugin->CopyParamCustomDialog(ACopyParams, ActualCopyParamAttrs())) + { + FCopyParams = ACopyParams; + Change(); + } +} + +bool TSynchronizeDialog::Execute(TSynchronizeParamType & Params, + const TCopyParamType * CopyParams, bool & SaveSettings) +{ + RemoteDirectoryEdit->SetText(Params.RemoteDirectory); + LocalDirectoryEdit->SetText(Params.LocalDirectory); + SynchronizeDeleteCheck->SetChecked(FLAGSET(Params.Params, TTerminal::spDelete)); + SynchronizeExistingOnlyCheck->SetChecked(FLAGSET(Params.Params, TTerminal::spExistingOnly)); + SynchronizeSelectedOnlyCheck->SetChecked(FLAGSET(Params.Params, spSelectedOnly)); + SynchronizeRecursiveCheck->SetChecked(FLAGSET(Params.Options, soRecurse)); + SynchronizeSynchronizeCheck->SetSelected( + FLAGSET(Params.Options, soSynchronizeAsk) ? BSTATE_3STATE : + (FLAGSET(Params.Options, soSynchronize) ? BSTATE_CHECKED : BSTATE_UNCHECKED)); + SaveSettingsCheck->SetChecked(SaveSettings); + + FParams = Params; + FCopyParams = *CopyParams; + + ShowModal(); + + Params = GetParams(); + SaveSettings = SaveSettingsCheck->GetChecked(); + + return true; +} + +TSynchronizeParamType TSynchronizeDialog::GetParams() +{ + TSynchronizeParamType Result = FParams; + Result.RemoteDirectory = RemoteDirectoryEdit->GetText(); + Result.LocalDirectory = LocalDirectoryEdit->GetText(); + Result.Params = + (Result.Params & ~(TTerminal::spDelete | TTerminal::spExistingOnly | + spSelectedOnly | TTerminal::spTimestamp)) | + FLAGMASK(SynchronizeDeleteCheck->GetChecked(), TTerminal::spDelete) | + FLAGMASK(SynchronizeExistingOnlyCheck->GetChecked(), TTerminal::spExistingOnly) | + FLAGMASK(SynchronizeSelectedOnlyCheck->GetChecked(), spSelectedOnly); + Result.Options = + (Result.Options & ~(soRecurse | soSynchronize | soSynchronizeAsk)) | + FLAGMASK(SynchronizeRecursiveCheck->GetChecked(), soRecurse) | + FLAGMASK(SynchronizeSynchronizeCheck->GetSelected() == BSTATE_CHECKED, soSynchronize) | + FLAGMASK(SynchronizeSynchronizeCheck->GetSelected() == BSTATE_3STATE, soSynchronizeAsk); + return Result; +} + +void TSynchronizeDialog::DoStartStop(bool Start, bool Synchronize) +{ + if (FOnStartStop) + { + TSynchronizeParamType SParams = GetParams(); + SParams.Options = + (SParams.Options & ~(soSynchronize | soSynchronizeAsk)) | + FLAGMASK(Synchronize, soSynchronize); + if (Start) + { + SAFE_DESTROY(FSynchronizeOptions); + FSynchronizeOptions = new TSynchronizeOptions; + FOnGetOptions(SParams.Params, *FSynchronizeOptions); + } + FOnStartStop(this, Start, SParams, GetCopyParams(), FSynchronizeOptions, + MAKE_CALLBACK(TSynchronizeDialog::DoAbort, this), + MAKE_CALLBACK(TSynchronizeDialog::DoSynchronizeThreads, this), + MAKE_CALLBACK(TSynchronizeDialog::DoLog, this)); + } +} + +void TSynchronizeDialog::DoSynchronizeThreads(TObject * /*Sender*/, + TThreadMethod Slot) +{ + if (FStarted) + { + Synchronize(Slot); + } +} + +LONG_PTR TSynchronizeDialog::DialogProc(int Msg, intptr_t Param1, LONG_PTR Param2) +{ + if (FAbort) + { + FAbort = false; + + if (FSynchronizing) + { + Stop(); + } + + if (FClose) + { + DebugAssert(CloseButton->GetEnabled()); + Close(CloseButton); + } + } + + return TFarDialog::DialogProc(Msg, Param1, Param2); +} + +bool TSynchronizeDialog::CloseQuery() +{ + return TFarDialog::CloseQuery() && !FSynchronizing; +} + +void TSynchronizeDialog::DoAbort(TObject * /*Sender*/, bool Close) +{ + FAbort = true; + FClose = Close; +} + +void TSynchronizeDialog::DoLog(TSynchronizeController * /*Controller*/, + TSynchronizeLogEntry /*Entry*/, const UnicodeString & /*Message*/) +{ + // void +} + +void TSynchronizeDialog::StartButtonClick(TFarButton * /*Sender*/, + bool & /*Close*/) +{ + bool Synchronize = false; + bool Continue = true; + if (SynchronizeSynchronizeCheck->GetSelected() == BSTATE_3STATE) + { + TMessageParams Params; + Params.Params = qpNeverAskAgainCheck; + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + switch (WinSCPPlugin->MoreMessageDialog(GetMsg(SYNCHRONISE_BEFORE_KEEPUPTODATE), + nullptr, qtConfirmation, qaYes | qaNo | qaCancel, &Params)) + { + case qaNeverAskAgain: + SynchronizeSynchronizeCheck->SetSelected(BSTATE_CHECKED); + // fall thru + + case qaYes: + Synchronize = true; + break; + + case qaNo: + Synchronize = false; + break; + + default: + case qaCancel: + Continue = false; + break; + } + } + else + { + Synchronize = SynchronizeSynchronizeCheck->GetChecked(); + } + + if (Continue) + { + DebugAssert(!FSynchronizing); + + FSynchronizing = true; + try + { + UpdateControls(); + + DoStartStop(true, Synchronize); + + StopButton->SetFocus(); + FStarted = true; + } + catch (Exception & E) + { + FSynchronizing = false; + UpdateControls(); + + FarPlugin->HandleException(&E); + } + } +} + +void TSynchronizeDialog::StopButtonClick(TFarButton * /*Sender*/, + bool & /*Close*/) +{ + Stop(); +} + +void TSynchronizeDialog::Stop() +{ + FSynchronizing = false; + FStarted = false; + BreakSynchronize(); + DoStartStop(false, false); + UpdateControls(); + StartButton->SetFocus(); +} + +void TSynchronizeDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle() && !ChangesLocked()) + { + UpdateControls(); + + UnicodeString InfoStr = FCopyParams.GetInfoStr(L"; ", ActualCopyParamAttrs()); + std::unique_ptr InfoStrLines(new TStringList()); + FarWrapText(InfoStr, InfoStrLines.get(), GetBorderBox()->GetWidth() - 4); + CopyParamLister->SetItems(InfoStrLines.get()); + CopyParamLister->SetRight(GetBorderBox()->GetRight() - (CopyParamLister->GetScrollBar() ? 0 : 1)); + } +} + +bool TSynchronizeDialog::Key(TFarDialogItem * /*Item*/, LONG_PTR KeyCode) +{ + bool Result = false; + if ((KeyCode == KEY_ESC) && FSynchronizing) + { + Stop(); + Result = true; + } + return Result; +} + +void TSynchronizeDialog::UpdateControls() +{ + SetCaption(GetMsg(FSynchronizing ? SYNCHRONIZE_SYCHRONIZING : SYNCHRONIZE_TITLE)); + StartButton->SetEnabled(!FSynchronizing); + StopButton->SetEnabled(FSynchronizing); + CloseButton->SetEnabled(!FSynchronizing); + EnableGroup(1, !FSynchronizing); + SynchronizeSelectedOnlyCheck->SetEnabled( + !FSynchronizing && FLAGSET(FOptions, soAllowSelectedOnly)); +} + +TCopyParamType TSynchronizeDialog::GetCopyParams() +{ + TCopyParamType Result = FCopyParams; + Result.SetPreserveTime(true); + return Result; +} + +intptr_t TSynchronizeDialog::ActualCopyParamAttrs() +{ + return FCopyParamAttrs | cpaNoPreserveTime; +} + +bool TWinSCPFileSystem::SynchronizeDialog(TSynchronizeParamType & Params, + const TCopyParamType * CopyParams, TSynchronizeStartStopEvent OnStartStop, + bool & SaveSettings, intptr_t Options, intptr_t CopyParamAttrs, TGetSynchronizeOptionsEvent OnGetOptions) +{ + std::unique_ptr Dialog(new TSynchronizeDialog(FPlugin, OnStartStop, + Options, CopyParamAttrs, OnGetOptions)); + bool Result = Dialog->Execute(Params, CopyParams, SaveSettings); + return Result; +} + +bool TWinSCPFileSystem::RemoteTransferDialog(TStrings * AFileList, + UnicodeString & Target, UnicodeString & FileMask, bool Move) +{ + UnicodeString Prompt = FileNameFormatString( + GetMsg(Move ? REMOTE_MOVE_FILE : REMOTE_COPY_FILE), + GetMsg(Move ? REMOTE_MOVE_FILES : REMOTE_COPY_FILES), AFileList, true); + + UnicodeString Value = core::UnixIncludeTrailingBackslash(Target) + FileMask; + bool Result = FPlugin->InputBox( + GetMsg(Move ? REMOTE_MOVE_TITLE : REMOTE_COPY_TITLE), Prompt, + Value, 0, MOVE_TO_HISTORY) && !Value.IsEmpty(); + if (Result) + { + Target = core::UnixExtractFilePath(Value); + FileMask = base::UnixExtractFileName(Value); + } + return Result; +} + +bool TWinSCPFileSystem::RenameFileDialog(TRemoteFile * AFile, + UnicodeString & NewName) +{ + return FPlugin->InputBox(GetMsg(RENAME_FILE_TITLE).c_str(), + FORMAT(GetMsg(RENAME_FILE).c_str(), AFile->GetFileName().c_str()), NewName, 0) && + !NewName.IsEmpty(); +} + +class TQueueDialog : TFarDialog +{ +CUSTOM_MEM_ALLOCATION_IMPL +NB_DISABLE_COPY(TQueueDialog) +public: + explicit TQueueDialog(TCustomFarPlugin * AFarPlugin, + TWinSCPFileSystem * AFileSystem, bool ClosingPlugin); + virtual ~TQueueDialog() {} + bool Execute(TTerminalQueueStatus * Status); + +protected: + virtual void Change(); + virtual void Idle(); + bool UpdateQueue(); + void LoadQueue(); + void RefreshQueue(); + bool FillQueueItemLine(UnicodeString & Line, + TQueueItemProxy * QueueItem, size_t Index); + bool QueueItemNeedsFrequentRefresh(TQueueItemProxy * QueueItem); + void UpdateControls(); + virtual bool Key(TFarDialogItem * Item, LONG_PTR KeyCode); + virtual bool CloseQuery(); + +private: + void OperationButtonClick(TFarButton * Sender, bool & Close); + +private: + TTerminalQueueStatus * FStatus; + TWinSCPFileSystem * FFileSystem; + bool FClosingPlugin; + + TFarListBox * QueueListBox; + TFarButton * ShowButton; + TFarButton * ExecuteButton; + TFarButton * DeleteButton; + TFarButton * MoveUpButton; + TFarButton * MoveDownButton; + TFarButton * CloseButton; +}; + +TQueueDialog::TQueueDialog(TCustomFarPlugin * AFarPlugin, + TWinSCPFileSystem * AFileSystem, bool ClosingPlugin) : + TFarDialog(AFarPlugin), + FStatus(nullptr), + FFileSystem(AFileSystem), + FClosingPlugin(ClosingPlugin), + QueueListBox(nullptr), + ShowButton(nullptr), + ExecuteButton(nullptr), + DeleteButton(nullptr), + MoveUpButton(nullptr), + MoveDownButton(nullptr), + CloseButton(nullptr) +{ + TFarSeparator * Separator = nullptr; + TFarText * Text = nullptr; + + SetSize(TPoint(80, 23)); + // TRect CRect = GetClientRect(); + intptr_t ListTop; + intptr_t ListHeight = GetClientSize().y - 4; + + SetCaption(GetMsg(QUEUE_TITLE)); + + Text = new TFarText(this); + Text->SetCaption(GetMsg(QUEUE_HEADER)); + + Separator = new TFarSeparator(this); + ListTop = Separator->GetBottom(); + + Separator = new TFarSeparator(this); + Separator->Move(0, ListHeight); + + ExecuteButton = new TFarButton(this); + ExecuteButton->SetCaption(GetMsg(QUEUE_EXECUTE)); + ExecuteButton->SetOnClick(MAKE_CALLBACK(TQueueDialog::OperationButtonClick, this)); + ExecuteButton->SetCenterGroup(true); + + SetNextItemPosition(ipRight); + + DeleteButton = new TFarButton(this); + DeleteButton->SetCaption(GetMsg(QUEUE_DELETE)); + DeleteButton->SetOnClick(MAKE_CALLBACK(TQueueDialog::OperationButtonClick, this)); + DeleteButton->SetCenterGroup(true); + + MoveUpButton = new TFarButton(this); + MoveUpButton->SetCaption(GetMsg(QUEUE_MOVE_UP)); + MoveUpButton->SetOnClick(MAKE_CALLBACK(TQueueDialog::OperationButtonClick, this)); + MoveUpButton->SetCenterGroup(true); + + MoveDownButton = new TFarButton(this); + MoveDownButton->SetCaption(GetMsg(QUEUE_MOVE_DOWN)); + MoveDownButton->SetOnClick(MAKE_CALLBACK(TQueueDialog::OperationButtonClick, this)); + MoveDownButton->SetCenterGroup(true); + + CloseButton = new TFarButton(this); + CloseButton->SetCaption(GetMsg(QUEUE_CLOSE)); + CloseButton->SetResult(brCancel); + CloseButton->SetCenterGroup(true); + CloseButton->SetDefault(true); + + SetNextItemPosition(ipNewLine); + + QueueListBox = new TFarListBox(this); + QueueListBox->SetTop(ListTop + 1); + QueueListBox->SetHeight(ListHeight); + QueueListBox->SetNoBox(true); + QueueListBox->SetFocus(); +} + +void TQueueDialog::OperationButtonClick(TFarButton * Sender, + bool & /*Close*/) +{ + if (QueueListBox->GetItems()->GetSelected() != NPOS) + { + TQueueItemProxy * QueueItem = NB_STATIC_DOWNCAST(TQueueItemProxy, + QueueListBox->GetItems()->GetObj(QueueListBox->GetItems()->GetSelected())); + + if (Sender == ExecuteButton) + { + if (QueueItem->GetStatus() == TQueueItem::qsProcessing) + { + QueueItem->Pause(); + } + else if (QueueItem->GetStatus() == TQueueItem::qsPaused) + { + QueueItem->Resume(); + } + else if (QueueItem->GetStatus() == TQueueItem::qsPending) + { + QueueItem->ExecuteNow(); + } + else if (TQueueItem::IsUserActionStatus(QueueItem->GetStatus())) + { + QueueItem->ProcessUserAction(); + } + else + { + DebugAssert(false); + } + } + else if ((Sender == MoveUpButton) || (Sender == MoveDownButton)) + { + QueueItem->Move(Sender == MoveUpButton); + } + else if (Sender == DeleteButton) + { + QueueItem->Delete(); + } + } +} + +bool TQueueDialog::Key(TFarDialogItem * /*Item*/, LONG_PTR KeyCode) +{ + bool Result = false; + if (QueueListBox->Focused()) + { + TFarButton * DoButton = nullptr; + if (KeyCode == KEY_ENTER) + { + if (ExecuteButton->GetEnabled()) + { + DoButton = ExecuteButton; + } + Result = true; + } + else if (KeyCode == KEY_DEL) + { + if (DeleteButton->GetEnabled()) + { + DoButton = DeleteButton; + } + Result = true; + } + else if (KeyCode == KEY_CTRLUP) + { + if (MoveUpButton->GetEnabled()) + { + DoButton = MoveUpButton; + } + Result = true; + } + else if (KeyCode == KEY_CTRLDOWN) + { + if (MoveDownButton->GetEnabled()) + { + DoButton = MoveDownButton; + } + Result = true; + } + + if (DoButton != nullptr) + { + bool Close; + OperationButtonClick(DoButton, Close); + } + } + return Result; +} + +void TQueueDialog::UpdateControls() +{ + TQueueItemProxy * QueueItem = nullptr; + if (QueueListBox->GetItems()->GetSelected() >= 0) + { + QueueItem = NB_STATIC_DOWNCAST(TQueueItemProxy, + QueueListBox->GetItems()->GetObj(QueueListBox->GetItems()->GetSelected())); + } + + if ((QueueItem != nullptr) && (QueueItem->GetStatus() == TQueueItem::qsProcessing)) + { + ExecuteButton->SetCaption(GetMsg(QUEUE_PAUSE)); + ExecuteButton->SetEnabled(true); + } + else if ((QueueItem != nullptr) && (QueueItem->GetStatus() == TQueueItem::qsPaused)) + { + ExecuteButton->SetCaption(GetMsg(QUEUE_RESUME)); + ExecuteButton->SetEnabled(true); + } + else if ((QueueItem != nullptr) && TQueueItem::IsUserActionStatus(QueueItem->GetStatus())) + { + ExecuteButton->SetCaption(GetMsg(QUEUE_SHOW)); + ExecuteButton->SetEnabled(true); + } + else + { + ExecuteButton->SetCaption(GetMsg(QUEUE_EXECUTE)); + ExecuteButton->SetEnabled( + (QueueItem != nullptr) && (QueueItem->GetStatus() == TQueueItem::qsPending)); + } + DeleteButton->SetEnabled((QueueItem != nullptr) && + (QueueItem->GetStatus() != TQueueItem::qsDone)); + MoveUpButton->SetEnabled((QueueItem != nullptr) && + (QueueItem->GetStatus() == TQueueItem::qsPending) && + (QueueItem->GetIndex() > FStatus->GetActiveCount())); + MoveDownButton->SetEnabled((QueueItem != nullptr) && + (QueueItem->GetStatus() == TQueueItem::qsPending) && + (QueueItem->GetIndex() < FStatus->GetCount() - 1)); +} + +void TQueueDialog::Idle() +{ + TFarDialog::Idle(); + + if (UpdateQueue()) + { + LoadQueue(); + UpdateControls(); + } + else + { + RefreshQueue(); + } +} + +bool TQueueDialog::CloseQuery() +{ + bool Result = TFarDialog::CloseQuery(); + if (Result) + { + TWinSCPPlugin * WinSCPPlugin = NB_STATIC_DOWNCAST(TWinSCPPlugin, FarPlugin); + Result = !FClosingPlugin || (FStatus->GetCount() == 0) || + (WinSCPPlugin->MoreMessageDialog(GetMsg(QUEUE_PENDING_ITEMS), nullptr, + qtWarning, qaOK | qaCancel) == qaCancel); + } + return Result; +} + +bool TQueueDialog::UpdateQueue() +{ + DebugAssert(FFileSystem != nullptr); + TTerminalQueueStatus * Status = FFileSystem->ProcessQueue(false); + bool Result = (Status != nullptr); + if (Result) + { + FStatus = Status; + } + return Result; +} + +void TQueueDialog::Change() +{ + TFarDialog::Change(); + + if (GetHandle()) + { + UpdateControls(); + } +} + +void TQueueDialog::RefreshQueue() +{ + if (QueueListBox->GetItems()->GetCount() > 0) + { + bool Change = false; + intptr_t TopIndex = QueueListBox->GetItems()->GetTopIndex(); + intptr_t Index = TopIndex; + + intptr_t ILine = 0; + while ((Index > ILine) && + (QueueListBox->GetItems()->GetObj(Index) == + QueueListBox->GetItems()->GetObj(Index - ILine - 1))) + { + ILine++; + } + + TQueueItemProxy * PrevQueueItem = nullptr; + TQueueItemProxy * QueueItem = nullptr; + UnicodeString Line; + while ((Index < QueueListBox->GetItems()->GetCount()) && + (Index < TopIndex + QueueListBox->GetHeight())) + { + QueueItem = NB_STATIC_DOWNCAST(TQueueItemProxy, + QueueListBox->GetItems()->GetObj(Index)); + DebugAssert(QueueItem != nullptr); + if ((PrevQueueItem != nullptr) && (QueueItem != PrevQueueItem)) + { + ILine = 0; + } + + if (QueueItemNeedsFrequentRefresh(QueueItem) && + !QueueItem->GetProcessingUserAction()) + { + FillQueueItemLine(Line, QueueItem, ILine); + if (QueueListBox->GetItems()->GetString(Index) != Line) + { + Change = true; + QueueListBox->GetItems()->SetString(Index, Line); + } + } + + PrevQueueItem = QueueItem; + ++Index; + ++ILine; + } + + if (Change) + { + Redraw(); + } + } +} + +void TQueueDialog::LoadQueue() +{ + std::unique_ptr List(new TFarList()); + UnicodeString Line; + for (intptr_t Index = 0; Index < FStatus->GetCount(); ++Index) + { + TQueueItemProxy * QueueItem = FStatus->GetItem(Index); + size_t ILine = 0; + while (FillQueueItemLine(Line, QueueItem, ILine)) + { + List->AddObject(Line, QueueItem); + List->SetDisabled(List->GetCount() - 1, (ILine > 0)); + ILine++; + } + } + QueueListBox->SetItems(List.get()); +} + +bool TQueueDialog::FillQueueItemLine(UnicodeString & Line, + TQueueItemProxy * QueueItem, size_t Index) +{ + int PathMaxLen = 49; + + if ((Index > 2) || + ((Index == 2) && (QueueItem->GetStatus() == TQueueItem::qsPending))) + { + return false; + } + + UnicodeString ProgressStr; + + switch (QueueItem->GetStatus()) + { + case TQueueItem::qsPending: + ProgressStr = GetMsg(QUEUE_PENDING); + break; + + case TQueueItem::qsConnecting: + ProgressStr = GetMsg(QUEUE_CONNECTING); + break; + + case TQueueItem::qsQuery: + ProgressStr = GetMsg(QUEUE_QUERY); + break; + + case TQueueItem::qsError: + ProgressStr = GetMsg(QUEUE_ERROR); + break; + + case TQueueItem::qsPrompt: + ProgressStr = GetMsg(QUEUE_PROMPT); + break; + + case TQueueItem::qsPaused: + ProgressStr = GetMsg(QUEUE_PAUSED); + break; + } + + bool BlinkHide = QueueItemNeedsFrequentRefresh(QueueItem) && + !QueueItem->GetProcessingUserAction() && + ((::GetTickCount() % 2000) >= 1000); + + UnicodeString Operation; + UnicodeString Direction; + UnicodeString Values[2]; + TFileOperationProgressType * ProgressData = QueueItem->GetProgressData(); + TQueueItem::TInfo * Info = QueueItem->GetInfo(); + + if (Index == 0) + { + if (!BlinkHide) + { + switch (Info->Operation) + { + case foCopy: + Operation = GetMsg(QUEUE_COPY); + break; + + case foMove: + Operation = GetMsg(QUEUE_MOVE); + break; + } + Direction = GetMsg((Info->Side == osLocal) ? QUEUE_UPLOAD : QUEUE_DOWNLOAD); + } + + Values[0] = core::MinimizeName(Info->Source, PathMaxLen, (Info->Side == osRemote)); + + if ((ProgressData != nullptr) && + (ProgressData->Operation == Info->Operation)) + { + Values[1] = FormatBytes(ProgressData->TotalTransfered); + } + } + else if (Index == 1) + { + Values[0] = core::MinimizeName(Info->Destination, PathMaxLen, (Info->Side == osLocal)); + + if (ProgressStr.IsEmpty()) + { + if (ProgressData != nullptr) + { + if (ProgressData->Operation == Info->Operation) + { + Values[1] = FORMAT(L"%d%%", ProgressData->OverallProgress()); + } + else if (ProgressData->Operation == foCalculateSize) + { + Values[1] = GetMsg(QUEUE_CALCULATING_SIZE); + } + } + } + else if (!BlinkHide) + { + Values[1] = ProgressStr; + } + } + else + { + if (ProgressData != nullptr) + { + Values[0] = core::MinimizeName(ProgressData->FileName, PathMaxLen, + (Info->Side == osRemote)); + if (ProgressData->Operation == Info->Operation) + { + Values[1] = FORMAT(L"%d%%", ProgressData->TransferProgress()); + } + } + else + { + Values[0] = ProgressStr; + } + } + + Line = FORMAT(L"%1s %1s %-49s %s", + Operation.c_str(), Direction.c_str(), Values[0].c_str(), Values[1].c_str()); + + return true; +} + +bool TQueueDialog::QueueItemNeedsFrequentRefresh( + TQueueItemProxy * QueueItem) +{ + return (QueueItem && + (TQueueItem::IsUserActionStatus(QueueItem->GetStatus()) || + (QueueItem->GetStatus() == TQueueItem::qsPaused))); +} + +bool TQueueDialog::Execute(TTerminalQueueStatus * Status) +{ + FStatus = Status; + + UpdateQueue(); + LoadQueue(); + + bool Result = (ShowModal() != brCancel); + + FStatus = nullptr; + + return Result; +} + +bool TWinSCPFileSystem::QueueDialog( + TTerminalQueueStatus * Status, bool ClosingPlugin) +{ + std::unique_ptr Dialog(new TQueueDialog(FPlugin, this, ClosingPlugin)); + bool Result = Dialog->Execute(Status); + return Result; +} + +bool TWinSCPFileSystem::CreateDirectoryDialog(UnicodeString & Directory, + TRemoteProperties * Properties, bool & SaveSettings) +{ + std::unique_ptr Dialog(new TWinSCPDialog(FPlugin)); + TFarText * Text; + TFarSeparator * Separator; + + Dialog->SetCaption(GetMsg(CREATE_FOLDER_TITLE)); + Dialog->SetSize(TPoint(66, 15)); + + Text = new TFarText(Dialog.get()); + Text->SetCaption(GetMsg(CREATE_FOLDER_PROMPT)); + + TFarEdit * DirectoryEdit = new TFarEdit(Dialog.get()); + DirectoryEdit->SetHistory(L"NewFolder"); + + Separator = new TFarSeparator(Dialog.get()); + Separator->SetCaption(GetMsg(CREATE_FOLDER_ATTRIBUTES)); + + TFarCheckBox * SetRightsCheck = new TFarCheckBox(Dialog.get()); + SetRightsCheck->SetCaption(GetMsg(CREATE_FOLDER_SET_RIGHTS)); + + TRightsContainer * RightsContainer = new TRightsContainer(Dialog.get(), false, true, + true, SetRightsCheck); + + TFarCheckBox * SaveSettingsCheck = new TFarCheckBox(Dialog.get()); + SaveSettingsCheck->SetCaption(GetMsg(CREATE_FOLDER_REUSE_SETTINGS)); + SaveSettingsCheck->Move(0, 6); + + Dialog->AddStandardButtons(); + + DirectoryEdit->SetText(Directory); + SaveSettingsCheck->SetChecked(SaveSettings); + DebugAssert(Properties != nullptr); + SetRightsCheck->SetChecked(Properties->Valid.Contains(vpRights)); + // expect sensible value even if rights are not set valid + RightsContainer->SetRights(Properties->Rights); + + bool Result = (Dialog->ShowModal() == brOK); + + if (Result) + { + Directory = DirectoryEdit->GetText(); + SaveSettings = SaveSettingsCheck->GetChecked(); + if (SetRightsCheck->GetChecked()) + { + Properties->Valid = Properties->Valid << vpRights; + Properties->Rights = RightsContainer->GetRights(); + } + else + { + Properties->Valid = Properties->Valid >> vpRights; + } + } + return Result; +} + +NB_IMPLEMENT_CLASS(TTabButton, NB_GET_CLASS_INFO(TFarButton), nullptr) +NB_IMPLEMENT_CLASS(TLabelList, NB_GET_CLASS_INFO(TList), nullptr) + diff --git a/netbox/src/NetBox/WinSCPFileSystem.cpp b/netbox/src/NetBox/WinSCPFileSystem.cpp new file mode 100644 index 000000000..e782f6af5 --- /dev/null +++ b/netbox/src/NetBox/WinSCPFileSystem.cpp @@ -0,0 +1,4180 @@ +#include +#pragma hdrstop + +#include "WinSCPFileSystem.h" +#include "WinSCPPlugin.h" +#include "FarDialog.h" +#include "FarConfiguration.h" +#include "farkeys.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PuttyIntf.h" +#include "XmlStorage.h" +#include + +TSessionPanelItem::TSessionPanelItem(const TSessionData * ASessionData) : + TCustomFarPanelItem() +{ + DebugAssert(ASessionData); + FSessionData = ASessionData; +} + +void TSessionPanelItem::SetPanelModes(TFarPanelModes * PanelModes) +{ + DebugAssert(FarPlugin); + std::unique_ptr ColumnTitles(new TStringList()); + ColumnTitles->Add(FarPlugin->GetMsg(SESSION_NAME_COL_TITLE)); + for (intptr_t Index = 0; Index < PANEL_MODES_COUNT; ++Index) + { + PanelModes->SetPanelMode(Index, L"N", L"0", ColumnTitles.get(), false, false, false); + } +} + +void TSessionPanelItem::SetKeyBarTitles(TFarKeyBarTitles * KeyBarTitles) +{ + KeyBarTitles->ClearKeyBarTitle(fsNone, 6); + KeyBarTitles->SetKeyBarTitle(fsNone, 5, FarPlugin->GetMsg(EXPORT_SESSION_KEYBAR)); + KeyBarTitles->ClearKeyBarTitle(fsShift, 1, 3); + KeyBarTitles->SetKeyBarTitle(fsShift, 4, FarPlugin->GetMsg(NEW_SESSION_KEYBAR)); + KeyBarTitles->SetKeyBarTitle(fsShift, 5, FarPlugin->GetMsg(COPY_SESSION_KEYBAR)); + KeyBarTitles->SetKeyBarTitle(fsShift, 6, FarPlugin->GetMsg(RENAME_SESSION_KEYBAR)); + KeyBarTitles->ClearKeyBarTitle(fsShift, 7, 8); + KeyBarTitles->ClearKeyBarTitle(fsAlt, 6); + KeyBarTitles->ClearKeyBarTitle(fsCtrl, 4, 11); +} + +void TSessionPanelItem::GetData( + DWORD & /*Flags*/, UnicodeString & AFileName, int64_t & /*Size*/, + DWORD & /*FileAttributes*/, + TDateTime & /*LastWriteTime*/, TDateTime & /*LastAccess*/, + DWORD & /*NumberOfLinks*/, UnicodeString & /*Description*/, + UnicodeString & /*Owner*/, void *& UserData, int & /*CustomColumnNumber*/) +{ + AFileName = base::UnixExtractFileName(FSessionData->GetName()); + UserData = (void *)FSessionData; +} + +TSessionFolderPanelItem::TSessionFolderPanelItem(const UnicodeString & Folder) : + TCustomFarPanelItem(), + FFolder(Folder) +{ +} + +void TSessionFolderPanelItem::GetData( + DWORD & /*Flags*/, UnicodeString & AFileName, int64_t & /*Size*/, + DWORD & FileAttributes, + TDateTime & /*LastWriteTime*/, TDateTime & /*LastAccess*/, + DWORD & /*NumberOfLinks*/, UnicodeString & /*Description*/, + UnicodeString & /*Owner*/, void *& /*UserData*/, int & /*CustomColumnNumber*/) +{ + AFileName = FFolder; + FileAttributes = FILE_ATTRIBUTE_DIRECTORY; +} + +TRemoteFilePanelItem::TRemoteFilePanelItem(TRemoteFile * ARemoteFile) : + TCustomFarPanelItem() +{ + DebugAssert(ARemoteFile); + FRemoteFile = ARemoteFile; +} + +void TRemoteFilePanelItem::GetData( + DWORD & /*Flags*/, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & /*NumberOfLinks*/, UnicodeString & /*Description*/, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber) +{ + AFileName = FRemoteFile->GetFileName(); + Size = FRemoteFile->GetSize(); + if (Size < 0) + Size = 0; + if (FRemoteFile->GetIsDirectory()) + Size = 0; + FileAttributes = + FLAGMASK(FRemoteFile->GetIsDirectory(), FILE_ATTRIBUTE_DIRECTORY) | + FLAGMASK(FRemoteFile->GetIsHidden(), FILE_ATTRIBUTE_HIDDEN) | + FLAGMASK(FRemoteFile->GetRights()->GetReadOnly(), FILE_ATTRIBUTE_READONLY) | + FLAGMASK(FRemoteFile->GetIsSymLink(), FILE_ATTRIBUTE_REPARSE_POINT); + LastWriteTime = FRemoteFile->GetModification(); + LastAccess = FRemoteFile->GetLastAccess(); + Owner = FRemoteFile->GetFileOwner().GetName(); + UserData = FRemoteFile; + CustomColumnNumber = 4; +} + +UnicodeString TRemoteFilePanelItem::GetCustomColumnData(size_t Column) +{ + switch (Column) + { + case 0: return FRemoteFile->GetFileGroup().GetName(); + case 1: return FRemoteFile->GetRightsStr(); + case 2: return FRemoteFile->GetRights()->GetOctal(); + case 3: return FRemoteFile->GetLinkTo(); + default: + DebugFail(); + return UnicodeString(); + } +} + +void TRemoteFilePanelItem::TranslateColumnTypes(UnicodeString & AColumnTypes, + TStrings * ColumnTitles) +{ + UnicodeString ColumnTypes = AColumnTypes; + AColumnTypes.Clear(); + UnicodeString Column; + UnicodeString Title; + while (!ColumnTypes.IsEmpty()) + { + Column = CutToChar(ColumnTypes, ',', false); + if (Column == L"G") + { + Column = L"C0"; + Title = FarPlugin->GetMsg(GROUP_COL_TITLE); + } + else if (Column == L"R") + { + Column = L"C1"; + Title = FarPlugin->GetMsg(RIGHTS_COL_TITLE); + } + else if (Column == L"RO") + { + Column = L"C2"; + Title = FarPlugin->GetMsg(RIGHTS_OCTAL_COL_TITLE); + } + else if (Column == L"L") + { + Column = L"C3"; + Title = FarPlugin->GetMsg(LINK_TO_COL_TITLE); + } + else + { + Title.Clear(); + } + AColumnTypes += (AColumnTypes.IsEmpty() ? L"" : L",") + Column; + if (ColumnTitles) + { + ColumnTitles->Add(Title); + } + } +} + +void TRemoteFilePanelItem::SetPanelModes(TFarPanelModes * PanelModes) +{ + DebugAssert(FarPlugin); + std::unique_ptr ColumnTitles(new TStringList()); + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + if (FarConfiguration->GetCustomPanelModeDetailed()) + { + UnicodeString ColumnTypes = FarConfiguration->GetColumnTypesDetailed(); + UnicodeString StatusColumnTypes = FarConfiguration->GetStatusColumnTypesDetailed(); + + TranslateColumnTypes(ColumnTypes, ColumnTitles.get()); + TranslateColumnTypes(StatusColumnTypes, nullptr); + + PanelModes->SetPanelMode(5 /*detailed*/, + ColumnTypes, FarConfiguration->GetColumnWidthsDetailed(), + ColumnTitles.get(), FarConfiguration->GetFullScreenDetailed(), false, true, false, + StatusColumnTypes, FarConfiguration->GetStatusColumnWidthsDetailed()); + } +} + +void TRemoteFilePanelItem::SetKeyBarTitles(TFarKeyBarTitles * KeyBarTitles) +{ + KeyBarTitles->ClearKeyBarTitle(fsShift, 1, 3); // archive commands + KeyBarTitles->SetKeyBarTitle(fsShift, 5, FarPlugin->GetMsg(COPY_TO_FILE_KEYBAR)); + KeyBarTitles->SetKeyBarTitle(fsShift, 6, FarPlugin->GetMsg(MOVE_TO_FILE_KEYBAR)); + KeyBarTitles->SetKeyBarTitle(fsAltShift, 12, + FarPlugin->GetMsg(OPEN_DIRECTORY_KEYBAR)); + KeyBarTitles->SetKeyBarTitle(fsAltShift, 6, + FarPlugin->GetMsg(RENAME_FILE_KEYBAR)); +} + +class TFarInteractiveCustomCommand : public TInteractiveCustomCommand +{ +public: + TFarInteractiveCustomCommand(TCustomFarPlugin * Plugin, + TCustomCommand * ChildCustomCommand); + +protected: + virtual void Prompt(const UnicodeString & APrompt, + UnicodeString & Value) const; + +private: + TCustomFarPlugin * FPlugin; +}; + +TFarInteractiveCustomCommand::TFarInteractiveCustomCommand( + TCustomFarPlugin * Plugin, TCustomCommand * ChildCustomCommand) : + TInteractiveCustomCommand(ChildCustomCommand) +{ + FPlugin = Plugin; +} + +void TFarInteractiveCustomCommand::Prompt(const UnicodeString & APrompt, + UnicodeString & Value) const +{ + UnicodeString Prompt = APrompt; + if (Prompt.IsEmpty()) + { + Prompt = FPlugin->GetMsg(APPLY_COMMAND_PARAM_PROMPT); + } + if (!FPlugin->InputBox(FPlugin->GetMsg(APPLY_COMMAND_PARAM_TITLE), + Prompt, Value, 0, APPLY_COMMAND_PARAM_HISTORY)) + { + Abort(); + } +} + +// Attempt to allow keepalives from background thread. +// Not finished nor used. +class TKeepaliveThread : public TSimpleThread +{ +public: + explicit TKeepaliveThread(TWinSCPFileSystem * FileSystem, const TDateTime & Interval); + virtual ~TKeepaliveThread() + {} + virtual void Init(); + virtual void Execute(); + virtual void Terminate(); + +private: + TWinSCPFileSystem * FFileSystem; + TDateTime FInterval; + HANDLE FEvent; +}; + +TKeepaliveThread::TKeepaliveThread(TWinSCPFileSystem * FileSystem, + const TDateTime & Interval) : + TSimpleThread(), + FFileSystem(FileSystem), + FInterval(Interval), + FEvent(0) +{ +} + +void TKeepaliveThread::Init() +{ + TSimpleThread::Init(); + FEvent = ::CreateEvent(nullptr, false, false, nullptr); + Start(); +} + +void TKeepaliveThread::Terminate() +{ + // TCompThread::Terminate(); + ::SetEvent(FEvent); +} + +void TKeepaliveThread::Execute() +{ + while (!IsFinished()) + { + if ((::WaitForSingleObject(FEvent, static_cast( + ToDouble(FInterval) * MSecsPerDay)) != WAIT_FAILED) && + !IsFinished()) + { + FFileSystem->KeepaliveThreadCallback(); + } + } + ::CloseHandle(FEvent); +} + +TWinSCPFileSystem::TWinSCPFileSystem(TCustomFarPlugin * APlugin) : + TCustomFarFileSystem(APlugin), + FTerminal(nullptr), + FQueue(nullptr), + FQueueStatus(nullptr), + FProgressSaveScreenHandle(0), + FSynchronizationSaveScreenHandle(0), + FAuthenticationSaveScreenHandle(0), + FSynchronizationCompare(false), + FFileList(nullptr), + FPanelItems(nullptr), + FSavedFindFolder(L""), + FQueueStatusInvalidated(false), + FQueueItemInvalidated(false), + FRefreshLocalDirectory(false), + FRefreshRemoteDirectory(false), + FQueueEventPending(false), + FNoProgress(false), + FNoProgressFinish(false), + FKeepaliveThread(nullptr), + FSynchronisingBrowse(false), + FSynchronizeController(nullptr), + FCapturedLog(nullptr), + FAuthenticationLog(nullptr), + FLastEditorID(-1), + FLoadingSessionList(false), + FPathHistory(new TStringList()), + FCurrentDirectoryWasChanged(false), + + FReloadDirectory(false), + FLastMultipleEditReadOnly(false), + FEditorPendingSave(false), + FOutputLog(false) +{ +} + +void TWinSCPFileSystem::Init(TSecureShell * /*SecureShell*/) +{ + TCustomFarFileSystem::Init(); +} + +TWinSCPFileSystem::~TWinSCPFileSystem() +{ + Disconnect(); + SAFE_DESTROY(FPathHistory); +} + +void TWinSCPFileSystem::HandleException(Exception * E, int OpMode) +{ + if ((GetTerminal() != nullptr) && (NB_STATIC_DOWNCAST(EFatal, E) != nullptr)) + { + bool Reopen = GetTerminal()->QueryReopen(E, 0, nullptr); + if (Reopen) + { + UpdatePanel(); + } + else + { + if (!FClosed) + { + ClosePlugin(); + } + //GetTerminal()->ShowExtendedException(E); + } + } + else + { + TCustomFarFileSystem::HandleException(E, OpMode); + } +} + +void TWinSCPFileSystem::KeepaliveThreadCallback() +{ + TGuard Guard(FCriticalSection); + + if (Connected()) + { + FTerminal->Idle(); + } +} + +bool TWinSCPFileSystem::IsSessionList() const +{ + return (FTerminal == nullptr); +} + +bool TWinSCPFileSystem::Connected() const +{ + // Check for active added to avoid "disconnected" message popup repeatedly + // from "idle" + return !IsSessionList() && FTerminal->GetActive(); +} + +TWinSCPPlugin * TWinSCPFileSystem::GetWinSCPPlugin() +{ + return NB_STATIC_DOWNCAST(TWinSCPPlugin, FPlugin); +} + +void TWinSCPFileSystem::Close() +{ + SCOPE_EXIT + { + TCustomFarFileSystem::Close(); + }; + SAFE_DESTROY(FKeepaliveThread); + + if (Connected()) + { + if (FQueue != nullptr) + { + if (!FQueue->GetIsEmpty() && + (MoreMessageDialog(GetMsg(PENDING_QUEUE_ITEMS), nullptr, qtWarning, + qaOK | qaCancel) == qaOK)) + { + QueueShow(true); + } + } + } +} + +void TWinSCPFileSystem::GetOpenPluginInfoEx(DWORD & Flags, + UnicodeString & /*HostFile*/, UnicodeString & CurDir, UnicodeString & Format, + UnicodeString & PanelTitle, TFarPanelModes * PanelModes, int & /*StartPanelMode*/, + int & /*StartSortMode*/, bool & /*StartSortOrder*/, TFarKeyBarTitles * KeyBarTitles, + UnicodeString & ShortcutData) +{ + if (!IsSessionList()) + { + Flags = OPIF_USEFILTER | OPIF_USESORTGROUPS | OPIF_USEHIGHLIGHTING | + OPIF_SHOWPRESERVECASE | OPIF_COMPAREFATTIME; + + // When slash is added to the end of path, windows style paths + // (vandyke: c:/windows/system) are displayed correctly on command-line, but + // leaved subdirectory is not focused, when entering parent directory. + CurDir = FTerminal->GetCurrDirectory(); + Format = GetSessionData()->GetSessionName(); + if (GetFarConfiguration()->GetHostNameInTitle()) + { + PanelTitle = FORMAT(L" %s:%s ", Format.c_str(), CurDir.c_str()); + } + else + { + PanelTitle = FORMAT(L" %s ", CurDir.c_str()); + } + ShortcutData = FORMAT(L"%s\1%s", GetSessionData()->GenerateSessionUrl(sufComplete).c_str(), CurDir.c_str()); + + TRemoteFilePanelItem::SetPanelModes(PanelModes); + TRemoteFilePanelItem::SetKeyBarTitles(KeyBarTitles); + } + else + { + CurDir = FSessionsFolder; + Format = L"netbox"; + Flags = OPIF_USESORTGROUPS | OPIF_USEHIGHLIGHTING | OPIF_USEATTRHIGHLIGHTING | + OPIF_ADDDOTS | OPIF_SHOWPRESERVECASE; + + PanelTitle = FORMAT(L" %s [/%s]", GetMsg(NB_STORED_SESSION_TITLE).c_str(), FSessionsFolder.c_str()); + + TSessionPanelItem::SetPanelModes(PanelModes); + TSessionPanelItem::SetKeyBarTitles(KeyBarTitles); + } +} + +bool TWinSCPFileSystem::GetFindDataEx(TObjectList * PanelItems, int OpMode) +{ + bool Result = false; + if (Connected()) + { + DebugAssert(!FNoProgress); + // OPM_FIND is used also for calculation of directory size (F3, quick view). + // However directory is usually read from SetDirectory, so FNoProgress + // seems to have no effect here. + // Do not know if OPM_SILENT is even used. + FNoProgress = FLAGSET(OpMode, OPM_FIND) || FLAGSET(OpMode, OPM_SILENT); + { + SCOPE_EXIT + { + FNoProgress = false; + }; + if (FReloadDirectory && FTerminal->GetActive()) + { + FReloadDirectory = false; + FTerminal->ReloadDirectory(); + } + + TCustomFileSystem * FileSystem = GetTerminal()->GetFileSystem(); + bool ResolveSymlinks = GetSessionData()->GetResolveSymlinks(); + for (intptr_t Index = 0; Index < GetTerminal()->GetFiles()->GetCount(); ++Index) + { + TRemoteFile * File = GetTerminal()->GetFiles()->GetFile(Index); + if (ResolveSymlinks && File->GetIsSymLink()) + { + if (FarPlugin->CheckForEsc()) + break; + // Check what kind of symlink this is + const UnicodeString LinkFileName = File->GetLinkTo(); + if (!LinkFileName.IsEmpty()) + { + TRemoteFile * LinkFile = nullptr; + try + { + FileSystem->ReadFile(LinkFileName, LinkFile); + } + catch (const Exception & /*E*/) + { + LinkFile = nullptr; + } + if ((LinkFile != nullptr) && LinkFile->GetIsDirectory()) + { + File->SetType(FILETYPE_DIRECTORY); + } + SAFE_DESTROY(LinkFile); + } + } + PanelItems->Add(new TRemoteFilePanelItem(File)); + } + } + Result = true; + } + else if (IsSessionList()) + { + Result = true; + DebugAssert(StoredSessions); + StoredSessions->Load(); + UnicodeString Folder = FSessionsFolder; + if (!FSessionsFolder.IsEmpty()) + { + Folder = core::UnixIncludeTrailingBackslash(FSessionsFolder); + } + + std::unique_ptr ChildPaths(new TStringList()); + ChildPaths->SetCaseSensitive(false); + for (intptr_t Index = 0; Index < StoredSessions->GetCount(); ++Index) + { + const TSessionData * Data = StoredSessions->GetSession(Index); + UnicodeString SessionName = Data->GetName(); + if (SessionName.SubString(1, Folder.Length()) == Folder) + { + UnicodeString Name = SessionName.SubString( + Folder.Length() + 1, SessionName.Length() - Folder.Length()); + intptr_t Slash = Name.Pos(L'/'); + if (Slash > 0) + { + Name.SetLength(Slash - 1); + if (ChildPaths->IndexOf(Name.c_str()) < 0) + { + PanelItems->Add(new TSessionFolderPanelItem(Name)); + ChildPaths->Add(Name); + } + } + else + { + PanelItems->Add(new TSessionPanelItem(Data)); + } + } + } + + if (!FNewSessionsFolder.IsEmpty()) + { + PanelItems->Add(new TSessionFolderPanelItem(FNewSessionsFolder)); + } + if (PanelItems->GetCount() == 0) + { + PanelItems->Add(new THintPanelItem(GetMsg(NEW_SESSION_HINT))); + } + + TWinSCPFileSystem * OppositeFileSystem = + NB_STATIC_DOWNCAST(TWinSCPFileSystem, GetOppositeFileSystem()); + if ((OppositeFileSystem != nullptr) && !OppositeFileSystem->Connected() && + !OppositeFileSystem->FLoadingSessionList) + { + FLoadingSessionList = true; + { + SCOPE_EXIT + { + FLoadingSessionList = false; + }; + UpdatePanel(false, true); + RedrawPanel(true); + } + } + if (!FPrevSessionName.IsEmpty()) + { + const TSessionData * PrevSession = StoredSessions->GetSessionByName(FPrevSessionName); + FPrevSessionName.Clear(); + if (UpdatePanel()) + { + RedrawPanel(); + FocusSession(PrevSession); + } + } + } + else + { + Result = false; + } + return Result; +} + +void TWinSCPFileSystem::DuplicateOrRenameSession(TSessionData * Data, + bool Duplicate) +{ + DebugAssert(Data); + UnicodeString Name = Data->GetName(); + if (GetWinSCPPlugin()->InputBox(GetMsg(Duplicate ? DUPLICATE_SESSION_TITLE : RENAME_SESSION_TITLE), + GetMsg(Duplicate ? DUPLICATE_SESSION_PROMPT : RENAME_SESSION_PROMPT), + Name, 0) && + !Name.IsEmpty() && (Name != Data->GetName())) + { + TNamedObject * EData = StoredSessions->FindByName(Name); + if ((EData != nullptr) && (EData != Data)) + { + throw Exception(FORMAT(GetMsg(SESSION_ALREADY_EXISTS_ERROR).c_str(), Name.c_str())); + } + else + { + TSessionData * NData = StoredSessions->NewSession(Name, Data); + FSessionsFolder = ::ExcludeTrailingBackslash(core::UnixExtractFilePath(Name)); + + // change of letter case during duplication degrades the operation to rename + if (!Duplicate || (Data == NData)) + { + Data->Remove(); + if (NData != Data) + { + StoredSessions->Remove(Data); + } + } + + // modified only, explicit + StoredSessions->Save(false, true); + + if (UpdatePanel()) + { + RedrawPanel(); + + FocusSession(NData); + } + } + } +} + +void TWinSCPFileSystem::FocusSession(const TSessionData * Data) +{ + TFarPanelInfo ** PanelInfo = GetPanelInfo(); + const TFarPanelItem * SessionItem = PanelInfo && *PanelInfo ? (*PanelInfo)->FindUserData(Data) : nullptr; + if (SessionItem != nullptr) + { + (*PanelInfo)->SetFocusedItem(SessionItem); + } +} + +void TWinSCPFileSystem::EditConnectSession(TSessionData * Data, bool Edit) +{ + bool NewData = !Data; + bool FillInConnect = !Edit && Data && !Data->GetCanLogin(); + if (NewData || FillInConnect) + { + Data = new TSessionData(L""); + } + + SCOPE_EXIT + { + if (NewData || FillInConnect) + { + SAFE_DESTROY(Data); + } + }; + EditConnectSession(Data, Edit, NewData, FillInConnect); +} + +void TWinSCPFileSystem::EditConnectSession(TSessionData * Data, bool Edit, bool NewData, bool FillInConnect) +{ + TSessionData * OrigData = Data; + if (FillInConnect) + { + Data->Assign(OrigData); + Data->SetName(L""); + } + + TSessionActionEnum Action; + if (Edit || FillInConnect) + { + Action = (FillInConnect ? saConnect : (OrigData == nullptr ? saAdd : saEdit)); + if (SessionDialog(Data, Action)) + { + if ((!NewData && !FillInConnect) || (Action != saConnect)) + { + TSessionData * SelectSession = nullptr; + if (NewData) + { + // UnicodeString Name = + // IncludeTrailingBackslash(FSessionsFolder) + Data->GetSessionName(); + UnicodeString Name; + if (!FSessionsFolder.IsEmpty()) + { + Name = core::UnixIncludeTrailingBackslash(FSessionsFolder); + } + Name += Data->GetSessionName(); + if (GetWinSCPPlugin()->InputBox(GetMsg(NEW_SESSION_NAME_TITLE), + GetMsg(NEW_SESSION_NAME_PROMPT), Name, 0) && + !Name.IsEmpty()) + { + if (StoredSessions->FindByName(Name)) + { + throw Exception(FORMAT(GetMsg(SESSION_ALREADY_EXISTS_ERROR).c_str(), Name.c_str())); + } + else + { + SelectSession = StoredSessions->NewSession(Name, Data); + FSessionsFolder = ::ExcludeTrailingBackslash(core::UnixExtractFilePath(Name)); + } + } + } + else if (FillInConnect) + { + UnicodeString OrigName = OrigData->GetName(); + OrigData->Assign(Data); + OrigData->SetName(OrigName); + } + + // modified only, explicit + StoredSessions->Save(false, true); + if (UpdatePanel()) + { + if (SelectSession != nullptr) + { + FocusSession(SelectSession); + } + // rarely we need to redraw even when new session is created + // (e.g. when there there were only the focused hint line before) + RedrawPanel(); + } + } + } + } + else + { + Action = saConnect; + } + + if ((Action == saConnect) && Connect(Data)) + { + if (UpdatePanel()) + { + RedrawPanel(); + TFarPanelInfo ** PanelInfo = GetPanelInfo(); + if (PanelInfo && *PanelInfo && (*PanelInfo)->GetItemCount()) + { + (*PanelInfo)->SetFocusedIndex(0); + } + } + } +} + +bool TWinSCPFileSystem::ProcessEventEx(intptr_t Event, void * Param) +{ + bool Result = false; + if (Connected()) + { + if (Event == FE_COMMAND) + { + UnicodeString Command = static_cast(Param); + if (!::Trim(Command).IsEmpty() && + (::LowerCase(Command.SubString(1, 3)) != L"cd ")) + { + Result = ExecuteCommand(Command); + } + } + else if (Event == FE_IDLE) + { + // FAR WORKAROUND + // Control(FCTL_CLOSEPLUGIN) does not seem to close plugin when called from + // ProcessEvent(FE_IDLE). So if TTerminal::Idle() causes session to close + // we must count on having ProcessEvent(FE_IDLE) called again. + FTerminal->Idle(); + if (FQueue != nullptr) + { + FQueue->Idle(); + } + ProcessQueue(true); + } + else if ((Event == FE_GOTFOCUS) || (Event == FE_KILLFOCUS)) + { + DEBUG_PRINTF("Event = %d, Plugin = %p, Param = %p", Event, this, Param); + Result = true; + } + else if (Event == FE_REDRAW) + { + if (FCurrentDirectoryWasChanged) + { + UpdatePanel(); + FCurrentDirectoryWasChanged = false; + } + } + } + return Result; +} + +void TWinSCPFileSystem::TerminalCaptureLog( + const UnicodeString & AddedLine, TCaptureOutputType /*OutputEvent*/) +{ + if (FOutputLog) + { + GetWinSCPPlugin()->FarWriteConsole(AddedLine + L"\n"); + } + if (FCapturedLog != nullptr) + { + FCapturedLog->Add(AddedLine); + } +} + +void TWinSCPFileSystem::RequireLocalPanel(TFarPanelInfo * Panel, const UnicodeString & Message) +{ + if (Panel->GetIsPlugin() || (Panel->GetType() != ptFile)) + { + throw Exception(Message); + } +} + +void TWinSCPFileSystem::RequireCapability(intptr_t Capability) +{ + if (!FTerminal->GetIsCapable(static_cast(Capability))) + { + throw Exception(FORMAT(GetMsg(OPERATION_NOT_SUPPORTED).c_str(), + FTerminal->GetFileSystemInfo().ProtocolName.c_str())); + } +} + +bool TWinSCPFileSystem::EnsureCommandSessionFallback(TFSCapability Capability) +{ + bool Result = FTerminal->GetIsCapable(Capability) || + FTerminal->GetCommandSessionOpened(); + + if (!Result) + { + if (!GetGUIConfiguration()->GetConfirmCommandSession()) + { + Result = true; + } + else + { + TMessageParams Params; + Params.Params = qpNeverAskAgainCheck; + uintptr_t Answer = MoreMessageDialog( + FORMAT(GetMsg(PERFORM_ON_COMMAND_SESSION).c_str(), + FTerminal->GetFileSystemInfo().ProtocolName.c_str(), + FTerminal->GetFileSystemInfo().ProtocolName.c_str()), + nullptr, + qtConfirmation, qaOK | qaCancel, &Params); + if (Answer == qaNeverAskAgain) + { + GetGUIConfiguration()->SetConfirmCommandSession(false); + GetGUIConfiguration()->DoSave(false, false); // modified, implicit + Result = true; + } + else + { + Result = (Answer == qaOK); + } + } + + if (Result) + { + ConnectTerminal(FTerminal->GetCommandSession()); + } + } + + return Result; +} + +bool TWinSCPFileSystem::ExecuteCommand(const UnicodeString & Command) +{ + if (FTerminal->AllowedAnyCommand(Command) && + EnsureCommandSessionFallback(fcAnyCommand)) + { + FTerminal->BeginTransaction(); + { + SCOPE_EXIT + { + if (FTerminal->GetActive()) + { + FTerminal->EndTransaction(); + UpdatePanel(); + } + else + { + RedrawPanel(); + RedrawPanel(true); + } + }; + FarControl(FCTL_SETCMDLINE, 0, reinterpret_cast(L"")); + TWinSCPPlugin * WinSCPPlugin = GetWinSCPPlugin(); + WinSCPPlugin->ShowConsoleTitle(Command); + { + SCOPE_EXIT + { + WinSCPPlugin->ScrollTerminalScreen(1); + WinSCPPlugin->SaveTerminalScreen(); + WinSCPPlugin->ClearConsoleTitle(); + }; + WinSCPPlugin->ShowTerminalScreen(); + + FOutputLog = true; + FTerminal->AnyCommand(Command, MAKE_CALLBACK(TWinSCPFileSystem::TerminalCaptureLog, this)); + } + } + } + return true; +} + +bool TWinSCPFileSystem::ProcessKeyEx(intptr_t Key, uintptr_t ControlState) +{ + bool Handled = false; + + TFarPanelInfo * const * PanelInfo = GetPanelInfo(); + const TFarPanelItem * Focused = PanelInfo && *PanelInfo ? (*PanelInfo)->GetFocusedItem() : nullptr; + + if ((Key == 'W') && (ControlState & PKF_SHIFT) && + (ControlState & PKF_ALT)) + { + GetWinSCPPlugin()->CommandsMenu(true); + Handled = true; + } + else if (IsSessionList()) + { + TSessionData * Data = nullptr; + if ((Focused != nullptr) && Focused->GetIsFile() && Focused->GetUserData()) + { + Data = NB_STATIC_DOWNCAST(TSessionData, Focused->GetUserData()); + } + + if ((Key == 'F') && FLAGSET(ControlState, PKF_CONTROL)) + { + InsertSessionNameOnCommandLine(); + Handled = true; + } + + if ((Key == VK_RETURN) && FLAGSET(ControlState, PKF_CONTROL)) + { + InsertSessionNameOnCommandLine(); + Handled = true; + } + + if (Key == VK_RETURN && (ControlState == 0 || ControlState & ENHANCED_KEY) && Data) + { + EditConnectSession(Data, false); + Handled = true; + } + + if (Key == VK_F4 && (ControlState == 0)) + { + if ((Data != nullptr) || (StoredSessions->GetCount() == 0)) + { + EditConnectSession(Data, true); + } + Handled = true; + } + + if (Key == VK_F4 && (ControlState & PKF_SHIFT)) + { + EditConnectSession(nullptr, true); + Handled = true; + } + + if (((Key == VK_F5) || (Key == VK_F6)) && + (ControlState & PKF_SHIFT)) + { + if (Data != nullptr) + { + DuplicateOrRenameSession(Data, Key == VK_F5); + } + Handled = true; + } + } + else if (Connected()) + { + if ((Key == 'F') && (ControlState & PKF_CONTROL)) + { + InsertFileNameOnCommandLine(true); + Handled = true; + } + + if ((Key == VK_RETURN) && FLAGSET(ControlState, PKF_CONTROL)) + { + InsertFileNameOnCommandLine(false); + Handled = true; + } + + if ((Key == 'R') && (ControlState & PKF_CONTROL)) + { + FReloadDirectory = true; + } + + if ((Key == 'A') && (ControlState & PKF_CONTROL)) + { + FileProperties(); + Handled = true; + } + + if ((Key == 'G') && (ControlState & PKF_CONTROL)) + { + ApplyCommand(); + Handled = true; + } + + if ((Key == 'Q') && (ControlState & PKF_SHIFT) && + (ControlState & PKF_ALT)) + { + QueueShow(false); + Handled = true; + } + + if ((Key == 'B') && (ControlState & PKF_CONTROL) && + (ControlState & PKF_ALT)) + { + ToggleSynchronizeBrowsing(); + Handled = true; + } + + if ((Key == VK_INSERT) && + (FLAGSET(ControlState, PKF_ALT | PKF_SHIFT) || FLAGSET(ControlState, PKF_CONTROL | PKF_ALT))) + { + CopyFullFileNamesToClipboard(); + Handled = true; + } + + if ((Key == VK_F6) && ((ControlState & (PKF_ALT | PKF_SHIFT)) == PKF_ALT)) + { + CreateLink(); + Handled = true; + } + + if (Focused && ((Key == VK_F5) || (Key == VK_F6)) && + ((ControlState & (PKF_ALT | PKF_SHIFT)) == PKF_SHIFT)) + { + TransferFiles((Key == VK_F6)); + Handled = true; + } + + if (Focused && (Key == VK_F6) && + ((ControlState & (PKF_ALT | PKF_SHIFT)) == (PKF_SHIFT | PKF_ALT))) + { + RenameFile(); + Handled = true; + } + + if ((Key == VK_F12) && (ControlState & PKF_SHIFT) && + (ControlState & PKF_ALT)) + { + OpenDirectory(false); + Handled = true; + } + + if ((Key == VK_F4) && (ControlState == 0) && + GetFarConfiguration()->GetEditorMultiple()) + { + MultipleEdit(); + Handled = true; + } + } + return Handled; +} + +void TWinSCPFileSystem::CreateLink() +{ + RequireCapability(fcResolveSymlink); + RequireCapability(fcSymbolicLink); + + bool Edit = false; + TRemoteFile * File = nullptr; + UnicodeString FileName; + UnicodeString PointTo; + bool SymbolicLink = true; + + TFarPanelInfo * const * PanelInfo = GetPanelInfo(); + if (PanelInfo && *PanelInfo && (*PanelInfo)->GetFocusedItem() && (*PanelInfo)->GetFocusedItem()->GetUserData()) + { + File = NB_STATIC_DOWNCAST(TRemoteFile, (*PanelInfo)->GetFocusedItem()->GetUserData()); + + Edit = File->GetIsSymLink() && GetTerminal()->GetSessionData()->GetResolveSymlinks(); + if (Edit) + { + FileName = File->GetFileName(); + PointTo = File->GetLinkTo(); + } + else + { + PointTo = File->GetFileName(); + } + } + + if (LinkDialog(FileName, PointTo, SymbolicLink, Edit, + GetTerminal()->GetIsCapable(fcHardLink))) + { + if (Edit) + { + DebugAssert(File->GetFileName() == FileName); + intptr_t Params = dfNoRecursive; + GetTerminal()->SetExceptionOnFail(true); + { + SCOPE_EXIT + { + GetTerminal()->SetExceptionOnFail(false); + }; + GetTerminal()->RemoteDeleteFile(L"", File, &Params); + } + } + GetTerminal()->CreateLink(FileName, PointTo, SymbolicLink, File->GetIsDirectory()); + if (UpdatePanel()) + { + RedrawPanel(); + } + } +} + +void TWinSCPFileSystem::TemporarilyDownloadFiles(TStrings * AFileList, TCopyParamType & CopyParam, UnicodeString & TempDir) +{ + CopyParam.SetFileNameCase(ncNoChange); + CopyParam.SetPreserveReadOnly(false); + CopyParam.SetResumeSupport(rsOff); + + TempDir = GetWinSCPPlugin()->GetTemporaryDir(); + if (TempDir.IsEmpty() || !::ForceDirectories(ApiPath(TempDir))) + { + throw Exception(FMTLOAD(CREATE_TEMP_DIR_ERROR, TempDir.c_str())); + } + + FTerminal->SetExceptionOnFail(true); + { + SCOPE_EXIT + { + FTerminal->SetExceptionOnFail(false); + }; + try + { + FTerminal->CopyToLocal(AFileList, TempDir, &CopyParam, cpTemporary); + } + catch (...) + { + try + { + RecursiveDeleteFile(::ExcludeTrailingBackslash(TempDir), false); + } + catch (...) + { + } + throw; + } + } +} + +void TWinSCPFileSystem::ApplyCommand() +{ + TFarPanelInfo ** PanelInfo = this->GetPanelInfo(); + if (PanelInfo && *PanelInfo && (*PanelInfo)->GetSelectedCount(true) == 0) + { + MoreMessageDialog(GetMsg(MSG_NO_FILES_SELECTED), nullptr, + qtInformation, qaOK); + return; + } + + std::unique_ptr FileList(CreateSelectedFileList(osRemote, PanelInfo)); + if (FileList.get() != nullptr) + { + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + intptr_t Params = FarConfiguration->GetApplyCommandParams(); + UnicodeString Command = FarConfiguration->GetApplyCommandCommand(); + if (ApplyCommandDialog(Command, Params)) + { + FarConfiguration->SetApplyCommandParams(Params); + FarConfiguration->SetApplyCommandCommand(Command); + if (FLAGCLEAR(Params, ccLocal)) + { + if (EnsureCommandSessionFallback(fcShellAnyCommand)) + { + TCustomCommandData Data(GetTerminal()); + TRemoteCustomCommand RemoteCustomCommand(Data, GetTerminal()->GetCurrDirectory()); + TFarInteractiveCustomCommand InteractiveCustomCommand( + GetWinSCPPlugin(), &RemoteCustomCommand); + + Command = InteractiveCustomCommand.Complete(Command, false); + + { + SCOPE_EXIT + { + (*GetPanelInfo())->ApplySelection(); + if (UpdatePanel()) + { + RedrawPanel(); + } + }; + TCaptureOutputEvent OutputEvent = nullptr; + FOutputLog = false; + if (FLAGSET(Params, ccShowResults)) + { + DebugAssert(!FNoProgress); + FNoProgress = true; + FOutputLog = true; + OutputEvent = MAKE_CALLBACK(TWinSCPFileSystem::TerminalCaptureLog, this); + } + + if (FLAGSET(Params, ccCopyResults)) + { + DebugAssert(FCapturedLog == nullptr); + FCapturedLog = new TStringList(); + OutputEvent = MAKE_CALLBACK(TWinSCPFileSystem::TerminalCaptureLog, this); + } + { + SCOPE_EXIT + { + if (FLAGSET(Params, ccShowResults)) + { + FNoProgress = false; + GetWinSCPPlugin()->ScrollTerminalScreen(1); + GetWinSCPPlugin()->SaveTerminalScreen(); + } + + if (FLAGSET(Params, ccCopyResults)) + { + GetWinSCPPlugin()->FarCopyToClipboard(FCapturedLog); + SAFE_DESTROY(FCapturedLog); + } + }; + if (FLAGSET(Params, ccShowResults)) + { + GetWinSCPPlugin()->ShowTerminalScreen(); + } + + FTerminal->CustomCommandOnFiles(Command, Params, FileList.get(), OutputEvent); + } + } + } + } + else + { + TCustomCommandData Data1(GetTerminal()); + TLocalCustomCommand LocalCustomCommand(Data1, GetTerminal()->GetCurrDirectory(), L""); + TFarInteractiveCustomCommand InteractiveCustomCommand(GetWinSCPPlugin(), + &LocalCustomCommand); + + Command = InteractiveCustomCommand.Complete(Command, false); + + std::unique_ptr LocalFileList; + std::unique_ptr RemoteFileList; + { + bool FileListCommand = LocalCustomCommand.IsFileListCommand(Command); + bool LocalFileCommand = LocalCustomCommand.HasLocalFileName(Command); + + if (LocalFileCommand) + { + TFarPanelInfo ** AnotherPanel = GetAnotherPanelInfo(); + RequireLocalPanel(*AnotherPanel, GetMsg(APPLY_COMMAND_LOCAL_PATH_REQUIRED)); + + LocalFileList.reset(CreateSelectedFileList(osLocal, AnotherPanel)); + + if (FileListCommand) + { + if ((LocalFileList.get() == nullptr) || (LocalFileList->GetCount() != 1)) + { + throw Exception(GetMsg(CUSTOM_COMMAND_SELECTED_UNMATCH1)); + } + } + else + { + if ((LocalFileList.get() == nullptr) || + ((LocalFileList->GetCount() != 1) && + (FileList->GetCount() != 1) && + (LocalFileList->GetCount() != FileList->GetCount()))) + { + throw Exception(GetMsg(CUSTOM_COMMAND_SELECTED_UNMATCH)); + } + } + } + + UnicodeString TempDir; + + TemporarilyDownloadFiles(FileList.get(), GetGUIConfiguration()->GetDefaultCopyParam(), TempDir); + + { + SCOPE_EXIT + { + RecursiveDeleteFile(::ExcludeTrailingBackslash(TempDir), false); + }; + RemoteFileList.reset(new TStringList()); + + TMakeLocalFileListParams MakeFileListParam; + MakeFileListParam.FileList = RemoteFileList.get(); + MakeFileListParam.IncludeDirs = FLAGSET(Params, ccApplyToDirectories); + MakeFileListParam.Recursive = + FLAGSET(Params, ccRecursive) && !FileListCommand; + + ProcessLocalDirectory(TempDir, MAKE_CALLBACK(TTerminal::MakeLocalFileList, FTerminal), &MakeFileListParam); + + TFileOperationProgressType Progress(MAKE_CALLBACK(TWinSCPFileSystem::OperationProgress, this), MAKE_CALLBACK(TWinSCPFileSystem::OperationFinished, this)); + + Progress.Start(foCustomCommand, osRemote, static_cast(FileListCommand ? 1 : FileList->GetCount())); + { + SCOPE_EXIT + { + Progress.Stop(); + }; + if (FileListCommand) + { + UnicodeString LocalFile; + UnicodeString FileList2 = core::MakeFileList(RemoteFileList.get()); + + if (LocalFileCommand) + { + DebugAssert(LocalFileList->GetCount() == 1); + LocalFile = LocalFileList->GetString(0); + } + + TCustomCommandData Data2(FTerminal); + TLocalCustomCommand CustomCommand(Data2, + GetTerminal()->GetCurrDirectory(), L"", L"", LocalFile, FileList2); + ExecuteShellAndWait(GetWinSCPPlugin()->GetHandle(), CustomCommand.Complete(Command, true), + TProcessMessagesEvent()); + } + else if (LocalFileCommand) + { + if (LocalFileList->GetCount() == 1) + { + UnicodeString LocalFile = LocalFileList->GetString(0); + + for (intptr_t Index = 0; Index < RemoteFileList->GetCount(); ++Index) + { + UnicodeString FileName = RemoteFileList->GetString(Index); + TCustomCommandData Data3(FTerminal); + TLocalCustomCommand CustomCommand(Data3, + GetTerminal()->GetCurrDirectory(), FileName, L"", LocalFile, L""); + ExecuteShellAndWait(GetWinSCPPlugin()->GetHandle(), + CustomCommand.Complete(Command, true), TProcessMessagesEvent()); + } + } + else if (RemoteFileList->GetCount() == 1) + { + UnicodeString FileName = RemoteFileList->GetString(0); + + for (intptr_t Index = 0; Index < LocalFileList->GetCount(); ++Index) + { + TCustomCommandData Data4(FTerminal); + TLocalCustomCommand CustomCommand( + Data4, GetTerminal()->GetCurrDirectory(), + L"", FileName, LocalFileList->GetString(Index), L""); + ExecuteShellAndWait(GetWinSCPPlugin()->GetHandle(), + CustomCommand.Complete(Command, true), TProcessMessagesEvent()); + } + } + else + { + if (LocalFileList->GetCount() != RemoteFileList->GetCount()) + { + throw Exception(GetMsg(CUSTOM_COMMAND_PAIRS_DOWNLOAD_FAILED)); + } + + for (intptr_t Index = 0; Index < LocalFileList->GetCount(); ++Index) + { + UnicodeString FileName = RemoteFileList->GetString(Index); + TCustomCommandData Data5(FTerminal); + TLocalCustomCommand CustomCommand( + Data5, GetTerminal()->GetCurrDirectory(), + L"", FileName, LocalFileList->GetString(Index), L""); + ExecuteShellAndWait(GetWinSCPPlugin()->GetHandle(), + CustomCommand.Complete(Command, true), TProcessMessagesEvent()); + } + } + } + else + { + for (intptr_t Index = 0; Index < RemoteFileList->GetCount(); ++Index) + { + TCustomCommandData Data6(FTerminal); + TLocalCustomCommand CustomCommand(Data6, + GetTerminal()->GetCurrDirectory(), L"", RemoteFileList->GetString(Index), L"", L""); + ExecuteShellAndWait(GetWinSCPPlugin()->GetHandle(), + CustomCommand.Complete(Command, true), TProcessMessagesEvent()); + } + } + } + } + } + } + } + } +} + +void TWinSCPFileSystem::Synchronize(const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, TTerminal::TSynchronizeMode Mode, + const TCopyParamType & CopyParam, intptr_t Params, TSynchronizeChecklist ** Checklist, + TSynchronizeOptions * Options) +{ + TSynchronizeChecklist * AChecklist = nullptr; + { + SCOPE_EXIT + { + if (Checklist == nullptr) + { + SAFE_DESTROY(AChecklist); + } + else + { + *Checklist = AChecklist; + } + }; + GetWinSCPPlugin()->SaveScreen(FSynchronizationSaveScreenHandle); + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(SYNCHRONIZE_PROGRESS_COMPARE_TITLE)); + FSynchronizationStart = Now(); + FSynchronizationCompare = true; + { + SCOPE_EXIT + { + GetWinSCPPlugin()->ClearConsoleTitle(); + GetWinSCPPlugin()->RestoreScreen(FSynchronizationSaveScreenHandle); + }; + { + AChecklist = FTerminal->SynchronizeCollect(LocalDirectory, RemoteDirectory, + Mode, &CopyParam, Params | TTerminal::spNoConfirmation, + MAKE_CALLBACK(TWinSCPFileSystem::TerminalSynchronizeDirectory, this), Options); + } + } + + GetWinSCPPlugin()->SaveScreen(FSynchronizationSaveScreenHandle); + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(SYNCHRONIZE_PROGRESS_TITLE)); + FSynchronizationStart = Now(); + FSynchronizationCompare = false; + { + SCOPE_EXIT + { + GetWinSCPPlugin()->ClearConsoleTitle(); + GetWinSCPPlugin()->RestoreScreen(FSynchronizationSaveScreenHandle); + }; + FTerminal->SynchronizeApply(AChecklist, LocalDirectory, RemoteDirectory, + &CopyParam, Params | TTerminal::spNoConfirmation, + MAKE_CALLBACK(TWinSCPFileSystem::TerminalSynchronizeDirectory, this)); + } + } +} + +bool TWinSCPFileSystem::SynchronizeAllowSelectedOnly() +{ + return + ((*GetPanelInfo())->GetSelectedCount() > 0) || + ((*GetAnotherPanelInfo())->GetSelectedCount() > 0); +} + +void TWinSCPFileSystem::GetSynchronizeOptions( + intptr_t Params, TSynchronizeOptions & Options) +{ + if (FLAGSET(Params, spSelectedOnly) && SynchronizeAllowSelectedOnly()) + { + Options.Filter = new TStringList(); + Options.Filter->SetCaseSensitive(false); + Options.Filter->SetDuplicates(dupAccept); + + TFarPanelInfo ** PanelInfo = GetPanelInfo(); + if (PanelInfo && *PanelInfo && (*PanelInfo)->GetSelectedCount() > 0) + { + CreateFileList((*PanelInfo)->GetItems(), osRemote, true, L"", true, Options.Filter); + } + if ((*GetAnotherPanelInfo())->GetSelectedCount() > 0) + { + CreateFileList((*GetAnotherPanelInfo())->GetItems(), osLocal, true, L"", true, Options.Filter); + } + Options.Filter->Sort(); + } +} + +void TWinSCPFileSystem::FullSynchronize(bool Source) +{ + TFarPanelInfo ** AnotherPanel = GetAnotherPanelInfo(); + RequireLocalPanel(*AnotherPanel, GetMsg(SYNCHRONIZE_LOCAL_PATH_REQUIRED)); + + UnicodeString LocalDirectory = (*AnotherPanel)->GetCurrDirectory(); + UnicodeString RemoteDirectory = FTerminal->GetCurrDirectory(); + + bool SaveMode = !(GetGUIConfiguration()->GetSynchronizeModeAuto() < 0); + TTerminal::TSynchronizeMode Mode = + SaveMode ? static_cast(GetGUIConfiguration()->GetSynchronizeModeAuto()) : + (Source ? TTerminal::smLocal : TTerminal::smRemote); + intptr_t Params = GetGUIConfiguration()->GetSynchronizeParams(); + bool SaveSettings = false; + + TGUICopyParamType CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + TUsableCopyParamAttrs CopyParamAttrs = GetTerminal()->UsableCopyParamAttrs(0); + intptr_t Options = + FLAGMASK(!FTerminal->GetIsCapable(fcTimestampChanging), fsoDisableTimestamp) | + FLAGMASK(SynchronizeAllowSelectedOnly(), fsoAllowSelectedOnly); + if (FullSynchronizeDialog(Mode, Params, LocalDirectory, RemoteDirectory, + &CopyParam, SaveSettings, SaveMode, Options, CopyParamAttrs)) + { + TSynchronizeOptions SynchronizeOptions; + GetSynchronizeOptions(Params, SynchronizeOptions); + + if (SaveSettings) + { + GetGUIConfiguration()->SetSynchronizeParams(Params); + if (SaveMode) + { + GetGUIConfiguration()->SetSynchronizeModeAuto(Mode); + } + } + + std::unique_ptr Checklist; + { + SCOPE_EXIT + { + if (UpdatePanel()) + { + RedrawPanel(); + } + }; + GetWinSCPPlugin()->SaveScreen(FSynchronizationSaveScreenHandle); + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(SYNCHRONIZE_PROGRESS_COMPARE_TITLE)); + FSynchronizationStart = Now(); + FSynchronizationCompare = true; + { + SCOPE_EXIT + { + GetWinSCPPlugin()->ClearConsoleTitle(); + GetWinSCPPlugin()->RestoreScreen(FSynchronizationSaveScreenHandle); + }; + Checklist.reset(FTerminal->SynchronizeCollect(LocalDirectory, RemoteDirectory, + Mode, &CopyParam, Params | TTerminal::spNoConfirmation, + MAKE_CALLBACK(TWinSCPFileSystem::TerminalSynchronizeDirectory, this), &SynchronizeOptions)); + } + + if (Checklist.get() && Checklist->GetCount() == 0) + { + MoreMessageDialog(GetMsg(COMPARE_NO_DIFFERENCES), nullptr, + qtInformation, qaOK); + } + else if (FLAGCLEAR(Params, TTerminal::spPreviewChanges) || + SynchronizeChecklistDialog(Checklist.get(), Mode, Params, + LocalDirectory, RemoteDirectory)) + { + if (FLAGSET(Params, TTerminal::spPreviewChanges)) + { + FSynchronizationStart = Now(); + } + GetWinSCPPlugin()->SaveScreen(FSynchronizationSaveScreenHandle); + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(SYNCHRONIZE_PROGRESS_TITLE)); + FSynchronizationStart = Now(); + FSynchronizationCompare = false; + { + SCOPE_EXIT + { + GetWinSCPPlugin()->ClearConsoleTitle(); + GetWinSCPPlugin()->RestoreScreen(FSynchronizationSaveScreenHandle); + }; + FTerminal->SynchronizeApply(Checklist.get(), LocalDirectory, RemoteDirectory, + &CopyParam, Params | TTerminal::spNoConfirmation, + MAKE_CALLBACK(TWinSCPFileSystem::TerminalSynchronizeDirectory, this)); + } + } + } + } +} + +void TWinSCPFileSystem::TerminalSynchronizeDirectory( + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory, + bool & Continue, bool Collect) +{ + static uint32_t LastTicks; + uint32_t Ticks = ::GetTickCount(); + if ((LastTicks == 0) || (Ticks - LastTicks > 500)) + { + LastTicks = Ticks; + + static const intptr_t ProgressWidth = 48; + static UnicodeString ProgressTitle; + static UnicodeString ProgressTitleCompare; + static UnicodeString LocalLabel; + static UnicodeString RemoteLabel; + static UnicodeString StartTimeLabel; + static UnicodeString TimeElapsedLabel; + + if (ProgressTitle.IsEmpty()) + { + ProgressTitle = GetMsg(SYNCHRONIZE_PROGRESS_TITLE); + ProgressTitleCompare = GetMsg(SYNCHRONIZE_PROGRESS_COMPARE_TITLE); + LocalLabel = GetMsg(SYNCHRONIZE_PROGRESS_LOCAL); + RemoteLabel = GetMsg(SYNCHRONIZE_PROGRESS_REMOTE); + StartTimeLabel = GetMsg(SYNCHRONIZE_PROGRESS_START_TIME); + TimeElapsedLabel = GetMsg(SYNCHRONIZE_PROGRESS_ELAPSED); + } + + UnicodeString Message; + + Message = LocalLabel + core::MinimizeName(LocalDirectory, + ProgressWidth - LocalLabel.Length(), false); + Message += ::StringOfChar(L' ', ProgressWidth - Message.Length()) + L"\n"; + Message += RemoteLabel + core::MinimizeName(RemoteDirectory, + ProgressWidth - RemoteLabel.Length(), true) + L"\n"; + Message += StartTimeLabel + FSynchronizationStart.TimeString(false) + L"\n"; + Message += TimeElapsedLabel + + FormatDateTimeSpan(GetConfiguration()->GetTimeFormat(), TDateTime(Now() - FSynchronizationStart)) + L"\n"; + + GetWinSCPPlugin()->Message(0, (Collect ? ProgressTitleCompare : ProgressTitle), Message); + + if (GetWinSCPPlugin()->CheckForEsc() && + (MoreMessageDialog(GetMsg(CANCEL_OPERATION), nullptr, + qtConfirmation, qaOK | qaCancel) == qaOK)) + { + Continue = false; + } + } +} + +void TWinSCPFileSystem::Synchronize() +{ + TFarPanelInfo ** AnotherPanel = GetAnotherPanelInfo(); + RequireLocalPanel(*AnotherPanel, GetMsg(SYNCHRONIZE_LOCAL_PATH_REQUIRED)); + + TSynchronizeParamType Params; + Params.LocalDirectory = (*AnotherPanel)->GetCurrDirectory(); + Params.RemoteDirectory = FTerminal->GetCurrDirectory(); + int UnusedParams = (GetGUIConfiguration()->GetSynchronizeParams() & + (TTerminal::spPreviewChanges | TTerminal::spTimestamp | + TTerminal::spNotByTime | TTerminal::spBySize)); + Params.Params = GetGUIConfiguration()->GetSynchronizeParams() & ~UnusedParams; + Params.Options = GetGUIConfiguration()->GetSynchronizeOptions(); + TSynchronizeController Controller( + MAKE_CALLBACK(TWinSCPFileSystem::DoSynchronize, this), + MAKE_CALLBACK(TWinSCPFileSystem::DoSynchronizeInvalid, this), + MAKE_CALLBACK(TWinSCPFileSystem::DoSynchronizeTooManyDirectories, this)); + DebugAssert(FSynchronizeController == nullptr); + FSynchronizeController = &Controller; + + { + SCOPE_EXIT + { + FSynchronizeController = nullptr; + // plugin might have been closed during some synchronization already + if (!FClosed) + { + if (UpdatePanel()) + { + RedrawPanel(); + } + } + }; + bool SaveSettings = false; + TCopyParamType CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + DWORD CopyParamAttrs = GetTerminal()->UsableCopyParamAttrs(0).Upload; + uintptr_t Options = + FLAGMASK(SynchronizeAllowSelectedOnly(), soAllowSelectedOnly); + if (SynchronizeDialog(Params, &CopyParam, + MAKE_CALLBACK(TSynchronizeController::StartStop, &Controller), + SaveSettings, Options, CopyParamAttrs, + MAKE_CALLBACK(TWinSCPFileSystem::GetSynchronizeOptions, this)) && + SaveSettings) + { + GetGUIConfiguration()->SetSynchronizeParams(Params.Params | UnusedParams); + GetGUIConfiguration()->SetSynchronizeOptions(Params.Options); + } + } +} + +void TWinSCPFileSystem::DoSynchronize( + TSynchronizeController * /*Sender*/, const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, const TCopyParamType & CopyParam, + const TSynchronizeParamType & Params, TSynchronizeChecklist ** Checklist, + TSynchronizeOptions * Options, bool Full) +{ + try + { + intptr_t PParams = Params.Params; + if (!Full) + { + PParams |= TTerminal::spNoRecurse | TTerminal::spUseCache | + TTerminal::spDelayProgress | TTerminal::spSubDirs; + } + else + { + // if keepuptodate is non-recursive, + // full sync before has to be non-recursive as well + if (FLAGCLEAR(Params.Options, soRecurse)) + { + PParams |= TTerminal::spNoRecurse; + } + } + Synchronize(LocalDirectory, RemoteDirectory, TTerminal::smRemote, CopyParam, + PParams, Checklist, Options); + } + catch (Exception & E) + { + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + throw; + } +} + +void TWinSCPFileSystem::DoSynchronizeInvalid( + TSynchronizeController * /*Sender*/, const UnicodeString & Directory, + const UnicodeString & /*ErrorStr*/) +{ + UnicodeString Message; + if (!Directory.IsEmpty()) + { + Message = FORMAT(GetMsg(WATCH_ERROR_DIRECTORY).c_str(), Directory.c_str()); + } + else + { + Message = GetMsg(WATCH_ERROR_GENERAL); + } + + MoreMessageDialog(Message, nullptr, qtError, qaOK); +} + +void TWinSCPFileSystem::DoSynchronizeTooManyDirectories( + TSynchronizeController * /*Sender*/, intptr_t & MaxDirectories) +{ + if (MaxDirectories < GetGUIConfiguration()->GetMaxWatchDirectories()) + { + MaxDirectories = GetGUIConfiguration()->GetMaxWatchDirectories(); + } + else + { + TMessageParams Params; + Params.Params = qpNeverAskAgainCheck; + uintptr_t Result = MoreMessageDialog( + FORMAT(GetMsg(TOO_MANY_WATCH_DIRECTORIES).c_str(), MaxDirectories, MaxDirectories), nullptr, + qtConfirmation, qaYes | qaNo, &Params); + + if ((Result == qaYes) || (Result == qaNeverAskAgain)) + { + MaxDirectories *= 2; + if (Result == qaNeverAskAgain) + { + GetGUIConfiguration()->SetMaxWatchDirectories(MaxDirectories); + } + } + else + { + Abort(); + } + } +} + +void TWinSCPFileSystem::CustomCommandGetParamValue( + const UnicodeString & AName, UnicodeString & Value) +{ + UnicodeString Name = AName; + if (Name.IsEmpty()) + { + Name = GetMsg(APPLY_COMMAND_PARAM_PROMPT); + } + if (!GetWinSCPPlugin()->InputBox(GetMsg(APPLY_COMMAND_PARAM_TITLE), + Name, Value, 0, APPLY_COMMAND_PARAM_HISTORY)) + { + Abort(); + } +} + +void TWinSCPFileSystem::TransferFiles(bool Move) +{ + if (Move) + { + RequireCapability(fcRemoteMove); + } + + if (Move || EnsureCommandSessionFallback(fcRemoteCopy)) + { + std::unique_ptr FileList(CreateSelectedFileList(osRemote)); + if (FileList.get()) + { + DebugAssert(!FPanelItems); + UnicodeString Target = FTerminal->GetCurrDirectory(); + UnicodeString FileMask = L"*.*"; + if (FileList->GetCount() == 1) + FileMask = base::UnixExtractFileName(FileList->GetString(0)); + if (RemoteTransferDialog(FileList.get(), Target, FileMask, Move)) + { + SCOPE_EXIT + { + (*GetPanelInfo())->ApplySelection(); + if (UpdatePanel()) + { + RedrawPanel(); + } + }; + if (Move) + { + GetTerminal()->MoveFiles(FileList.get(), Target, FileMask); + } + else + { + GetTerminal()->CopyFiles(FileList.get(), Target, FileMask); + } + } + } + } +} + +void TWinSCPFileSystem::RenameFile() +{ + const TFarPanelItem * PanelItem = (*GetPanelInfo())->GetFocusedItem(); + DebugAssert(PanelItem != nullptr); + + if (!PanelItem->GetIsParentDirectory()) + { + RequireCapability(fcRename); + + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, PanelItem->GetUserData()); + UnicodeString NewName = File->GetFileName(); + if (RenameFileDialog(File, NewName)) + { + SCOPE_EXIT + { + if (UpdatePanel()) + { + RedrawPanel(); + } + }; + GetTerminal()->TerminalRenameFile(File, NewName, true); + } + } +} + +void TWinSCPFileSystem::FileProperties() +{ + std::unique_ptr FileList(CreateSelectedFileList(osRemote)); + if (FileList.get()) + { + DebugAssert(!FPanelItems); + TRemoteProperties CurrentProperties; + + bool Cont = true; + if (!GetTerminal()->LoadFilesProperties(FileList.get())) + { + if (UpdatePanel()) + { + RedrawPanel(); + } + else + { + Cont = false; + } + } + + if (Cont) + { + CurrentProperties = TRemoteProperties::CommonProperties(FileList.get()); + + intptr_t Flags = 0; + if (FTerminal->GetIsCapable(fcModeChanging)) + { + Flags |= cpMode; + } + if (FTerminal->GetIsCapable(fcOwnerChanging)) + { + Flags |= cpOwner; + } + if (FTerminal->GetIsCapable(fcGroupChanging)) + { + Flags |= cpGroup; + } + + TRemoteProperties NewProperties = CurrentProperties; + if (PropertiesDialog(FileList.get(), FTerminal->GetCurrDirectory(), + FTerminal->GetGroups(), FTerminal->GetUsers(), &NewProperties, Flags)) + { + NewProperties = TRemoteProperties::ChangedProperties(CurrentProperties, + NewProperties); + SCOPE_EXIT + { + (*GetPanelInfo())->ApplySelection(); + if (UpdatePanel()) + { + RedrawPanel(); + } + }; + FTerminal->ChangeFilesProperties(FileList.get(), &NewProperties); + } + } + } +} + +void TWinSCPFileSystem::InsertTokenOnCommandLine(const UnicodeString & Token, bool Separate) +{ + UnicodeString Token2 = Token; + if (!Token2.IsEmpty()) + { + if (Token2.Pos(L' ') > 0) + { + Token2 = FORMAT(L"\"%s\"", Token2.c_str()); + } + + if (Separate) + { + Token2 += L" "; + } + + FarControl(FCTL_INSERTCMDLINE, 0, reinterpret_cast(Token2.c_str())); + } +} + +void TWinSCPFileSystem::InsertSessionNameOnCommandLine() +{ + const TFarPanelItem * Focused = (*GetPanelInfo())->GetFocusedItem(); + + if (Focused != nullptr) + { + TSessionData * SessionData = NB_STATIC_DOWNCAST(TSessionData, Focused->GetUserData()); + UnicodeString Name; + if (SessionData != nullptr) + { + Name = SessionData->GetName(); + } + else + { + Name = core::UnixIncludeTrailingBackslash(FSessionsFolder); + if (!Focused->GetIsParentDirectory()) + { + Name = core::UnixIncludeTrailingBackslash(Name + Focused->GetFileName()); + } + } + InsertTokenOnCommandLine(Name, true); + } +} + +void TWinSCPFileSystem::InsertFileNameOnCommandLine(bool Full) +{ + const TFarPanelItem * Focused = (*GetPanelInfo())->GetFocusedItem(); + + if (Focused != nullptr) + { + if (!Focused->GetIsParentDirectory()) + { + const TRemoteFile * File = reinterpret_cast(Focused->GetUserData()); + if (File != nullptr) + { + UnicodeString Path; + if (Full) + { + // Get full address (with server address) + Path = GetFullFilePath(File); + } + else + { + Path = File->GetFileName(); + } + InsertTokenOnCommandLine(Path, true); + } + } + else + { + InsertTokenOnCommandLine(core::UnixIncludeTrailingBackslash(FTerminal->GetCurrDirectory()), true); + } + } +} + +UnicodeString TWinSCPFileSystem::GetFullFilePath(const TRemoteFile * AFile) const +{ + UnicodeString SessionUrl = GetSessionUrl(FTerminal, true); + UnicodeString Result = FORMAT(L"%s%s", SessionUrl.c_str(), AFile->GetFullFileName().c_str()); + return Result; +} + +// not used +void TWinSCPFileSystem::InsertPathOnCommandLine() +{ + InsertTokenOnCommandLine(FTerminal->GetCurrDirectory(), false); +} + +void TWinSCPFileSystem::CopyFullFileNamesToClipboard() +{ + std::unique_ptr FileList(CreateSelectedFileList(osRemote)); + std::unique_ptr FileNames(new TStringList()); + if (FileList.get() != nullptr) + { + for (intptr_t Index = 0; Index < FileList->GetCount(); ++Index) + { + const TRemoteFile * File = reinterpret_cast(FileList->GetObj(Index)); + if (File != nullptr) + { + FileNames->Add(GetFullFilePath(File)); + } + else + { + DebugAssert(false); + } + } + } + else + { + TFarPanelInfo * const * PanelInfo = GetPanelInfo(); + if (PanelInfo && *PanelInfo && ((*PanelInfo)->GetSelectedCount() == 0) && + (*PanelInfo)->GetFocusedItem()->GetIsParentDirectory()) + { + FileNames->Add(core::UnixIncludeTrailingBackslash(FTerminal->GetCurrDirectory())); + } + } + + GetWinSCPPlugin()->FarCopyToClipboard(FileNames.get()); +} + +void TWinSCPFileSystem::GetSpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable, bool & Close) +{ + // terminal can be already closed (e.g. dropped connection) + if ((GetTerminal() != nullptr) && GetTerminal()->GetIsCapable(fcCheckingSpaceAvailable)) + { + try + { + GetTerminal()->SpaceAvailable(APath, ASpaceAvailable); + } + catch (Exception & E) + { + if (!GetTerminal()->GetActive()) + { + Close = true; + } + DEBUG_PRINTF("before HandleException"); + HandleException(&E); + } + } +} + +void TWinSCPFileSystem::ShowInformation() +{ + const TSessionInfo & SessionInfo = GetTerminal()->GetSessionInfo(); + TFileSystemInfo FileSystemInfo = GetTerminal()->GetFileSystemInfo(); + TGetSpaceAvailableEvent OnGetSpaceAvailable; + if (GetTerminal()->GetIsCapable(fcCheckingSpaceAvailable)) + { + OnGetSpaceAvailable = MAKE_CALLBACK(TWinSCPFileSystem::GetSpaceAvailable, this); + } + FileSystemInfoDialog(SessionInfo, FileSystemInfo, GetTerminal()->GetCurrDirectory(), + OnGetSpaceAvailable); +} + +bool TWinSCPFileSystem::AreCachesEmpty() +{ + DebugAssert(Connected()); + return FTerminal->GetAreCachesEmpty(); +} + +void TWinSCPFileSystem::ClearCaches() +{ + DebugAssert(Connected()); + FTerminal->ClearCaches(); +} + +void TWinSCPFileSystem::OpenSessionInPutty() +{ + DebugAssert(Connected()); + ::OpenSessionInPutty(GetGUIConfiguration()->GetPuttyPath(), GetSessionData()); +} + +void TWinSCPFileSystem::QueueShow(bool ClosingPlugin) +{ + DebugAssert(Connected()); + TTerminalQueueStatus * QueueStatus = GetQueueStatus(); + DebugAssert(QueueStatus != nullptr); + QueueDialog(QueueStatus, ClosingPlugin); + ProcessQueue(true); +} + +void TWinSCPFileSystem::OpenDirectory(bool Add) +{ + std::unique_ptr BookmarkList(new TBookmarkList()); + UnicodeString Directory = FTerminal->GetCurrDirectory(); + UnicodeString SessionKey = GetSessionData()->GetSessionKey(); + TBookmarkList * CurrentBookmarkList; + + CurrentBookmarkList = GetFarConfiguration()->GetBookmarks(SessionKey); + if (CurrentBookmarkList != nullptr) + { + BookmarkList->Assign(CurrentBookmarkList); + } + + if (Add) + { + TBookmark * Bookmark = new TBookmark(); + Bookmark->SetRemote(Directory); + Bookmark->SetName(Directory); + BookmarkList->Add(Bookmark); + GetFarConfiguration()->SetBookmarks(SessionKey, BookmarkList.get()); + } + + bool Result = OpenDirectoryDialog(Add, Directory, BookmarkList.get()); + + GetFarConfiguration()->SetBookmarks(SessionKey, BookmarkList.get()); + GetConfiguration()->Save(); + + if (Result) + { + FTerminal->RemoteChangeDirectory(Directory); + if (UpdatePanel(true)) + { + RedrawPanel(); + } + } +} + +void TWinSCPFileSystem::HomeDirectory() +{ + FTerminal->HomeDirectory(); + if (UpdatePanel(true)) + { + RedrawPanel(); + } +} + +bool TWinSCPFileSystem::IsSynchronizedBrowsing() const +{ + return FSynchronisingBrowse; +} + +void TWinSCPFileSystem::ToggleSynchronizeBrowsing() +{ + FSynchronisingBrowse = !FSynchronisingBrowse; + + if (GetFarConfiguration()->GetConfirmSynchronizedBrowsing()) + { + UnicodeString Message = FSynchronisingBrowse ? + GetMsg(SYNCHRONIZE_BROWSING_ON) : GetMsg(SYNCHRONIZE_BROWSING_OFF); + TMessageParams Params; + Params.Params = qpNeverAskAgainCheck; + if (MoreMessageDialog(Message, nullptr, qtInformation, qaOK, &Params) == + qaNeverAskAgain) + { + GetFarConfiguration()->SetConfirmSynchronizedBrowsing(false); + } + } +} + +bool TWinSCPFileSystem::SynchronizeBrowsing(const UnicodeString & NewPath) +{ + bool Result; + TFarPanelInfo ** AnotherPanel = GetAnotherPanelInfo(); + UnicodeString OldPath = AnotherPanel && *AnotherPanel ? (*AnotherPanel)->GetCurrDirectory() : L""; + // IncludeTrailingBackslash to expand C: to C:\. + UnicodeString LocalPath = ::IncludeTrailingBackslash(NewPath); + if (!FarControl(FCTL_SETPANELDIR, + 0, + reinterpret_cast(LocalPath.c_str()), + reinterpret_cast(PANEL_PASSIVE))) + { + Result = false; + } + else + { + ResetCachedInfo(); + AnotherPanel = GetAnotherPanelInfo(); + if (AnotherPanel && *AnotherPanel && !ComparePaths((*AnotherPanel)->GetCurrDirectory(), NewPath)) + { + // FAR WORKAROUND + // If FCTL_SETPANELDIR above fails, Far default current + // directory to initial (?) one. So move this back to + // previous directory. + FarControl(FCTL_SETPANELDIR, + 0, + reinterpret_cast(OldPath.c_str()), + reinterpret_cast(PANEL_PASSIVE)); + Result = false; + } + else + { + RedrawPanel(true); + Result = true; + } + } + + return Result; +} + +bool TWinSCPFileSystem::SetDirectoryEx(const UnicodeString & Dir, int OpMode) +{ + if (!IsSessionList() && !Connected()) + { + return false; + } + // FAR WORKAROUND + // workaround to ignore "change to root directory" command issued by FAR, + // before file is opened for viewing/editing from "find file" dialog + // when plugin uses UNIX style paths + else if ((OpMode & OPM_FIND) && (OpMode & OPM_SILENT) && (Dir == L"\\")) + { + if (FSavedFindFolder.IsEmpty()) + { + return true; + } + else + { + bool Result = false; + SCOPE_EXIT + { + FSavedFindFolder.Clear(); + }; + Result = SetDirectoryEx(FSavedFindFolder, OpMode); + return Result; + } + } + else + { + if ((OpMode & OPM_FIND) && FSavedFindFolder.IsEmpty() && FTerminal) + { + FSavedFindFolder = FTerminal->GetCurrDirectory(); + } + + if (IsSessionList()) + { + FSessionsFolder = core::AbsolutePath(ROOTDIRECTORY + FSessionsFolder, Dir); + DebugAssert(FSessionsFolder[1] == L'/'); + FSessionsFolder.Delete(1, 1); + FNewSessionsFolder.Clear(); + } + else + { + DebugAssert(!FNoProgress); + bool Normal = FLAGCLEAR(OpMode, OPM_FIND | OPM_SILENT); + UnicodeString PrevPath = FTerminal ? FTerminal->GetCurrDirectory() : L""; + FNoProgress = !Normal; + if (!FNoProgress) + { + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(CHANGING_DIRECTORY_TITLE)); + } + if (FTerminal) + { + FTerminal->SetExceptionOnFail(true); + } + { + SCOPE_EXIT + { + if (FTerminal) + { + FTerminal->SetExceptionOnFail(false); + } + if (!FNoProgress) + { + GetWinSCPPlugin()->ClearConsoleTitle(); + } + FNoProgress = false; + }; + if (Dir == L"\\") + { + FTerminal->RemoteChangeDirectory(ROOTDIRECTORY); + } + else if ((Dir == PARENTDIRECTORY) && (FTerminal->GetCurrDirectory() == ROOTDIRECTORY)) + { + // ClosePlugin(); + Disconnect(); + } + else + { + FTerminal->RemoteChangeDirectory(Dir); + FCurrentDirectoryWasChanged = true; + } + } + + if (FTerminal && Normal && FSynchronisingBrowse && + (PrevPath != FTerminal->GetCurrDirectory())) + { + TFarPanelInfo ** AnotherPanel = GetAnotherPanelInfo(); + if (AnotherPanel && *AnotherPanel && ((*AnotherPanel)->GetIsPlugin() || ((*AnotherPanel)->GetType() != ptFile))) + { + MoreMessageDialog(GetMsg(SYNCHRONIZE_LOCAL_PATH_REQUIRED), nullptr, qtError, qaOK); + } + else if (AnotherPanel && *AnotherPanel) + { + try + { + UnicodeString RemotePath = core::UnixIncludeTrailingBackslash(FTerminal->GetCurrDirectory()); + UnicodeString FullPrevPath = core::UnixIncludeTrailingBackslash(PrevPath); + UnicodeString LocalPath; + if (RemotePath.SubString(1, FullPrevPath.Length()) == FullPrevPath && AnotherPanel) + { + LocalPath = ::IncludeTrailingBackslash((*AnotherPanel)->GetCurrDirectory()) + + core::FromUnixPath(RemotePath.SubString(FullPrevPath.Length() + 1, + RemotePath.Length() - FullPrevPath.Length())); + } + else if (FullPrevPath.SubString(1, RemotePath.Length()) == RemotePath && AnotherPanel) + { + UnicodeString NewLocalPath; + LocalPath = ::ExcludeTrailingBackslash((*AnotherPanel)->GetCurrDirectory()); + while (!core::UnixSamePath(FullPrevPath, RemotePath)) + { + NewLocalPath = ::ExcludeTrailingBackslash(::ExtractFileDir(LocalPath)); + if (NewLocalPath == LocalPath) + { + Abort(); + } + LocalPath = NewLocalPath; + FullPrevPath = core::UnixExtractFilePath(core::UnixExcludeTrailingBackslash(FullPrevPath)); + } + } + else + { + Abort(); + } + + if (!SynchronizeBrowsing(LocalPath)) + { + if (MoreMessageDialog(FORMAT(GetMsg(SYNC_DIR_BROWSE_CREATE).c_str(), LocalPath.c_str()), + nullptr, qtInformation, qaYes | qaNo) == qaYes) + { + if (!::ForceDirectories(ApiPath(LocalPath))) + { + ::RaiseLastOSError(); + } + else + { + if (!SynchronizeBrowsing(LocalPath)) + { + Abort(); + } + } + } + else + { + FSynchronisingBrowse = false; + } + } + } + catch (Exception & E) + { + FSynchronisingBrowse = false; + GetWinSCPPlugin()->ShowExtendedException(&E); + MoreMessageDialog(GetMsg(SYNC_DIR_BROWSE_ERROR), nullptr, qtInformation, qaOK); + } + } + } + } + + return true; + } +} + +intptr_t TWinSCPFileSystem::MakeDirectoryEx(UnicodeString & Name, int OpMode) +{ + if (Connected()) + { + DebugAssert(!(OpMode & OPM_SILENT) || !Name.IsEmpty()); + + TRemoteProperties Properties = GetGUIConfiguration()->GetNewDirectoryProperties(); + bool SaveSettings = false; + + if ((OpMode & OPM_SILENT) || + CreateDirectoryDialog(Name, &Properties, SaveSettings)) + { + if (SaveSettings) + { + GetGUIConfiguration()->SetNewDirectoryProperties(Properties); + } + + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(CREATING_FOLDER)); + SCOPE_EXIT + { + GetWinSCPPlugin()->ClearConsoleTitle(); + }; + FTerminal->RemoteCreateDirectory(Name, &Properties); + return 1; + } + else + { + Name.Clear(); + return -1; + } + } + else if (IsSessionList()) + { + DebugAssert(!(OpMode & OPM_SILENT) || !Name.IsEmpty()); + + if (((OpMode & OPM_SILENT) || + GetWinSCPPlugin()->InputBox(GetMsg(CREATE_FOLDER_TITLE), + ::StripHotkey(GetMsg(CREATE_FOLDER_PROMPT)), + Name, 0, MAKE_SESSION_FOLDER_HISTORY)) && + !Name.IsEmpty()) + { + TSessionData::ValidateName(Name); + FNewSessionsFolder = Name; + return 1; + } + else + { + Name.Clear(); + return -1; + } + } + else + { + Name.Clear(); + return -1; + } +} + +void TWinSCPFileSystem::DeleteSession(TSessionData * Data, void * /*Param*/) +{ + Data->Remove(); + StoredSessions->Remove(Data); +} + +void TWinSCPFileSystem::ProcessSessions(TObjectList * PanelItems, + TProcessSessionEvent ProcessSession, void * Param) +{ + for (intptr_t Index = 0; Index < PanelItems->GetCount(); ++Index) + { + TFarPanelItem * PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(Index)); + DebugAssert(PanelItem); + if (PanelItem->GetIsFile()) + { + if (PanelItem->GetUserData() != nullptr) + { + ProcessSession(NB_STATIC_DOWNCAST(TSessionData, PanelItem->GetUserData()), Param); + PanelItem->SetSelected(false); + } + else + { + UnicodeString Msg = GetMsg(NEW_SESSION_HINT); + DebugAssert(PanelItem->GetFileName() == Msg); + } + } + else + { + DebugAssert(PanelItem->GetUserData() == nullptr); + UnicodeString Folder = core::UnixIncludeTrailingBackslash( + core::UnixIncludeTrailingBackslash(FSessionsFolder) + PanelItem->GetFileName()); + intptr_t Index2 = 0; + while (Index2 < StoredSessions->GetCount()) + { + TSessionData * Data = StoredSessions->GetSession(Index2); + if (Data->GetName().SubString(1, Folder.Length()) == Folder) + { + ProcessSession(Data, Param); + if ((Index2 < StoredSessions->GetCount()) && StoredSessions->GetSession(Index2) != Data) + { + Index2--; + } + } + Index2++; + } + PanelItem->SetSelected(false); + } + } +} + +bool TWinSCPFileSystem::DeleteFilesEx(TObjectList * PanelItems, int OpMode) +{ + if (Connected()) + { + FFileList = CreateFileList(PanelItems, osRemote); + FPanelItems = PanelItems; + SCOPE_EXIT + { + FPanelItems = nullptr; + SAFE_DESTROY(FFileList); + }; + UnicodeString Query; + bool Recycle = GetSessionData()->GetDeleteToRecycleBin() && + !FTerminal->IsRecycledFile(FFileList->GetString(0)); //-V522 + if (PanelItems->GetCount() > 1) + { + Query = FORMAT(GetMsg(Recycle ? RECYCLE_FILES_CONFIRM : DELETE_FILES_CONFIRM).c_str(), + PanelItems->GetCount()); + } + else + { + Query = FORMAT(GetMsg(Recycle ? RECYCLE_FILE_CONFIRM : DELETE_FILE_CONFIRM).c_str(), + NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(0))->GetFileName().c_str()); + } + + if ((OpMode & OPM_SILENT) || !GetFarConfiguration()->GetConfirmDeleting() || + (MoreMessageDialog(Query, nullptr, qtConfirmation, qaOK | qaCancel) == qaOK)) + { + FTerminal->RemoteDeleteFiles(FFileList); + } + return true; + } + else if (IsSessionList()) + { + if ((OpMode & OPM_SILENT) || !GetFarConfiguration()->GetConfirmDeleting() || + (MoreMessageDialog(GetMsg(DELETE_SESSIONS_CONFIRM), nullptr, qtConfirmation, qaOK | qaCancel) == qaOK)) + { + ProcessSessions(PanelItems, MAKE_CALLBACK(TWinSCPFileSystem::DeleteSession, this), nullptr); + } + return true; + } + else + { + return false; + } +} + +void TWinSCPFileSystem::QueueAddItem(TQueueItem * Item) +{ + GetFarConfiguration()->CacheFarSettings(); + GetQueue()->AddItem(Item); +} + +struct TExportSessionParam +{ + UnicodeString DestPath; +}; + +intptr_t TWinSCPFileSystem::GetFilesEx(TObjectList * PanelItems, bool Move, + UnicodeString & DestPath, int OpMode) +{ + intptr_t Result = -1; + if (Connected()) + { + FFileList = CreateFileList(PanelItems, osRemote); + SCOPE_EXIT + { + FPanelItems = nullptr; + SAFE_DESTROY(FFileList); + }; + Result = GetFilesRemote(PanelItems, Move, DestPath, OpMode); + } + else if (IsSessionList()) + { + UnicodeString Title = GetMsg(EXPORT_SESSION_TITLE); + UnicodeString Prompt; + if (PanelItems->GetCount() == 1) + { + Prompt = FORMAT(GetMsg(EXPORT_SESSION_PROMPT).c_str(), + NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(0))->GetFileName().c_str()); + } + else + { + Prompt = FORMAT(GetMsg(EXPORT_SESSIONS_PROMPT).c_str(), PanelItems->GetCount()); + } + + bool AResult = (OpMode & OPM_SILENT) || + GetWinSCPPlugin()->InputBox(Title, Prompt, DestPath, 0, L"Copy"); + if (AResult) + { + TExportSessionParam Param; + Param.DestPath = DestPath; + ProcessSessions(PanelItems, MAKE_CALLBACK(TWinSCPFileSystem::ExportSession, this), &Param); + Result = 1; + } + } + return Result; +} + +intptr_t TWinSCPFileSystem::GetFilesRemote(TObjectList * PanelItems, bool Move, + UnicodeString & DestPath, int OpMode) +{ + intptr_t Result = -1; + bool EditView = (OpMode & (OPM_EDIT | OPM_VIEW)) != 0; + bool Confirmed = + (OpMode & OPM_SILENT) && + (!EditView || GetFarConfiguration()->GetEditorDownloadDefaultMode()); + + TGUICopyParamType CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + if (EditView) + { + EditViewCopyParam(CopyParam); + } + + // these parameters are known in advance + intptr_t Params = + FLAGMASK(Move, cpDelete); + + if (!Confirmed) + { + intptr_t CopyParamAttrs = + GetTerminal()->UsableCopyParamAttrs(Params).Download; + ; // FLAGMASK(EditView, cpaNoExcludeMask); + + uintptr_t Options = + FLAGMASK(EditView, coTempTransfer | coDisableNewerOnly); + Confirmed = CopyDialog(false, Move, FFileList, + Options, CopyParamAttrs, + DestPath, &CopyParam); + + if (Confirmed && !EditView && CopyParam.GetQueue()) + { + // these parameters are known only after transfer dialog + Params |= + FLAGMASK(CopyParam.GetQueueNoConfirmation(), cpNoConfirmation) | + FLAGMASK(CopyParam.GetNewerOnly(), cpNewerOnly); + QueueAddItem(new TDownloadQueueItem(FTerminal, FFileList, + DestPath, &CopyParam, Params, false)); + Confirmed = false; + } + } + + if (Confirmed) + { + if ((FFileList->GetCount() == 1) && (OpMode & OPM_EDIT)) + { + FOriginalEditFile = ::IncludeTrailingBackslash(DestPath) + + base::UnixExtractFileName(FFileList->GetString(0)); + FLastEditFile = FOriginalEditFile; + FLastEditCopyParam = CopyParam; + FLastEditorID = -1; + } + else + { + FOriginalEditFile.Clear(); + FLastEditFile.Clear(); + FLastEditorID = -1; + } + + FPanelItems = PanelItems; + // these parameters are known only after transfer dialog + Params |= + FLAGMASK(EditView, cpTemporary) | + FLAGMASK(CopyParam.GetNewerOnly(), cpNewerOnly); + FTerminal->CopyToLocal(FFileList, DestPath, &CopyParam, Params); + Result = 1; + } + return Result; +} + +TTerminalQueue * TWinSCPFileSystem::GetQueue() +{ + if (FQueue == nullptr) + { + FQueue = new TTerminalQueue(FTerminal, GetConfiguration()); + FQueue->Init(); + FQueue->SetTransfersLimit(GetGUIConfiguration()->GetQueueTransfersLimit()); + FQueue->SetOnQueryUser(MAKE_CALLBACK(TWinSCPFileSystem::TerminalQueryUser, this)); + FQueue->SetOnPromptUser(MAKE_CALLBACK(TWinSCPFileSystem::TerminalPromptUser, this)); + FQueue->SetOnShowExtendedException(MAKE_CALLBACK(TWinSCPFileSystem::TerminalShowExtendedException, this)); + FQueue->SetOnListUpdate(MAKE_CALLBACK(TWinSCPFileSystem::QueueListUpdate, this)); + FQueue->SetOnQueueItemUpdate(MAKE_CALLBACK(TWinSCPFileSystem::QueueItemUpdate, this)); + FQueue->SetOnEvent(MAKE_CALLBACK(TWinSCPFileSystem::QueueEvent, this)); + } + return FQueue; +} + +TTerminalQueueStatus * TWinSCPFileSystem::GetQueueStatus() +{ + if (FQueueStatus == nullptr) + { + FQueueStatus = GetQueue()->CreateStatus(nullptr); + } + return FQueueStatus; +} + +void TWinSCPFileSystem::ExportSession(TSessionData * Data, void * AParam) +{ + TExportSessionParam & Param = *static_cast(AParam); + + std::unique_ptr ExportData(new TSessionData(Data->GetName())); + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + ExportData->Assign(Data); + ExportData->SetModified(true); + UnicodeString XmlFileName = ::IncludeTrailingBackslash(Param.DestPath) + + ::ValidLocalFileName(::ExtractFilename(ExportData->GetName())) + L".netbox"; + std::unique_ptr ExportStorage(new TXmlStorage(XmlFileName, GetConfiguration()->GetStoredSessionsSubKey())); + ExportStorage->Init(); + ExportStorage->SetAccessMode(smReadWrite); + if (ExportStorage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), true)) + { + ExportData->Save(ExportStorage.get(), false, FactoryDefaults.get()); + } +} + +intptr_t TWinSCPFileSystem::UploadFiles(bool Move, int OpMode, bool Edit, + UnicodeString & DestPath) +{ + intptr_t Result = 1; + bool Confirmed = (OpMode & OPM_SILENT); + bool Ask = !Confirmed; + + TGUICopyParamType CopyParam; + + if (Edit) + { + CopyParam = FLastEditCopyParam; + Confirmed = GetFarConfiguration()->GetEditorUploadSameOptions(); + Ask = false; + } + else + { + CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + } + + // these parameters are known in advance + intptr_t Params = + FLAGMASK(Move, cpDelete); + + if (!Confirmed) + { + intptr_t CopyParamAttrs = + GetTerminal()->UsableCopyParamAttrs(Params).Upload | + FLAGMASK(Edit, cpaNoClearArchive); + // heuristics: do not ask for target directory when uploaded file + // was downloaded in edit mode + uintptr_t Options = + FLAGMASK(Edit, coTempTransfer) | + FLAGMASK(Edit || !GetTerminal()->GetIsCapable(fcNewerOnlyUpload), coDisableNewerOnly); + Confirmed = CopyDialog(true, Move, FFileList, + Options, CopyParamAttrs, + DestPath, &CopyParam); + + if (Confirmed && !Edit && CopyParam.GetQueue()) + { + // these parameters are known only after transfer dialog + Params |= + FLAGMASK(CopyParam.GetQueueNoConfirmation(), cpNoConfirmation) | + FLAGMASK(CopyParam.GetNewerOnly(), cpNewerOnly); + QueueAddItem(new TUploadQueueItem(FTerminal, FFileList, + DestPath, &CopyParam, Params, false)); + Confirmed = false; + } + } + + if (Confirmed) + { + DebugAssert(!FNoProgressFinish); + // it does not make sense to unselect file being uploaded from editor, + // moreover we may upload the file under name that does not exist in + // remote panel + FNoProgressFinish = Edit; + { + SCOPE_EXIT + { + FNoProgressFinish = false; + }; + // these parameters are known only after transfer dialog + Params |= + FLAGMASK(!Ask, cpNoConfirmation) | + FLAGMASK(Edit, cpTemporary) | + FLAGMASK(CopyParam.GetNewerOnly(), cpNewerOnly); + FTerminal->CopyToRemote(FFileList, DestPath, &CopyParam, Params); + } + } + else + { + Result = -1; + } + return Result; +} + +intptr_t TWinSCPFileSystem::PutFilesEx(TObjectList * PanelItems, bool Move, int OpMode) +{ + intptr_t Result; + if (Connected()) + { + FFileList = CreateFileList(PanelItems, osLocal); + SCOPE_EXIT + { + FPanelItems = nullptr; + SAFE_DESTROY(FFileList); + }; + FPanelItems = PanelItems; + + // if file is saved under different name, FAR tries to upload original file, + // but let's be robust and check for new name, in case it changes. + // OPM_EDIT is set since 1.70 final, only. + // When comparing, beware that one path may be long path and the other short + // (since 1.70 alpha 6, DestPath in GetFiles is short path, + // while current path in PutFiles is long path) + if (FLAGCLEAR(OpMode, OPM_SILENT) && (FFileList->GetCount() == 1) && //-V522 + (CompareFileName(FFileList->GetString(0), FOriginalEditFile) || //-V522 + CompareFileName(FFileList->GetString(0), FLastEditFile))) + { + // editor should be closed already + DebugAssert(FLastEditorID < 0); + + if (GetFarConfiguration()->GetEditorUploadOnSave()) + { + // already uploaded from EE_REDRAW + Result = -1; + } + else + { + // just in case file was saved under different name + FFileList->SetString(0, FLastEditFile); + + FOriginalEditFile.Clear(); + FLastEditFile.Clear(); + + UnicodeString CurrentDirectory = FTerminal->GetCurrDirectory(); + Result = UploadFiles(Move, OpMode, true, CurrentDirectory); + FTerminal->TerminalSetCurrentDirectory(CurrentDirectory); + } + } + else + { + UnicodeString CurrentDirectory = FTerminal->GetCurrDirectory(); + Result = UploadFiles(Move, OpMode, false, CurrentDirectory); + FTerminal->TerminalSetCurrentDirectory(CurrentDirectory); + } + } + else if (IsSessionList()) + { + if (!ImportSessions(PanelItems, Move, OpMode)) + { + Result = -1; + } + else + { + Result = 1; + } + } + else + { + Result = -1; + } + return Result; +} + +bool TWinSCPFileSystem::ImportSessions(TObjectList * PanelItems, bool /*Move*/, + int OpMode) +{ + bool Result = (OpMode & OPM_SILENT) || + (MoreMessageDialog(GetMsg(IMPORT_SESSIONS_PROMPT), nullptr, + qtConfirmation, qaYes | qaNo) == qaYes); + + if (Result) + { + UnicodeString FileName; + TFarPanelItem * PanelItem; + for (intptr_t Index = 0; Index < PanelItems->GetCount(); ++Index) + { + PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(Index)); + bool AnyData = false; + FileName = PanelItem->GetFileName(); + if (PanelItem->GetIsFile()) + { + UnicodeString XmlFileName = ::IncludeTrailingBackslash(::GetCurrentDir()) + FileName; + std::unique_ptr ImportStorage(new TXmlStorage(XmlFileName, GetConfiguration()->GetStoredSessionsSubKey())); + ImportStorage->Init(); + ImportStorage->SetAccessMode(smRead); + if (ImportStorage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), false) && + ImportStorage->HasSubKeys()) + { + AnyData = true; + StoredSessions->Load(ImportStorage.get(), /*AsModified*/ true, /*UseDefaults*/ true); + // modified only, explicit + StoredSessions->Save(false, true); + } + } + if (!AnyData) + { + throw Exception(FORMAT(GetMsg(IMPORT_SESSIONS_EMPTY).c_str(), FileName.c_str())); + } + } + } + return Result; +} + +TStrings * TWinSCPFileSystem::CreateFocusedFileList(TOperationSide Side, TFarPanelInfo ** PanelInfo) +{ + if (!PanelInfo || !*PanelInfo) + { + PanelInfo = this->GetPanelInfo(); + } + + TStrings * Result; + const TFarPanelItem * PanelItem = (*PanelInfo)->GetFocusedItem(); + if (PanelItem->GetIsParentDirectory()) + { + Result = nullptr; + } + else + { + Result = new TStringList(); + DebugAssert((Side == osLocal) || PanelItem->GetUserData()); + UnicodeString FileName = PanelItem->GetFileName(); + if (Side == osLocal) + { + FileName = ::IncludeTrailingBackslash((*PanelInfo)->GetCurrDirectory()) + FileName; + } + Result->AddObject(FileName, static_cast(PanelItem->GetUserData())); + } + return Result; +} + +TStrings * TWinSCPFileSystem::CreateSelectedFileList(TOperationSide Side, TFarPanelInfo ** APanelInfo) +{ + TFarPanelInfo ** PanelInfo = APanelInfo; + if (PanelInfo == nullptr) + { + PanelInfo = this->GetPanelInfo(); + } + + TStrings * Result; + if (PanelInfo && *PanelInfo && (*PanelInfo)->GetSelectedCount() > 0) + { + UnicodeString CurrDirectory = Connected() ? FTerminal->GetCurrDirectory() : (*PanelInfo)->GetCurrDirectory(); + Result = CreateFileList((*PanelInfo)->GetItems(), Side, true, CurrDirectory); + } + else + { + Result = CreateFocusedFileList(Side, PanelInfo); + } + return Result; +} + +TStrings * TWinSCPFileSystem::CreateFileList(TObjectList * PanelItems, + TOperationSide Side, bool SelectedOnly, const UnicodeString & Directory, bool FileNameOnly, + TStrings * AFileList) +{ + std::unique_ptr FileList(AFileList == nullptr ? new TStringList() : AFileList); + FileList->SetDuplicates(dupAccept); + + UnicodeString FileName; + TFarPanelItem * PanelItem; + TObject * Data = nullptr; + for (intptr_t Index = 0; Index < PanelItems->GetCount(); ++Index) + { + PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(Index)); + DebugAssert(PanelItem); + if ((!SelectedOnly || PanelItem->GetSelected()) && + !PanelItem->GetIsParentDirectory()) + { + FileName = PanelItem->GetFileName(); + if (Side == osRemote) + { + Data = NB_STATIC_DOWNCAST(TRemoteFile, PanelItem->GetUserData()); + DebugAssert(Data); + } + if (Side == osLocal) + { + if (::ExtractFilePath(FileName).IsEmpty()) + { + if (!FileNameOnly) + { + UnicodeString Dir = Directory; + if (Dir.IsEmpty()) + { + Dir = ::GetCurrentDir(); + } + FileName = ::IncludeTrailingBackslash(Dir) + FileName; + } + } + else + { + if (FileNameOnly) + { + FileName = base::ExtractFileName(FileName, false); + } + } + } + FileList->AddObject(FileName, Data); + } + } + + if (FileList->GetCount() == 0) + { + Abort(); + } + return FileList.release(); +} + +void TWinSCPFileSystem::SaveSession() +{ + if (FTerminal->GetActive() && !GetSessionData()->GetName().IsEmpty()) + { + GetSessionData()->SetRemoteDirectory(FTerminal->GetCurrDirectory()); + + TSessionData * Data = NB_STATIC_DOWNCAST(TSessionData, StoredSessions->FindByName(GetSessionData()->GetName())); + if (Data) + { + bool Changed = false; + if (GetTerminal()->GetSessionData()->GetUpdateDirectories()) + { + Data->SetRemoteDirectory(GetTerminal()->GetSessionData()->GetRemoteDirectory()); + Changed = true; + } + + if (Changed) + { + // modified only, implicit + StoredSessions->Save(false, false); + } + } + } +} + +bool TWinSCPFileSystem::Connect(TSessionData * Data) +{ + bool Result = false; + DebugAssert(!FTerminal); + FTerminal = new TTerminal(); + FTerminal->Init(Data, GetConfiguration()); + try + { + FTerminal->SetOnQueryUser(MAKE_CALLBACK(TWinSCPFileSystem::TerminalQueryUser, this)); + FTerminal->SetOnPromptUser(MAKE_CALLBACK(TWinSCPFileSystem::TerminalPromptUser, this)); + FTerminal->SetOnDisplayBanner(MAKE_CALLBACK(TWinSCPFileSystem::TerminalDisplayBanner, this)); + FTerminal->SetOnShowExtendedException(MAKE_CALLBACK(TWinSCPFileSystem::TerminalShowExtendedException, this)); + FTerminal->SetOnChangeDirectory(MAKE_CALLBACK(TWinSCPFileSystem::TerminalChangeDirectory, this)); + FTerminal->SetOnReadDirectory(MAKE_CALLBACK(TWinSCPFileSystem::TerminalReadDirectory, this)); + FTerminal->SetOnStartReadDirectory(MAKE_CALLBACK(TWinSCPFileSystem::TerminalStartReadDirectory, this)); + FTerminal->SetOnReadDirectoryProgress(MAKE_CALLBACK(TWinSCPFileSystem::TerminalReadDirectoryProgress, this)); + FTerminal->SetOnInformation(MAKE_CALLBACK(TWinSCPFileSystem::TerminalInformation, this)); + FTerminal->SetOnFinished(MAKE_CALLBACK(TWinSCPFileSystem::OperationFinished, this)); + FTerminal->SetOnProgress(MAKE_CALLBACK(TWinSCPFileSystem::OperationProgress, this)); + FTerminal->SetOnDeleteLocalFile(MAKE_CALLBACK(TWinSCPFileSystem::TerminalDeleteLocalFile, this)); + FTerminal->SetOnCreateLocalFile(MAKE_CALLBACK(TWinSCPFileSystem::TerminalCreateLocalFile, this)); + FTerminal->SetOnGetLocalFileAttributes(MAKE_CALLBACK(TWinSCPFileSystem::TerminalGetLocalFileAttributes, this)); + FTerminal->SetOnSetLocalFileAttributes(MAKE_CALLBACK(TWinSCPFileSystem::TerminalSetLocalFileAttributes, this)); + FTerminal->SetOnMoveLocalFile(MAKE_CALLBACK(TWinSCPFileSystem::TerminalMoveLocalFile, this)); + FTerminal->SetOnRemoveLocalDirectory(MAKE_CALLBACK(TWinSCPFileSystem::TerminalRemoveLocalDirectory, this)); + FTerminal->SetOnCreateLocalDirectory(MAKE_CALLBACK(TWinSCPFileSystem::TerminalCreateLocalDirectory, this)); + FTerminal->SetOnCheckForEsc(MAKE_CALLBACK(TWinSCPFileSystem::TerminalCheckForEsc, this)); + ConnectTerminal(FTerminal); + + FTerminal->SetOnClose(MAKE_CALLBACK(TWinSCPFileSystem::TerminalClose, this)); + + DebugAssert(FQueue == nullptr); + DebugAssert(FQueueStatus == nullptr); + + TODO("Create instance of TKeepaliveThread here, once its implementation is complete"); + + Result = FTerminal->GetActive(); + if (!Result) + { + throw Exception(FORMAT(GetMsg(CANNOT_INIT_SESSION).c_str(), Data->GetSessionName().c_str())); + } + } + catch (Exception & E) + { + EFatal * Fatal = NB_STATIC_DOWNCAST(EFatal, &E); + if ((Fatal == nullptr) || !Fatal->GetReopenQueried()) + { + FTerminal->ShowExtendedException(&E); + } + SAFE_DESTROY(FTerminal); + SAFE_DESTROY(FQueue); + SAFE_DESTROY(FQueueStatus); + } + + if (FTerminal != nullptr) + { + FSynchronisingBrowse = GetSessionData()->GetSynchronizeBrowsing(); + } + return Result; +} + +void TWinSCPFileSystem::Disconnect() +{ + if (FTerminal && FTerminal->GetActive()) + { + if (!GetSessionData()->GetName().IsEmpty()) + { + FPrevSessionName = GetSessionData()->GetName(); + } + SaveSession(); + } + DebugAssert(FSynchronizeController == nullptr); + DebugAssert(!FAuthenticationSaveScreenHandle); + DebugAssert(!FProgressSaveScreenHandle); + DebugAssert(!FSynchronizationSaveScreenHandle); + DebugAssert(!FFileList); + DebugAssert(!FPanelItems); + SAFE_DESTROY(FQueue); + SAFE_DESTROY(FQueueStatus); + if (FTerminal != nullptr) + { + GetSessionData()->SetSynchronizeBrowsing(FSynchronisingBrowse); + } + SAFE_DESTROY(FTerminal); +} + +void TWinSCPFileSystem::ConnectTerminal(TTerminal * Terminal) +{ + Terminal->Open(); +} + +bool TWinSCPFileSystem::TerminalCheckForEsc() +{ + return GetWinSCPPlugin()->CheckForEsc(); +} + +void TWinSCPFileSystem::TerminalClose(TObject * /*Sender*/) +{ + // Plugin closure is now invoked from HandleException +} + +void TWinSCPFileSystem::LogAuthentication( + TTerminal * Terminal, const UnicodeString & Msg) +{ + DebugAssert(FAuthenticationLog != nullptr); + FAuthenticationLog->Add(Msg); + std::unique_ptr AuthenticationLogLines(new TStringList()); + intptr_t Width = 42; + intptr_t Height = 11; + FarWrapText(::TrimRight(FAuthenticationLog->GetText()), AuthenticationLogLines.get(), Width); + intptr_t Count; + UnicodeString Message; + if (AuthenticationLogLines->GetCount() == 0) + { + Message = ::StringOfChar(' ', Width) + L"\n"; + Count = 1; + } + else + { + while (AuthenticationLogLines->GetCount() > Height) + { + AuthenticationLogLines->Delete(0); + } + AuthenticationLogLines->SetString(0, AuthenticationLogLines->GetString(0) + + ::StringOfChar(' ', Width - AuthenticationLogLines->GetString(0).Length())); + Message = ::AnsiReplaceStr(AuthenticationLogLines->GetText(), L"\r", L""); + Count = AuthenticationLogLines->GetCount(); + } + + Message += ::StringOfChar(L'\n', Height - Count); + + GetWinSCPPlugin()->Message(0, Terminal->GetSessionData()->GetSessionName(), Message); +} + +void TWinSCPFileSystem::TerminalInformation( + TTerminal * Terminal, const UnicodeString & Str, bool /*Status*/, intptr_t Phase) +{ + if (Phase != 0) + { + if (GetTerminal() && (GetTerminal()->GetStatus() == ssOpening)) + { + if (FAuthenticationLog == nullptr) + { + FAuthenticationLog = new TStringList(); + GetWinSCPPlugin()->SaveScreen(FAuthenticationSaveScreenHandle); + GetWinSCPPlugin()->ShowConsoleTitle(GetTerminal()->GetSessionData()->GetSessionName()); + } + + LogAuthentication(Terminal, Str); + GetWinSCPPlugin()->UpdateConsoleTitle(Str); + } + } + else + { + if (FAuthenticationLog != nullptr) + { + GetWinSCPPlugin()->ClearConsoleTitle(); + GetWinSCPPlugin()->RestoreScreen(FAuthenticationSaveScreenHandle); + SAFE_DESTROY(FAuthenticationLog); + } + } +} + +void TWinSCPFileSystem::TerminalChangeDirectory(TObject * /*Sender*/) +{ + if (!FNoProgress) + { + UnicodeString Directory = FTerminal->GetCurrDirectory(); + intptr_t Index = FPathHistory->IndexOf(Directory.c_str()); + if (Index >= 0) + { + FPathHistory->Delete(Index); + } + + if (!FLastPath.IsEmpty()) + { + FPathHistory->Add(FLastPath); + } + + FLastPath = Directory; + SaveSession(); // To save changed directory + } +} + +void TWinSCPFileSystem::TerminalStartReadDirectory(TObject * /*Sender*/) +{ + if (!FNoProgress) + { + GetWinSCPPlugin()->ShowConsoleTitle(GetMsg(READING_DIRECTORY_TITLE)); + } +} + +void TWinSCPFileSystem::TerminalReadDirectoryProgress( + TObject * /*Sender*/, intptr_t Progress, intptr_t /*ResolvedLinks*/, bool & Cancel) +{ + if (Progress < 0) + { + if (!FNoProgress && (Progress == -2)) + { + MoreMessageDialog(GetMsg(DIRECTORY_READING_CANCELLED), nullptr, + qtWarning, qaOK); + } + } + else + { + if (GetWinSCPPlugin()->CheckForEsc()) + { + Cancel = true; + } + + if (!FNoProgress) + { + GetWinSCPPlugin()->UpdateConsoleTitle( + FORMAT(L"%s (%d)", GetMsg(READING_DIRECTORY_TITLE).c_str(), Progress)); + } + } +} + +void TWinSCPFileSystem::TerminalReadDirectory(TObject * /*Sender*/, + bool /*ReloadOnly*/) +{ + if (!FNoProgress) + { + GetWinSCPPlugin()->ClearConsoleTitle(); + } +} + +void TWinSCPFileSystem::TerminalDeleteLocalFile(const UnicodeString & AFileName, + bool Alternative) +{ + if (!RecursiveDeleteFile(AFileName, + (FLAGSET(GetWinSCPPlugin()->GetFarSystemSettings(), FSS_DELETETORECYCLEBIN)) != Alternative)) + { + throw Exception(FORMAT(GetMsg(DELETE_LOCAL_FILE_ERROR).c_str(), AFileName.c_str())); + } +} + +HANDLE TWinSCPFileSystem::TerminalCreateLocalFile(const UnicodeString & LocalFileName, + DWORD DesiredAccess, DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes) +{ + return ::CreateFile(ApiPath(LocalFileName).c_str(), DesiredAccess, ShareMode, nullptr, CreationDisposition, FlagsAndAttributes, 0); +} + +inline DWORD TWinSCPFileSystem::TerminalGetLocalFileAttributes(const UnicodeString & LocalFileName) +{ + return ::FileGetAttrFix(LocalFileName); +} + +inline BOOL TWinSCPFileSystem::TerminalSetLocalFileAttributes(const UnicodeString & LocalFileName, DWORD FileAttributes) +{ + return ::FileSetAttr(LocalFileName, FileAttributes) != 0; +} + +BOOL TWinSCPFileSystem::TerminalMoveLocalFile(const UnicodeString & LocalFileName, const UnicodeString & NewLocalFileName, DWORD Flags) +{ +#ifndef __linux__ + return ::MoveFileExW(ApiPath(LocalFileName).c_str(), NewLocalFileName.c_str(), Flags) != 0; +#else + return FALSE; +#endif +} + +BOOL TWinSCPFileSystem::TerminalRemoveLocalDirectory(const UnicodeString & LocalDirName) +{ + return ::RemoveDir(LocalDirName); +} + +BOOL TWinSCPFileSystem::TerminalCreateLocalDirectory(const UnicodeString & LocalDirName, LPSECURITY_ATTRIBUTES SecurityAttributes) +{ + return ::CreateDirectory(ApiPath(LocalDirName).c_str(), SecurityAttributes) != 0; +} + +uintptr_t TWinSCPFileSystem::MoreMessageDialog(const UnicodeString & Str, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, const TMessageParams * AParams) +{ + TMessageParams Params; + + //if ((FProgressSaveScreenHandle != 0) || + // (FSynchronizationSaveScreenHandle != 0)) + { + if (AParams != nullptr) + { + Params.Assign(AParams); + } + AParams = &Params; + Params.Flags |= FMSG_WARNING; + } + + return GetWinSCPPlugin()->MoreMessageDialog(Str, MoreMessages, Type, + Answers, &Params); +} + +void TWinSCPFileSystem::TerminalQueryUser(TObject * /*Sender*/, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * AParams, uintptr_t & Answer, TQueryType Type, void * /*Arg*/) +{ + TMessageParams Params; + UnicodeString Query = AQuery; + + if (AParams != nullptr) + { + if (AParams->Params & qpFatalAbort) + { + Query = FORMAT(GetMsg(WARN_FATAL_ERROR).c_str(), Query.c_str()); + } + + Params.Aliases = AParams->Aliases; + Params.AliasesCount = AParams->AliasesCount; + Params.Params = AParams->Params & (qpNeverAskAgainCheck | qpAllowContinueOnError); + Params.Timer = AParams->Timer; + Params.TimerEvent = AParams->TimerEvent; + Params.TimerMessage = AParams->TimerMessage; + Params.TimerAnswers = AParams->TimerAnswers; + Params.Timeout = AParams->Timeout; + Params.TimeoutAnswer = AParams->TimeoutAnswer; + } + + Answer = MoreMessageDialog(Query, MoreMessages, Type, Answers, &Params); +} + +void TWinSCPFileSystem::TerminalPromptUser(TTerminal * Terminal, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, + TStrings * Prompts, TStrings * Results, bool & AResult, + void * /*Arg*/) +{ + if (Kind == pkPrompt) + { + DebugAssert(Instructions.IsEmpty()); + DebugAssert(Prompts->GetCount() == 1); + DebugAssert(Prompts->GetObj(0) != nullptr); + UnicodeString Result = Results->GetString(0); + + AResult = GetWinSCPPlugin()->InputBox(Name, ::StripHotkey(Prompts->GetString(0)), Result, FIB_NOUSELASTHISTORY); + if (AResult) + { + Results->SetString(0, Result); + } + } + else + { + AResult = PasswordDialog(Terminal->GetSessionData(), Kind, Name, Instructions, + Prompts, Results, GetTerminal()->GetStoredCredentialsTried()); + } +} + +void TWinSCPFileSystem::TerminalDisplayBanner( + TTerminal * /*Terminal*/, const UnicodeString & SessionName, + const UnicodeString & Banner, bool & NeverShowAgain, intptr_t Options) +{ + BannerDialog(SessionName, Banner, NeverShowAgain, Options); +} + +void TWinSCPFileSystem::TerminalShowExtendedException( + TTerminal * /*Terminal*/, Exception * E, void * /*Arg*/) +{ + GetWinSCPPlugin()->ShowExtendedException(E); +} + +void TWinSCPFileSystem::OperationProgress( + TFileOperationProgressType & ProgressData) +{ + if (FNoProgress) + { + return; + } + + bool First = false; + if (ProgressData.InProgress && !FProgressSaveScreenHandle) + { + GetWinSCPPlugin()->SaveScreen(FProgressSaveScreenHandle); + First = true; + } + + // operation is finished (or terminated), so we hide progress form + if (!ProgressData.InProgress && FProgressSaveScreenHandle) + { + GetWinSCPPlugin()->RestoreScreen(FProgressSaveScreenHandle); + GetWinSCPPlugin()->ClearConsoleTitle(); + } + else + { + ShowOperationProgress(ProgressData, First); + } +} + +void TWinSCPFileSystem::OperationFinished(TFileOperation Operation, + TOperationSide Side, bool /*Temp*/, const UnicodeString & AFileName, bool Success, + TOnceDoneOperation & /*DisconnectWhenComplete*/) +{ + DebugUsedParam(Side); + + if ((Operation != foCalculateSize) && + (Operation != foGetProperties) && + (Operation != foCalculateChecksum) && + (FSynchronizationSaveScreenHandle == 0) && + !FNoProgress && !FNoProgressFinish) + { + TFarPanelItem * PanelItem = nullptr; + + if (!FPanelItems) + { + TObjectList * PanelItems = (*GetPanelInfo())->GetItems(); + for (intptr_t Index = 0; Index < PanelItems->GetCount(); ++Index) + { + if ((NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(Index)))->GetFileName() == AFileName) + { + PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, PanelItems->GetObj(Index)); + break; + } + } + } + else + { + DebugAssert(FFileList); + DebugAssert(FPanelItems->GetCount() == FFileList->GetCount()); + intptr_t Index = FFileList->IndexOf(AFileName.c_str()); + DebugAssert(Index >= 0); + PanelItem = NB_STATIC_DOWNCAST(TFarPanelItem, FPanelItems->GetItem(Index)); + } + + DebugAssert(PanelItem && PanelItem->GetFileName() == + ((Side == osLocal) ? base::ExtractFileName(AFileName, false) : AFileName)); + if (Success && PanelItem) + { + PanelItem->SetSelected(false); + } + } + + if (Success && (FSynchronizeController != nullptr)) + { + if (Operation == foCopy) + { + DebugAssert(Side == osLocal); + FSynchronizeController->LogOperation(soUpload, AFileName); + } + else if (Operation == foDelete) + { + DebugAssert(Side == osRemote); + FSynchronizeController->LogOperation(soDelete, AFileName); + } + } +} + +void TWinSCPFileSystem::ShowOperationProgress( + TFileOperationProgressType & ProgressData, bool First) +{ + static uint32_t LastTicks; + uint32_t Ticks = ::GetTickCount(); + short percents = static_cast(ProgressData.OverallProgress()); + if (Ticks - LastTicks > 500 || First) + { + LastTicks = Ticks; + + static const intptr_t ProgressWidth = 48; + static const int Captions[] = {PROGRESS_COPY, PROGRESS_MOVE, PROGRESS_DELETE, + PROGRESS_SETPROPERTIES, 0, 0, PROGRESS_CALCULATE_SIZE, + PROGRESS_REMOTE_MOVE, PROGRESS_REMOTE_COPY, PROGRESS_GETPROPERTIES, + PROGRESS_CALCULATE_CHECKSUM }; + static UnicodeString ProgressFileLabel; + static UnicodeString TargetDirLabel; + static UnicodeString StartTimeLabel; + static UnicodeString TimeElapsedLabel; + static UnicodeString BytesTransferredLabel; + static UnicodeString CPSLabel; + static UnicodeString TimeLeftLabel; + + if (ProgressFileLabel.IsEmpty()) + { + ProgressFileLabel = GetMsg(PROGRESS_FILE_LABEL); + TargetDirLabel = GetMsg(TARGET_DIR_LABEL); + StartTimeLabel = GetMsg(START_TIME_LABEL); + TimeElapsedLabel = GetMsg(TIME_ELAPSED_LABEL); + BytesTransferredLabel = GetMsg(BYTES_TRANSFERED_LABEL); + CPSLabel = GetMsg(CPS_LABEL); + TimeLeftLabel = GetMsg(TIME_LEFT_LABEL); + } + + bool TransferOperation = + ((ProgressData.Operation == foCopy) || (ProgressData.Operation == foMove)); + + UnicodeString Message1; + UnicodeString ProgressBarCurrentFile; + UnicodeString Message2; + UnicodeString ProgressBarTotal; + UnicodeString Title = GetMsg(Captions[static_cast(ProgressData.Operation - 1)]); + UnicodeString FileName = ProgressData.FileName; + // for upload from temporary directory, + // do not show source directory + if (TransferOperation && (ProgressData.Side == osLocal) && ProgressData.Temp) + { + FileName = base::ExtractFileName(FileName, false); + } + Message1 = ProgressFileLabel + core::MinimizeName(FileName, + ProgressWidth - ProgressFileLabel.Length(), ProgressData.Side == osRemote) + L"\n"; + // for downloads to temporary directory, + // do not show target directory + if (TransferOperation && !((ProgressData.Side == osRemote) && ProgressData.Temp)) + { + Message1 += TargetDirLabel + core::MinimizeName(ProgressData.Directory, + ProgressWidth - TargetDirLabel.Length(), ProgressData.Side == osLocal) + L"\n"; + } + ProgressBarTotal = ProgressBar(ProgressData.OverallProgress(), ProgressWidth) + L"\n"; + if (TransferOperation) + { + Message2 = L"\1\n"; + UnicodeString StatusLine; + UnicodeString Value; + + Value = FormatDateTimeSpan(GetConfiguration()->GetTimeFormat(), ProgressData.TimeElapsed()); + StatusLine = TimeElapsedLabel + + ::StringOfChar(L' ', ProgressWidth / 2 - 1 - TimeElapsedLabel.Length() - Value.Length()) + + Value + L" "; + + UnicodeString LabelText; + if (ProgressData.TotalSizeSet) + { + Value = FormatDateTimeSpan(GetConfiguration()->GetTimeFormat(), ProgressData.TotalTimeLeft()); + LabelText = TimeLeftLabel; + } + else + { + Value = ProgressData.StartTime.TimeString(true); + LabelText = StartTimeLabel; + } + StatusLine = StatusLine + LabelText + + ::StringOfChar(' ', ProgressWidth - StatusLine.Length() - + LabelText.Length() - Value.Length()) + Value; + Message2 += StatusLine + L"\n"; + + Value = FormatBytes(ProgressData.TotalTransfered); + StatusLine = BytesTransferredLabel + + ::StringOfChar(' ', ProgressWidth / 2 - 1 - BytesTransferredLabel.Length() - Value.Length()) + + Value + L" "; + Value = FORMAT(L"%s/s", FormatBytes(ProgressData.CPS()).c_str()); + StatusLine = StatusLine + CPSLabel + + ::StringOfChar(' ', ProgressWidth - StatusLine.Length() - + CPSLabel.Length() - Value.Length()) + Value; + Message2 += StatusLine + L"\n"; + ProgressBarCurrentFile = ProgressBar(ProgressData.TransferProgress(), ProgressWidth) + L"\n"; + } + UnicodeString Message = + Message1 + ProgressBarCurrentFile + Message2 + ProgressBarTotal; + GetWinSCPPlugin()->Message(0, Title, Message, nullptr, nullptr); + + if (First) + { + GetWinSCPPlugin()->ShowConsoleTitle(Title); + } + GetWinSCPPlugin()->UpdateConsoleTitleProgress(percents); + + if (GetWinSCPPlugin()->CheckForEsc()) + { + CancelConfiguration(ProgressData); + } + } + if (percents == 100) + { + GetWinSCPPlugin()->UpdateConsoleTitleProgress(percents); + } +} + +UnicodeString TWinSCPFileSystem::ProgressBar(intptr_t Percentage, intptr_t Width) +{ + UnicodeString Result; + // 0xB0 - 0x2591 + // 0xDB - 0x2588 + Result = ::StringOfChar(0x2588, (Width - 5) * (Percentage > 100 ? 100 : Percentage) / 100); + Result += ::StringOfChar(0x2591, (Width - 5) - Result.Length()); + Result += FORMAT(L"%4d%%", Percentage > 100 ? 100 : Percentage); + return Result; +} + +TTerminalQueueStatus * TWinSCPFileSystem::ProcessQueue(bool Hidden) +{ + TTerminalQueueStatus * Result = nullptr; + if (FQueue == nullptr) + return Result; + + TTerminalQueueStatus * QueueStatus = GetQueueStatus(); + DebugAssert(QueueStatus != nullptr); + FarPlugin->UpdateProgress(QueueStatus->GetCount() > 0 ? PS_INDETERMINATE : PS_NOPROGRESS, 0); + + if (FQueueStatusInvalidated || FQueueItemInvalidated) + { + if (FQueueStatusInvalidated) + { + TGuard Guard(FQueueStatusSection); + + FQueueStatusInvalidated = false; + + if (FQueue != nullptr) + FQueueStatus = FQueue->CreateStatus(FQueueStatus); + Result = FQueueStatus; + } + + FQueueItemInvalidated = false; + + for (intptr_t Index = 0; Index < FQueueStatus->GetActiveCount(); ++Index) + { + TQueueItemProxy * QueueItem = FQueueStatus->GetItem(Index); + if (QueueItem->GetUserData() != nullptr) + { + QueueItem->Update(); + Result = FQueueStatus; + } + + if (GetGUIConfiguration()->GetQueueAutoPopup() && + TQueueItem::IsUserActionStatus(QueueItem->GetStatus())) + { + QueueItem->ProcessUserAction(); + } + } + } + + if (FRefreshRemoteDirectory) + { + if ((GetTerminal() != nullptr) && GetTerminal()->GetActive()) + { + GetTerminal()->RefreshDirectory(); + if (UpdatePanel()) + { + RedrawPanel(); + } + } + FRefreshRemoteDirectory = false; + } + if (FRefreshLocalDirectory) + { + if (GetOppositeFileSystem() == nullptr) + { + if (UpdatePanel(false, true)) + { + RedrawPanel(true); + } + } + FRefreshLocalDirectory = false; + } + + if (FQueueEventPending) + { + TQueueEvent Event; + + { + TGuard Guard(FQueueStatusSection); + Event = FQueueEvent; + FQueueEventPending = false; + } + + switch (Event) + { + case qeEmpty: + if (Hidden && GetFarConfiguration()->GetQueueBeep()) + { +#ifndef __linux__ + ::MessageBeep(MB_OK); +#endif + } + break; + + case qePendingUserAction: + if (Hidden && !GetGUIConfiguration()->GetQueueAutoPopup() && GetFarConfiguration()->GetQueueBeep()) + { + // MB_ICONQUESTION would be more appropriate, but in default Windows Sound + // schema it has no sound associated +#ifndef __linux__ + ::MessageBeep(MB_OK); +#endif + } + break; + } + } + + return Result; +} + +void TWinSCPFileSystem::QueueListUpdate(TTerminalQueue * Queue) +{ + if (GetQueue() == Queue) + { + FQueueStatusInvalidated = true; + } +} + +void TWinSCPFileSystem::QueueItemUpdate(TTerminalQueue * Queue, + TQueueItem * Item) +{ + if (GetQueue() == Queue) + { + TGuard Guard(FQueueStatusSection); + + TTerminalQueueStatus * QueueStatus = GetQueueStatus(); + DebugAssert(QueueStatus != nullptr); + + TQueueItemProxy * QueueItem = QueueStatus->FindByQueueItem(Item); + + if ((Item->GetStatus() == TQueueItem::qsDone) && (GetTerminal() != nullptr)) + { + FRefreshLocalDirectory = (QueueItem == nullptr) || + (!QueueItem->GetInfo()->ModifiedLocal.IsEmpty()); + FRefreshRemoteDirectory = (QueueItem == nullptr) || + (!QueueItem->GetInfo()->ModifiedRemote.IsEmpty()); + } + + if (QueueItem != nullptr) + { + QueueItem->SetUserData(ToPtr(1)); + FQueueItemInvalidated = true; + } + } +} + +void TWinSCPFileSystem::QueueEvent(TTerminalQueue * Queue, + TQueueEvent Event) +{ + TGuard Guard(FQueueStatusSection); + if (Queue == GetQueue()) + { + FQueueEventPending = true; + FQueueEvent = Event; + } +} + +void TWinSCPFileSystem::CancelConfiguration(TFileOperationProgressType & ProgressData) +{ + if (!ProgressData.Suspended) + { + ProgressData.Suspend(); + SCOPE_EXIT + { + ProgressData.Resume(); + }; + TCancelStatus ACancel; + uintptr_t Result = 0; + if (ProgressData.TransferingFile && + (ProgressData.TimeExpected() > GetGUIConfiguration()->GetIgnoreCancelBeforeFinish())) + { + Result = MoreMessageDialog(GetMsg(CANCEL_OPERATION_FATAL), nullptr, + qtWarning, qaYes | qaNo | qaCancel); + } + else + { + Result = MoreMessageDialog(GetMsg(CANCEL_OPERATION), nullptr, + qtConfirmation, qaOK | qaCancel); + } + switch (Result) + { + case qaYes: + ACancel = csCancelTransfer; + break; + case qaOK: + ACancel = csCancel; + break; + case qaNo: + ACancel = csContinue; + break; + default: + ACancel = csContinue; + break; + } + + if (ACancel > ProgressData.Cancel) + { + ProgressData.Cancel = ACancel; + } + } +} + +void TWinSCPFileSystem::UploadFromEditor(bool NoReload, + const UnicodeString & AFileName, const UnicodeString & RealFileName, + UnicodeString & DestPath) +{ + DebugAssert(FFileList == nullptr); + FFileList = new TStringList(); + DebugAssert(FTerminal->GetAutoReadDirectory()); + bool PrevAutoReadDirectory = FTerminal->GetAutoReadDirectory(); + if (NoReload) + { + FTerminal->SetAutoReadDirectory(false); + if (core::UnixSamePath(DestPath, FTerminal->GetCurrDirectory())) + { + FReloadDirectory = true; + } + } + + std::unique_ptr File(new TRemoteFile()); + File->SetFileName(RealFileName); + SCOPE_EXIT + { + FTerminal->SetAutoReadDirectory(PrevAutoReadDirectory); + SAFE_DESTROY(FFileList); + }; + FFileList->AddObject(AFileName, File.get()); //-V522 + UploadFiles(false, 0, true, DestPath); +} + +void TWinSCPFileSystem::UploadOnSave(bool NoReload) +{ + std::unique_ptr Info(GetWinSCPPlugin()->EditorInfo()); + if (Info.get() != nullptr) + { + bool NativeEdit = + (FLastEditorID >= 0) && + (FLastEditorID == Info->GetEditorID()) && + !FLastEditFile.IsEmpty(); + + TMultipleEdits::iterator it = FMultipleEdits.find(Info->GetEditorID()); + bool MultipleEdit = (it != FMultipleEdits.end()); + + if (NativeEdit || MultipleEdit) + { + // make sure this is reset before any dialog is shown as it may cause recursion + FEditorPendingSave = false; + + if (NativeEdit) + { + DebugAssert(FLastEditFile == Info->GetFileName()); + // always upload under the most recent name + UnicodeString CurrentDirectory = FTerminal->GetCurrDirectory(); + UploadFromEditor(NoReload, FLastEditFile, FLastEditFile, CurrentDirectory); + FTerminal->TerminalSetCurrentDirectory(CurrentDirectory); + } + + if (MultipleEdit) + { + UploadFromEditor(NoReload, Info->GetFileName(), it->second.FileTitle, it->second.Directory); + // note that panel gets not refreshed upon switch to + // panel view. but that's intentional + } + } + } +} + +void TWinSCPFileSystem::ProcessEditorEvent(intptr_t Event, void * /*Param*/) +{ + // EE_REDRAW is the first for optimization + if (Event == EE_REDRAW) + { + if (FEditorPendingSave) + { + UploadOnSave(true); + } + + // Whenever editor title is changed (and restored back), it is restored + // to default FAR text, not to ours (see EE_SAVE). Hence we periodically + // reset the title. + static uint32_t LastTicks = 0; + uint32_t Ticks = ::GetTickCount(); + if ((LastTicks == 0) || (Ticks - LastTicks > 500)) + { + LastTicks = Ticks; + std::unique_ptr Info(GetWinSCPPlugin()->EditorInfo()); + if (Info.get() != nullptr) + { + TMultipleEdits::iterator it = FMultipleEdits.find(Info->GetEditorID()); + if (it != FMultipleEdits.end()) + { + UnicodeString FullFileName = core::UnixIncludeTrailingBackslash(it->second.Directory) + + it->second.FileTitle; + GetWinSCPPlugin()->FarEditorControl(ECTL_SETTITLE, + static_cast(const_cast(FullFileName.c_str()))); + } + } + } + } + else if (Event == EE_READ) + { + // file can be read from active filesystem only anyway. this prevents + // both filesystems in both panels intercepting the very same file in case the + // file with the same name is read from both filesystems recently + if (IsActiveFileSystem()) + { + std::unique_ptr Info(GetWinSCPPlugin()->EditorInfo()); + if (Info.get() != nullptr) + { + if (!FLastEditFile.IsEmpty() && + ::AnsiSameText(FLastEditFile, Info->GetFileName())) + { + FLastEditorID = Info->GetEditorID(); + FEditorPendingSave = false; + } + + if (!FLastMultipleEditFile.IsEmpty()) + { + bool IsLastMultipleEditFile = ::AnsiSameText(core::FromUnixPath(FLastMultipleEditFile), core::FromUnixPath(Info->GetFileName())); + DebugAssert(IsLastMultipleEditFile); + if (IsLastMultipleEditFile) + { + FLastMultipleEditFile.Clear(); + + TMultipleEdit MultipleEdit; + MultipleEdit.FileName = base::ExtractFileName(Info->GetFileName(), false); + MultipleEdit.FileTitle = FLastMultipleEditFileTitle; + MultipleEdit.Directory = FLastMultipleEditDirectory; + MultipleEdit.LocalFileName = Info->GetFileName(); + MultipleEdit.PendingSave = false; + FMultipleEdits[Info->GetEditorID()] = MultipleEdit; + if (FLastMultipleEditReadOnly) + { + EditorSetParameter Parameter; + ClearStruct(Parameter); + Parameter.Type = ESPT_LOCKMODE; + Parameter.Param.iParam = TRUE; + GetWinSCPPlugin()->FarEditorControl(ECTL_SETPARAM, &Parameter); + } + } + } + } + } + } + else if (Event == EE_CLOSE) + { + if (FEditorPendingSave) + { + DebugAssert(false); // should not happen, but let's be robust + UploadOnSave(false); + } + + std::unique_ptr Info(GetWinSCPPlugin()->EditorInfo()); + if (Info.get() != nullptr) + { + if (FLastEditorID == Info->GetEditorID()) + { + FLastEditorID = -1; + } + + TMultipleEdits::iterator it = FMultipleEdits.find(Info->GetEditorID()); + if (it != FMultipleEdits.end()) + { + TMultipleEdit & ed = it->second; + if (ed.PendingSave) + { + UploadFromEditor(true, Info->GetFileName(), ed.FileTitle, ed.Directory); + // reload panel content (if uploaded to current directory. + // no need for RefreshPanel as panel is not visible yet. + UpdatePanel(); + } + + if (::RemoveFile(Info->GetFileName())) + { + // remove directory only if it is empty + // (to avoid deleting another directory if user uses "save as") + ::RemoveDir(::ExcludeTrailingBackslash(::ExtractFilePath(Info->GetFileName()))); + } + + FMultipleEdits.erase(it->first); + } + } + } + else if (Event == EE_SAVE) + { + std::unique_ptr Info(GetWinSCPPlugin()->EditorInfo()); + if (Info.get() != nullptr) + { + if ((FLastEditorID >= 0) && (FLastEditorID == Info->GetEditorID())) + { + // if the file is saved under different name ("save as"), we upload + // the file back under that name + FLastEditFile = Info->GetFileName(); + + if (GetFarConfiguration()->GetEditorUploadOnSave()) + { + FEditorPendingSave = true; + } + } + + TMultipleEdits::iterator it = FMultipleEdits.find(Info->GetEditorID()); + if (it != FMultipleEdits.end()) + { + if (it->second.LocalFileName != Info->GetFileName()) + { + // update file name (after "save as") + it->second.LocalFileName = Info->GetFileName(); + it->second.FileName = base::ExtractFileName(Info->GetFileName(), true); + // update editor title + UnicodeString FullFileName = core::UnixIncludeTrailingBackslash(it->second.Directory) + + it->second.FileTitle; + // note that we need to reset the title periodically (see EE_REDRAW) + GetWinSCPPlugin()->FarEditorControl(ECTL_SETTITLE, + static_cast(const_cast(FullFileName.c_str()))); + } + + if (GetFarConfiguration()->GetEditorUploadOnSave()) + { + FEditorPendingSave = true; + } + else + { + it->second.PendingSave = true; + } + } + } + } +} + +void TWinSCPFileSystem::EditViewCopyParam(TCopyParamType & CopyParam) +{ + CopyParam.SetFileNameCase(ncNoChange); + CopyParam.SetPreserveReadOnly(false); + CopyParam.SetResumeSupport(rsOff); + CopyParam.SetReplaceInvalidChars(true); + CopyParam.SetFileMask(L""); +} + +void TWinSCPFileSystem::MultipleEdit() +{ + const TFarPanelItem * Focused = (*GetPanelInfo())->GetFocusedItem(); + if ((Focused != nullptr) && + Focused->GetIsFile() && + (Focused->GetUserData() != nullptr)) + { + std::unique_ptr FileList(CreateFocusedFileList(osRemote)); + DebugAssert((FileList.get() == nullptr) || (FileList->GetCount() == 1)); + + if ((FileList.get() != nullptr) && (FileList->GetCount() == 1)) + { + MultipleEdit(FTerminal->GetCurrDirectory(), FileList->GetString(0), + NB_STATIC_DOWNCAST(TRemoteFile, FileList->GetObj(0))); + } + } +} + +void TWinSCPFileSystem::MultipleEdit(const UnicodeString & Directory, + const UnicodeString & AFileName, TRemoteFile * AFile) +{ + DebugAssert(AFile); + TEditHistory EditHistory; + EditHistory.Directory = Directory; + EditHistory.FileName = AFileName; + + TEditHistories::iterator it_h = std::find(FEditHistories.begin(), FEditHistories.end(), EditHistory); + if (it_h != FEditHistories.end()) + { + FEditHistories.erase(it_h); + } + FEditHistories.push_back(EditHistory); + + UnicodeString FullFileName = core::UnixIncludeTrailingBackslash(Directory) + AFileName; + + std::unique_ptr FileDuplicate(AFile ? AFile->Duplicate() : nullptr); + UnicodeString NewFileName = AFileName; // FullFileName; + FileDuplicate->SetFileName(NewFileName); + + TMultipleEdits::iterator it_e = FMultipleEdits.begin(); + while (it_e != FMultipleEdits.end()) + { + const TMultipleEdit & ed = it_e->second; + if (core::UnixSamePath(Directory, ed.Directory) && + (NewFileName == ed.FileName)) + { + break; + } + ++it_e; + } + + FLastMultipleEditReadOnly = false; + bool EditCurrent = false; + if (it_e != FMultipleEdits.end()) + { + TMessageParams Params; + TQueryButtonAlias Aliases[3]; + Aliases[0].Button = qaYes; + Aliases[0].Alias = GetMsg(EDITOR_CURRENT); + Aliases[1].Button = qaNo; + Aliases[1].Alias = GetMsg(EDITOR_NEW_INSTANCE); + Aliases[2].Button = qaOK; + Aliases[2].Alias = GetMsg(EDITOR_NEW_INSTANCE_RO); + Params.Aliases = Aliases; + Params.AliasesCount = _countof(Aliases); + switch (MoreMessageDialog(FORMAT(GetMsg(EDITOR_ALREADY_LOADED).c_str(), FullFileName.c_str()), + nullptr, qtConfirmation, qaYes | qaNo | qaOK | qaCancel, &Params)) + { + case qaYes: + EditCurrent = true; + break; + + case qaNo: + // noop + break; + + case qaOK: + FLastMultipleEditReadOnly = true; + break; + + case qaCancel: + default: + Abort(); + break; + } + } + + if (EditCurrent) + { + DebugAssert(it_e != FMultipleEdits.end()); + + intptr_t WindowCount = FarPlugin->FarAdvControl(ACTL_GETWINDOWCOUNT); + int Pos = 0; + while (Pos < WindowCount) + { + WindowInfo Window; + ClearStruct(Window); + Window.Pos = Pos; + UnicodeString EditedFileName(1024, 0); + Window.Name = const_cast(EditedFileName.c_str()); + Window.NameSize = static_cast(EditedFileName.GetLength()); + if (FarPlugin->FarAdvControl(ACTL_GETWINDOWINFO, &Window) != 0) + { + if ((Window.Type == WTYPE_EDITOR) && + Window.Name && ::AnsiSameText(Window.Name, it_e->second.LocalFileName)) + { + // Switch to current editor. + if (FarPlugin->FarAdvControl(ACTL_SETCURRENTWINDOW, + ToPtr(Pos)) != 0) + { + FarPlugin->FarAdvControl(ACTL_COMMIT, 0); + } + break; + } + } + Pos++; + } + + DebugAssert(Pos < WindowCount); + } + else + { + UnicodeString TempDir; + TGUICopyParamType & CopyParam = GetGUIConfiguration()->GetDefaultCopyParam(); + EditViewCopyParam(CopyParam); + + std::unique_ptr FileList(new TStringList()); + DebugAssert(!FNoProgressFinish); + FNoProgressFinish = true; + { + SCOPE_EXIT + { + FNoProgressFinish = false; + }; + FileList->AddObject(FullFileName, FileDuplicate.get()); + TemporarilyDownloadFiles(FileList.get(), CopyParam, TempDir); + } + UnicodeString ValidLocalFileName = CopyParam.ValidLocalFileName(NewFileName); + FLastMultipleEditFile = ::IncludeTrailingBackslash(TempDir) + ValidLocalFileName; + FLastMultipleEditFileTitle = AFileName; + FLastMultipleEditDirectory = Directory; + + if (FarPlugin->Editor(FLastMultipleEditFile, FullFileName, + EF_NONMODAL | EF_IMMEDIATERETURN | EF_DISABLEHISTORY)) + { + // DebugAssert(FLastMultipleEditFile.IsEmpty()); + } + FLastMultipleEditFile.Clear(); + FLastMultipleEditFileTitle.Clear(); + } +} + +bool TWinSCPFileSystem::IsEditHistoryEmpty() const +{ + return FEditHistories.empty(); +} + +void TWinSCPFileSystem::EditHistory() +{ + std::unique_ptr MenuItems(new TFarMenuItems()); + TEditHistories::const_iterator it = FEditHistories.begin(); + while (it != FEditHistories.end()) + { + MenuItems->Add(core::MinimizeName(core::UnixIncludeTrailingBackslash(it->Directory) + it->FileName, + GetWinSCPPlugin()->MaxMenuItemLength(), true)); + ++it; + } + + MenuItems->Add(L""); + MenuItems->SetItemFocused(MenuItems->GetCount() - 1); + + const int BreakKeys[] = { VK_F4, 0 }; + + int BreakCode = 0; + intptr_t Result = GetWinSCPPlugin()->Menu(FMENU_REVERSEAUTOHIGHLIGHT | FMENU_SHOWAMPERSAND | FMENU_WRAPMODE, + GetMsg(MENU_EDIT_HISTORY), L"", MenuItems.get(), BreakKeys, BreakCode); + + if ((Result >= 0) && (Result < static_cast(FEditHistories.size()))) + { + TRemoteFile * File = nullptr; + const TEditHistory & EditHistory = FEditHistories[Result]; + UnicodeString FullFileName = + core::UnixIncludeTrailingBackslash(EditHistory.Directory) + EditHistory.FileName; + FTerminal->ReadFile(FullFileName, File); + std::unique_ptr FilePtr(File); + DebugAssert(FilePtr.get()); + if (File && !File->GetHaveFullFileName()) + { + File->SetFullFileName(FullFileName); + } + MultipleEdit(EditHistory.Directory, EditHistory.FileName, File); + } +} + +bool TWinSCPFileSystem::IsLogging() const +{ + return Connected() && FTerminal->GetLog()->GetLoggingToFile(); +} + +void TWinSCPFileSystem::ShowLog() +{ + DebugAssert(Connected() && FTerminal->GetLog()->GetLoggingToFile()); + const TSessionLog * Log = FTerminal->GetLog(); + GetWinSCPPlugin()->Viewer(Log->GetCurrentFileName(), Log->GetCurrentFileName(), VF_NONMODAL); +} + +UnicodeString TWinSCPFileSystem::GetFileNameHash(const UnicodeString & AFileName) const +{ + RawByteString Result; + Result.SetLength(16); + md5checksum( + reinterpret_cast(AFileName.c_str()), static_cast(AFileName.Length() * sizeof(wchar_t)), + reinterpret_cast(const_cast(Result.c_str()))); + return BytesToHex(Result); +} + +NB_IMPLEMENT_CLASS(TWinSCPFileSystem, NB_GET_CLASS_INFO(TCustomFarFileSystem), nullptr) + diff --git a/netbox/src/NetBox/WinSCPFileSystem.h b/netbox/src/NetBox/WinSCPFileSystem.h new file mode 100644 index 000000000..7cd3b9e68 --- /dev/null +++ b/netbox/src/NetBox/WinSCPFileSystem.h @@ -0,0 +1,418 @@ +#pragma once + +#include +#include "FarPlugin.h" +#include +#include +#include +#include +#include +#include + +class TTerminal; +class TSessionData; +class TRemoteFile; +class TBookmarkList; +class TWinSCPPlugin; +class TNetBoxPlugin; +class TFarButton; +class TFarDialogItem; +class TFarDialog; +class TTerminalQueue; +class TTerminalQueueStatus; +class TQueueItem; +class TKeepaliveThread; +struct TMessageParams; +#define REMOTE_DIR_HISTORY L"WinscpRemoteDirectory" +#define ASCII_MASK_HISTORY L"WinscpAsciiMask" +#define LINK_FILENAME_HISTORY L"WinscpRemoteLink" +#define LINK_POINT_TO_HISTORY L"WinscpRemoteLinkPointTo" +#define APPLY_COMMAND_HISTORY L"WinscpApplyCmd" +#define APPLY_COMMAND_PARAM_HISTORY L"WinscpApplyCmdParam" +#define LOG_FILE_HISTORY L"WinscpLogFile" +#define REMOTE_SYNC_HISTORY L"WinscpRemoteSync" +#define LOCAL_SYNC_HISTORY L"WinscpLocalSync" +#define MOVE_TO_HISTORY L"WinscpMoveTo" +#define WINSCP_FILE_MASK_HISTORY L"WinscpFileMask" +#define MAKE_SESSION_FOLDER_HISTORY L"WinscpSessionFolder" + +// for Properties dialog +//const int cpMode = 0x01; +//const int cpOwner = 0x02; +//const int cpGroup = 0x04; +// for Copy dialog +//const int coTempTransfer = 0x01; +//const int coDisableNewerOnly = 0x04; +//// for Synchronize and FullSynchronize dialogs +//const int spSelectedOnly = 0x800; +//// for Synchronize dialogs +//const int soAllowSelectedOnly = 0x01; +//// for FullSynchronize dialog +//const int fsoDisableTimestamp = 0x01; +//const int fsoAllowSelectedOnly = 0x02; +enum TSessionActionEnum +{ + saAdd, + saEdit, + saConnect +}; + +//DEFINE_CALLBACK_TYPE2(TGetSynchronizeOptionsEvent, void, +// intptr_t /*Params*/, TSynchronizeOptions & /*Options*/); +DEFINE_CALLBACK_TYPE3(TGetSpaceAvailableEvent, void, + const UnicodeString & /*Path*/, TSpaceAvailable & /*ASpaceAvailable*/, + bool & /*Close*/); + +struct TMultipleEdit : public TObject +{ + UnicodeString FileName; + UnicodeString FileTitle; + UnicodeString Directory; + UnicodeString LocalFileName; + bool PendingSave; +}; + +struct TEditHistory : public TObject +{ + UnicodeString FileName; + UnicodeString Directory; + bool operator==(const TEditHistory & rh) const { return (FileName == rh.FileName) && (Directory == rh.Directory); } +}; + +DEFINE_CALLBACK_TYPE2(TProcessSessionEvent, void, TSessionData * /*Data*/, void * /*Param*/); + +class TWinSCPFileSystem : public TCustomFarFileSystem +{ +friend class TWinSCPPlugin; +friend class TNetBoxPlugin; +friend class TKeepaliveThread; +friend class TQueueDialog; +NB_DISABLE_COPY(TWinSCPFileSystem) +NB_DECLARE_CLASS(TWinSCPFileSystem) +public: + explicit TWinSCPFileSystem(TCustomFarPlugin * APlugin); + void Init(TSecureShell * SecureShell); + virtual ~TWinSCPFileSystem(); + + virtual void Close(); + +protected: + bool Connect(TSessionData * Data); + void Disconnect(); + void SaveSession(); + + virtual void GetOpenPluginInfoEx(DWORD & Flags, + UnicodeString & HostFile, UnicodeString & CurDir, UnicodeString & Format, + UnicodeString & PanelTitle, TFarPanelModes * PanelModes, int & StartPanelMode, + int & StartSortMode, bool & StartSortOrder, TFarKeyBarTitles * KeyBarTitles, + UnicodeString & ShortcutData); + virtual bool GetFindDataEx(TObjectList * PanelItems, int OpMode); + virtual bool ProcessKeyEx(intptr_t Key, uintptr_t ControlState); + virtual bool SetDirectoryEx(const UnicodeString & Dir, int OpMode); + virtual intptr_t MakeDirectoryEx(UnicodeString & Name, int OpMode); + virtual bool DeleteFilesEx(TObjectList * PanelItems, int OpMode); + virtual intptr_t GetFilesEx(TObjectList * PanelItems, bool Move, + UnicodeString & DestPath, int OpMode); + virtual intptr_t PutFilesEx(TObjectList * PanelItems, bool Move, int OpMode); + virtual bool ProcessEventEx(intptr_t Event, void * Param); + + void ProcessEditorEvent(intptr_t Event, void * Param); + + virtual void HandleException(Exception * E, int OpMode = 0); + void KeepaliveThreadCallback(); + + inline bool IsSessionList() const; + inline bool Connected() const; + TWinSCPPlugin * GetWinSCPPlugin(); + void ShowOperationProgress(TFileOperationProgressType & ProgressData, + bool Force); + bool SessionDialog(TSessionData * Data, TSessionActionEnum & Action); + void EditConnectSession(TSessionData * Data, bool Edit); + void EditConnectSession(TSessionData * Data, bool Edit, bool NewData, bool FillInConnect); + void DuplicateOrRenameSession(TSessionData * Data, + bool Duplicate); + void FocusSession(const TSessionData * Data); + void DeleteSession(TSessionData * Data, void * Param); + void ProcessSessions(TObjectList * PanelItems, + TProcessSessionEvent ProcessSession, void * Param); + void ExportSession(TSessionData * Data, void * Param); + bool ImportSessions(TObjectList * PanelItems, bool Move, int OpMode); + void FileProperties(); + void CreateLink(); + void TransferFiles(bool Move); + void RenameFile(); + void ApplyCommand(); + void ShowInformation(); + void InsertTokenOnCommandLine(const UnicodeString & Token, bool Separate); + void InsertSessionNameOnCommandLine(); + void InsertFileNameOnCommandLine(bool Full); + UnicodeString GetFullFilePath(const TRemoteFile * AFile) const; + void InsertPathOnCommandLine(); + void CopyFullFileNamesToClipboard(); + void FullSynchronize(bool Source); + void Synchronize(); + void OpenDirectory(bool Add); + void HomeDirectory(); + void ToggleSynchronizeBrowsing(); + bool IsSynchronizedBrowsing() const; + bool PropertiesDialog(TStrings * AFileList, + const UnicodeString & Directory, + const TRemoteTokenList * GroupList, const TRemoteTokenList * UserList, + TRemoteProperties * Properties, intptr_t AllowedChanges); + bool ExecuteCommand(const UnicodeString & Command); + void TerminalCaptureLog(const UnicodeString & AddedLine, TCaptureOutputType OutputEvent); + bool CopyDialog(bool ToRemote, bool Move, const TStrings * AFileList, + intptr_t Options, + intptr_t CopyParamAttrs, + OUT UnicodeString & TargetDirectory, + OUT TGUICopyParamType * Params); + bool LinkDialog(UnicodeString & AFileName, UnicodeString & PointTo, bool & Symbolic, + bool Edit, bool AllowSymbolic); + void FileSystemInfoDialog(const TSessionInfo & SessionInfo, + const TFileSystemInfo & FileSystemInfo, const UnicodeString & SpaceAvailablePath, + TGetSpaceAvailableEvent OnGetSpaceAvailable); + bool OpenDirectoryDialog(bool Add, UnicodeString & Directory, + TBookmarkList * BookmarkList); + bool ApplyCommandDialog(UnicodeString & Command, intptr_t & Params); + bool FullSynchronizeDialog(TTerminal::TSynchronizeMode & Mode, + intptr_t & Params, UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TCopyParamType * CopyParams, bool & SaveSettings, bool & SaveMode, intptr_t Options, + const TUsableCopyParamAttrs & CopyParamAttrs); + bool SynchronizeChecklistDialog(TSynchronizeChecklist * Checklist, + TTerminal::TSynchronizeMode Mode, intptr_t Params, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory); + bool RemoteTransferDialog(TStrings * AFileList, UnicodeString & Target, + UnicodeString & FileMask, bool Move); + bool RenameFileDialog(TRemoteFile * AFile, UnicodeString & NewName); + uintptr_t MoreMessageDialog(const UnicodeString & Str, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, const TMessageParams * AParams = nullptr); + bool PasswordDialog(TSessionData * SessionData, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Results, bool StoredCredentialsTried); + bool BannerDialog(const UnicodeString & SessionName, const UnicodeString & Banner, + bool & NeverShowAgain, intptr_t Options); + bool CreateDirectoryDialog(UnicodeString & Directory, + TRemoteProperties * Properties, bool & SaveSettings); + bool QueueDialog(TTerminalQueueStatus * Status, bool ClosingPlugin); + bool SynchronizeDialog(TSynchronizeParamType & Params, + const TCopyParamType * CopyParams, TSynchronizeStartStopEvent OnStartStop, + bool & SaveSettings, intptr_t Options, intptr_t CopyParamAttrs, + TGetSynchronizeOptionsEvent OnGetOptions); + void DoSynchronize(TSynchronizeController * Sender, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory, + const TCopyParamType & CopyParam, const TSynchronizeParamType & Params, + TSynchronizeChecklist ** Checklist, TSynchronizeOptions * Options, bool Full); + void DoSynchronizeInvalid(TSynchronizeController * Sender, + const UnicodeString & Directory, const UnicodeString & ErrorStr); + void DoSynchronizeTooManyDirectories(TSynchronizeController * Sender, + intptr_t & MaxDirectories); + void Synchronize(const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, TTerminal::TSynchronizeMode Mode, + const TCopyParamType & CopyParam, intptr_t Params, TSynchronizeChecklist ** Checklist, + TSynchronizeOptions * Options); + bool SynchronizeAllowSelectedOnly(); + void GetSynchronizeOptions(intptr_t Params, TSynchronizeOptions & Options); + void RequireCapability(intptr_t Capability); + void RequireLocalPanel(TFarPanelInfo * Panel, const UnicodeString & Message); + bool AreCachesEmpty(); + void ClearCaches(); + void OpenSessionInPutty(); + void QueueShow(bool ClosingPlugin); + TTerminalQueueStatus * ProcessQueue(bool Hidden); + bool EnsureCommandSessionFallback(TFSCapability Capability); + void ConnectTerminal(TTerminal * Terminal); + void TemporarilyDownloadFiles(TStrings * AFileList, + TCopyParamType & CopyParam, UnicodeString & TempDir); + intptr_t UploadFiles(bool Move, int OpMode, bool Edit, UnicodeString & DestPath); + void UploadOnSave(bool NoReload); + void UploadFromEditor(bool NoReload, const UnicodeString & AFileName, + const UnicodeString & RealFileName, UnicodeString & DestPath); + void LogAuthentication(TTerminal * Terminal, const UnicodeString & Msg); + void MultipleEdit(); + void MultipleEdit(const UnicodeString & Directory, const UnicodeString & AFileName, TRemoteFile * AFile); + void EditViewCopyParam(TCopyParamType & CopyParam); + bool SynchronizeBrowsing(const UnicodeString & NewPath); + bool IsEditHistoryEmpty() const; + void EditHistory(); + UnicodeString ProgressBar(intptr_t Percentage, intptr_t Width); + bool IsLogging() const; + void ShowLog(); + + TTerminal * GetTerminal() const { return FTerminal; } + TSessionData * GetSessionData() const { return FTerminal ? FTerminal->GetSessionData() : nullptr; } + TSessionData * GetSessionData() { return FTerminal ? FTerminal->GetSessionData() : nullptr; } + +protected: + virtual UnicodeString GetCurrDirectory() const { return FTerminal ? FTerminal->GetCurrDirectory() : UnicodeString(); } + +private: + bool TerminalCheckForEsc(); + void TerminalClose(TObject * Sender); + void TerminalUpdateStatus(TTerminal * Terminal, bool Active); + void TerminalChangeDirectory(TObject * Sender); + void TerminalReadDirectory(TObject * Sender, bool ReloadOnly); + void TerminalStartReadDirectory(TObject * Sender); + void TerminalReadDirectoryProgress(TObject * Sender, intptr_t Progress, + intptr_t ResolvedLinks, bool & Cancel); + void TerminalInformation(TTerminal * Terminal, + const UnicodeString & Str, bool Status, intptr_t Phase); + void TerminalQueryUser(TObject * Sender, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * AParams, uintptr_t & Answer, TQueryType Type, void * Arg); + void TerminalPromptUser(TTerminal * Terminal, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, + TStrings * Prompts, TStrings * Results, bool & AResult, + void * Arg); + void TerminalDisplayBanner(TTerminal * Terminal, + const UnicodeString & SessionName, const UnicodeString & Banner, bool & NeverShowAgain, + intptr_t Options); + void TerminalShowExtendedException(TTerminal * Terminal, + Exception * E, void * Arg); + void TerminalDeleteLocalFile(const UnicodeString & AFileName, bool Alternative); + HANDLE TerminalCreateLocalFile(const UnicodeString & LocalFileName, + DWORD DesiredAccess, DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes); + inline DWORD TerminalGetLocalFileAttributes(const UnicodeString & LocalFileName); + inline BOOL TerminalSetLocalFileAttributes(const UnicodeString & LocalFileName, DWORD FileAttributes); + BOOL TerminalMoveLocalFile(const UnicodeString & LocalFileName, const UnicodeString & NewLocalFileName, DWORD Flags); + BOOL TerminalRemoveLocalDirectory(const UnicodeString & LocalDirName); + BOOL TerminalCreateLocalDirectory(const UnicodeString & LocalDirName, LPSECURITY_ATTRIBUTES SecurityAttributes); + void OperationProgress( + TFileOperationProgressType & ProgressData); + void OperationFinished(TFileOperation Operation, + TOperationSide Side, bool DragDrop, const UnicodeString & AFileName, bool Success, + TOnceDoneOperation & DisconnectWhenComplete); + void CancelConfiguration(TFileOperationProgressType & ProgressData); + TStrings * CreateFileList(TObjectList * PanelItems, + TOperationSide Side, bool SelectedOnly = false, const UnicodeString & Directory = L"", + bool FileNameOnly = false, TStrings * AFileList = nullptr); + TStrings * CreateSelectedFileList(TOperationSide Side, + TFarPanelInfo ** PanelInfo = nullptr); + TStrings * CreateFocusedFileList(TOperationSide Side, + TFarPanelInfo ** PanelInfo = nullptr); + void CustomCommandGetParamValue( + const UnicodeString & AName, UnicodeString & Value); + void TerminalSynchronizeDirectory(const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, bool & Continue, bool Collect); + void QueueListUpdate(TTerminalQueue * Queue); + void QueueItemUpdate(TTerminalQueue * Queue, TQueueItem * Item); + void QueueEvent(TTerminalQueue * Queue, TQueueEvent Event); + void GetSpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable, bool & Close); + void QueueAddItem(TQueueItem * Item); + UnicodeString GetFileNameHash(const UnicodeString & AFileName) const; + intptr_t GetFilesRemote(TObjectList * PanelItems, bool Move, + UnicodeString & DestPath, int OpMode); + +private: + TTerminalQueue * GetQueue(); + TTerminalQueueStatus * GetQueueStatus(); + +private: + TTerminal * FTerminal; + TTerminalQueue * FQueue; + TTerminalQueueStatus * FQueueStatus; + TCriticalSection FQueueStatusSection; + TQueueEvent FQueueEvent; + HANDLE FProgressSaveScreenHandle; + HANDLE FSynchronizationSaveScreenHandle; + HANDLE FAuthenticationSaveScreenHandle; + TDateTime FSynchronizationStart; + TStrings * FFileList; + TList * FPanelItems; + UnicodeString FSavedFindFolder; + UnicodeString FOriginalEditFile; + UnicodeString FLastEditFile; + UnicodeString FLastMultipleEditFile; + UnicodeString FLastMultipleEditFileTitle; + UnicodeString FLastMultipleEditDirectory; + intptr_t FLastEditorID; + TGUICopyParamType FLastEditCopyParam; + TKeepaliveThread * FKeepaliveThread; + TSynchronizeController * FSynchronizeController; + TStrings * FCapturedLog; + TStrings * FAuthenticationLog; + typedef std::map TMultipleEdits; + TMultipleEdits FMultipleEdits; + typedef std::vector TEditHistories; + TEditHistories FEditHistories; + UnicodeString FLastPath; + TStrings * FPathHistory; + UnicodeString FSessionsFolder; + UnicodeString FNewSessionsFolder; + UnicodeString FPrevSessionName; + bool FQueueStatusInvalidated; + bool FQueueItemInvalidated; + bool FRefreshLocalDirectory; + bool FRefreshRemoteDirectory; + bool FQueueEventPending; + bool FReloadDirectory; + bool FLastMultipleEditReadOnly; + bool FNoProgress; + bool FSynchronizationCompare; + bool FEditorPendingSave; + bool FNoProgressFinish; + bool FSynchronisingBrowse; + bool FOutputLog; + bool FLoadingSessionList; + bool FCurrentDirectoryWasChanged; +}; + +class TSessionPanelItem : public TCustomFarPanelItem +{ +NB_DISABLE_COPY(TSessionPanelItem) +public: + explicit TSessionPanelItem(const UnicodeString & APath); + explicit TSessionPanelItem(const TSessionData * ASessionData); + static void SetPanelModes(TFarPanelModes * PanelModes); + static void SetKeyBarTitles(TFarKeyBarTitles * KeyBarTitles); + +protected: + UnicodeString FPath; + const TSessionData * FSessionData; + + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber); +}; + +class TSessionFolderPanelItem : public TCustomFarPanelItem +{ +public: + explicit TSessionFolderPanelItem(const UnicodeString & Folder); + +protected: + UnicodeString FFolder; + + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber); +}; + +class TRemoteFilePanelItem : public TCustomFarPanelItem +{ +NB_DISABLE_COPY(TRemoteFilePanelItem) +public: + explicit TRemoteFilePanelItem(TRemoteFile * ARemoteFile); + static void SetPanelModes(TFarPanelModes * PanelModes); + static void SetKeyBarTitles(TFarKeyBarTitles * KeyBarTitles); + +protected: + TRemoteFile * FRemoteFile; + + virtual void GetData( + DWORD & Flags, UnicodeString & AFileName, int64_t & Size, + DWORD & FileAttributes, + TDateTime & LastWriteTime, TDateTime & LastAccess, + DWORD & NumberOfLinks, UnicodeString & Description, + UnicodeString & Owner, void *& UserData, int & CustomColumnNumber); + virtual UnicodeString GetCustomColumnData(size_t Column); + static void TranslateColumnTypes(UnicodeString & AColumnTypes, + TStrings * ColumnTitles); +}; + diff --git a/netbox/src/NetBox/WinSCPPlugin.cpp b/netbox/src/NetBox/WinSCPPlugin.cpp new file mode 100644 index 000000000..d951b8dce --- /dev/null +++ b/netbox/src/NetBox/WinSCPPlugin.cpp @@ -0,0 +1,805 @@ +#include +#pragma hdrstop + +#include +#include +#include +#include +#include +#include +#include +#include "WinSCPPlugin.h" +#include "WinSCPFileSystem.h" +#include "FarConfiguration.h" +#include "FarDialog.h" +#include "XmlStorage.h" + +TCustomFarPlugin * CreateFarPlugin(HINSTANCE HInst) +{ + return new TWinSCPPlugin(HInst); +} + +TWinSCPPlugin::TWinSCPPlugin(HINSTANCE HInst) : + TCustomFarPlugin(HInst), + FInitialized(false) +{ +} + +TWinSCPPlugin::~TWinSCPPlugin() +{ + if (FInitialized) + { + GetFarConfiguration()->SetPlugin(nullptr); + CoreFinalize(); + } +} + +bool TWinSCPPlugin::HandlesFunction(THandlesFunction Function) const +{ + return (Function == hfProcessKey || Function == hfProcessEvent); +} + +intptr_t TWinSCPPlugin::GetMinFarVersion() const +{ + return MAKEFARVERSION(2, 0, 1667); +} + +void TWinSCPPlugin::SetStartupInfo(const struct PluginStartupInfo * Info) +{ + try + { + TCustomFarPlugin::SetStartupInfo(Info); + } + catch (Exception & E) + { + HandleException(&E); + } +} + +void TWinSCPPlugin::GetPluginInfoEx(DWORD & Flags, + TStrings * DiskMenuStrings, TStrings * PluginMenuStrings, + TStrings * PluginConfigStrings, TStrings * CommandPrefixes) +{ + CoreInitializeOnce(); + Flags = PF_FULLCMDLINE; + TFarConfiguration * FarConfiguration = GetFarConfiguration(); + if (FarConfiguration->GetDisksMenu()) + { + DiskMenuStrings->AddObject(GetMsg(PLUGIN_NAME), + reinterpret_cast(static_cast(FarConfiguration->GetDisksMenuHotKey()))); + } + if (FarConfiguration->GetPluginsMenu()) + { + PluginMenuStrings->Add(GetMsg(PLUGIN_NAME)); + } + if (FarConfiguration->GetPluginsMenuCommands()) + { + PluginMenuStrings->Add(GetMsg(MENU_COMMANDS)); + } + PluginConfigStrings->Add(GetMsg(PLUGIN_NAME)); + CommandPrefixes->SetCommaText(FarConfiguration->GetCommandPrefixes()); +} + +bool TWinSCPPlugin::ConfigureEx(intptr_t /*Item*/) +{ + bool Change = false; + + std::unique_ptr MenuItems(new TFarMenuItems()); + intptr_t MInterface = MenuItems->Add(GetMsg(CONFIG_INTERFACE)); + intptr_t MConfirmations = MenuItems->Add(GetMsg(CONFIG_CONFIRMATIONS)); + intptr_t MPanel = MenuItems->Add(GetMsg(CONFIG_PANEL)); + intptr_t MTransfer = MenuItems->Add(GetMsg(CONFIG_TRANSFER)); + intptr_t MBackground = MenuItems->Add(GetMsg(CONFIG_BACKGROUND)); + intptr_t MEndurance = MenuItems->Add(GetMsg(CONFIG_ENDURANCE)); + intptr_t MTransferEditor = MenuItems->Add(GetMsg(CONFIG_TRANSFER_EDITOR)); + intptr_t MLogging = MenuItems->Add(GetMsg(CONFIG_LOGGING)); + intptr_t MIntegration = MenuItems->Add(GetMsg(CONFIG_INTEGRATION)); + MenuItems->AddSeparator(); + intptr_t MAbout = MenuItems->Add(GetMsg(CONFIG_ABOUT)); + + intptr_t Result = 0; + + do + { + Result = Menu(FMENU_WRAPMODE, GetMsg(PLUGIN_TITLE), L"", MenuItems.get()); + + if (Result >= 0) + { + if (Result == MInterface) + { + if (ConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MTransfer) + { + if (TransferConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MBackground) + { + if (QueueConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MEndurance) + { + if (EnduranceConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MPanel) + { + if (PanelConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MTransferEditor) + { + if (TransferEditorConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MConfirmations) + { + if (ConfirmationsConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MLogging) + { + if (LoggingConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MIntegration) + { + if (IntegrationConfigurationDialog()) + { + Change = true; + } + } + else if (Result == MAbout) + { + AboutDialog(); + } + } + + if (Change) + { + // only modified, implicit + GetConfiguration()->DoSave(false, false); + } + } + while (Result >= 0); + + return Change; +} + +intptr_t TWinSCPPlugin::ProcessEditorEventEx(intptr_t Event, void * Param) +{ + // for performance reasons, do not pass the event to file systems on redraw + if ((Event != EE_REDRAW) || GetFarConfiguration()->GetEditorUploadOnSave() || + GetFarConfiguration()->GetEditorMultiple()) + { + for (intptr_t Index = 0; Index < FOpenedPlugins->GetCount(); ++Index) + { + TWinSCPFileSystem * FileSystem = NB_STATIC_DOWNCAST(TWinSCPFileSystem, FOpenedPlugins->GetObj(Index)); + FileSystem->ProcessEditorEvent(Event, Param); + } + } + + return 0; +} + +intptr_t TWinSCPPlugin::ProcessEditorInputEx(const INPUT_RECORD * Rec) +{ + intptr_t Result = 0; + if ((Rec->EventType == KEY_EVENT) && + Rec->Event.KeyEvent.bKeyDown && + (Rec->Event.KeyEvent.uChar.AsciiChar == 'W') && + (FLAGSET(Rec->Event.KeyEvent.dwControlKeyState, LEFT_ALT_PRESSED) || + FLAGSET(Rec->Event.KeyEvent.dwControlKeyState, RIGHT_ALT_PRESSED)) && + FLAGSET(Rec->Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED)) + { + CommandsMenu(false); + Result = 1; + } + + return Result; +} + +TCustomFarFileSystem * TWinSCPPlugin::OpenPluginEx(intptr_t OpenFrom, intptr_t Item) +{ + std::unique_ptr FileSystem; + CoreInitializeOnce(); + + if ((OpenFrom == OPEN_PLUGINSMENU) && + (!GetFarConfiguration()->GetPluginsMenu() || (Item == 1))) + { + CommandsMenu(true); + } + else + { + FileSystem.reset(new TWinSCPFileSystem(this)); + FileSystem->Init(nullptr); + + if (OpenFrom == OPEN_DISKMENU || OpenFrom == OPEN_PLUGINSMENU || + OpenFrom == OPEN_FINDLIST) + { + // nothing + } + else if (OpenFrom == OPEN_SHORTCUT || OpenFrom == OPEN_COMMANDLINE) + { + UnicodeString Directory; + UnicodeString CommandLine = reinterpret_cast(Item); + if (OpenFrom == OPEN_SHORTCUT) + { + intptr_t P = CommandLine.Pos(L"\1"); + if (P > 0) + { + Directory = CommandLine.SubString(P + 1, CommandLine.Length() - P); + CommandLine.SetLength(P - 1); + } + + TWinSCPFileSystem * PanelSystem; + PanelSystem = NB_STATIC_DOWNCAST(TWinSCPFileSystem, GetPanelFileSystem()); + if (PanelSystem && PanelSystem->Connected() && + PanelSystem->GetTerminal()->GetSessionData()->GenerateSessionUrl(sufComplete) == CommandLine) + { + PanelSystem->SetDirectoryEx(Directory, OPM_SILENT); + if (PanelSystem->UpdatePanel()) + { + PanelSystem->RedrawPanel(); + } + Abort(); + } + // directory will be set by FAR itself + Directory.Clear(); + } + DebugAssert(StoredSessions); + bool DefaultsOnly = false; + std::unique_ptr Options(new TProgramParams()); + ParseCommandLine(CommandLine, Options.get()); + std::unique_ptr Session(StoredSessions->ParseUrl(CommandLine, Options.get(), DefaultsOnly)); + if (DefaultsOnly) + { + Abort(); + } + if (!Session->GetCanLogin()) + { + DebugAssert(false); + Abort(); + } + FileSystem->Connect(Session.get()); + if (!Directory.IsEmpty()) + { + FileSystem->SetDirectoryEx(Directory, OPM_SILENT); + } + } + else if (OpenFrom == OPEN_ANALYSE) + { + const wchar_t * XmlFileName = reinterpret_cast(Item); + std::unique_ptr ImportStorage(new TXmlStorage(XmlFileName, GetConfiguration()->GetStoredSessionsSubKey())); + ImportStorage->Init(); + ImportStorage->SetAccessMode(smRead); + if (!(ImportStorage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), false) && + ImportStorage->HasSubKeys())) + { + DebugAssert(false); + Abort(); + } + UnicodeString SessionName = ::PuttyUnMungeStr(ImportStorage->ReadStringRaw("Session", L"")); + std::unique_ptr Session(new TSessionData(SessionName)); + Session->Load(ImportStorage.get()); + Session->SetModified(true); + if (!Session->GetCanLogin()) + { + DebugAssert(false); + Abort(); + } + FileSystem->Connect(Session.get()); + } + else + { + DebugAssert(false); + } + } + + return FileSystem.release(); +} + +void TWinSCPPlugin::ParseCommandLine(UnicodeString & CommandLine, + TOptions * Options) +{ + UnicodeString CmdLine = CommandLine; + intptr_t Index = 1; + // Skip session name + { + while ((Index < CmdLine.Length()) && (CmdLine[Index] == L' ')) + ++Index; + if (Index >= CmdLine.Length()) + return; + if (CmdLine[Index] == L'"') + { + ++Index; + while ((Index < CmdLine.Length()) && (CmdLine[Index] != L'"')) + ++Index; + ++Index; + } + while ((Index < CmdLine.Length()) && (CmdLine[Index] != L' ')) + ++Index; + } + CmdLine = CmdLine.SubString(Index, -1); + // Parse params + intptr_t Pos = ::FirstDelimiter(Options->GetSwitchMarks(), CmdLine); + UnicodeString CommandLineParams; + if (Pos > 0) + CommandLineParams = CmdLine.SubString(Pos, -1); + if (!CommandLineParams.IsEmpty()) + { + TODO("implement Options->ParseParams(CommandLineParams)"); + ThrowNotImplemented(3015); + CommandLine = CommandLine.SubString(1, CommandLine.Length() - CommandLineParams.Length()).Trim(); + } +} + +void TWinSCPPlugin::CommandsMenu(bool FromFileSystem) +{ + std::unique_ptr MenuItems(new TFarMenuItems()); + TWinSCPFileSystem * FileSystem; + TWinSCPFileSystem * AnotherFileSystem; + FileSystem = NB_STATIC_DOWNCAST(TWinSCPFileSystem, GetPanelFileSystem()); + AnotherFileSystem = NB_STATIC_DOWNCAST(TWinSCPFileSystem, GetPanelFileSystem(true)); + bool FSConnected = (FileSystem != nullptr) && FileSystem->Connected(); + bool AnotherFSConnected = (AnotherFileSystem != nullptr) && AnotherFileSystem->Connected(); + bool FSVisible = FSConnected && FromFileSystem; + bool AnyFSVisible = (FSConnected || AnotherFSConnected) && FromFileSystem; + + intptr_t MAttributes = MenuItems->Add(GetMsg(MENU_COMMANDS_ATTRIBUTES), FSVisible); + intptr_t MLink = MenuItems->Add(GetMsg(MENU_COMMANDS_LINK), FSVisible); + intptr_t MApplyCommand = MenuItems->Add(GetMsg(MENU_COMMANDS_APPLY_COMMAND), FSVisible); + intptr_t MFullSynchronize = MenuItems->Add(GetMsg(MENU_COMMANDS_FULL_SYNCHRONIZE), AnyFSVisible); + intptr_t MSynchronize = MenuItems->Add(GetMsg(MENU_COMMANDS_SYNCHRONIZE), AnyFSVisible); + intptr_t MQueue = MenuItems->Add(GetMsg(MENU_COMMANDS_QUEUE), FSVisible); + intptr_t MInformation = MenuItems->Add(GetMsg(MENU_COMMANDS_INFORMATION), FSVisible); + intptr_t MLog = MenuItems->Add(GetMsg(MENU_COMMANDS_LOG), FSVisible); + intptr_t MClearCaches = MenuItems->Add(GetMsg(MENU_COMMANDS_CLEAR_CACHES), FSVisible); + intptr_t MPutty = MenuItems->Add(GetMsg(MENU_COMMANDS_PUTTY), FSVisible); + intptr_t MEditHistory = MenuItems->Add(GetMsg(MENU_COMMANDS_EDIT_HISTORY), FSConnected); + MenuItems->AddSeparator(FSConnected || FSVisible); + intptr_t MAddBookmark = MenuItems->Add(GetMsg(MENU_COMMANDS_ADD_BOOKMARK), FSVisible); + intptr_t MOpenDirectory = MenuItems->Add(GetMsg(MENU_COMMANDS_OPEN_DIRECTORY), FSVisible); + intptr_t MHomeDirectory = MenuItems->Add(GetMsg(MENU_COMMANDS_HOME_DIRECTORY), FSVisible); + intptr_t MSynchronizeBrowsing = MenuItems->Add(GetMsg(MENU_COMMANDS_SYNCHRONIZE_BROWSING), FSVisible); + MenuItems->AddSeparator(FSVisible); + intptr_t MPageant = MenuItems->Add(GetMsg(MENU_COMMANDS_PAGEANT), FromFileSystem); + intptr_t MPuttygen = MenuItems->Add(GetMsg(MENU_COMMANDS_PUTTYGEN), FromFileSystem); + MenuItems->AddSeparator(FromFileSystem); + intptr_t MConfigure = MenuItems->Add(GetMsg(MENU_COMMANDS_CONFIGURE)); + intptr_t MAbout = MenuItems->Add(GetMsg(CONFIG_ABOUT)); + + MenuItems->SetDisabled(MLog, !FSVisible || (FileSystem && !FileSystem->IsLogging())); + MenuItems->SetDisabled(MClearCaches, !FSVisible || (FileSystem && FileSystem->AreCachesEmpty())); + MenuItems->SetDisabled(MPutty, !FSVisible || !FileExistsEx(::ExpandEnvironmentVariables(ExtractProgram(GetFarConfiguration()->GetPuttyPath())))); + MenuItems->SetDisabled(MEditHistory, !FSConnected || (FileSystem && FileSystem->IsEditHistoryEmpty())); + MenuItems->SetChecked(MSynchronizeBrowsing, FSVisible && (FileSystem && FileSystem->IsSynchronizedBrowsing())); + MenuItems->SetDisabled(MPageant, !FileExistsEx(::ExpandEnvironmentVariables(ExtractProgram(GetFarConfiguration()->GetPageantPath())))); + MenuItems->SetDisabled(MPuttygen, !FileExistsEx(::ExpandEnvironmentVariables(ExtractProgram(GetFarConfiguration()->GetPuttygenPath())))); + + intptr_t Result = Menu(FMENU_WRAPMODE, GetMsg(MENU_COMMANDS), L"", MenuItems.get()); + + if (Result >= 0) + { + if ((Result == MLog) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->ShowLog(); + } + else if ((Result == MAttributes) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->FileProperties(); + } + else if ((Result == MLink) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->CreateLink(); + } + else if ((Result == MApplyCommand) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->ApplyCommand(); + } + else if (Result == MFullSynchronize) + { + if (FileSystem != nullptr) + { + FileSystem->FullSynchronize(true); + } + else + { + DebugAssert(AnotherFileSystem != nullptr); + if (AnotherFileSystem) + AnotherFileSystem->FullSynchronize(false); + } + } + else if (Result == MSynchronize) + { + if (FileSystem != nullptr) + { + FileSystem->Synchronize(); + } + else + { + DebugAssert(AnotherFileSystem != nullptr); + if (AnotherFileSystem) + AnotherFileSystem->Synchronize(); + } + } + else if ((Result == MQueue) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->QueueShow(false); + } + else if ((Result == MAddBookmark || Result == MOpenDirectory) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->OpenDirectory(Result == MAddBookmark); + } + else if (Result == MHomeDirectory && FileSystem) + { + FileSystem->HomeDirectory(); + } + else if (Result == MConfigure) + { + ConfigureEx(0); + } + else if (Result == MAbout) + { + AboutDialog(); + } + else if ((Result == MPutty) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->OpenSessionInPutty(); + } + else if ((Result == MEditHistory) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->EditHistory(); + } + else if (Result == MPageant || Result == MPuttygen) + { + UnicodeString Path = (Result == MPageant) ? + GetFarConfiguration()->GetPageantPath() : GetFarConfiguration()->GetPuttygenPath(); + UnicodeString Program, Params, Dir; + SplitCommand(::ExpandEnvironmentVariables(Path), Program, Params, Dir); + ExecuteShell(Program, Params); + } + else if ((Result == MClearCaches) && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->ClearCaches(); + } + else if ((Result == MSynchronizeBrowsing) && FileSystem) + { + DebugAssert(FileSystem != nullptr); + FileSystem->ToggleSynchronizeBrowsing(); + } + else if (Result == MInformation && FileSystem) + { + DebugAssert(FileSystem); + FileSystem->ShowInformation(); + } + else + { + DebugAssert(false); + } + } +} + +void TWinSCPPlugin::ShowExtendedException(Exception * E) +{ + if (E && !E->Message.IsEmpty()) + { + if (NB_STATIC_DOWNCAST(EAbort, E) == nullptr) + { + TQueryType Type; + Type = NB_STATIC_DOWNCAST(ESshTerminate, E) != nullptr ? + qtInformation : qtError; + + TStrings * MoreMessages = nullptr; + if (NB_STATIC_DOWNCAST(ExtException, E) != nullptr) + { + MoreMessages = NB_STATIC_DOWNCAST(ExtException, E)->GetMoreMessages(); + } + UnicodeString Message = TranslateExceptionMessage(E); + MoreMessageDialog(Message, MoreMessages, Type, qaOK); + } + } +} + +void TWinSCPPlugin::HandleException(Exception * E, int OpMode) +{ + if (((OpMode & OPM_FIND) == 0) || (NB_STATIC_DOWNCAST(EFatal, E) != nullptr)) + { + ShowExtendedException(E); + } +} + +struct TFarMessageData : public TObject +{ +NB_DECLARE_CLASS(TFarMessageData) +NB_DISABLE_COPY(TFarMessageData) +public: + TFarMessageData() : + Params(nullptr), + ButtonCount(0) + { + ClearArray(Buttons); + } + + const TMessageParams * Params; + uintptr_t Buttons[15 + 1]; + uintptr_t ButtonCount; +}; + +void TWinSCPPlugin::MessageClick(void * Token, uintptr_t Result, bool & Close) +{ + DebugAssert(Token); + TFarMessageData & Data = *NB_STATIC_DOWNCAST(TFarMessageData, Token); + + DebugAssert(Result != static_cast(-1) && Result < Data.ButtonCount); + + if ((Data.Params != nullptr) && (Data.Params->Aliases != nullptr)) + { + for (uintptr_t Index = 0; Index < Data.Params->AliasesCount; ++Index) + { + const TQueryButtonAlias & Alias = Data.Params->Aliases[Index]; + if ((Alias.Button == Data.Buttons[Result]) && + (Alias.OnClick)) + { + Alias.OnClick(nullptr); + Close = false; + break; + } + } + } +} + +uintptr_t TWinSCPPlugin::MoreMessageDialog(const UnicodeString & Str, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, + const TMessageParams * Params) +{ + uintptr_t Result = 0; + UnicodeString DialogStr = Str; + std::unique_ptr ButtonLabels(new TStringList()); + uintptr_t Flags = 0; + + if (Params != nullptr) + { + Flags = Params->Flags; + } + + intptr_t TitleId = 0; + switch (Type) + { + case qtConfirmation: + TitleId = MSG_TITLE_CONFIRMATION; + break; + case qtInformation: + TitleId = MSG_TITLE_INFORMATION; + break; + case qtError: + TitleId = MSG_TITLE_ERROR; + Flags |= FMSG_WARNING; + break; + case qtWarning: + TitleId = MSG_TITLE_WARNING; + Flags |= FMSG_WARNING; + break; + default: + DebugAssert(false); + } + TFarMessageData Data; + Data.Params = Params; + + // make sure to do the check on full answers, not on reduced "timer answers" + if (((Answers & qaAbort) && (Answers & qaRetry)) || + (GetTopDialog() != nullptr)) + { + // use warning colors for abort/retry confirmation dialog + Flags |= FMSG_WARNING; + } + + if (Params != nullptr) + { + if (Params->Timer > 0) + { + if (Params->TimerAnswers > 0) + { + Answers = Params->TimerAnswers; + } + if (!Params->TimerMessage.IsEmpty()) + { + DialogStr = Params->TimerMessage; + } + } + } + + uintptr_t AAnswers = Answers; + bool NeverAskAgainCheck = (Params != nullptr) && FLAGSET(Params->Params, qpNeverAskAgainCheck); + bool NeverAskAgainPending = NeverAskAgainCheck; + uintptr_t TimeoutButton = 0; + +#define ADD_BUTTON_EX(TYPE, CANNEVERASK) \ + if (AAnswers & qa ## TYPE) \ + { \ + ButtonLabels->Add(GetMsg(MSG_BUTTON_ ## TYPE)); \ + Data.Buttons[Data.ButtonCount] = qa ## TYPE; \ + Data.ButtonCount++; \ + AAnswers -= qa ## TYPE; \ + if ((Params != nullptr) && (Params->Timeout != 0) && \ + (Params->TimeoutAnswer == qa ## TYPE)) \ + { \ + TimeoutButton = ButtonLabels->GetCount() - 1; \ + } \ + if (NeverAskAgainPending && CANNEVERASK) \ + { \ + ButtonLabels->SetObj(ButtonLabels->GetCount() - 1, reinterpret_cast((size_t)true)); \ + NeverAskAgainPending = false; \ + } \ + } +#define ADD_BUTTON(TYPE) ADD_BUTTON_EX(TYPE, false) +#pragma warning(push) +#pragma warning(disable: 4127) + ADD_BUTTON_EX(Yes, true); + ADD_BUTTON(No); + ADD_BUTTON_EX(OK, true); + ADD_BUTTON(Cancel); + ADD_BUTTON(Abort); + ADD_BUTTON(Retry); + ADD_BUTTON(Ignore); + ADD_BUTTON(Skip); + ADD_BUTTON(All); + ADD_BUTTON(NoToAll); + ADD_BUTTON_EX(YesToAll, true); + ADD_BUTTON(Help); +#pragma warning(pop) +#undef ADD_BUTTON +#undef ADD_BUTTON_EX + + DebugUsedParam(AAnswers); + DebugAssert(!AAnswers); + DebugUsedParam(NeverAskAgainPending); + DebugAssert(!NeverAskAgainPending); + + uintptr_t DefaultButtonIndex = 0; + if ((Params != nullptr) && (Params->Aliases != nullptr)) + { + for (uintptr_t bi = 0; bi < Data.ButtonCount; bi++) + { + for (uintptr_t ai = 0; ai < Params->AliasesCount; ai++) + { + if (Params->Aliases[ai].Button == Data.Buttons[bi] && + !Params->Aliases[ai].Alias.IsEmpty()) + { + ButtonLabels->SetString(bi, Params->Aliases[ai].Alias); + if (Params->Aliases[ai].Default) + DefaultButtonIndex = bi; + break; + } + } + } + } + +#define MORE_BUTTON_ID -2 + TFarMessageParams FarParams; + + if (NeverAskAgainCheck) + { + FarParams.CheckBoxLabel = + (Answers == qaOK) ? GetMsg(MSG_CHECK_NEVER_SHOW_AGAIN) : + GetMsg(MSG_CHECK_NEVER_ASK_AGAIN); + } + + if (Params != nullptr) + { + if (Params->Timer > 0) + { + FarParams.Timer = Params->Timer; + FarParams.TimerEvent = Params->TimerEvent; + } + + if (Params->Timeout > 0) + { + FarParams.Timeout = Params->Timeout; + FarParams.TimeoutButton = TimeoutButton; + FarParams.TimeoutStr = GetMsg(MSG_BUTTON_TIMEOUT); + } + } + + FarParams.Token = &Data; + FarParams.DefaultButton = DefaultButtonIndex; + FarParams.ClickEvent = MAKE_CALLBACK(TWinSCPPlugin::MessageClick, this); + + if (MoreMessages && (MoreMessages->GetCount() > 0)) + { + FarParams.MoreMessages = MoreMessages; + } + else + { + FarParams.MoreMessages = nullptr; + } + + Result = Message(static_cast(Flags), GetMsg(TitleId), DialogStr, ButtonLabels.get(), &FarParams); + if (FarParams.TimerAnswer > 0) + { + Result = FarParams.TimerAnswer; + } + else if (Result == NPOS) + { + Result = CancelAnswer(Answers); + } + else + { + DebugAssert(Result != static_cast(-1) && Result < Data.ButtonCount); + Result = Data.Buttons[Result]; + } + + if (FarParams.CheckBox) + { + DebugAssert(NeverAskAgainCheck); + Result = qaNeverAskAgain; + } + + return Result; +} + +void TWinSCPPlugin::CleanupConfiguration() +{ + // Check if key Configuration\Version exists + std::unique_ptr Storage(GetFarConfiguration()->CreateConfigStorage()); + Storage->SetAccessMode(smReadWrite); + if (Storage->OpenSubKey(GetFarConfiguration()->GetConfigurationSubKey(), false)) + { + if (!Storage->ValueExists("Version")) + { + Storage->DeleteSubKey("CDCache"); + } + else + { + UnicodeString Version = Storage->ReadString("Version", L""); + if (::StrToVersionNumber(Version) < MAKEVERSIONNUMBER(2, 1, 19)) + { + Storage->DeleteSubKey("CDCache"); + } + } + Storage->WriteStringRaw("Version", ::VersionNumberToStr(::GetCurrentVersionNumber())); + Storage->CloseSubKey(); + } +} + +void TWinSCPPlugin::CoreInitializeOnce() +{ + if (!FInitialized) + { + CoreInitialize(); + CleanupConfiguration(); + FInitialized = true; + } +} + +NB_IMPLEMENT_CLASS(TWinSCPPlugin, NB_GET_CLASS_INFO(TCustomFarPlugin), nullptr) +NB_IMPLEMENT_CLASS(TFarMessageData, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/NetBox/WinSCPPlugin.h b/netbox/src/NetBox/WinSCPPlugin.h new file mode 100644 index 000000000..046d44d7e --- /dev/null +++ b/netbox/src/NetBox/WinSCPPlugin.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include "FarPlugin.h" + +class TWinSCPFileSystem; +class TCopyParamType; + +class TWinSCPPlugin : public TCustomFarPlugin +{ +friend TWinSCPFileSystem; +NB_DECLARE_CLASS(TWinSCPPlugin) +public: + explicit TWinSCPPlugin(HINSTANCE HInst); + virtual ~TWinSCPPlugin(); + virtual intptr_t GetMinFarVersion() const; + + virtual void HandleException(Exception * E, int OpMode = 0); + uintptr_t MoreMessageDialog(const UnicodeString & Str, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, const TMessageParams * Params = nullptr); + void ShowExtendedException(Exception * E); + bool CopyParamCustomDialog(TCopyParamType & CopyParam, + intptr_t CopyParamAttrs); + virtual void SetStartupInfo(const struct PluginStartupInfo * Info); + +protected: + virtual bool HandlesFunction(THandlesFunction Function) const; + virtual void GetPluginInfoEx(DWORD & Flags, TStrings * DiskMenuStrings, + TStrings * PluginMenuStrings, TStrings * PluginConfigStrings, + TStrings * CommandPrefixes); + virtual TCustomFarFileSystem * OpenPluginEx(intptr_t OpenFrom, intptr_t Item); + virtual bool ConfigureEx(intptr_t Item); + virtual intptr_t ProcessEditorEventEx(intptr_t Event, void * Param); + virtual intptr_t ProcessEditorInputEx(const INPUT_RECORD * Rec); + bool CopyParamDialog(const UnicodeString & Caption, TCopyParamType & CopyParam, + intptr_t CopyParamAttrs); + void MessageClick(void * Token, uintptr_t Result, bool & Close); + + void CommandsMenu(bool FromFileSystem); + bool ConfigurationDialog(); + bool PanelConfigurationDialog(); + bool TransferConfigurationDialog(); + bool QueueConfigurationDialog(); + bool EnduranceConfigurationDialog(); + bool TransferEditorConfigurationDialog(); + bool LoggingConfigurationDialog(); + bool ConfirmationsConfigurationDialog(); + bool IntegrationConfigurationDialog(); + void AboutDialog(); + +private: + void CleanupConfiguration(); + void CoreInitializeOnce(); + void ParseCommandLine(UnicodeString & CommandLine, + TOptions * Options); + +private: + bool FInitialized; +}; + diff --git a/netbox/src/NetBox/XmlStorage.cpp b/netbox/src/NetBox/XmlStorage.cpp new file mode 100644 index 000000000..952c82277 --- /dev/null +++ b/netbox/src/NetBox/XmlStorage.cpp @@ -0,0 +1,429 @@ + +#include +#include +#include "XmlStorage.h" +#include "TextsCore.h" +#include "FarUtil.h" + +static const char * CONST_XML_VERSION21 = "2.1"; +static const char * CONST_ROOT_NODE = "NetBox"; +static const char * CONST_SESSION_NODE = "Session"; +static const char * CONST_VERSION_ATTR = "version"; +static const char * CONST_NAME_ATTR = "name"; + +TXmlStorage::TXmlStorage(const UnicodeString & AStorage, + const UnicodeString & StoredSessionsSubKey) : + THierarchicalStorage(::ExcludeTrailingBackslash(AStorage)), + FXmlDoc(nullptr), + FCurrentElement(nullptr), + FStoredSessionsSubKey(StoredSessionsSubKey), + FFailed(0), + FStoredSessionsOpened(false) +{ +} + +void TXmlStorage::Init() +{ + THierarchicalStorage::Init(); + FXmlDoc = new tinyxml2::XMLDocument(); +} + +TXmlStorage::~TXmlStorage() +{ + if (GetAccessMode() == smReadWrite) + { + WriteXml(); + } + SAFE_DESTROY_EX(tinyxml2::XMLDocument, FXmlDoc); +} + +bool TXmlStorage::ReadXml() +{ + CNBFile xmlFile; + if (!xmlFile.OpenRead(GetStorage().c_str())) + { + return false; + } + size_t buffSize = static_cast(xmlFile.GetFileSize() + 1); + if (buffSize > 1000000) + { + return false; + } + std::string buff(buffSize, 0); + if (!xmlFile.Read(&buff[0], buffSize)) + { + return false; + } + + FXmlDoc->Parse(buff.c_str()); + if (FXmlDoc->Error()) + { + return false; + } + + // Get and check root node + tinyxml2::XMLElement * xmlRoot = FXmlDoc->RootElement(); + if (!xmlRoot) + return false; + const char * Value = xmlRoot->Value(); + if (!Value) + return false; + if (strcmp(Value, CONST_ROOT_NODE) != 0) + return false; + const char * Attr = xmlRoot->Attribute(CONST_VERSION_ATTR); + if (!Attr) + return false; + uintptr_t Version = ::StrToVersionNumber(UnicodeString(Attr)); + if (Version < MAKEVERSIONNUMBER(2,0,0)) + return false; + tinyxml2::XMLElement * Element = xmlRoot->FirstChildElement(ToStdString(FStoredSessionsSubKey).c_str()); + if (Element != nullptr) + { + FCurrentElement = FXmlDoc->RootElement(); + return true; + } + return false; +} + +bool TXmlStorage::WriteXml() +{ + tinyxml2::XMLPrinter xmlPrinter; + // xmlPrinter.SetIndent(" "); + // xmlPrinter.SetLineBreak("\r\n"); + FXmlDoc->Accept(&xmlPrinter); + const char * xmlContent = xmlPrinter.CStr(); + if (!xmlContent || !*xmlContent) + { + return false; + } + + return (CNBFile::SaveFile(GetStorage().c_str(), xmlContent) == ERROR_SUCCESS); +} + +bool TXmlStorage::Copy(TXmlStorage * /*Storage*/) +{ + ThrowNotImplemented(3020); + bool Result = false; + return Result; +} + +void TXmlStorage::SetAccessMode(TStorageAccessMode Value) +{ + THierarchicalStorage::SetAccessMode(Value); + switch (GetAccessMode()) + { + case smRead: + ReadXml(); + break; + + case smReadWrite: + default: + FXmlDoc->LinkEndChild(FXmlDoc->NewDeclaration()); + DebugAssert(FCurrentElement == nullptr); + FCurrentElement = FXmlDoc->NewElement(CONST_ROOT_NODE); + FCurrentElement->SetAttribute(CONST_VERSION_ATTR, CONST_XML_VERSION21); + FXmlDoc->LinkEndChild(FCurrentElement); + break; + } +} + +bool TXmlStorage::DoKeyExists(const UnicodeString & SubKey, bool /*ForceAnsi*/) +{ + UnicodeString K = PuttyMungeStr(SubKey); + const tinyxml2::XMLElement * Element = FindChildElement(ToStdString(K)); + bool Result = Element != nullptr; + return Result; +} + +bool TXmlStorage::DoOpenSubKey(const UnicodeString & MungedSubKey, bool CanCreate) +{ + tinyxml2::XMLElement * OldCurrentElement = FCurrentElement; + tinyxml2::XMLElement * Element = nullptr; + std::string subKey = ToStdString(MungedSubKey); + if (CanCreate) + { + if (FStoredSessionsOpened) + { + Element = FXmlDoc->NewElement(CONST_SESSION_NODE); + Element->SetAttribute(CONST_NAME_ATTR, subKey.c_str()); + } + else + { + Element = FXmlDoc->NewElement(subKey.c_str()); + } + FCurrentElement->LinkEndChild(Element); + } + else + { + Element = FindChildElement(subKey); + } + bool Result = Element != nullptr; + if (Result) + { + FSubElements.push_back(OldCurrentElement); + FCurrentElement = Element; + FStoredSessionsOpened = (MungedSubKey == FStoredSessionsSubKey); + } + return Result; +} + +void TXmlStorage::CloseSubKey() +{ + THierarchicalStorage::CloseSubKey(); + if (FKeyHistory->GetCount() && !FSubElements.empty()) + { + FCurrentElement = FSubElements.back(); + FSubElements.pop_back(); + } + else + { + FCurrentElement = nullptr; + } +} + +bool TXmlStorage::DeleteSubKey(const UnicodeString & SubKey) +{ + bool Result = false; + tinyxml2::XMLElement * Element = FindElement(SubKey); + if (Element != nullptr) + { + FCurrentElement->DeleteChild(Element); + Result = true; + } + return Result; +} + +void TXmlStorage::GetSubKeyNames(TStrings * Strings) +{ + for (tinyxml2::XMLElement * Element = FCurrentElement->FirstChildElement(); + Element != nullptr; Element = Element->NextSiblingElement()) + { + UnicodeString val = GetValue(Element); + Strings->Add(PuttyUnMungeStr(val)); + } +} + +void TXmlStorage::GetValueNames(TStrings * /*Strings*/) const +{ + ThrowNotImplemented(3022); + // FRegistry->GetValueNames(Strings); +} + +bool TXmlStorage::DeleteValue(const UnicodeString & Name) +{ + bool Result = false; + tinyxml2::XMLElement * Element = FindElement(Name); + if (Element != nullptr) + { + FCurrentElement->DeleteChild(Element); + Result = true; + } + return Result; +} + +void TXmlStorage::RemoveIfExists(const UnicodeString & Name) +{ + tinyxml2::XMLElement * Element = FindElement(Name); + if (Element != nullptr) + { + FCurrentElement->DeleteChild(Element); + } +} + +void TXmlStorage::AddNewElement(const UnicodeString & Name, const UnicodeString & Value) +{ + std::string name = ToStdString(Name); + std::string StrValue = ToStdString(Value); + tinyxml2::XMLElement * Element = FXmlDoc->NewElement(name.c_str()); + Element->LinkEndChild(FXmlDoc->NewText(StrValue.c_str())); + FCurrentElement->LinkEndChild(Element); +} + +UnicodeString TXmlStorage::GetSubKeyText(const UnicodeString & Name) const +{ + tinyxml2::XMLElement * Element = FindElement(Name); + if (!Element) + { + return UnicodeString(); + } + if (ToUnicodeString(CONST_SESSION_NODE) == Name) + { + return ToUnicodeString(Element->Attribute(CONST_NAME_ATTR)); + } + else + { + return ToUnicodeString(Element->GetText()); + } +} + +tinyxml2::XMLElement * TXmlStorage::FindElement(const UnicodeString & Name) const +{ + for (const tinyxml2::XMLElement * Element = FCurrentElement->FirstChildElement(); + Element != nullptr; Element = Element->NextSiblingElement()) + { + UnicodeString ElementName = ToUnicodeString(Element->Name()); + if (ElementName == Name) + { + return const_cast(Element); + } + } + return nullptr; +} + +tinyxml2::XMLElement * TXmlStorage::FindChildElement(const std::string & subKey) const +{ + tinyxml2::XMLElement * Result = nullptr; + // DebugAssert(FCurrentElement); + if (FStoredSessionsOpened) + { + tinyxml2::XMLElement * Element = FCurrentElement->FirstChildElement(CONST_SESSION_NODE); + if (Element && !strcmp(Element->Attribute(CONST_NAME_ATTR), subKey.c_str())) + { + Result = Element; + } + } + else if (FCurrentElement) + { + Result = FCurrentElement->FirstChildElement(subKey.c_str()); + } + return Result; +} + +UnicodeString TXmlStorage::GetValue(tinyxml2::XMLElement * Element) const +{ + DebugAssert(Element); + UnicodeString Result; + if (FStoredSessionsOpened && Element->Attribute(CONST_NAME_ATTR)) + { + Result = ToUnicodeString(Element->Attribute(CONST_NAME_ATTR)); + } + else + { + Result = ToUnicodeString(Element->GetText()); + } + return Result; +} + +bool TXmlStorage::ValueExists(const UnicodeString & Value) const +{ + bool Result = false; + tinyxml2::XMLElement * Element = FindElement(Value); + if (Element != nullptr) + { + Result = true; + } + return Result; +} + +size_t TXmlStorage::BinaryDataSize(const UnicodeString & /*Name*/) const +{ + ThrowNotImplemented(3026); + size_t Result = 0; // FRegistry->GetDataSize(Name); + return Result; +} + +UnicodeString TXmlStorage::GetSource() const +{ + return GetStorage(); +} + +UnicodeString TXmlStorage::GetSource() +{ + return GetStorage(); +} + +bool TXmlStorage::ReadBool(const UnicodeString & Name, bool Default) const +{ + UnicodeString Result = ReadString(Name, L""); + if (Result.IsEmpty()) + { + return Default; + } + else + { + return ::AnsiCompareIC(Result, ::BooleanToEngStr(true)) == 0; + } +} + +TDateTime TXmlStorage::ReadDateTime(const UnicodeString & Name, const TDateTime & Default) const +{ + double Result = ReadFloat(Name, Default.GetValue()); + return TDateTime(Result); +} + +double TXmlStorage::ReadFloat(const UnicodeString & Name, double Default) const +{ + return ::StrToFloatDef(GetSubKeyText(Name), Default); +} + +intptr_t TXmlStorage::ReadInteger(const UnicodeString & Name, intptr_t Default) const +{ + return ::StrToIntDef(GetSubKeyText(Name), Default); +} + +int64_t TXmlStorage::ReadInt64(const UnicodeString & Name, int64_t Default) const +{ + return ::StrToInt64Def(GetSubKeyText(Name), Default); +} + +UnicodeString TXmlStorage::ReadStringRaw(const UnicodeString & Name, const UnicodeString & Default) const +{ + UnicodeString Result = GetSubKeyText(Name); + return Result.IsEmpty() ? Default : Result; +} + +size_t TXmlStorage::ReadBinaryData(const UnicodeString & /*Name*/, + void * /*Buffer*/, size_t /*Size*/) const +{ + ThrowNotImplemented(3028); + size_t Result = 0; + return Result; +} + +void TXmlStorage::WriteBool(const UnicodeString & Name, bool Value) +{ + WriteString(Name, ::BooleanToEngStr(Value)); +} + +void TXmlStorage::WriteDateTime(const UnicodeString & Name, const TDateTime & Value) +{ + WriteFloat(Name, Value); +} + +void TXmlStorage::WriteFloat(const UnicodeString & Name, double Value) +{ + RemoveIfExists(Name); + AddNewElement(Name, FORMAT(L"%.5f", Value)); +} + +void TXmlStorage::WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value) +{ + RemoveIfExists(Name); + AddNewElement(Name, Value); +} + +void TXmlStorage::WriteInteger(const UnicodeString & Name, intptr_t Value) +{ + RemoveIfExists(Name); + AddNewElement(Name, ::IntToStr(Value)); +} + +void TXmlStorage::WriteInt64(const UnicodeString & Name, int64_t Value) +{ + RemoveIfExists(Name); + AddNewElement(Name, ::Int64ToStr(Value)); +} + +void TXmlStorage::WriteBinaryData(const UnicodeString & Name, + const void * Buffer, size_t Size) +{ + RemoveIfExists(Name); + AddNewElement(Name, ::StrToHex(UnicodeString(reinterpret_cast(Buffer), Size), true)); +} + +intptr_t TXmlStorage::GetFailed() +{ + intptr_t Result = FFailed; + FFailed = 0; + return Result; +} diff --git a/netbox/src/NetBox/XmlStorage.h b/netbox/src/NetBox/XmlStorage.h new file mode 100644 index 000000000..caa251b22 --- /dev/null +++ b/netbox/src/NetBox/XmlStorage.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include "HierarchicalStorage.h" +#include "tinyxml2.h" + +class TXmlStorage : public THierarchicalStorage +{ +public: + explicit TXmlStorage(const UnicodeString & AStorage, const UnicodeString & StoredSessionsSubKey); + virtual void Init(); + virtual ~TXmlStorage(); + + bool Copy(TXmlStorage * Storage); + + virtual void CloseSubKey(); + virtual bool DeleteSubKey(const UnicodeString & SubKey); + virtual void GetSubKeyNames(TStrings * Strings); + virtual bool ValueExists(const UnicodeString & Value) const; + virtual bool DeleteValue(const UnicodeString & Name); + virtual size_t BinaryDataSize(const UnicodeString & Name) const; + virtual UnicodeString GetSource() const; + virtual UnicodeString GetSource(); + + virtual bool ReadBool(const UnicodeString & Name, bool Default) const; + virtual intptr_t ReadInteger(const UnicodeString & Name, intptr_t Default) const; + virtual int64_t ReadInt64(const UnicodeString & Name, int64_t Default) const; + virtual TDateTime ReadDateTime(const UnicodeString & Name, const TDateTime & Default) const; + virtual double ReadFloat(const UnicodeString & Name, double Default) const; + virtual UnicodeString ReadStringRaw(const UnicodeString & Name, const UnicodeString & Default) const; + virtual size_t ReadBinaryData(const UnicodeString & Name, void * Buffer, size_t Size) const; + + virtual void WriteBool(const UnicodeString & Name, bool Value); + virtual void WriteInteger(const UnicodeString & Name, intptr_t Value); + virtual void WriteInt64(const UnicodeString & Name, int64_t Value); + virtual void WriteDateTime(const UnicodeString & Name, const TDateTime & Value); + virtual void WriteFloat(const UnicodeString & Name, double Value); + virtual void WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value); + virtual void WriteBinaryData(const UnicodeString & Name, const void * Buffer, size_t Size); + + virtual void GetValueNames(TStrings * Strings) const; + + virtual void SetAccessMode(TStorageAccessMode Value); + virtual bool DoKeyExists(const UnicodeString & SubKey, bool ForceAnsi); + virtual bool DoOpenSubKey(const UnicodeString & MungedSubKey, bool CanCreate); + +protected: + intptr_t GetFailed(); + void SetFailed(intptr_t Value) { FFailed = Value; } + +private: + UnicodeString GetSubKeyText(const UnicodeString & Name) const; + tinyxml2::XMLElement * FindElement(const UnicodeString & Value) const; + std::string ToStdString(const UnicodeString & String) const { return std::string(::W2MB(String.c_str()).c_str()); } + UnicodeString ToUnicodeString(const char * String) const { return ::MB2W(String ? String : ""); } + void RemoveIfExists(const UnicodeString & Name); + void AddNewElement(const UnicodeString & Name, const UnicodeString & Value); + tinyxml2::XMLElement * FindChildElement(const std::string & subKey) const; + UnicodeString GetValue(tinyxml2::XMLElement * Element) const; + + bool ReadXml(); + bool WriteXml(); + +private: + tinyxml2::XMLDocument * FXmlDoc; + std::vector FSubElements; + tinyxml2::XMLElement * FCurrentElement; + UnicodeString FStoredSessionsSubKey; + intptr_t FFailed; + bool FStoredSessionsOpened; +}; diff --git a/netbox/src/NetBox/boostdefines.hpp b/netbox/src/NetBox/boostdefines.hpp new file mode 100644 index 000000000..db9251dec --- /dev/null +++ b/netbox/src/NetBox/boostdefines.hpp @@ -0,0 +1,18 @@ + +// boostdefines.hpp +// + +#pragma once + +//#define BOOST_ALL_DYN_LINK +#define BOOST_LIB_DIAGNOSTIC + +#ifdef BOOST_ALL_DYN_LINK +#pragma message("Include: dynamic linking with boost libraries") +#endif + +#ifdef NETBOX_DEBUG +//#define BOOST_ENABLE_ASSERT_HANDLER +#endif + +#define BOOST_FILESYSTEM_VERSION 2 diff --git a/netbox/src/NetBox/plugin_version.hpp b/netbox/src/NetBox/plugin_version.hpp new file mode 100644 index 000000000..fe40b1bbf --- /dev/null +++ b/netbox/src/NetBox/plugin_version.hpp @@ -0,0 +1,15 @@ +//------------------------------------------------------------------------------ +// plugin_version.hpp +// +//------------------------------------------------------------------------------ +#pragma once + +#include + +#define NETBOX_VERSION_MAJOR 2 +#define NETBOX_VERSION_MINOR 3 +#define NETBOX_VERSION_PATCH 0 +#define NETBOX_VERSION_BUILD 436 + +static const std::wstring NETBOX_VERSION_NUMBER(L"2.3.0"); + diff --git a/netbox/src/NetBox/resource.h b/netbox/src/NetBox/resource.h new file mode 100644 index 000000000..64bbb852d --- /dev/null +++ b/netbox/src/NetBox/resource.h @@ -0,0 +1,16 @@ +#pragma once + +#define PLUGIN_VERSION_NUM 2,3,0,436 +#define PLUGIN_VERSION_TXT "2.3.0" +#define PLUGIN_VERSION_WTXT L"2.3.0" + +#define PLUGIN_AUTHOR L"Michael Lukashov" +#define PLUGIN_DESCR L"SFTP/FTP(S)/SCP/WebDAV client for FAR 2.0 x86/x64" +#define PLUGIN_FILENAME L"NetBox.dll" + +#define WINSCP_PLUGIN_VERSION_WTXT L"1.6.2.151" +#define PUTTY_VERSION_WTXT L"0.67" +#define FILEZILLA_VERSION_WTXT L"2.2.32" +#define ZLIB_VERSION_WTXT L"1.2.8" +#define OPENSSL_VERSION_WTXT L"1.0.2h" +#define WINSCP_VERSION_WTXT L"5.8.3" diff --git a/netbox/src/NetBox/resource.h.template b/netbox/src/NetBox/resource.h.template new file mode 100644 index 000000000..748f92290 --- /dev/null +++ b/netbox/src/NetBox/resource.h.template @@ -0,0 +1,16 @@ +#pragma once + +#define PLUGIN_VERSION_NUM ${file.ver.major},${file.ver.minor},${file.ver.version_patch},${file.ver.build} +#define PLUGIN_VERSION_TXT "${file.ver.major}.${file.ver.minor}.${file.ver.version_patch}" +#define PLUGIN_VERSION_WTXT L"${file.ver.major}.${file.ver.minor}.${file.ver.version_patch}" + +#define PLUGIN_AUTHOR L"Michael Lukashov" +#define PLUGIN_DESCR L"SFTP/FTP(S)/SCP/WebDAV client for FAR 2.0 x86/x64" +#define PLUGIN_FILENAME L"NetBox.dll" + +#define WINSCP_PLUGIN_VERSION_WTXT L"1.6.2.151" +#define PUTTY_VERSION_WTXT L"0.67" +#define FILEZILLA_VERSION_WTXT L"2.2.32" +#define ZLIB_VERSION_WTXT L"1.2.8" +#define OPENSSL_VERSION_WTXT L"1.0.2h" +#define WINSCP_VERSION_WTXT L"5.8.3" diff --git a/netbox/src/NetBox/scripts/build_netbox.cmd b/netbox/src/NetBox/scripts/build_netbox.cmd new file mode 100644 index 000000000..da1325740 --- /dev/null +++ b/netbox/src/NetBox/scripts/build_netbox.cmd @@ -0,0 +1,22 @@ +@echo off +@setlocal + +if "s%FAR_VERSION%"=="s" set FAR_VERSION=Far2 +if "s%PROJECT_ROOT%"=="s" set PROJECT_ROOT=%~dp0..\..\.. + +if "s%PROJECT_CONFIG%"=="s" set PROJECT_CONFIG=Debug +if "s%PROJECT_BUILD%"=="s" set PROJECT_BUILD=Build + +if "s%PROJECT_PLATFORM%"=="s" set PROJECT_PLATFORM=x86 +if "s%PROJECT_GEN%"=="s" set PROJECT_GEN=NMake Makefiles +if "s%PROJECT_VARS%"=="s" set PROJECT_VARS=x86 + +set PROJECT_BUIILDDIR=%PROJECT_ROOT%\build\%PROJECT_CONFIG%\%PROJECT_PLATFORM% +if not exist %PROJECT_BUIILDDIR% ( mkdir %PROJECT_BUIILDDIR% > NUL ) +cd %PROJECT_BUIILDDIR% + +@call "%VS100COMNTOOLS%..\..\VC\vcvarsall.bat" %PROJECT_VARS% +cmake.exe -D PROJECT_ROOT=%PROJECT_ROOT% -D CMAKE_BUILD_TYPE=%PROJECT_CONFIG% -G "%PROJECT_GEN%" -D FAR_VERSION=%FAR_VERSION% %PROJECT_ROOT%\src\NetBox +nmake + +@endlocal diff --git a/netbox/src/NetBox/scripts/build_netbox_release.cmd b/netbox/src/NetBox/scripts/build_netbox_release.cmd new file mode 100644 index 000000000..08378774b --- /dev/null +++ b/netbox/src/NetBox/scripts/build_netbox_release.cmd @@ -0,0 +1,35 @@ +@echo off +@setlocal + +set FAR_VERSION=Far2 +set PROJECT_ROOT=%~dp0..\..\.. + +set PROJECT_CONFIG=Release +set PROJECT_BUILD=Build + +if "%1" == "" goto x86 +if "%1" == "x64" goto x64 +goto x86 + +:x64 +set PROJECT_PLATFORM=x64 +set PROJECT_GEN=NMake Makefiles +set PROJECT_VARS=x86_amd64 + +call %~dp0\build_netbox.cmd + + +goto end + +:x86 +set PROJECT_PLATFORM=x86 +set PROJECT_GEN=NMake Makefiles +set PROJECT_VARS=x86 + +call %~dp0\build_netbox.cmd + +goto end + +:end + +@endlocal diff --git a/netbox/src/NetBox/scripts/build_openssl.bat b/netbox/src/NetBox/scripts/build_openssl.bat new file mode 100644 index 000000000..cbb0fbf1a --- /dev/null +++ b/netbox/src/NetBox/scripts/build_openssl.bat @@ -0,0 +1,69 @@ +@rem @echo off +@setlocal + +@rem +rm -rf out32dll tmp32dll tmp32 inc32 out32 + +set CONF_PARAMS=no-unit-test no-cast no-err no-bf no-sctp no-rsax no-asm enable-static-engine no-shared no-hw no-camellia no-seed no-rc4 no-rc5 no-krb5 no-whirlpool no-srp no-gost no-idea no-ripemd -Ox -Ob1 -Oi -Os -Oy -GF -GS- -Gy -DNDEBUG;OPENSSL_NO_CAPIENG;NO_CHMOD;OPENSSL_NO_DGRAM;OPENSSL_NO_RIJNDAEL;DSO_WIN32 + +if "%1" == "" goto vs2010-x86 +if "%1" == "x86" goto vs2010-x86 +if "%1" == "vs2010-x86" goto vs2010-x86 +if "%1" == "vs2015-x86" goto vs2015-x86 +if "%1" == "x64" goto vs2010-x64 +if "%1" == "vs2010-x64" goto vs2010-x64 +if "%1" == "vs2015-x64" goto vs2015-x64 +goto vs2010-x86 + +:vs2010-x64 +rm -rf x64 +call "%VS100COMNTOOLS%\..\..\VC\vcvarsall.bat" x86_amd64 +perl Configure VC-WIN64A %CONF_PARAMS% +call ms\do_win64a +nmake -f ms\nt.mak +mkdir x64 +cp out32/ssleay32.lib out32/libeay32.lib x64 +cp tmp32/lib.pdb x64 +cp -R inc32 x64 +goto end + +:vs2015-x64 +rm -rf vs2015-x64 +call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86_amd64 +perl Configure VC-WIN64A %CONF_PARAMS% +call ms\do_win64a +nmake -f ms\nt.mak +mkdir vs2015-x64 +cp out32/ssleay32.lib out32/libeay32.lib vs2015-x64 +cp tmp32/lib.pdb vs2015-x64 +cp -R inc32 vs2015-x64 +goto end + +:vs2010-x86 +rm -rf x86 +call "%VS100COMNTOOLS%\..\..\VC\vcvarsall.bat" x86 +perl Configure VC-WIN32 %CONF_PARAMS% +rem call ms\do_nasm +call ms\do_ms +nmake -f ms\nt.mak +mkdir x86 +cp out32/ssleay32.lib out32/libeay32.lib x86 +cp tmp32/lib.pdb x86 +cp -R inc32 x86 +goto end + +:vs2015-x86 +rm -rf vs2015-x86 +call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86 +perl Configure VC-WIN32 %CONF_PARAMS% +rem call ms\do_nasm +call ms\do_ms +nmake -f ms\nt.mak +mkdir vs2015-x86 +cp out32/ssleay32.lib out32/libeay32.lib vs2015-x86 +cp tmp32/lib.pdb vs2015-x86 +cp -R inc32 vs2015-x86 +goto end + +:end +@endlocal diff --git a/netbox/src/NetBox/scripts/make_dist.cmd b/netbox/src/NetBox/scripts/make_dist.cmd new file mode 100644 index 000000000..282cf01b2 --- /dev/null +++ b/netbox/src/NetBox/scripts/make_dist.cmd @@ -0,0 +1,52 @@ +@echo off + +set PLUGINNAME=NetBox + +set FARVER=%1 +if "%FARVER%" == "" set FARVER=Far2 +set PLUGINARCH=%2 +if "%PLUGINARCH%" == "" set PLUGINARCH=x86 + +:: Get plugin version from resource +gawk "/PLUGIN_VERSION_TXT/ {print $3;}" resource.h > version.tmp +for /F %%i in (version.tmp) do set PLUGINVER=%%i +rm version.tmp +if "%PLUGINVER%" == "" ( + echo Undefined version + exit 1 +) + +:: Package name +set PKGNAME=Far%PLUGINNAME%-%PLUGINVER%_%FARVER%_%PLUGINARCH%.7z + +:: Create temp directory +set PKGDIR=..\..\build\%PLUGINNAME%\%FARVER%\ +set PKGDIRARCH=%PKGDIR%\%PLUGINARCH% +if exist %PKGDIRARCH% rmdir /S /Q %PKGDIRARCH% + +:: Copy files +if not exist %PKGDIR% ( mkdir %PKGDIR% > NUL ) +mkdir %PKGDIRARCH% > NUL + +copy *.lng %PKGDIRARCH% > NUL +copy *.hlf %PKGDIRARCH% > NUL +copy ..\..\ChangeLog %PKGDIRARCH% > NUL +copy ..\..\*.md %PKGDIRARCH% > NUL +copy ..\..\LICENSE.txt %PKGDIRARCH% > NUL + +REM if exist "C:\Program Files\PESuite\PETrim.exe" ( + REM "C:\Program Files\PESuite\PETrim.exe" ..\..\%FARVER%_%PLUGINARCH%\Plugins\%PLUGINNAME%\%PLUGINNAME%.dll /Sf:Y /Sd:Y +REM ) +copy ..\..\%FARVER%_%PLUGINARCH%\Plugins\%PLUGINNAME%\%PLUGINNAME%.dll %PKGDIRARCH% > NUL + +:: Make archive +if exist %PKGNAME% del %PKGNAME% +if exist ../../build/%PLUGINNAME%/%FARVER%/%PLUGINARCH% ( + if exist "C:\Program Files\7-Zip\7z.exe" ( + call "C:\Program Files\7-Zip\7z.exe" a -mx9 -t7z -r ../../build/%PKGNAME% ../../build/%PLUGINNAME%/%FARVER%/%PLUGINARCH%/* > NUL + if errorlevel 1 echo Error creating archive & exit 1 /b + @rem rmdir /S /Q %PKGDIRARCH% + echo Package %PKGNAME% created + ) +) +exit 0 /b diff --git a/netbox/src/NetBox/scripts/run_astyle.bat b/netbox/src/NetBox/scripts/run_astyle.bat new file mode 100644 index 000000000..1293c21f0 --- /dev/null +++ b/netbox/src/NetBox/scripts/run_astyle.bat @@ -0,0 +1,2 @@ +rem astyle --style=allman --indent=spaces=4 --add-brackets --convert-tabs --align-pointer=name --align-reference=name --mode=c --keep-one-line-statements --keep-one-line-blocks %* +astyle --style=allman --indent=spaces=2 --convert-tabs --align-pointer=middle --align-reference=middle --mode=c --keep-one-line-statements --keep-one-line-blocks --indent-switches --suffix=none --lineend=windows --pad-oper %* diff --git a/netbox/src/NetBox/scripts/update_version.py b/netbox/src/NetBox/scripts/update_version.py new file mode 100644 index 000000000..9e63aea8e --- /dev/null +++ b/netbox/src/NetBox/scripts/update_version.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os, sys, string +import re, time + +plugin_version_h = \ +"""\ +//------------------------------------------------------------------------------ +// plugin_version.hpp +// +//------------------------------------------------------------------------------ +#pragma once + +#include + +#define NETBOX_VERSION_MAJOR %(version_major)s +#define NETBOX_VERSION_MINOR %(version_minor)s +#define NETBOX_VERSION_PATCH %(version_patch)s +#define NETBOX_VERSION_BUILD %(build)s + +static const std::wstring NETBOX_VERSION_NUMBER(L"%(version_major)s.%(version_minor)s.%(version_patch)s"); + +""" + +PLUGIN_VERSION_FILE = 'plugin_version.hpp' + +def write_header(version_major, version_minor, version_patch, build, git_revision): + f = open(PLUGIN_VERSION_FILE, "w") + compile_time = time.strftime('%d.%m.%Y %H:%M:%S') + plugin_version_h_content = plugin_version_h % locals() + f.write(plugin_version_h_content) + f.close() + return + +############################################################################### +# transform_template_file: generate file from .template file +# +############################################################################### + +def transform_template_file(template_file_name, file_name, + major_version, minor_version, version_patch, build, git_revision): + # print 'transform_template_file: major_version = %s' % major_version + # print '%s: %s.%s.%s.%s' % (rc_file, major_version, minor_version, version_patch, git_revision) + contents = [] + f = open(template_file_name) + for line in f.readlines(): + # print 'line = %s' % line.rstrip() + s = line.replace('${file.ver.major}', major_version) + s = s.replace('${file.ver.minor}', minor_version) + s = s.replace('${file.ver.version_patch}', version_patch) + s = s.replace('${file.ver.build}', build) + # s = s.replace('${file.ver.revision}', revision) + s = s.replace('${file.ver.gitrevision}', git_revision) + contents.append(s) + f.close() + + f = open(file_name, 'w') + for line in contents: + f.write(line) + f.close() + return + #--transform_template_file + +def transform_template_files(version_major, version_minor, version_patch, build, git_revision): + template_files = ['resource.h.template', 'NetBox.rc.template'] + for template_file_name in template_files : + file_name = template_file_name[0:-len('.template')] # + '.tmp' + transform_template_file(template_file_name, file_name, + version_major, version_minor, version_patch, build, git_revision) + return + +def getstatusoutput(cmdstr): + """ replacement for command.getstatusoutput """ + pipe = os.popen(cmdstr) + text = pipe.read() + sts = pipe.close() + if sts is None: sts = 0 + return sts, text + #--getstatusoutput + +def get_git_revision(): + git_revision = '' + git_re = re.compile(r'^.+g(\w+)$') + git_re2 = re.compile(r'^(\w+)$') + try: + status, output = getstatusoutput('git describe --always') + # print('output = %s' % output) + # print(status) + # print(output) + context = git_re.match(output) + # print(context) + if context: + git_revision = context.groups(1)[0] + # print(git_revision) + git_revision = git_revision + else: + context = git_re2.match(output) + if context: + git_revision = context.groups(1)[0] + git_revision = git_revision + except Exception, info: + git_revision = '' + print(info) + pass + return git_revision + +def get_revision(): + git_revision = get_git_revision() + if git_revision is None: + git_revision = '' + return git_revision + +def get_build_number(): + """ read build number from plugin_version.hpp """ + build_number = "0" + build_re = re.compile(r'^\#define NETBOX_VERSION_BUILD\s+(\d+).*$') + try : + f = open(PLUGIN_VERSION_FILE) + for line in f.readlines(): + context = build_re.match(line) + if context: + build_number = context.groups(1)[0] + build_number = str(int(build_number) + 1) + # print('build_number = %s' % build_number) + break + except Exception : + pass + return build_number + +def main(): + ver_re = re.compile(r'^NetBox\s(\d+)\.(\d+)\.(\d+).*$') + f = open('../../ChangeLog') + + for line in f.readlines(): + context = ver_re.match(line) + if context: + version_major = context.groups(1)[0] + version_minor = context.groups(1)[1] + version_patch = context.groups(1)[2] + build = get_build_number() + git_revision = get_revision() + write_header(version_major, version_minor, version_patch, build, git_revision) + transform_template_files(version_major, version_minor, version_patch, build, git_revision) + break + return + +if __name__ == '__main__': + main() diff --git a/netbox/src/NetBox/vc_crt_fix.asm b/netbox/src/NetBox/vc_crt_fix.asm new file mode 100644 index 000000000..a3eab9620 --- /dev/null +++ b/netbox/src/NetBox/vc_crt_fix.asm @@ -0,0 +1,69 @@ +;vc_crt_fix.asm + +;Workaround for Visual C++ CRT incompatibility with old Windows versions + +;Copyright 2010 Far Group +;All rights reserved. +; +;Redistribution and use in source and binary forms, with or without +;modification, are permitted provided that the following conditions +;are met: +;1. Redistributions of source code must retain the above copyright +; notice, this list of conditions and the following disclaimer. +;2. Redistributions in binary form must reproduce the above copyright +; notice, this list of conditions and the following disclaimer in the +; documentation and/or other materials provided with the distribution. +;3. The name of the authors may not be used to endorse or promote products +; derived from this software without specific prior written permission. +; +;THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +;IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +;OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +;IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +;INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +;NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +;THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +.486 +.model flat + +EncodePointerWrapper proto stdcall :dword +DecodePointerWrapper proto stdcall :dword +GetModuleHandleExWWrapper proto stdcall :dword, :dword, :dword +InitializeSListHeadWrapper proto stdcall :dword +InterlockedFlushSListWrapper proto stdcall :dword +InterlockedPopEntrySListWrapper proto stdcall :dword +InterlockedPushEntrySListWrapper proto stdcall :dword, :dword +InterlockedPushListSListExWrapper proto stdcall :dword, :dword, :dword, :dword +RtlFirstEntrySListWrapper proto stdcall :dword +QueryDepthSListWrapper proto stdcall :dword + +.const +align 4 +__imp__EncodePointer@4 dd EncodePointerWrapper +__imp__DecodePointer@4 dd DecodePointerWrapper +__imp__GetModuleHandleExW@12 dd GetModuleHandleExWWrapper +__imp__InitializeSListHead@4 dd InitializeSListHeadWrapper +__imp__InterlockedFlushSList@4 dd InterlockedFlushSListWrapper +__imp__InterlockedPopEntrySList@4 dd InterlockedPopEntrySListWrapper +__imp__InterlockedPushEntrySList@8 dd InterlockedPushEntrySListWrapper +__imp__InterlockedPushListSListEx@16 dd InterlockedPushListSListExWrapper +__imp__RtlFirstEntrySList@4 dd RtlFirstEntrySListWrapper +__imp__QueryDepthSList@4 dd QueryDepthSListWrapper + +public \ +__imp__EncodePointer@4, +__imp__DecodePointer@4, +__imp__GetModuleHandleExW@12, +__imp__InitializeSListHead@4, +__imp__InterlockedFlushSList@4, +__imp__InterlockedPopEntrySList@4, +__imp__InterlockedPushEntrySList@8, +__imp__InterlockedPushListSListEx@16, +__imp__RtlFirstEntrySList@4, +__imp__QueryDepthSList@4 +end diff --git a/netbox/src/NetBox/vc_crt_fix_impl.cpp b/netbox/src/NetBox/vc_crt_fix_impl.cpp new file mode 100644 index 000000000..3f7e9ae62 --- /dev/null +++ b/netbox/src/NetBox/vc_crt_fix_impl.cpp @@ -0,0 +1,228 @@ +/* +vc_crt_fix_impl.cpp + +Workaround for Visual C++ CRT incompatibility with old Windows versions +*/ +/* +Copyright © 2011 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FAR_USE_INTERNALS +//#define FAR_USE_INTERNALS +#endif // END FAR_USE_INTERNALS +#ifdef FAR_USE_INTERNALS +#include +//---------------------------------------------------------------------------- +#endif // END FAR_USE_INTERNALS +#include +#ifdef FAR_USE_INTERNALS +//---------------------------------------------------------------------------- +#include +#endif // END FAR_USE_INTERNALS + +template +inline T GetFunctionPointer(const wchar_t* ModuleName, const char* FunctionName, T Replacement) +{ + const auto Address = GetProcAddress(GetModuleHandleW(ModuleName), FunctionName); + return Address? reinterpret_cast(Address) : Replacement; +} + +#define CREATE_FUNCTION_POINTER(ModuleName, FunctionName)\ +static const auto Function = GetFunctionPointer(ModuleName, #FunctionName, &implementation::FunctionName) + +static const wchar_t kernel32[] = L"kernel32"; + +// EncodePointer (VC2010) +extern "C" PVOID WINAPI EncodePointerWrapper(PVOID Ptr) +{ + struct implementation + { + static PVOID WINAPI EncodePointer(PVOID Ptr) { return Ptr; } + }; + + CREATE_FUNCTION_POINTER(kernel32, EncodePointer); + return Function(Ptr); +} + +// DecodePointer(VC2010) +extern "C" PVOID WINAPI DecodePointerWrapper(PVOID Ptr) +{ + struct implementation + { + static PVOID WINAPI DecodePointer(PVOID Ptr) { return Ptr; } + }; + + CREATE_FUNCTION_POINTER(kernel32, DecodePointer); + return Function(Ptr); +} + +// GetModuleHandleExW (VC2012) +extern "C" BOOL WINAPI GetModuleHandleExWWrapper(DWORD Flags, LPCWSTR ModuleName, HMODULE *Module) +{ + struct implementation + { + static BOOL WINAPI GetModuleHandleExW(DWORD Flags, LPCWSTR ModuleName, HMODULE *Module) + { + BOOL Result = FALSE; + if (Flags) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + } + else + { + *Module = GetModuleHandleW(ModuleName); + if (*Module) + { + Result = TRUE; + } + } + return Result; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, GetModuleHandleExW); + return Function(Flags, ModuleName, Module); +} + +// InitializeSListHead (VC2015) +extern "C" void WINAPI InitializeSListHeadWrapper(PSLIST_HEADER ListHead) +{ + struct implementation + { + static void WINAPI InitializeSListHead(PSLIST_HEADER ListHead) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + } + }; + + CREATE_FUNCTION_POINTER(kernel32, InitializeSListHead); + return Function(ListHead); +} + +// InterlockedFlushSList (VC2015) +extern "C" PSLIST_ENTRY WINAPI InterlockedFlushSListWrapper(PSLIST_HEADER ListHead) +{ + struct implementation + { + static PSLIST_ENTRY WINAPI InterlockedFlushSList(PSLIST_HEADER ListHead) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return nullptr; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, InterlockedFlushSList); + return Function(ListHead); +} + +// InterlockedPopEntrySList (VC2015) +extern "C" PSLIST_ENTRY WINAPI InterlockedPopEntrySListWrapper(PSLIST_HEADER ListHead) +{ + struct implementation + { + static PSLIST_ENTRY WINAPI InterlockedPopEntrySList(PSLIST_HEADER ListHead) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return nullptr; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, InterlockedPopEntrySList); + return Function(ListHead); +} + +// InterlockedPushEntrySList (VC2015) +extern "C" PSLIST_ENTRY WINAPI InterlockedPushEntrySListWrapper(PSLIST_HEADER ListHead, PSLIST_ENTRY ListEntry) +{ + struct implementation + { + static PSLIST_ENTRY WINAPI InterlockedPushEntrySList(PSLIST_HEADER ListHead, PSLIST_ENTRY ListEntry) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return nullptr; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, InterlockedPushEntrySList); + return Function(ListHead, ListEntry); +} + +// InterlockedPushListSList (VC2015) +extern "C" PSLIST_ENTRY WINAPI InterlockedPushListSListExWrapper(PSLIST_HEADER ListHead, PSLIST_ENTRY List, PSLIST_ENTRY ListEnd, ULONG Count) +{ + struct implementation + { + static PSLIST_ENTRY WINAPI InterlockedPushListSListEx(PSLIST_HEADER ListHead, PSLIST_ENTRY List, PSLIST_ENTRY ListEnd, ULONG Count) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return nullptr; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, InterlockedPushListSListEx); + return Function(ListHead, List, ListEnd, Count); +} + +// RtlFirstEntrySList (VC2015) +extern "C" PSLIST_ENTRY WINAPI RtlFirstEntrySListWrapper(PSLIST_HEADER ListHead) +{ + struct implementation + { + static PSLIST_ENTRY WINAPI RtlFirstEntrySList(PSLIST_HEADER ListHead) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return nullptr; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, RtlFirstEntrySList); + return Function(ListHead); +} + +// QueryDepthSList (VC2015) +extern "C" USHORT WINAPI QueryDepthSListWrapper(PSLIST_HEADER ListHead) +{ + struct implementation + { + static USHORT WINAPI QueryDepthSList(PSLIST_HEADER ListHead) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return 0; + } + }; + + CREATE_FUNCTION_POINTER(kernel32, QueryDepthSList); + return Function(ListHead); +} + +// RtlGenRandom (VC2015) +// RtlGenRandom is used in rand_s implementation in ucrt. +// As long as we don't use the rand_s in the code (which is easy: it's non-standard, requires _CRT_RAND_S to be defined first and not recommended in general) +// this function is never called so it's ok to provide this dummy implementation only to have the _SystemFunction036@8 symbol in binary to make their loader happy. +extern "C" BOOLEAN WINAPI SystemFunction036(PVOID Buffer, ULONG Size) +{ + return TRUE; +} diff --git a/netbox/src/NetBox/vc_crt_fix_ulink.cpp b/netbox/src/NetBox/vc_crt_fix_ulink.cpp new file mode 100644 index 000000000..694f57ed4 --- /dev/null +++ b/netbox/src/NetBox/vc_crt_fix_ulink.cpp @@ -0,0 +1,63 @@ +/* +vc_crt_fix_ulink.cpp + +Workaround for Visual C++ CRT incompatibility with old Windows versions (ulink version) +*/ +/* +Copyright © 2010 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +//---------------------------------------------------------------------------- +static LPVOID WINAPI no_recode_pointer(LPVOID p) +{ + return p; +} + +//---------------------------------------------------------------------------- +static FARPROC WINAPI delayFailureHook(/*dliNotification*/unsigned dliNotify, + PDelayLoadInfo pdli) +{ + if( dliNotify == /*dliFailGetProcAddress*/dliFailGetProc + && pdli && pdli->cb == sizeof(*pdli) + && pdli->hmodCur == GetModuleHandleA("kernel32") + && pdli->dlp.fImportByName && pdli->dlp.szProcName + && ( !lstrcmpA(pdli->dlp.szProcName, "EncodePointer") + || !lstrcmpA(pdli->dlp.szProcName, "DecodePointer"))) + { + return (FARPROC)no_recode_pointer; + } + return nullptr; +} + +//---------------------------------------------------------------------------- +PfnDliHook __pfnDliFailureHook2 = (PfnDliHook)delayFailureHook; + +//---------------------------------------------------------------------------- + +// TODO: Add GetModuleHandleExW diff --git a/netbox/src/PluginSDK/Far2/farcolor.hpp b/netbox/src/PluginSDK/Far2/farcolor.hpp new file mode 100644 index 000000000..780c4f590 --- /dev/null +++ b/netbox/src/PluginSDK/Far2/farcolor.hpp @@ -0,0 +1,221 @@ +#pragma once +#ifndef __COLORS_HPP__ +#define __COLORS_HPP__ +/* + farcolor.hpp + + Colors Index for FAR Manager 2.0 build 1807 + HKCU\Software\Far\Colors\CurrentPalette +*/ + +/* +Copyright (c) 1996 Eugene Roshal +Copyright (c) 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +EXCEPTION: +Far Manager plugins that use this header file can be distributed under any +other possible license with no implications from the above license on them. +*/ + + +enum PaletteColors +{ + COL_MENUTEXT, + COL_MENUSELECTEDTEXT, + COL_MENUHIGHLIGHT, + COL_MENUSELECTEDHIGHLIGHT, + COL_MENUBOX, + COL_MENUTITLE, + + COL_HMENUTEXT, + COL_HMENUSELECTEDTEXT, + COL_HMENUHIGHLIGHT, + COL_HMENUSELECTEDHIGHLIGHT, + + COL_PANELTEXT, + COL_PANELSELECTEDTEXT, + COL_PANELHIGHLIGHTTEXT, + COL_PANELINFOTEXT, + COL_PANELCURSOR, + COL_PANELSELECTEDCURSOR, + COL_PANELTITLE, + COL_PANELSELECTEDTITLE, + COL_PANELCOLUMNTITLE, + COL_PANELTOTALINFO, + COL_PANELSELECTEDINFO, + + COL_DIALOGTEXT, + COL_DIALOGHIGHLIGHTTEXT, + COL_DIALOGBOX, + COL_DIALOGBOXTITLE, + COL_DIALOGHIGHLIGHTBOXTITLE, + COL_DIALOGEDIT, + COL_DIALOGBUTTON, + COL_DIALOGSELECTEDBUTTON, + COL_DIALOGHIGHLIGHTBUTTON, + COL_DIALOGHIGHLIGHTSELECTEDBUTTON, + + COL_DIALOGLISTTEXT, + COL_DIALOGLISTSELECTEDTEXT, + COL_DIALOGLISTHIGHLIGHT, + COL_DIALOGLISTSELECTEDHIGHLIGHT, + + COL_WARNDIALOGTEXT, + COL_WARNDIALOGHIGHLIGHTTEXT, + COL_WARNDIALOGBOX, + COL_WARNDIALOGBOXTITLE, + COL_WARNDIALOGHIGHLIGHTBOXTITLE, + COL_WARNDIALOGEDIT, + COL_WARNDIALOGBUTTON, + COL_WARNDIALOGSELECTEDBUTTON, + COL_WARNDIALOGHIGHLIGHTBUTTON, + COL_WARNDIALOGHIGHLIGHTSELECTEDBUTTON, + + COL_KEYBARNUM, + COL_KEYBARTEXT, + COL_KEYBARBACKGROUND, + + COL_COMMANDLINE, + + COL_CLOCK, + + COL_VIEWERTEXT, + COL_VIEWERSELECTEDTEXT, + COL_VIEWERSTATUS, + + COL_EDITORTEXT, + COL_EDITORSELECTEDTEXT, + COL_EDITORSTATUS, + + COL_HELPTEXT, + COL_HELPHIGHLIGHTTEXT, + COL_HELPTOPIC, + COL_HELPSELECTEDTOPIC, + COL_HELPBOX, + COL_HELPBOXTITLE, + + COL_PANELDRAGTEXT, + COL_DIALOGEDITUNCHANGED, + COL_PANELSCROLLBAR, + COL_HELPSCROLLBAR, + COL_PANELBOX, + COL_PANELSCREENSNUMBER, + COL_DIALOGEDITSELECTED, + COL_COMMANDLINESELECTED, + COL_VIEWERARROWS, + + COL_RESERVED0, + + COL_DIALOGLISTSCROLLBAR, + COL_MENUSCROLLBAR, + COL_VIEWERSCROLLBAR, + COL_COMMANDLINEPREFIX, + COL_DIALOGDISABLED, + COL_DIALOGEDITDISABLED, + COL_DIALOGLISTDISABLED, + COL_WARNDIALOGDISABLED, + COL_WARNDIALOGEDITDISABLED, + COL_WARNDIALOGLISTDISABLED, + + COL_MENUDISABLEDTEXT, + + COL_EDITORCLOCK, + COL_VIEWERCLOCK, + + COL_DIALOGLISTTITLE, + COL_DIALOGLISTBOX, + + COL_WARNDIALOGEDITSELECTED, + COL_WARNDIALOGEDITUNCHANGED, + + COL_DIALOGCOMBOTEXT, + COL_DIALOGCOMBOSELECTEDTEXT, + COL_DIALOGCOMBOHIGHLIGHT, + COL_DIALOGCOMBOSELECTEDHIGHLIGHT, + COL_DIALOGCOMBOBOX, + COL_DIALOGCOMBOTITLE, + COL_DIALOGCOMBODISABLED, + COL_DIALOGCOMBOSCROLLBAR, + + COL_WARNDIALOGLISTTEXT, + COL_WARNDIALOGLISTSELECTEDTEXT, + COL_WARNDIALOGLISTHIGHLIGHT, + COL_WARNDIALOGLISTSELECTEDHIGHLIGHT, + COL_WARNDIALOGLISTBOX, + COL_WARNDIALOGLISTTITLE, + COL_WARNDIALOGLISTSCROLLBAR, + + COL_WARNDIALOGCOMBOTEXT, + COL_WARNDIALOGCOMBOSELECTEDTEXT, + COL_WARNDIALOGCOMBOHIGHLIGHT, + COL_WARNDIALOGCOMBOSELECTEDHIGHLIGHT, + COL_WARNDIALOGCOMBOBOX, + COL_WARNDIALOGCOMBOTITLE, + COL_WARNDIALOGCOMBODISABLED, + COL_WARNDIALOGCOMBOSCROLLBAR, + + COL_DIALOGLISTARROWS, + COL_DIALOGLISTARROWSDISABLED, + COL_DIALOGLISTARROWSSELECTED, + COL_DIALOGCOMBOARROWS, + COL_DIALOGCOMBOARROWSDISABLED, + COL_DIALOGCOMBOARROWSSELECTED, + COL_WARNDIALOGLISTARROWS, + COL_WARNDIALOGLISTARROWSDISABLED, + COL_WARNDIALOGLISTARROWSSELECTED, + COL_WARNDIALOGCOMBOARROWS, + COL_WARNDIALOGCOMBOARROWSDISABLED, + COL_WARNDIALOGCOMBOARROWSSELECTED, + COL_MENUARROWS, + COL_MENUARROWSDISABLED, + COL_MENUARROWSSELECTED, + COL_COMMANDLINEUSERSCREEN, + COL_EDITORSCROLLBAR, + + COL_MENUGRAYTEXT, + COL_MENUSELECTEDGRAYTEXT, + COL_DIALOGCOMBOGRAY, + COL_DIALOGCOMBOSELECTEDGRAYTEXT, + COL_DIALOGLISTGRAY, + COL_DIALOGLISTSELECTEDGRAYTEXT, + COL_WARNDIALOGCOMBOGRAY, + COL_WARNDIALOGCOMBOSELECTEDGRAYTEXT, + COL_WARNDIALOGLISTGRAY, + COL_WARNDIALOGLISTSELECTEDGRAYTEXT, + + COL_DIALOGDEFAULTBUTTON, + COL_DIALOGSELECTEDDEFAULTBUTTON, + COL_DIALOGHIGHLIGHTDEFAULTBUTTON, + COL_DIALOGHIGHLIGHTSELECTEDDEFAULTBUTTON, + COL_WARNDIALOGDEFAULTBUTTON, + COL_WARNDIALOGSELECTEDDEFAULTBUTTON, + COL_WARNDIALOGHIGHLIGHTDEFAULTBUTTON, + COL_WARNDIALOGHIGHLIGHTSELECTEDDEFAULTBUTTON, + + COL_LASTPALETTECOLOR +}; + +#endif // __COLORS_HPP__ diff --git a/netbox/src/PluginSDK/Far2/farkeys.hpp b/netbox/src/PluginSDK/Far2/farkeys.hpp new file mode 100644 index 000000000..4904973ae --- /dev/null +++ b/netbox/src/PluginSDK/Far2/farkeys.hpp @@ -0,0 +1,706 @@ +#pragma once +#ifndef __FARKEYS_HPP__ +#define __FARKEYS_HPP__ +/* + farkeys.hpp + + Inside KeyName for FAR Manager 2.0 build 1807 +*/ + +/* +Copyright (c) 1996 Eugene Roshal +Copyright (c) 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +EXCEPTION: +Far Manager plugins that use this header file can be distributed under any +other possible license with no implications from the above license on them. +*/ + +#define EXTENDED_KEY_BASE 0x00010000 +#define INTERNAL_KEY_BASE 0x00020000 +#define INTERNAL_KEY_BASE_2 0x00030000 + +enum BaseDefKeyboard +{ + KEY_CTRLMASK =0xFFF00000, + KEY_CTRL =0x01000000, + KEY_ALT =0x02000000, + KEY_SHIFT =0x04000000, + // + KEY_RCTRL =0x10000000, + KEY_RALT =0x20000000, + + KEY_BRACKET ='[', + KEY_BACKBRACKET =']', + KEY_COMMA =',', + KEY_QUOTE ='"', + KEY_DOT ='.', + KEY_SLASH ='/', + KEY_COLON =':', + KEY_SEMICOLON =';', + KEY_BACKSLASH ='\\', + + KEY_BS =0x00000008, + KEY_TAB =0x00000009, + KEY_ENTER =0x0000000D, + KEY_ESC =0x0000001B, + KEY_SPACE =0x00000020, + + KEY_MASKF =0x0001FFFF, + + KEY_FKEY_BEGIN =EXTENDED_KEY_BASE, + + KEY_BREAK =EXTENDED_KEY_BASE+VK_CANCEL, + + KEY_PAUSE =EXTENDED_KEY_BASE+VK_PAUSE, + KEY_CAPSLOCK =EXTENDED_KEY_BASE+VK_CAPITAL, + + KEY_PGUP =EXTENDED_KEY_BASE+VK_PRIOR, + KEY_PGDN =EXTENDED_KEY_BASE+VK_NEXT, + KEY_END =EXTENDED_KEY_BASE+VK_END, + KEY_HOME =EXTENDED_KEY_BASE+VK_HOME, + KEY_LEFT =EXTENDED_KEY_BASE+VK_LEFT, + KEY_UP =EXTENDED_KEY_BASE+VK_UP, + KEY_RIGHT =EXTENDED_KEY_BASE+VK_RIGHT, + KEY_DOWN =EXTENDED_KEY_BASE+VK_DOWN, + KEY_PRNTSCRN =EXTENDED_KEY_BASE+VK_SNAPSHOT, + KEY_INS =EXTENDED_KEY_BASE+VK_INSERT, + KEY_DEL =EXTENDED_KEY_BASE+VK_DELETE, + + KEY_LWIN =EXTENDED_KEY_BASE+VK_LWIN, + KEY_RWIN =EXTENDED_KEY_BASE+VK_RWIN, + KEY_APPS =EXTENDED_KEY_BASE+VK_APPS, + KEY_STANDBY =EXTENDED_KEY_BASE+VK_SLEEP, + + KEY_NUMPAD0 =EXTENDED_KEY_BASE+VK_NUMPAD0, + KEY_NUMPAD1 =EXTENDED_KEY_BASE+VK_NUMPAD1, + KEY_NUMPAD2 =EXTENDED_KEY_BASE+VK_NUMPAD2, + KEY_NUMPAD3 =EXTENDED_KEY_BASE+VK_NUMPAD3, + KEY_NUMPAD4 =EXTENDED_KEY_BASE+VK_NUMPAD4, + KEY_NUMPAD5 =EXTENDED_KEY_BASE+VK_NUMPAD5, + KEY_CLEAR =KEY_NUMPAD5, + KEY_NUMPAD6 =EXTENDED_KEY_BASE+VK_NUMPAD6, + KEY_NUMPAD7 =EXTENDED_KEY_BASE+VK_NUMPAD7, + KEY_NUMPAD8 =EXTENDED_KEY_BASE+VK_NUMPAD8, + KEY_NUMPAD9 =EXTENDED_KEY_BASE+VK_NUMPAD9, + + KEY_MULTIPLY =EXTENDED_KEY_BASE+VK_MULTIPLY, + KEY_ADD =EXTENDED_KEY_BASE+VK_ADD, + KEY_SUBTRACT =EXTENDED_KEY_BASE+VK_SUBTRACT, + KEY_DECIMAL =EXTENDED_KEY_BASE+VK_DECIMAL, + KEY_DIVIDE =EXTENDED_KEY_BASE+VK_DIVIDE, + + KEY_F1 =EXTENDED_KEY_BASE+VK_F1, + KEY_F2 =EXTENDED_KEY_BASE+VK_F2, + KEY_F3 =EXTENDED_KEY_BASE+VK_F3, + KEY_F4 =EXTENDED_KEY_BASE+VK_F4, + KEY_F5 =EXTENDED_KEY_BASE+VK_F5, + KEY_F6 =EXTENDED_KEY_BASE+VK_F6, + KEY_F7 =EXTENDED_KEY_BASE+VK_F7, + KEY_F8 =EXTENDED_KEY_BASE+VK_F8, + KEY_F9 =EXTENDED_KEY_BASE+VK_F9, + KEY_F10 =EXTENDED_KEY_BASE+VK_F10, + KEY_F11 =EXTENDED_KEY_BASE+VK_F11, + KEY_F12 =EXTENDED_KEY_BASE+VK_F12, + + KEY_F13 =EXTENDED_KEY_BASE+VK_F13, + KEY_F14 =EXTENDED_KEY_BASE+VK_F14, + KEY_F15 =EXTENDED_KEY_BASE+VK_F15, + KEY_F16 =EXTENDED_KEY_BASE+VK_F16, + KEY_F17 =EXTENDED_KEY_BASE+VK_F17, + KEY_F18 =EXTENDED_KEY_BASE+VK_F18, + KEY_F19 =EXTENDED_KEY_BASE+VK_F19, + KEY_F20 =EXTENDED_KEY_BASE+VK_F20, + KEY_F21 =EXTENDED_KEY_BASE+VK_F21, + KEY_F22 =EXTENDED_KEY_BASE+VK_F22, + KEY_F23 =EXTENDED_KEY_BASE+VK_F23, + KEY_F24 =EXTENDED_KEY_BASE+VK_F24, + + KEY_NUMLOCK =EXTENDED_KEY_BASE+VK_NUMLOCK, + KEY_SCROLLLOCK =EXTENDED_KEY_BASE+VK_SCROLL, + +#if (_WIN32_WINNT >= 0x0500) + KEY_BROWSER_BACK =EXTENDED_KEY_BASE+VK_BROWSER_BACK, + KEY_BROWSER_FORWARD =EXTENDED_KEY_BASE+VK_BROWSER_FORWARD, + KEY_BROWSER_REFRESH =EXTENDED_KEY_BASE+VK_BROWSER_REFRESH, + KEY_BROWSER_STOP =EXTENDED_KEY_BASE+VK_BROWSER_STOP, + KEY_BROWSER_SEARCH =EXTENDED_KEY_BASE+VK_BROWSER_SEARCH, + KEY_BROWSER_FAVORITES =EXTENDED_KEY_BASE+VK_BROWSER_FAVORITES, + KEY_BROWSER_HOME =EXTENDED_KEY_BASE+VK_BROWSER_HOME, + KEY_VOLUME_MUTE =EXTENDED_KEY_BASE+VK_VOLUME_MUTE, + KEY_VOLUME_DOWN =EXTENDED_KEY_BASE+VK_VOLUME_DOWN, + KEY_VOLUME_UP =EXTENDED_KEY_BASE+VK_VOLUME_UP, + KEY_MEDIA_NEXT_TRACK =EXTENDED_KEY_BASE+VK_MEDIA_NEXT_TRACK, + KEY_MEDIA_PREV_TRACK =EXTENDED_KEY_BASE+VK_MEDIA_PREV_TRACK, + KEY_MEDIA_STOP =EXTENDED_KEY_BASE+VK_MEDIA_STOP, + KEY_MEDIA_PLAY_PAUSE =EXTENDED_KEY_BASE+VK_MEDIA_PLAY_PAUSE, + KEY_LAUNCH_MAIL =EXTENDED_KEY_BASE+VK_LAUNCH_MAIL, + KEY_LAUNCH_MEDIA_SELECT =EXTENDED_KEY_BASE+VK_LAUNCH_MEDIA_SELECT, + KEY_LAUNCH_APP1 =EXTENDED_KEY_BASE+VK_LAUNCH_APP1, + KEY_LAUNCH_APP2 =EXTENDED_KEY_BASE+VK_LAUNCH_APP2, +#endif + + KEY_CTRLALTSHIFTPRESS =INTERNAL_KEY_BASE+1, + KEY_CTRLALTSHIFTRELEASE =INTERNAL_KEY_BASE+2, + + KEY_MSWHEEL_UP =INTERNAL_KEY_BASE+3, + KEY_MSWHEEL_DOWN =INTERNAL_KEY_BASE+4, + KEY_NUMDEL =INTERNAL_KEY_BASE+9, + KEY_NUMENTER =INTERNAL_KEY_BASE+0xB, + + KEY_MSWHEEL_LEFT =INTERNAL_KEY_BASE+0xC, + KEY_MSWHEEL_RIGHT =INTERNAL_KEY_BASE+0xD, + + KEY_MSLCLICK =INTERNAL_KEY_BASE+0xF, + KEY_MSRCLICK =INTERNAL_KEY_BASE+0x10, + + KEY_MSM1CLICK =INTERNAL_KEY_BASE+0x11, + KEY_MSM2CLICK =INTERNAL_KEY_BASE+0x12, + KEY_MSM3CLICK =INTERNAL_KEY_BASE+0x13, + + + + KEY_VK_0xFF_BEGIN =EXTENDED_KEY_BASE+0x00000100, + KEY_VK_0xFF_END =EXTENDED_KEY_BASE+0x000001FF, + + KEY_END_FKEY =0x0001FFFF, + + KEY_NONE =INTERNAL_KEY_BASE_2+1, + KEY_IDLE =INTERNAL_KEY_BASE_2+2, + + KEY_KILLFOCUS =INTERNAL_KEY_BASE_2+6, + KEY_GOTFOCUS =INTERNAL_KEY_BASE_2+7, + KEY_CONSOLE_BUFFER_RESIZE=INTERNAL_KEY_BASE_2+8, + + + KEY_END_SKEY =0x0003FFFF, + KEY_LAST_BASE =KEY_END_SKEY, + +}; + +enum AddDefKeyboard +{ + KEY_CTRLSHIFT =KEY_CTRL|KEY_SHIFT, + KEY_ALTSHIFT =KEY_ALT|KEY_SHIFT, + KEY_CTRLALT =KEY_CTRL|KEY_ALT, + + KEY_CTRL0 =KEY_CTRL+'0', + KEY_CTRL1 =KEY_CTRL+'1', + KEY_CTRL2 =KEY_CTRL+'2', + KEY_CTRL3 =KEY_CTRL+'3', + KEY_CTRL4 =KEY_CTRL+'4', + KEY_CTRL5 =KEY_CTRL+'5', + KEY_CTRL6 =KEY_CTRL+'6', + KEY_CTRL7 =KEY_CTRL+'7', + KEY_CTRL8 =KEY_CTRL+'8', + KEY_CTRL9 =KEY_CTRL+'9', + + KEY_RCTRL0 =KEY_RCTRL+'0', + KEY_RCTRL1 =KEY_RCTRL+'1', + KEY_RCTRL2 =KEY_RCTRL+'2', + KEY_RCTRL3 =KEY_RCTRL+'3', + KEY_RCTRL4 =KEY_RCTRL+'4', + KEY_RCTRL5 =KEY_RCTRL+'5', + KEY_RCTRL6 =KEY_RCTRL+'6', + KEY_RCTRL7 =KEY_RCTRL+'7', + KEY_RCTRL8 =KEY_RCTRL+'8', + KEY_RCTRL9 =KEY_RCTRL+'9', + + KEY_CTRLA =KEY_CTRL+'A', + KEY_CTRLB =KEY_CTRL+'B', + KEY_CTRLC =KEY_CTRL+'C', + KEY_CTRLD =KEY_CTRL+'D', + KEY_CTRLE =KEY_CTRL+'E', + KEY_CTRLF =KEY_CTRL+'F', + KEY_CTRLG =KEY_CTRL+'G', + KEY_CTRLH =KEY_CTRL+'H', + KEY_CTRLI =KEY_CTRL+'I', + KEY_CTRLJ =KEY_CTRL+'J', + KEY_CTRLK =KEY_CTRL+'K', + KEY_CTRLL =KEY_CTRL+'L', + KEY_CTRLM =KEY_CTRL+'M', + KEY_CTRLN =KEY_CTRL+'N', + KEY_CTRLO =KEY_CTRL+'O', + KEY_CTRLP =KEY_CTRL+'P', + KEY_CTRLQ =KEY_CTRL+'Q', + KEY_CTRLR =KEY_CTRL+'R', + KEY_CTRLS =KEY_CTRL+'S', + KEY_CTRLT =KEY_CTRL+'T', + KEY_CTRLU =KEY_CTRL+'U', + KEY_CTRLV =KEY_CTRL+'V', + KEY_CTRLW =KEY_CTRL+'W', + KEY_CTRLX =KEY_CTRL+'X', + KEY_CTRLY =KEY_CTRL+'Y', + KEY_CTRLZ =KEY_CTRL+'Z', + + KEY_CTRLBRACKET =KEY_CTRL|KEY_BRACKET, + KEY_CTRLBACKBRACKET =KEY_CTRL|KEY_BACKBRACKET, + KEY_CTRLCOMMA =KEY_CTRL|KEY_COMMA, + KEY_CTRLQUOTE =KEY_CTRL|KEY_QUOTE, + KEY_CTRLDOT =KEY_CTRL|KEY_DOT, + + KEY_ALT0 =KEY_ALT+'0', + KEY_ALT1 =KEY_ALT+'1', + KEY_ALT2 =KEY_ALT+'2', + KEY_ALT3 =KEY_ALT+'3', + KEY_ALT4 =KEY_ALT+'4', + KEY_ALT5 =KEY_ALT+'5', + KEY_ALT6 =KEY_ALT+'6', + KEY_ALT7 =KEY_ALT+'7', + KEY_ALT8 =KEY_ALT+'8', + KEY_ALT9 =KEY_ALT+'9', + + KEY_ALTADD =KEY_ALT|KEY_ADD, + KEY_ALTDOT =KEY_ALT|KEY_DOT, + KEY_ALTCOMMA =KEY_ALT|KEY_COMMA, + KEY_ALTMULTIPLY =KEY_ALT|KEY_MULTIPLY, + + KEY_ALTA =KEY_ALT+'A', + KEY_ALTB =KEY_ALT+'B', + KEY_ALTC =KEY_ALT+'C', + KEY_ALTD =KEY_ALT+'D', + KEY_ALTE =KEY_ALT+'E', + KEY_ALTF =KEY_ALT+'F', + KEY_ALTG =KEY_ALT+'G', + KEY_ALTH =KEY_ALT+'H', + KEY_ALTI =KEY_ALT+'I', + KEY_ALTJ =KEY_ALT+'J', + KEY_ALTK =KEY_ALT+'K', + KEY_ALTL =KEY_ALT+'L', + KEY_ALTM =KEY_ALT+'M', + KEY_ALTN =KEY_ALT+'N', + KEY_ALTO =KEY_ALT+'O', + KEY_ALTP =KEY_ALT+'P', + KEY_ALTQ =KEY_ALT+'Q', + KEY_ALTR =KEY_ALT+'R', + KEY_ALTS =KEY_ALT+'S', + KEY_ALTT =KEY_ALT+'T', + KEY_ALTU =KEY_ALT+'U', + KEY_ALTV =KEY_ALT+'V', + KEY_ALTW =KEY_ALT+'W', + KEY_ALTX =KEY_ALT+'X', + KEY_ALTY =KEY_ALT+'Y', + KEY_ALTZ =KEY_ALT+'Z', + + KEY_CTRLSHIFTADD =KEY_CTRL|KEY_SHIFT|KEY_ADD, + KEY_CTRLSHIFTSUBTRACT =KEY_CTRL|KEY_SHIFT|KEY_SUBTRACT, + KEY_CTRLSHIFTDOT =KEY_CTRL|KEY_SHIFT|KEY_DOT, + KEY_CTRLSHIFTSLASH =KEY_CTRL|KEY_SHIFT|KEY_SLASH, + + KEY_CTRLSHIFT0 =(KEY_CTRL|KEY_SHIFT)+'0', + KEY_CTRLSHIFT1 =(KEY_CTRL|KEY_SHIFT)+'1', + KEY_CTRLSHIFT2 =(KEY_CTRL|KEY_SHIFT)+'2', + KEY_CTRLSHIFT3 =(KEY_CTRL|KEY_SHIFT)+'3', + KEY_CTRLSHIFT4 =(KEY_CTRL|KEY_SHIFT)+'4', + KEY_CTRLSHIFT5 =(KEY_CTRL|KEY_SHIFT)+'5', + KEY_CTRLSHIFT6 =(KEY_CTRL|KEY_SHIFT)+'6', + KEY_CTRLSHIFT7 =(KEY_CTRL|KEY_SHIFT)+'7', + KEY_CTRLSHIFT8 =(KEY_CTRL|KEY_SHIFT)+'8', + KEY_CTRLSHIFT9 =(KEY_CTRL|KEY_SHIFT)+'9', + + KEY_RCTRLSHIFT0 =KEY_RCTRL+KEY_SHIFT+'0', + KEY_RCTRLSHIFT1 =KEY_RCTRL+KEY_SHIFT+'1', + KEY_RCTRLSHIFT2 =KEY_RCTRL+KEY_SHIFT+'2', + KEY_RCTRLSHIFT3 =KEY_RCTRL+KEY_SHIFT+'3', + KEY_RCTRLSHIFT4 =KEY_RCTRL+KEY_SHIFT+'4', + KEY_RCTRLSHIFT5 =KEY_RCTRL+KEY_SHIFT+'5', + KEY_RCTRLSHIFT6 =KEY_RCTRL+KEY_SHIFT+'6', + KEY_RCTRLSHIFT7 =KEY_RCTRL+KEY_SHIFT+'7', + KEY_RCTRLSHIFT8 =KEY_RCTRL+KEY_SHIFT+'8', + KEY_RCTRLSHIFT9 =KEY_RCTRL+KEY_SHIFT+'9', + + KEY_CTRLSHIFTA =(KEY_CTRL|KEY_SHIFT)+'A', + KEY_CTRLSHIFTB =(KEY_CTRL|KEY_SHIFT)+'B', + KEY_CTRLSHIFTC =(KEY_CTRL|KEY_SHIFT)+'C', + KEY_CTRLSHIFTD =(KEY_CTRL|KEY_SHIFT)+'D', + KEY_CTRLSHIFTE =(KEY_CTRL|KEY_SHIFT)+'E', + KEY_CTRLSHIFTF =(KEY_CTRL|KEY_SHIFT)+'F', + KEY_CTRLSHIFTG =(KEY_CTRL|KEY_SHIFT)+'G', + KEY_CTRLSHIFTH =(KEY_CTRL|KEY_SHIFT)+'H', + KEY_CTRLSHIFTI =(KEY_CTRL|KEY_SHIFT)+'I', + KEY_CTRLSHIFTJ =(KEY_CTRL|KEY_SHIFT)+'J', + KEY_CTRLSHIFTK =(KEY_CTRL|KEY_SHIFT)+'K', + KEY_CTRLSHIFTL =(KEY_CTRL|KEY_SHIFT)+'L', + KEY_CTRLSHIFTM =(KEY_CTRL|KEY_SHIFT)+'M', + KEY_CTRLSHIFTN =(KEY_CTRL|KEY_SHIFT)+'N', + KEY_CTRLSHIFTO =(KEY_CTRL|KEY_SHIFT)+'O', + KEY_CTRLSHIFTP =(KEY_CTRL|KEY_SHIFT)+'P', + KEY_CTRLSHIFTQ =(KEY_CTRL|KEY_SHIFT)+'Q', + KEY_CTRLSHIFTR =(KEY_CTRL|KEY_SHIFT)+'R', + KEY_CTRLSHIFTS =(KEY_CTRL|KEY_SHIFT)+'S', + KEY_CTRLSHIFTT =(KEY_CTRL|KEY_SHIFT)+'T', + KEY_CTRLSHIFTU =(KEY_CTRL|KEY_SHIFT)+'U', + KEY_CTRLSHIFTV =(KEY_CTRL|KEY_SHIFT)+'V', + KEY_CTRLSHIFTW =(KEY_CTRL|KEY_SHIFT)+'W', + KEY_CTRLSHIFTX =(KEY_CTRL|KEY_SHIFT)+'X', + KEY_CTRLSHIFTY =(KEY_CTRL|KEY_SHIFT)+'Y', + KEY_CTRLSHIFTZ =(KEY_CTRL|KEY_SHIFT)+'Z', + + KEY_CTRLSHIFTBRACKET =KEY_CTRL|KEY_SHIFT|KEY_BRACKET, + KEY_CTRLSHIFTBACKSLASH =KEY_CTRL|KEY_SHIFT|KEY_BACKSLASH, + KEY_CTRLSHIFTBACKBRACKET =KEY_CTRL|KEY_SHIFT|KEY_BACKBRACKET, + + KEY_ALTSHIFT0 =(KEY_ALT|KEY_SHIFT)+'0', + KEY_ALTSHIFT1 =(KEY_ALT|KEY_SHIFT)+'1', + KEY_ALTSHIFT2 =(KEY_ALT|KEY_SHIFT)+'2', + KEY_ALTSHIFT3 =(KEY_ALT|KEY_SHIFT)+'3', + KEY_ALTSHIFT4 =(KEY_ALT|KEY_SHIFT)+'4', + KEY_ALTSHIFT5 =(KEY_ALT|KEY_SHIFT)+'5', + KEY_ALTSHIFT6 =(KEY_ALT|KEY_SHIFT)+'6', + KEY_ALTSHIFT7 =(KEY_ALT|KEY_SHIFT)+'7', + KEY_ALTSHIFT8 =(KEY_ALT|KEY_SHIFT)+'8', + KEY_ALTSHIFT9 =(KEY_ALT|KEY_SHIFT)+'9', + + KEY_ALTSHIFTA =(KEY_ALT|KEY_SHIFT)+'A', + KEY_ALTSHIFTB =(KEY_ALT|KEY_SHIFT)+'B', + KEY_ALTSHIFTC =(KEY_ALT|KEY_SHIFT)+'C', + KEY_ALTSHIFTD =(KEY_ALT|KEY_SHIFT)+'D', + KEY_ALTSHIFTE =(KEY_ALT|KEY_SHIFT)+'E', + KEY_ALTSHIFTF =(KEY_ALT|KEY_SHIFT)+'F', + KEY_ALTSHIFTG =(KEY_ALT|KEY_SHIFT)+'G', + KEY_ALTSHIFTH =(KEY_ALT|KEY_SHIFT)+'H', + KEY_ALTSHIFTI =(KEY_ALT|KEY_SHIFT)+'I', + KEY_ALTSHIFTJ =(KEY_ALT|KEY_SHIFT)+'J', + KEY_ALTSHIFTK =(KEY_ALT|KEY_SHIFT)+'K', + KEY_ALTSHIFTL =(KEY_ALT|KEY_SHIFT)+'L', + KEY_ALTSHIFTM =(KEY_ALT|KEY_SHIFT)+'M', + KEY_ALTSHIFTN =(KEY_ALT|KEY_SHIFT)+'N', + KEY_ALTSHIFTO =(KEY_ALT|KEY_SHIFT)+'O', + KEY_ALTSHIFTP =(KEY_ALT|KEY_SHIFT)+'P', + KEY_ALTSHIFTQ =(KEY_ALT|KEY_SHIFT)+'Q', + KEY_ALTSHIFTR =(KEY_ALT|KEY_SHIFT)+'R', + KEY_ALTSHIFTS =(KEY_ALT|KEY_SHIFT)+'S', + KEY_ALTSHIFTT =(KEY_ALT|KEY_SHIFT)+'T', + KEY_ALTSHIFTU =(KEY_ALT|KEY_SHIFT)+'U', + KEY_ALTSHIFTV =(KEY_ALT|KEY_SHIFT)+'V', + KEY_ALTSHIFTW =(KEY_ALT|KEY_SHIFT)+'W', + KEY_ALTSHIFTX =(KEY_ALT|KEY_SHIFT)+'X', + KEY_ALTSHIFTY =(KEY_ALT|KEY_SHIFT)+'Y', + KEY_ALTSHIFTZ =(KEY_ALT|KEY_SHIFT)+'Z', + + KEY_ALTSHIFTBRACKET =KEY_ALT|KEY_SHIFT|KEY_BRACKET, + KEY_ALTSHIFTBACKBRACKET =KEY_ALT|KEY_SHIFT|KEY_BACKBRACKET, + + KEY_CTRLALT0 =(KEY_CTRL|KEY_ALT)+'0', + KEY_CTRLALT1 =(KEY_CTRL|KEY_ALT)+'1', + KEY_CTRLALT2 =(KEY_CTRL|KEY_ALT)+'2', + KEY_CTRLALT3 =(KEY_CTRL|KEY_ALT)+'3', + KEY_CTRLALT4 =(KEY_CTRL|KEY_ALT)+'4', + KEY_CTRLALT5 =(KEY_CTRL|KEY_ALT)+'5', + KEY_CTRLALT6 =(KEY_CTRL|KEY_ALT)+'6', + KEY_CTRLALT7 =(KEY_CTRL|KEY_ALT)+'7', + KEY_CTRLALT8 =(KEY_CTRL|KEY_ALT)+'8', + KEY_CTRLALT9 =(KEY_CTRL|KEY_ALT)+'9', + + KEY_CTRLALTA =(KEY_CTRL|KEY_ALT)+'A', + KEY_CTRLALTB =(KEY_CTRL|KEY_ALT)+'B', + KEY_CTRLALTC =(KEY_CTRL|KEY_ALT)+'C', + KEY_CTRLALTD =(KEY_CTRL|KEY_ALT)+'D', + KEY_CTRLALTE =(KEY_CTRL|KEY_ALT)+'E', + KEY_CTRLALTF =(KEY_CTRL|KEY_ALT)+'F', + KEY_CTRLALTG =(KEY_CTRL|KEY_ALT)+'G', + KEY_CTRLALTH =(KEY_CTRL|KEY_ALT)+'H', + KEY_CTRLALTI =(KEY_CTRL|KEY_ALT)+'I', + KEY_CTRLALTJ =(KEY_CTRL|KEY_ALT)+'J', + KEY_CTRLALTK =(KEY_CTRL|KEY_ALT)+'K', + KEY_CTRLALTL =(KEY_CTRL|KEY_ALT)+'L', + KEY_CTRLALTM =(KEY_CTRL|KEY_ALT)+'M', + KEY_CTRLALTN =(KEY_CTRL|KEY_ALT)+'N', + KEY_CTRLALTO =(KEY_CTRL|KEY_ALT)+'O', + KEY_CTRLALTP =(KEY_CTRL|KEY_ALT)+'P', + KEY_CTRLALTQ =(KEY_CTRL|KEY_ALT)+'Q', + KEY_CTRLALTR =(KEY_CTRL|KEY_ALT)+'R', + KEY_CTRLALTS =(KEY_CTRL|KEY_ALT)+'S', + KEY_CTRLALTT =(KEY_CTRL|KEY_ALT)+'T', + KEY_CTRLALTU =(KEY_CTRL|KEY_ALT)+'U', + KEY_CTRLALTV =(KEY_CTRL|KEY_ALT)+'V', + KEY_CTRLALTW =(KEY_CTRL|KEY_ALT)+'W', + KEY_CTRLALTX =(KEY_CTRL|KEY_ALT)+'X', + KEY_CTRLALTY =(KEY_CTRL|KEY_ALT)+'Y', + KEY_CTRLALTZ =(KEY_CTRL|KEY_ALT)+'Z', + + KEY_CTRLALTBRACKET =KEY_CTRL|KEY_ALT|KEY_BRACKET, + KEY_CTRLALTBACKBRACKET =KEY_CTRL|KEY_ALT|KEY_BACKBRACKET, + + KEY_CTRLF1 =KEY_CTRL|KEY_F1, + KEY_CTRLF2 =KEY_CTRL|KEY_F2, + KEY_CTRLF3 =KEY_CTRL|KEY_F3, + KEY_CTRLF4 =KEY_CTRL|KEY_F4, + KEY_CTRLF5 =KEY_CTRL|KEY_F5, + KEY_CTRLF6 =KEY_CTRL|KEY_F6, + KEY_CTRLF7 =KEY_CTRL|KEY_F7, + KEY_CTRLF8 =KEY_CTRL|KEY_F8, + KEY_CTRLF9 =KEY_CTRL|KEY_F9, + KEY_CTRLF10 =KEY_CTRL|KEY_F10, + KEY_CTRLF11 =KEY_CTRL|KEY_F11, + KEY_CTRLF12 =KEY_CTRL|KEY_F12, + + KEY_SHIFTF1 =KEY_SHIFT|KEY_F1, + KEY_SHIFTF2 =KEY_SHIFT|KEY_F2, + KEY_SHIFTF3 =KEY_SHIFT|KEY_F3, + KEY_SHIFTF4 =KEY_SHIFT|KEY_F4, + KEY_SHIFTF5 =KEY_SHIFT|KEY_F5, + KEY_SHIFTF6 =KEY_SHIFT|KEY_F6, + KEY_SHIFTF7 =KEY_SHIFT|KEY_F7, + KEY_SHIFTF8 =KEY_SHIFT|KEY_F8, + KEY_SHIFTF9 =KEY_SHIFT|KEY_F9, + KEY_SHIFTF10 =KEY_SHIFT|KEY_F10, + KEY_SHIFTF11 =KEY_SHIFT|KEY_F11, + KEY_SHIFTF12 =KEY_SHIFT|KEY_F12, + + KEY_ALTF1 =KEY_ALT|KEY_F1, + KEY_ALTF2 =KEY_ALT|KEY_F2, + KEY_ALTF3 =KEY_ALT|KEY_F3, + KEY_ALTF4 =KEY_ALT|KEY_F4, + KEY_ALTF5 =KEY_ALT|KEY_F5, + KEY_ALTF6 =KEY_ALT|KEY_F6, + KEY_ALTF7 =KEY_ALT|KEY_F7, + KEY_ALTF8 =KEY_ALT|KEY_F8, + KEY_ALTF9 =KEY_ALT|KEY_F9, + KEY_ALTF10 =KEY_ALT|KEY_F10, + KEY_ALTF11 =KEY_ALT|KEY_F11, + KEY_ALTF12 =KEY_ALT|KEY_F12, + + KEY_CTRLSHIFTF1 =KEY_CTRL|KEY_SHIFT|KEY_F1, + KEY_CTRLSHIFTF2 =KEY_CTRL|KEY_SHIFT|KEY_F2, + KEY_CTRLSHIFTF3 =KEY_CTRL|KEY_SHIFT|KEY_F3, + KEY_CTRLSHIFTF4 =KEY_CTRL|KEY_SHIFT|KEY_F4, + KEY_CTRLSHIFTF5 =KEY_CTRL|KEY_SHIFT|KEY_F5, + KEY_CTRLSHIFTF6 =KEY_CTRL|KEY_SHIFT|KEY_F6, + KEY_CTRLSHIFTF7 =KEY_CTRL|KEY_SHIFT|KEY_F7, + KEY_CTRLSHIFTF8 =KEY_CTRL|KEY_SHIFT|KEY_F8, + KEY_CTRLSHIFTF9 =KEY_CTRL|KEY_SHIFT|KEY_F9, + KEY_CTRLSHIFTF10 =KEY_CTRL|KEY_SHIFT|KEY_F10, + KEY_CTRLSHIFTF11 =KEY_CTRL|KEY_SHIFT|KEY_F11, + KEY_CTRLSHIFTF12 =KEY_CTRL|KEY_SHIFT|KEY_F12, + + KEY_ALTSHIFTF1 =KEY_ALT|KEY_SHIFT|KEY_F1, + KEY_ALTSHIFTF2 =KEY_ALT|KEY_SHIFT|KEY_F2, + KEY_ALTSHIFTF3 =KEY_ALT|KEY_SHIFT|KEY_F3, + KEY_ALTSHIFTF4 =KEY_ALT|KEY_SHIFT|KEY_F4, + KEY_ALTSHIFTF5 =KEY_ALT|KEY_SHIFT|KEY_F5, + KEY_ALTSHIFTF6 =KEY_ALT|KEY_SHIFT|KEY_F6, + KEY_ALTSHIFTF7 =KEY_ALT|KEY_SHIFT|KEY_F7, + KEY_ALTSHIFTF8 =KEY_ALT|KEY_SHIFT|KEY_F8, + KEY_ALTSHIFTF9 =KEY_ALT|KEY_SHIFT|KEY_F9, + KEY_ALTSHIFTF10 =KEY_ALT|KEY_SHIFT|KEY_F10, + KEY_ALTSHIFTF11 =KEY_ALT|KEY_SHIFT|KEY_F11, + KEY_ALTSHIFTF12 =KEY_ALT|KEY_SHIFT|KEY_F12, + + KEY_CTRLALTF1 =KEY_CTRL|KEY_ALT|KEY_F1, + KEY_CTRLALTF2 =KEY_CTRL|KEY_ALT|KEY_F2, + KEY_CTRLALTF3 =KEY_CTRL|KEY_ALT|KEY_F3, + KEY_CTRLALTF4 =KEY_CTRL|KEY_ALT|KEY_F4, + KEY_CTRLALTF5 =KEY_CTRL|KEY_ALT|KEY_F5, + KEY_CTRLALTF6 =KEY_CTRL|KEY_ALT|KEY_F6, + KEY_CTRLALTF7 =KEY_CTRL|KEY_ALT|KEY_F7, + KEY_CTRLALTF8 =KEY_CTRL|KEY_ALT|KEY_F8, + KEY_CTRLALTF9 =KEY_CTRL|KEY_ALT|KEY_F9, + KEY_CTRLALTF10 =KEY_CTRL|KEY_ALT|KEY_F10, + KEY_CTRLALTF11 =KEY_CTRL|KEY_ALT|KEY_F11, + KEY_CTRLALTF12 =KEY_CTRL|KEY_ALT|KEY_F12, + + KEY_CTRLHOME =KEY_CTRL|KEY_HOME, + KEY_CTRLUP =KEY_CTRL|KEY_UP, + KEY_CTRLPGUP =KEY_CTRL|KEY_PGUP, + KEY_CTRLLEFT =KEY_CTRL|KEY_LEFT, + KEY_CTRLRIGHT =KEY_CTRL|KEY_RIGHT, + KEY_CTRLEND =KEY_CTRL|KEY_END, + KEY_CTRLDOWN =KEY_CTRL|KEY_DOWN, + KEY_CTRLPGDN =KEY_CTRL|KEY_PGDN, + KEY_CTRLINS =KEY_CTRL|KEY_INS, + KEY_CTRLDEL =KEY_CTRL|KEY_DEL, + KEY_CTRLNUMDEL =KEY_CTRL|KEY_NUMDEL, + KEY_CTRLDECIMAL =KEY_CTRL|KEY_DECIMAL, + + KEY_SHIFTHOME =KEY_SHIFT|KEY_HOME, + KEY_SHIFTUP =KEY_SHIFT|KEY_UP, + KEY_SHIFTPGUP =KEY_SHIFT|KEY_PGUP, + KEY_SHIFTLEFT =KEY_SHIFT|KEY_LEFT, + KEY_SHIFTRIGHT =KEY_SHIFT|KEY_RIGHT, + KEY_SHIFTEND =KEY_SHIFT|KEY_END, + KEY_SHIFTDOWN =KEY_SHIFT|KEY_DOWN, + KEY_SHIFTPGDN =KEY_SHIFT|KEY_PGDN, + KEY_SHIFTINS =KEY_SHIFT|KEY_INS, + KEY_SHIFTDEL =KEY_SHIFT|KEY_DEL, + KEY_SHIFTNUMDEL =KEY_SHIFT|KEY_NUMDEL, + KEY_SHIFTDECIMAL =KEY_SHIFT|KEY_DECIMAL, + + KEY_ALTHOME =KEY_ALT|KEY_HOME, + KEY_ALTUP =KEY_ALT|KEY_UP, + KEY_ALTPGUP =KEY_ALT|KEY_PGUP, + KEY_ALTLEFT =KEY_ALT|KEY_LEFT, + KEY_ALTRIGHT =KEY_ALT|KEY_RIGHT, + KEY_ALTEND =KEY_ALT|KEY_END, + KEY_ALTDOWN =KEY_ALT|KEY_DOWN, + KEY_ALTPGDN =KEY_ALT|KEY_PGDN, + KEY_ALTINS =KEY_ALT|KEY_INS, + KEY_ALTDEL =KEY_ALT|KEY_DEL, + KEY_ALTNUMDEL =KEY_ALT|KEY_NUMDEL, + KEY_ALTDECIMAL =KEY_ALT|KEY_DECIMAL, + + KEY_CTRLSHIFTHOME =KEY_CTRL|KEY_SHIFT|KEY_HOME, + KEY_CTRLSHIFTUP =KEY_CTRL|KEY_SHIFT|KEY_UP, + KEY_CTRLSHIFTPGUP =KEY_CTRL|KEY_SHIFT|KEY_PGUP, + KEY_CTRLSHIFTLEFT =KEY_CTRL|KEY_SHIFT|KEY_LEFT, + KEY_CTRLSHIFTRIGHT =KEY_CTRL|KEY_SHIFT|KEY_RIGHT, + KEY_CTRLSHIFTEND =KEY_CTRL|KEY_SHIFT|KEY_END, + KEY_CTRLSHIFTDOWN =KEY_CTRL|KEY_SHIFT|KEY_DOWN, + KEY_CTRLSHIFTPGDN =KEY_CTRL|KEY_SHIFT|KEY_PGDN, + KEY_CTRLSHIFTINS =KEY_CTRL|KEY_SHIFT|KEY_INS, + KEY_CTRLSHIFTDEL =KEY_CTRL|KEY_SHIFT|KEY_DEL, + KEY_CTRLSHIFTNUMDEL =KEY_CTRL|KEY_SHIFT|KEY_NUMDEL, + KEY_CTRLSHIFTDECIMAL =KEY_CTRL|KEY_SHIFT|KEY_DECIMAL, + + KEY_ALTSHIFTHOME =KEY_ALT|KEY_SHIFT|KEY_HOME, + KEY_ALTSHIFTUP =KEY_ALT|KEY_SHIFT|KEY_UP, + KEY_ALTSHIFTPGUP =KEY_ALT|KEY_SHIFT|KEY_PGUP, + KEY_ALTSHIFTLEFT =KEY_ALT|KEY_SHIFT|KEY_LEFT, + KEY_ALTSHIFTRIGHT =KEY_ALT|KEY_SHIFT|KEY_RIGHT, + KEY_ALTSHIFTEND =KEY_ALT|KEY_SHIFT|KEY_END, + KEY_ALTSHIFTDOWN =KEY_ALT|KEY_SHIFT|KEY_DOWN, + KEY_ALTSHIFTPGDN =KEY_ALT|KEY_SHIFT|KEY_PGDN, + KEY_ALTSHIFTINS =KEY_ALT|KEY_SHIFT|KEY_INS, + KEY_ALTSHIFTDEL =KEY_ALT|KEY_SHIFT|KEY_DEL, + KEY_ALTSHIFTNUMDEL =KEY_ALT|KEY_SHIFT|KEY_NUMDEL, + KEY_ALTSHIFTDECIMAL =KEY_ALT|KEY_SHIFT|KEY_DECIMAL, + + KEY_CTRLALTHOME =KEY_CTRL|KEY_ALT|KEY_HOME, + KEY_CTRLALTUP =KEY_CTRL|KEY_ALT|KEY_UP, + KEY_CTRLALTPGUP =KEY_CTRL|KEY_ALT|KEY_PGUP, + KEY_CTRLALTLEFT =KEY_CTRL|KEY_ALT|KEY_LEFT, + KEY_CTRLALTRIGHT =KEY_CTRL|KEY_ALT|KEY_RIGHT, + KEY_CTRLALTEND =KEY_CTRL|KEY_ALT|KEY_END, + KEY_CTRLALTDOWN =KEY_CTRL|KEY_ALT|KEY_DOWN, + KEY_CTRLALTPGDN =KEY_CTRL|KEY_ALT|KEY_PGDN, + KEY_CTRLALTINS =KEY_CTRL|KEY_ALT|KEY_INS, + + KEY_CTRLNUMPAD0 =KEY_CTRL|KEY_NUMPAD0, + KEY_CTRLNUMPAD1 =KEY_CTRL|KEY_NUMPAD1, + KEY_CTRLNUMPAD2 =KEY_CTRL|KEY_NUMPAD2, + KEY_CTRLNUMPAD3 =KEY_CTRL|KEY_NUMPAD3, + KEY_CTRLNUMPAD4 =KEY_CTRL|KEY_NUMPAD4, + KEY_CTRLNUMPAD5 =KEY_CTRL|KEY_NUMPAD5, + KEY_CTRLNUMPAD6 =KEY_CTRL|KEY_NUMPAD6, + KEY_CTRLNUMPAD7 =KEY_CTRL|KEY_NUMPAD7, + KEY_CTRLNUMPAD8 =KEY_CTRL|KEY_NUMPAD8, + KEY_CTRLNUMPAD9 =KEY_CTRL|KEY_NUMPAD9, + + KEY_SHIFTNUMPAD0 =KEY_SHIFT|KEY_NUMPAD0, + KEY_SHIFTNUMPAD1 =KEY_SHIFT|KEY_NUMPAD1, + KEY_SHIFTNUMPAD2 =KEY_SHIFT|KEY_NUMPAD2, + KEY_SHIFTNUMPAD3 =KEY_SHIFT|KEY_NUMPAD3, + KEY_SHIFTNUMPAD4 =KEY_SHIFT|KEY_NUMPAD4, + KEY_SHIFTNUMPAD5 =KEY_SHIFT|KEY_NUMPAD5, + KEY_SHIFTNUMPAD6 =KEY_SHIFT|KEY_NUMPAD6, + KEY_SHIFTNUMPAD7 =KEY_SHIFT|KEY_NUMPAD7, + KEY_SHIFTNUMPAD8 =KEY_SHIFT|KEY_NUMPAD8, + KEY_SHIFTNUMPAD9 =KEY_SHIFT|KEY_NUMPAD9, + + KEY_CTRLSHIFTNUMPAD0 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD0, + KEY_CTRLSHIFTNUMPAD1 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD1, + KEY_CTRLSHIFTNUMPAD2 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD2, + KEY_CTRLSHIFTNUMPAD3 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD3, + KEY_CTRLSHIFTNUMPAD4 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD4, + KEY_CTRLSHIFTNUMPAD5 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD5, + KEY_CTRLSHIFTNUMPAD6 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD6, + KEY_CTRLSHIFTNUMPAD7 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD7, + KEY_CTRLSHIFTNUMPAD8 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD8, + KEY_CTRLSHIFTNUMPAD9 =KEY_CTRL|KEY_SHIFT|KEY_NUMPAD9, + + KEY_CTRLALTNUMPAD0 =KEY_CTRL|KEY_ALT|KEY_NUMPAD0, + KEY_CTRLALTNUMPAD1 =KEY_CTRL|KEY_ALT|KEY_NUMPAD1, + KEY_CTRLALTNUMPAD2 =KEY_CTRL|KEY_ALT|KEY_NUMPAD2, + KEY_CTRLALTNUMPAD3 =KEY_CTRL|KEY_ALT|KEY_NUMPAD3, + KEY_CTRLALTNUMPAD4 =KEY_CTRL|KEY_ALT|KEY_NUMPAD4, + KEY_CTRLALTNUMPAD5 =KEY_CTRL|KEY_ALT|KEY_NUMPAD5, + KEY_CTRLALTNUMPAD6 =KEY_CTRL|KEY_ALT|KEY_NUMPAD6, + KEY_CTRLALTNUMPAD7 =KEY_CTRL|KEY_ALT|KEY_NUMPAD7, + KEY_CTRLALTNUMPAD8 =KEY_CTRL|KEY_ALT|KEY_NUMPAD8, + KEY_CTRLALTNUMPAD9 =KEY_CTRL|KEY_ALT|KEY_NUMPAD9, + + KEY_ALTSHIFTNUMPAD0 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD0, + KEY_ALTSHIFTNUMPAD1 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD1, + KEY_ALTSHIFTNUMPAD2 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD2, + KEY_ALTSHIFTNUMPAD3 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD3, + KEY_ALTSHIFTNUMPAD4 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD4, + KEY_ALTSHIFTNUMPAD5 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD5, + KEY_ALTSHIFTNUMPAD6 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD6, + KEY_ALTSHIFTNUMPAD7 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD7, + KEY_ALTSHIFTNUMPAD8 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD8, + KEY_ALTSHIFTNUMPAD9 =KEY_ALT|KEY_SHIFT|KEY_NUMPAD9, + + KEY_CTRLSLASH =KEY_CTRL|KEY_SLASH, + KEY_CTRLBACKSLASH =KEY_CTRL|KEY_BACKSLASH, + KEY_CTRLCLEAR =KEY_CTRL|KEY_CLEAR, + KEY_CTRLSHIFTCLEAR =KEY_CTRL|KEY_SHIFT|KEY_CLEAR, + KEY_CTRLALTCLEAR =KEY_CTRL|KEY_ALT|KEY_CLEAR, + KEY_CTRLADD =KEY_CTRL|KEY_ADD, + KEY_SHIFTADD =KEY_SHIFT|KEY_ADD, + + KEY_CTRLSUBTRACT =KEY_CTRL|KEY_SUBTRACT, + KEY_ALTSUBTRACT =KEY_ALT|KEY_SUBTRACT, + KEY_SHIFTSUBTRACT =KEY_SHIFT|KEY_SUBTRACT, + KEY_CTRLMULTIPLY =KEY_CTRL|KEY_MULTIPLY, + + KEY_CTRLBS =KEY_CTRL|KEY_BS, + KEY_ALTBS =KEY_ALT|KEY_BS, + KEY_CTRLSHIFTBS =KEY_CTRL|KEY_SHIFT|KEY_BS, + KEY_SHIFTBS =KEY_SHIFT|KEY_BS, + + KEY_CTRLSHIFTTAB =KEY_CTRL|KEY_SHIFT|KEY_TAB, + KEY_CTRLTAB =KEY_CTRL|KEY_TAB, + KEY_SHIFTTAB =KEY_SHIFT|KEY_TAB, + + KEY_CTRLENTER =KEY_CTRL|KEY_ENTER, + KEY_SHIFTENTER =KEY_SHIFT|KEY_ENTER, + KEY_ALTSHIFTENTER =KEY_ALT|KEY_SHIFT|KEY_ENTER, + KEY_CTRLALTENTER =KEY_CTRL|KEY_ALT|KEY_ENTER, + KEY_CTRLSHIFTENTER =KEY_CTRL|KEY_SHIFT|KEY_ENTER, + + KEY_CTRLNUMENTER =KEY_CTRL|KEY_NUMENTER, + KEY_SHIFTNUMENTER =KEY_SHIFT|KEY_NUMENTER, + KEY_ALTSHIFTNUMENTER =KEY_ALT|KEY_SHIFT|KEY_NUMENTER, + KEY_CTRLALTNUMENTER =KEY_CTRL|KEY_ALT|KEY_NUMENTER, + KEY_CTRLSHIFTNUMENTER =KEY_CTRL|KEY_SHIFT|KEY_NUMENTER, + + KEY_CTRLAPPS =KEY_CTRL|KEY_APPS, + KEY_ALTAPPS =KEY_ALT|KEY_APPS, + KEY_SHIFTAPPS =KEY_SHIFT|KEY_APPS, + KEY_CTRLSHIFTAPPS =KEY_CTRL|KEY_SHIFT|KEY_APPS, + KEY_ALTSHIFTAPPS =KEY_ALT|KEY_SHIFT|KEY_APPS, + KEY_CTRLALTAPPS =KEY_CTRL|KEY_ALT|KEY_APPS, + + KEY_CTRLSPACE =KEY_CTRL|KEY_SPACE, + KEY_SHIFTSPACE =KEY_SHIFT|KEY_SPACE, + KEY_CTRLSHIFTSPACE =KEY_CTRL|KEY_SHIFT|KEY_SPACE, + + KEY_ALT_BASE =KEY_ALT, + KEY_ALTSHIFT_BASE =KEY_ALTSHIFT, +}; + + +#endif // __FARKEYS_HPP__ diff --git a/netbox/src/PluginSDK/Far2/plugin.hpp b/netbox/src/PluginSDK/Far2/plugin.hpp new file mode 100644 index 000000000..91d5ca8c5 --- /dev/null +++ b/netbox/src/PluginSDK/Far2/plugin.hpp @@ -0,0 +1,2106 @@ +#pragma once +#ifndef __PLUGIN_HPP__ +#define __PLUGIN_HPP__ + +/* + plugin.hpp + + Plugin API for FAR Manager 2.0 build 1807 +*/ + +/* +Copyright (c) 1996 Eugene Roshal +Copyright (c) 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +EXCEPTION: +Far Manager plugins that use this header file can be distributed under any +other possible license with no implications from the above license on them. +*/ + +#define FARMANAGERVERSION_MAJOR 2 +#define FARMANAGERVERSION_MINOR 0 +#define FARMANAGERVERSION_BUILD 1807 + +#ifndef RC_INVOKED + +#define MAKEFARVERSION(major,minor,build) ( ((major)<<8) | (minor) | ((build)<<16)) + +#define FARMANAGERVERSION MAKEFARVERSION(FARMANAGERVERSION_MAJOR,FARMANAGERVERSION_MINOR,FARMANAGERVERSION_BUILD) + +#if !defined(_INC_WINDOWS) && !defined(_WINDOWS_) +#if (defined(__GNUC__) || defined(_MSC_VER)) && !defined(_WIN64) +#if !defined(_WINCON_H) && !defined(_WINCON_) +#define _WINCON_H +#define _WINCON_ // to prevent including wincon.h +#if defined(_MSC_VER) +#pragma pack(push,2) +#else +#pragma pack(2) +#endif +#include +#if defined(_MSC_VER) +#pragma pack(pop) +#else +#pragma pack() +#endif +#undef _WINCON_ +#undef _WINCON_H + +#if defined(_MSC_VER) +#pragma pack(push,8) +#else +#pragma pack(8) +#endif +#include +#if defined(_MSC_VER) +#pragma pack(pop) +#else +#pragma pack() +#endif +#endif +#define _WINCON_ +#else +#include +#endif +#endif + +#if defined(__BORLANDC__) +#ifndef _WIN64 +#pragma option -a2 +#endif +#elif defined(__GNUC__) || (defined(__WATCOMC__) && (__WATCOMC__ < 1100)) || defined(__LCC__) +#ifndef _WIN64 +#pragma pack(2) +#define _export +#endif +#if defined(__LCC__) +#define _export __declspec(dllexport) +#endif +#else +#ifndef _WIN64 +#pragma pack(push,2) +#endif +#if _MSC_VER +#ifdef _export +#undef _export +#endif +#define _export +#endif +#endif + +#undef DefDlgProc + +#define FARMACRO_KEY_EVENT (KEY_EVENT|0x8000) + +// To ensure compatibility of plugin.hpp with compilers not supporting C++, +// you can #define _FAR_NO_NAMELESS_UNIONS. In this case, to access, +// for example, the Data field of the FarDialogItem structure +// you will need to use Data.Data, and the Selected field - Param.Selected +//#define _FAR_NO_NAMELESS_UNIONS + +#ifndef _WINCON_ +typedef struct _INPUT_RECORD INPUT_RECORD; +typedef struct _CHAR_INFO CHAR_INFO; +#endif + +#define CP_UNICODE 1200 +#define CP_REVERSEBOM 1201 +#define CP_AUTODETECT ((UINT)-1) + +enum FARMESSAGEFLAGS +{ + FMSG_WARNING = 0x00000001, + FMSG_ERRORTYPE = 0x00000002, + FMSG_KEEPBACKGROUND = 0x00000004, + FMSG_LEFTALIGN = 0x00000010, + + FMSG_ALLINONE = 0x00000020, + + FMSG_MB_OK = 0x00010000, + FMSG_MB_OKCANCEL = 0x00020000, + FMSG_MB_ABORTRETRYIGNORE = 0x00030000, + FMSG_MB_YESNO = 0x00040000, + FMSG_MB_YESNOCANCEL = 0x00050000, + FMSG_MB_RETRYCANCEL = 0x00060000, +}; + +typedef int (WINAPI *FARAPIMESSAGE)( + INT_PTR PluginNumber, + DWORD Flags, + const wchar_t *HelpTopic, + const wchar_t * const *Items, + int ItemsNumber, + int ButtonsNumber +); + + +enum DialogItemTypes +{ + DI_TEXT, + DI_VTEXT, + DI_SINGLEBOX, + DI_DOUBLEBOX, + DI_EDIT, + DI_PSWEDIT, + DI_FIXEDIT, + DI_BUTTON, + DI_CHECKBOX, + DI_RADIOBUTTON, + DI_COMBOBOX, + DI_LISTBOX, + + DI_USERCONTROL=255, +}; + +/* + Check diagol element type has inputstring? + (DI_EDIT, DI_FIXEDIT, DI_PSWEDIT, etc) +*/ +static __inline BOOL IsEdit(int Type) +{ + switch (Type) + { + case DI_EDIT: + case DI_FIXEDIT: + case DI_PSWEDIT: + case DI_COMBOBOX: + return TRUE; + default: + return FALSE; + } +} + + +enum FarDialogItemFlags +{ + DIF_NONE = 0, + DIF_COLORMASK = 0x000000ffUL, + DIF_SETCOLOR = 0x00000100UL, + DIF_BOXCOLOR = 0x00000200UL, + DIF_GROUP = 0x00000400UL, + DIF_LEFTTEXT = 0x00000800UL, + DIF_MOVESELECT = 0x00001000UL, + DIF_SHOWAMPERSAND = 0x00002000UL, + DIF_CENTERGROUP = 0x00004000UL, + DIF_NOBRACKETS = 0x00008000UL, + DIF_MANUALADDHISTORY = 0x00008000UL, + DIF_SEPARATOR = 0x00010000UL, + DIF_SEPARATOR2 = 0x00020000UL, + DIF_EDITOR = 0x00020000UL, + DIF_LISTNOAMPERSAND = 0x00020000UL, + DIF_LISTNOBOX = 0x00040000UL, + DIF_HISTORY = 0x00040000UL, + DIF_BTNNOCLOSE = 0x00040000UL, + DIF_CENTERTEXT = 0x00040000UL, + DIF_SETSHIELD = 0x00080000UL, + DIF_EDITEXPAND = 0x00080000UL, + DIF_DROPDOWNLIST = 0x00100000UL, + DIF_USELASTHISTORY = 0x00200000UL, + DIF_MASKEDIT = 0x00400000UL, + DIF_SELECTONENTRY = 0x00800000UL, + DIF_3STATE = 0x00800000UL, + DIF_EDITPATH = 0x01000000UL, + DIF_LISTWRAPMODE = 0x01000000UL, + DIF_NOAUTOCOMPLETE = 0x02000000UL, + DIF_LISTAUTOHIGHLIGHT = 0x02000000UL, + DIF_LISTNOCLOSE = 0x04000000UL, + DIF_HIDDEN = 0x10000000UL, + DIF_READONLY = 0x20000000UL, + DIF_NOFOCUS = 0x40000000UL, + DIF_DISABLE = 0x80000000UL, +}; + +enum FarMessagesProc +{ + DM_FIRST=0, + DM_CLOSE, + DM_ENABLE, + DM_ENABLEREDRAW, + DM_GETDLGDATA, + DM_GETDLGITEM, + DM_GETDLGRECT, + DM_GETTEXT, + DM_GETTEXTLENGTH, + DM_KEY, + DM_MOVEDIALOG, + DM_SETDLGDATA, + DM_SETDLGITEM, + DM_SETFOCUS, + DM_REDRAW, + DM_SETREDRAW=DM_REDRAW, + DM_SETTEXT, + DM_SETMAXTEXTLENGTH, + DM_SETTEXTLENGTH=DM_SETMAXTEXTLENGTH, + DM_SHOWDIALOG, + DM_GETFOCUS, + DM_GETCURSORPOS, + DM_SETCURSORPOS, + DM_GETTEXTPTR, + DM_SETTEXTPTR, + DM_SHOWITEM, + DM_ADDHISTORY, + + DM_GETCHECK, + DM_SETCHECK, + DM_SET3STATE, + + DM_LISTSORT, + DM_LISTGETITEM, + DM_LISTGETCURPOS, + DM_LISTSETCURPOS, + DM_LISTDELETE, + DM_LISTADD, + DM_LISTADDSTR, + DM_LISTUPDATE, + DM_LISTINSERT, + DM_LISTFINDSTRING, + DM_LISTINFO, + DM_LISTGETDATA, + DM_LISTSETDATA, + DM_LISTSETTITLES, + DM_LISTGETTITLES, + + DM_RESIZEDIALOG, + DM_SETITEMPOSITION, + + DM_GETDROPDOWNOPENED, + DM_SETDROPDOWNOPENED, + + DM_SETHISTORY, + + DM_GETITEMPOSITION, + DM_SETMOUSEEVENTNOTIFY, + + DM_EDITUNCHANGEDFLAG, + + DM_GETITEMDATA, + DM_SETITEMDATA, + + DM_LISTSET, + DM_LISTSETMOUSEREACTION, + + DM_GETCURSORSIZE, + DM_SETCURSORSIZE, + + DM_LISTGETDATASIZE, + + DM_GETSELECTION, + DM_SETSELECTION, + + DM_GETEDITPOSITION, + DM_SETEDITPOSITION, + + DM_SETCOMBOBOXEVENT, + DM_GETCOMBOBOXEVENT, + + DM_GETCONSTTEXTPTR, + DM_GETDLGITEMSHORT, + DM_SETDLGITEMSHORT, + + DM_GETDIALOGINFO, + + DN_FIRST=0x1000, + DN_BTNCLICK, + DN_CTLCOLORDIALOG, + DN_CTLCOLORDLGITEM, + DN_CTLCOLORDLGLIST, + DN_DRAWDIALOG, + DN_DRAWDLGITEM, + DN_EDITCHANGE, + DN_ENTERIDLE, + DN_GOTFOCUS, + DN_HELP, + DN_HOTKEY, + DN_INITDIALOG, + DN_KILLFOCUS, + DN_LISTCHANGE, + DN_MOUSECLICK, + DN_DRAGGED, + DN_RESIZECONSOLE, + DN_MOUSEEVENT, + DN_DRAWDIALOGDONE, + DN_LISTHOTKEY, + + DN_GETDIALOGINFO=DM_GETDIALOGINFO, + + DN_CLOSE=DM_CLOSE, + DN_KEY=DM_KEY, + + + DM_USER=0x4000, + +}; + +enum FARCHECKEDSTATE +{ + BSTATE_UNCHECKED = 0, + BSTATE_CHECKED = 1, + BSTATE_3STATE = 2, + BSTATE_TOGGLE = 3, +}; + +enum FARLISTMOUSEREACTIONTYPE +{ + LMRT_ONLYFOCUS = 0, + LMRT_ALWAYS = 1, + LMRT_NEVER = 2, +}; + +enum FARCOMBOBOXEVENTTYPE +{ + CBET_KEY = 0x00000001, + CBET_MOUSE = 0x00000002, +}; + +enum LISTITEMFLAGS +{ + LIF_SELECTED = 0x00010000UL, + LIF_CHECKED = 0x00020000UL, + LIF_SEPARATOR = 0x00040000UL, + LIF_DISABLE = 0x00080000UL, + LIF_GRAYED = 0x00100000UL, + LIF_HIDDEN = 0x00200000UL, + LIF_DELETEUSERDATA = 0x80000000UL, +}; + +struct FarListItem +{ + DWORD Flags; + const wchar_t *Text; + DWORD Reserved[3]; +}; + +struct FarListUpdate +{ + int Index; + struct FarListItem Item; +}; + +struct FarListInsert +{ + int Index; + struct FarListItem Item; +}; + +struct FarListGetItem +{ + int ItemIndex; + struct FarListItem Item; +}; + +struct FarListPos +{ + int SelectPos; + int TopPos; +}; + +enum FARLISTFINDFLAGS +{ + LIFIND_EXACTMATCH = 0x00000001, +}; + +struct FarListFind +{ + int StartIndex; + const wchar_t *Pattern; + DWORD Flags; + DWORD Reserved; +}; + +struct FarListDelete +{ + int StartIndex; + int Count; +}; + +enum FARLISTINFOFLAGS +{ + LINFO_SHOWNOBOX = 0x00000400, + LINFO_AUTOHIGHLIGHT = 0x00000800, + LINFO_REVERSEHIGHLIGHT = 0x00001000, + LINFO_WRAPMODE = 0x00008000, + LINFO_SHOWAMPERSAND = 0x00010000, +}; + +struct FarListInfo +{ + DWORD Flags; + int ItemsNumber; + int SelectPos; + int TopPos; + int MaxHeight; + int MaxLength; + DWORD Reserved[6]; +}; + +struct FarListItemData +{ + int Index; + int DataSize; + void *Data; + DWORD Reserved; +}; + +struct FarList +{ + int ItemsNumber; + struct FarListItem *Items; +}; + +struct FarListTitles +{ + int TitleLen; + const wchar_t *Title; + int BottomLen; + const wchar_t *Bottom; +}; + +struct FarListColors +{ + DWORD Flags; + DWORD Reserved; + int ColorCount; + LPBYTE Colors; +}; + +struct FarDialogItem +{ + int Type; + int X1,Y1,X2,Y2; + int Focus; + union + { + DWORD_PTR Reserved; + int Selected; + const wchar_t *History; + const wchar_t *Mask; + struct FarList *ListItems; + int ListPos; + CHAR_INFO *VBuf; + } +#ifdef _FAR_NO_NAMELESS_UNIONS + Param +#endif + ; + DWORD Flags; + int DefaultButton; + + const wchar_t *PtrData; + size_t MaxLen; // terminate 0 not included (if == 0 string size is unlimited) +}; + +struct FarDialogItemData +{ + size_t PtrLength; + wchar_t *PtrData; +}; + +struct FarDialogEvent +{ + HANDLE hDlg; + int Msg; + int Param1; + LONG_PTR Param2; + LONG_PTR Result; +}; + +struct OpenDlgPluginData +{ + int ItemNumber; + HANDLE hDlg; +}; + +struct DialogInfo +{ + int StructSize; + GUID Id; +}; + +#define Dlg_RedrawDialog(Info,hDlg) Info.SendDlgMessage(hDlg,DM_REDRAW,0,0) + +#define Dlg_GetDlgData(Info,hDlg) Info.SendDlgMessage(hDlg,DM_GETDLGDATA,0,0) +#define Dlg_SetDlgData(Info,hDlg,Data) Info.SendDlgMessage(hDlg,DM_SETDLGDATA,0,(LONG_PTR)Data) + +#define Dlg_GetDlgItemData(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_GETITEMDATA,0,0) +#define Dlg_SetDlgItemData(Info,hDlg,ID,Data) Info.SendDlgMessage(hDlg,DM_SETITEMDATA,0,(LONG_PTR)Data) + +#define DlgItem_GetFocus(Info,hDlg) Info.SendDlgMessage(hDlg,DM_GETFOCUS,0,0) +#define DlgItem_SetFocus(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_SETFOCUS,ID,0) +#define DlgItem_Enable(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_ENABLE,ID,TRUE) +#define DlgItem_Disable(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_ENABLE,ID,FALSE) +#define DlgItem_IsEnable(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_ENABLE,ID,-1) +#define DlgItem_SetText(Info,hDlg,ID,Str) Info.SendDlgMessage(hDlg,DM_SETTEXTPTR,ID,(LONG_PTR)Str) + +#define DlgItem_GetCheck(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_GETCHECK,ID,0) +#define DlgItem_SetCheck(Info,hDlg,ID,State) Info.SendDlgMessage(hDlg,DM_SETCHECK,ID,State) + +#define DlgEdit_AddHistory(Info,hDlg,ID,Str) Info.SendDlgMessage(hDlg,DM_ADDHISTORY,ID,(LONG_PTR)Str) + +#define DlgList_AddString(Info,hDlg,ID,Str) Info.SendDlgMessage(hDlg,DM_LISTADDSTR,ID,(LONG_PTR)Str) +#define DlgList_GetCurPos(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_LISTGETCURPOS,ID,0) +#define DlgList_SetCurPos(Info,hDlg,ID,NewPos) {struct FarListPos LPos={NewPos,-1};Info.SendDlgMessage(hDlg,DM_LISTSETCURPOS,ID,(LONG_PTR)&LPos);} +#define DlgList_ClearList(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_LISTDELETE,ID,0) +#define DlgList_DeleteItem(Info,hDlg,ID,Index) {struct FarListDelete FLDItem={Index,1}; Info.SendDlgMessage(hDlg,DM_LISTDELETE,ID,(LONG_PTR)&FLDItem);} +#define DlgList_SortUp(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_LISTSORT,ID,0) +#define DlgList_SortDown(Info,hDlg,ID) Info.SendDlgMessage(hDlg,DM_LISTSORT,ID,1) +#define DlgList_GetItemData(Info,hDlg,ID,Index) Info.SendDlgMessage(hDlg,DM_LISTGETDATA,ID,Index) +#define DlgList_SetItemStrAsData(Info,hDlg,ID,Index,Str) {struct FarListItemData FLID{Index,0,Str,0}; Info.SendDlgMessage(hDlg,DM_LISTSETDATA,ID,(LONG_PTR)&FLID);} + +enum FARDIALOGFLAGS +{ + FDLG_WARNING = 0x00000001, + FDLG_SMALLDIALOG = 0x00000002, + FDLG_NODRAWSHADOW = 0x00000004, + FDLG_NODRAWPANEL = 0x00000008, + FDLG_KEEPCONSOLETITLE = 0x00000020, +}; + +typedef LONG_PTR(WINAPI *FARWINDOWPROC)( + HANDLE hDlg, + int Msg, + int Param1, + LONG_PTR Param2 +); + +typedef LONG_PTR(WINAPI *FARAPISENDDLGMESSAGE)( + HANDLE hDlg, + int Msg, + int Param1, + LONG_PTR Param2 +); + +typedef LONG_PTR(WINAPI *FARAPIDEFDLGPROC)( + HANDLE hDlg, + int Msg, + int Param1, + LONG_PTR Param2 +); + +typedef HANDLE(WINAPI *FARAPIDIALOGINIT)( + INT_PTR PluginNumber, + int X1, + int Y1, + int X2, + int Y2, + const wchar_t *HelpTopic, + struct FarDialogItem *Item, + unsigned int ItemsNumber, + DWORD Reserved, + DWORD Flags, + FARWINDOWPROC DlgProc, + LONG_PTR Param +); + +typedef int (WINAPI *FARAPIDIALOGRUN)( + HANDLE hDlg +); + +typedef void (WINAPI *FARAPIDIALOGFREE)( + HANDLE hDlg +); + +struct FarMenuItem +{ + const wchar_t *Text; + int Selected; + int Checked; + int Separator; +}; + +enum MENUITEMFLAGS +{ + MIF_SELECTED = 0x00010000UL, + MIF_CHECKED = 0x00020000UL, + MIF_SEPARATOR = 0x00040000UL, + MIF_DISABLE = 0x00080000UL, + MIF_GRAYED = 0x00100000UL, + MIF_HIDDEN = 0x00200000UL, +}; + +struct FarMenuItemEx +{ + DWORD Flags; + const wchar_t *Text; + DWORD AccelKey; + DWORD Reserved; + DWORD_PTR UserData; +}; + +enum FARMENUFLAGS +{ + FMENU_SHOWAMPERSAND = 0x00000001, + FMENU_WRAPMODE = 0x00000002, + FMENU_AUTOHIGHLIGHT = 0x00000004, + FMENU_REVERSEAUTOHIGHLIGHT = 0x00000008, + FMENU_USEEXT = 0x00000020, + FMENU_CHANGECONSOLETITLE = 0x00000040, +}; + +typedef int (WINAPI *FARAPIMENU)( + INT_PTR PluginNumber, + int X, + int Y, + int MaxHeight, + DWORD Flags, + const wchar_t *Title, + const wchar_t *Bottom, + const wchar_t *HelpTopic, + const int *BreakKeys, + int *BreakCode, + const struct FarMenuItem *Item, + int ItemsNumber +); + + +enum PLUGINPANELITEMFLAGS +{ + PPIF_PROCESSDESCR = 0x80000000, + PPIF_SELECTED = 0x40000000, + PPIF_USERDATA = 0x20000000, +}; + +struct FAR_FIND_DATA +{ + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + uint64_t nFileSize; + uint64_t nPackSize; + const wchar_t *lpwszFileName; + const wchar_t *lpwszAlternateFileName; +}; + +struct PluginPanelItem +{ + struct FAR_FIND_DATA FindData; + DWORD Flags; + DWORD NumberOfLinks; + const wchar_t *Description; + const wchar_t *Owner; + const wchar_t * const *CustomColumnData; + int CustomColumnNumber; + DWORD_PTR UserData; + DWORD CRC32; + DWORD_PTR Reserved[2]; +}; + +enum PANELINFOFLAGS +{ + PFLAGS_SHOWHIDDEN = 0x00000001, + PFLAGS_HIGHLIGHT = 0x00000002, + PFLAGS_REVERSESORTORDER = 0x00000004, + PFLAGS_USESORTGROUPS = 0x00000008, + PFLAGS_SELECTEDFIRST = 0x00000010, + PFLAGS_REALNAMES = 0x00000020, + PFLAGS_NUMERICSORT = 0x00000040, + PFLAGS_PANELLEFT = 0x00000080, + PFLAGS_DIRECTORIESFIRST = 0x00000100, + PFLAGS_USECRC32 = 0x00000200, + PFLAGS_CASESENSITIVESORT = 0x00000400, +}; + +enum PANELINFOTYPE +{ + PTYPE_FILEPANEL, + PTYPE_TREEPANEL, + PTYPE_QVIEWPANEL, + PTYPE_INFOPANEL +}; + +struct PanelInfo +{ + int PanelType; + int Plugin; + RECT PanelRect; + int ItemsNumber; + int SelectedItemsNumber; + int CurrentItem; + int TopPanelItem; + int Visible; + int Focus; + int ViewMode; + int ShortNames; + int SortMode; + DWORD Flags; + DWORD Reserved; +}; + + +struct PanelRedrawInfo +{ + int CurrentItem; + int TopPanelItem; +}; + +struct CmdLineSelect +{ + int SelStart; + int SelEnd; +}; + +#define PANEL_NONE ((HANDLE)(-1)) +#define PANEL_ACTIVE ((HANDLE)(-1)) +#define PANEL_PASSIVE ((HANDLE)(-2)) + +enum FILE_CONTROL_COMMANDS +{ + FCTL_CLOSEPLUGIN, + FCTL_GETPANELINFO, + FCTL_UPDATEPANEL, + FCTL_REDRAWPANEL, + FCTL_GETCMDLINE, + FCTL_SETCMDLINE, + FCTL_SETSELECTION, + FCTL_SETVIEWMODE, + FCTL_INSERTCMDLINE, + FCTL_SETUSERSCREEN, + FCTL_SETPANELDIR, + FCTL_SETCMDLINEPOS, + FCTL_GETCMDLINEPOS, + FCTL_SETSORTMODE, + FCTL_SETSORTORDER, + FCTL_GETCMDLINESELECTEDTEXT, + FCTL_SETCMDLINESELECTION, + FCTL_GETCMDLINESELECTION, + FCTL_CHECKPANELSEXIST, + FCTL_SETNUMERICSORT, + FCTL_GETUSERSCREEN, + FCTL_ISACTIVEPANEL, + FCTL_GETPANELITEM, + FCTL_GETSELECTEDPANELITEM, + FCTL_GETCURRENTPANELITEM, + FCTL_GETPANELDIR, + FCTL_GETCOLUMNTYPES, + FCTL_GETCOLUMNWIDTHS, + FCTL_BEGINSELECTION, + FCTL_ENDSELECTION, + FCTL_CLEARSELECTION, + FCTL_SETDIRECTORIESFIRST, + FCTL_GETPANELFORMAT, + FCTL_GETPANELHOSTFILE, + FCTL_SETCASESENSITIVESORT, +}; + +typedef int (WINAPI *FARAPICONTROL)( + HANDLE hPlugin, + int Command, + int Param1, + LONG_PTR Param2 +); + +typedef void (WINAPI *FARAPITEXT)( + int X, + int Y, + int Color, + const wchar_t *Str +); + +typedef HANDLE(WINAPI *FARAPISAVESCREEN)(int X1, int Y1, int X2, int Y2); + +typedef void (WINAPI *FARAPIRESTORESCREEN)(HANDLE hScreen); + + +typedef int (WINAPI *FARAPIGETDIRLIST)( + const wchar_t *Dir, + struct FAR_FIND_DATA **pPanelItem, + int *pItemsNumber +); + +typedef int (WINAPI *FARAPIGETPLUGINDIRLIST)( + INT_PTR PluginNumber, + HANDLE hPlugin, + const wchar_t *Dir, + struct PluginPanelItem **pPanelItem, + int *pItemsNumber +); + +typedef void (WINAPI *FARAPIFREEDIRLIST)(struct FAR_FIND_DATA *PanelItem, int nItemsNumber); +typedef void (WINAPI *FARAPIFREEPLUGINDIRLIST)(struct PluginPanelItem *PanelItem, int nItemsNumber); + +enum VIEWER_FLAGS +{ + VF_NONMODAL = 0x00000001, + VF_DELETEONCLOSE = 0x00000002, + VF_ENABLE_F6 = 0x00000004, + VF_DISABLEHISTORY = 0x00000008, + VF_IMMEDIATERETURN = 0x00000100, + VF_DELETEONLYFILEONCLOSE = 0x00000200, +}; + +typedef int (WINAPI *FARAPIVIEWER)( + const wchar_t *FileName, + const wchar_t *Title, + int X1, + int Y1, + int X2, + int Y2, + DWORD Flags, + UINT CodePage +); + +enum EDITOR_FLAGS +{ + EF_NONMODAL = 0x00000001, + EF_CREATENEW = 0x00000002, + EF_ENABLE_F6 = 0x00000004, + EF_DISABLEHISTORY = 0x00000008, + EF_DELETEONCLOSE = 0x00000010, + EF_IMMEDIATERETURN = 0x00000100, + EF_DELETEONLYFILEONCLOSE = 0x00000200, +}; + +enum EDITOR_EXITCODE +{ + EEC_OPEN_ERROR = 0, + EEC_MODIFIED = 1, + EEC_NOT_MODIFIED = 2, + EEC_LOADING_INTERRUPTED = 3, +}; + +typedef int (WINAPI *FARAPIEDITOR)( + const wchar_t *FileName, + const wchar_t *Title, + int X1, + int Y1, + int X2, + int Y2, + DWORD Flags, + int StartLine, + int StartChar, + UINT CodePage +); + +typedef int (WINAPI *FARAPICMPNAME)( + const wchar_t *Pattern, + const wchar_t *String, + int SkipPath +); + + +typedef const wchar_t*(WINAPI *FARAPIGETMSG)( + INT_PTR PluginNumber, + int MsgId +); + + +enum FarHelpFlags +{ + FHELP_NOSHOWERROR = 0x80000000, + FHELP_SELFHELP = 0x00000000, + FHELP_FARHELP = 0x00000001, + FHELP_CUSTOMFILE = 0x00000002, + FHELP_CUSTOMPATH = 0x00000004, + FHELP_USECONTENTS = 0x40000000, +}; + +typedef BOOL (WINAPI *FARAPISHOWHELP)( + const wchar_t *ModuleName, + const wchar_t *Topic, + DWORD Flags +); + +enum ADVANCED_CONTROL_COMMANDS +{ + ACTL_GETFARVERSION = 0, + ACTL_GETSYSWORDDIV = 2, + ACTL_WAITKEY = 3, + ACTL_GETCOLOR = 4, + ACTL_GETARRAYCOLOR = 5, + ACTL_EJECTMEDIA = 6, + ACTL_KEYMACRO = 7, + ACTL_POSTKEYSEQUENCE = 8, + ACTL_GETWINDOWINFO = 9, + ACTL_GETWINDOWCOUNT = 10, + ACTL_SETCURRENTWINDOW = 11, + ACTL_COMMIT = 12, + ACTL_GETFARHWND = 13, + ACTL_GETSYSTEMSETTINGS = 14, + ACTL_GETPANELSETTINGS = 15, + ACTL_GETINTERFACESETTINGS = 16, + ACTL_GETCONFIRMATIONS = 17, + ACTL_GETDESCSETTINGS = 18, + ACTL_SETARRAYCOLOR = 19, + ACTL_GETPLUGINMAXREADDATA = 21, + ACTL_GETDIALOGSETTINGS = 22, + ACTL_GETSHORTWINDOWINFO = 23, + ACTL_REDRAWALL = 27, + ACTL_SYNCHRO = 28, + ACTL_SETPROGRESSSTATE = 29, + ACTL_SETPROGRESSVALUE = 30, + ACTL_QUIT = 31, + ACTL_GETFARRECT = 32, + ACTL_GETCURSORPOS = 33, + ACTL_SETCURSORPOS = 34, + ACTL_PROGRESSNOTIFY = 35, +}; + + +enum FarSystemSettings +{ + FSS_CLEARROATTRIBUTE = 0x00000001, + FSS_DELETETORECYCLEBIN = 0x00000002, + FSS_USESYSTEMCOPYROUTINE = 0x00000004, + FSS_COPYFILESOPENEDFORWRITING = 0x00000008, + FSS_CREATEFOLDERSINUPPERCASE = 0x00000010, + FSS_SAVECOMMANDSHISTORY = 0x00000020, + FSS_SAVEFOLDERSHISTORY = 0x00000040, + FSS_SAVEVIEWANDEDITHISTORY = 0x00000080, + FSS_USEWINDOWSREGISTEREDTYPES = 0x00000100, + FSS_AUTOSAVESETUP = 0x00000200, + FSS_SCANSYMLINK = 0x00000400, +}; + +enum FarPanelSettings +{ + FPS_SHOWHIDDENANDSYSTEMFILES = 0x00000001, + FPS_HIGHLIGHTFILES = 0x00000002, + FPS_AUTOCHANGEFOLDER = 0x00000004, + FPS_SELECTFOLDERS = 0x00000008, + FPS_ALLOWREVERSESORTMODES = 0x00000010, + FPS_SHOWCOLUMNTITLES = 0x00000020, + FPS_SHOWSTATUSLINE = 0x00000040, + FPS_SHOWFILESTOTALINFORMATION = 0x00000080, + FPS_SHOWFREESIZE = 0x00000100, + FPS_SHOWSCROLLBAR = 0x00000200, + FPS_SHOWBACKGROUNDSCREENSNUMBER = 0x00000400, + FPS_SHOWSORTMODELETTER = 0x00000800, +}; + +enum FarDialogSettings +{ + FDIS_HISTORYINDIALOGEDITCONTROLS = 0x00000001, + FDIS_PERSISTENTBLOCKSINEDITCONTROLS = 0x00000002, + FDIS_AUTOCOMPLETEININPUTLINES = 0x00000004, + FDIS_BSDELETEUNCHANGEDTEXT = 0x00000008, + FDIS_DELREMOVESBLOCKS = 0x00000010, + FDIS_MOUSECLICKOUTSIDECLOSESDIALOG = 0x00000020, +}; + +enum FarInterfaceSettings +{ + FIS_CLOCKINPANELS = 0x00000001, + FIS_CLOCKINVIEWERANDEDITOR = 0x00000002, + FIS_MOUSE = 0x00000004, + FIS_SHOWKEYBAR = 0x00000008, + FIS_ALWAYSSHOWMENUBAR = 0x00000010, + FIS_SHOWTOTALCOPYPROGRESSINDICATOR = 0x00000100, + FIS_SHOWCOPYINGTIMEINFO = 0x00000200, + FIS_USECTRLPGUPTOCHANGEDRIVE = 0x00000800, + FIS_SHOWTOTALDELPROGRESSINDICATOR = 0x00001000, +}; + +enum FarConfirmationsSettings +{ + FCS_COPYOVERWRITE = 0x00000001, + FCS_MOVEOVERWRITE = 0x00000002, + FCS_DRAGANDDROP = 0x00000004, + FCS_DELETE = 0x00000008, + FCS_DELETENONEMPTYFOLDERS = 0x00000010, + FCS_INTERRUPTOPERATION = 0x00000020, + FCS_DISCONNECTNETWORKDRIVE = 0x00000040, + FCS_RELOADEDITEDFILE = 0x00000080, + FCS_CLEARHISTORYLIST = 0x00000100, + FCS_EXIT = 0x00000200, + FCS_OVERWRITEDELETEROFILES = 0x00000400, +}; + +enum FarDescriptionSettings +{ + FDS_UPDATEALWAYS = 0x00000001, + FDS_UPDATEIFDISPLAYED = 0x00000002, + FDS_SETHIDDEN = 0x00000004, + FDS_UPDATEREADONLY = 0x00000008, +}; + +enum FAREJECTMEDIAFLAGS +{ + EJECT_NO_MESSAGE = 0x00000001, + EJECT_LOAD_MEDIA = 0x00000002, +}; + +struct ActlEjectMedia +{ + DWORD Letter; + DWORD Flags; +}; + + +enum FARKEYSEQUENCEFLAGS +{ + KSFLAGS_DISABLEOUTPUT = 0x00000001, + KSFLAGS_NOSENDKEYSTOPLUGINS = 0x00000002, + KSFLAGS_REG_MULTI_SZ = 0x00100000, + KSFLAGS_SILENTCHECK = 0x00000001, +}; + +struct KeySequence +{ + DWORD Flags; + int Count; + const DWORD *Sequence; +}; + +enum FARMACROCOMMAND +{ + MCMD_LOADALL = 0, + MCMD_SAVEALL = 1, + MCMD_POSTMACROSTRING = 2, + MCMD_CHECKMACRO = 4, + MCMD_GETSTATE = 5, + MCMD_GETAREA = 6, +}; + +enum FARMACROAREA +{ + MACROAREA_OTHER = 0, + MACROAREA_SHELL = 1, + MACROAREA_VIEWER = 2, + MACROAREA_EDITOR = 3, + MACROAREA_DIALOG = 4, + MACROAREA_SEARCH = 5, + MACROAREA_DISKS = 6, + MACROAREA_MAINMENU = 7, + MACROAREA_MENU = 8, + MACROAREA_HELP = 9, + MACROAREA_INFOPANEL =10, + MACROAREA_QVIEWPANEL =11, + MACROAREA_TREEPANEL =12, + MACROAREA_FINDFOLDER =13, + MACROAREA_USERMENU =14, + MACROAREA_AUTOCOMPLETION =15, +}; + +enum FARMACROSTATE +{ + MACROSTATE_NOMACRO = 0, + MACROSTATE_EXECUTING = 1, + MACROSTATE_EXECUTING_COMMON = 2, + MACROSTATE_RECORDING = 3, + MACROSTATE_RECORDING_COMMON = 4, +}; + +enum FARMACROPARSEERRORCODE +{ + MPEC_SUCCESS = 0, + MPEC_UNRECOGNIZED_KEYWORD = 1, + MPEC_UNRECOGNIZED_FUNCTION = 2, + MPEC_FUNC_PARAM = 3, + MPEC_NOT_EXPECTED_ELSE = 4, + MPEC_NOT_EXPECTED_END = 5, + MPEC_UNEXPECTED_EOS = 6, + MPEC_EXPECTED_TOKEN = 7, + MPEC_BAD_HEX_CONTROL_CHAR = 8, + MPEC_BAD_CONTROL_CHAR = 9, + MPEC_VAR_EXPECTED =10, + MPEC_EXPR_EXPECTED =11, + MPEC_ZEROLENGTHMACRO =12, + MPEC_INTPARSERERROR =13, + MPEC_CONTINUE_OTL =14, +}; + +struct MacroParseResult +{ + DWORD ErrCode; + COORD ErrPos; + const wchar_t *ErrSrc; +}; + +struct ActlKeyMacro +{ + int Command; + union + { + struct + { + const wchar_t *SequenceText; + DWORD Flags; + DWORD AKey; + } PlainText; + struct MacroParseResult MacroResult; + DWORD_PTR Reserved[3]; + } Param; +}; + + +enum FARCOLORFLAGS +{ + FCLR_REDRAW = 0x00000001, +}; + +struct FarSetColors +{ + DWORD Flags; + int StartIndex; + int ColorCount; + LPBYTE Colors; +}; + +enum WINDOWINFO_TYPE +{ + WTYPE_PANELS=1, + WTYPE_VIEWER, + WTYPE_EDITOR, + WTYPE_DIALOG, + WTYPE_VMENU, + WTYPE_HELP, +}; + +struct WindowInfo +{ + int Pos; + int Type; + int Modified; + int Current; + wchar_t *TypeName; + int TypeNameSize; + wchar_t *Name; + int NameSize; +}; + +enum PROGRESSTATE +{ + PS_NOPROGRESS =0x0, + PS_INDETERMINATE=0x1, + PS_NORMAL =0x2, + PS_ERROR =0x4, +#if !defined(__MINGW32__) + PS_PAUSED =0x8, +#endif +}; + +struct PROGRESSVALUE +{ + uint64_t Completed; + uint64_t Total; +}; + +typedef INT_PTR(WINAPI *FARAPIADVCONTROL)( + INT_PTR ModuleNumber, + int Command, + void *Param +); + + +enum VIEWER_CONTROL_COMMANDS +{ + VCTL_GETINFO, + VCTL_QUIT, + VCTL_REDRAW, + VCTL_SETKEYBAR, + VCTL_SETPOSITION, + VCTL_SELECT, + VCTL_SETMODE, +}; + +enum VIEWER_OPTIONS +{ + VOPT_SAVEFILEPOSITION=1, + VOPT_AUTODETECTCODEPAGE=2, +}; + +enum VIEWER_SETMODE_TYPES +{ + VSMT_HEX, + VSMT_WRAP, + VSMT_WORDWRAP, +}; + +enum VIEWER_SETMODEFLAGS_TYPES +{ + VSMFL_REDRAW = 0x00000001, +}; + +struct ViewerSetMode +{ + int Type; + union + { + int iParam; + wchar_t *wszParam; + } Param; + DWORD Flags; + DWORD Reserved; +}; + +struct ViewerSelect +{ + __int64 BlockStartPos; + int BlockLen; +}; + +enum VIEWER_SETPOS_FLAGS +{ + VSP_NOREDRAW = 0x0001, + VSP_PERCENT = 0x0002, + VSP_RELATIVE = 0x0004, + VSP_NORETNEWPOS = 0x0008, +}; + +struct ViewerSetPosition +{ + DWORD Flags; + __int64 StartPos; + __int64 LeftPos; +}; + +struct ViewerMode +{ + UINT CodePage; + int Wrap; + int WordWrap; + int Hex; + DWORD Reserved[4]; +}; + +struct ViewerInfo +{ + int StructSize; + int ViewerID; + const wchar_t *FileName; + __int64 FileSize; + __int64 FilePos; + int WindowSizeX; + int WindowSizeY; + DWORD Options; + int TabSize; + struct ViewerMode CurMode; + __int64 LeftPos; +}; + +typedef int (WINAPI *FARAPIVIEWERCONTROL)( + int Command, + void *Param +); + +enum VIEWER_EVENTS +{ + VE_READ =0, + VE_CLOSE =1, + + VE_GOTFOCUS =6, + VE_KILLFOCUS =7, +}; + + +enum EDITOR_EVENTS +{ + EE_READ =0, + EE_SAVE =1, + EE_REDRAW =2, + EE_CLOSE =3, + + EE_GOTFOCUS =6, + EE_KILLFOCUS =7, +}; + +enum DIALOG_EVENTS +{ + DE_DLGPROCINIT =0, + DE_DEFDLGPROCINIT =1, + DE_DLGPROCEND =2, +}; + +enum SYNCHRO_EVENTS +{ + SE_COMMONSYNCHRO =0, +}; + +#define EEREDRAW_ALL (void*)0 +#define EEREDRAW_CHANGE (void*)1 +#define EEREDRAW_LINE (void*)2 + +enum EDITOR_CONTROL_COMMANDS +{ + ECTL_GETSTRING, + ECTL_SETSTRING, + ECTL_INSERTSTRING, + ECTL_DELETESTRING, + ECTL_DELETECHAR, + ECTL_INSERTTEXT, + ECTL_GETINFO, + ECTL_SETPOSITION, + ECTL_SELECT, + ECTL_REDRAW, + ECTL_TABTOREAL, + ECTL_REALTOTAB, + ECTL_EXPANDTABS, + ECTL_SETTITLE, + ECTL_READINPUT, + ECTL_PROCESSINPUT, + ECTL_ADDCOLOR, + ECTL_GETCOLOR, + ECTL_SAVEFILE, + ECTL_QUIT, + ECTL_SETKEYBAR, + ECTL_PROCESSKEY, + ECTL_SETPARAM, + ECTL_GETBOOKMARKS, + ECTL_TURNOFFMARKINGBLOCK, + ECTL_DELETEBLOCK, + ECTL_ADDSTACKBOOKMARK, + ECTL_PREVSTACKBOOKMARK, + ECTL_NEXTSTACKBOOKMARK, + ECTL_CLEARSTACKBOOKMARKS, + ECTL_DELETESTACKBOOKMARK, + ECTL_GETSTACKBOOKMARKS, + ECTL_UNDOREDO, + ECTL_GETFILENAME, +}; + +enum EDITOR_SETPARAMETER_TYPES +{ + ESPT_TABSIZE, + ESPT_EXPANDTABS, + ESPT_AUTOINDENT, + ESPT_CURSORBEYONDEOL, + ESPT_CHARCODEBASE, + ESPT_CODEPAGE, + ESPT_SAVEFILEPOSITION, + ESPT_LOCKMODE, + ESPT_SETWORDDIV, + ESPT_GETWORDDIV, + ESPT_SHOWWHITESPACE, + ESPT_SETBOM, +}; + + + +struct EditorSetParameter +{ + int Type; + union + { + int iParam; + wchar_t *wszParam; + DWORD Reserved1; + } Param; + DWORD Flags; + DWORD Size; +}; + + +enum EDITOR_UNDOREDO_COMMANDS +{ + EUR_BEGIN, + EUR_END, + EUR_UNDO, + EUR_REDO +}; + + +struct EditorUndoRedo +{ + int Command; + DWORD_PTR Reserved[3]; +}; + +struct EditorGetString +{ + int StringNumber; + const wchar_t *StringText; + const wchar_t *StringEOL; + int StringLength; + int SelStart; + int SelEnd; +}; + + +struct EditorSetString +{ + int StringNumber; + const wchar_t *StringText; + const wchar_t *StringEOL; + int StringLength; +}; + +enum EXPAND_TABS +{ + EXPAND_NOTABS, + EXPAND_ALLTABS, + EXPAND_NEWTABS +}; + + +enum EDITOR_OPTIONS +{ + EOPT_EXPANDALLTABS = 0x00000001, + EOPT_PERSISTENTBLOCKS = 0x00000002, + EOPT_DELREMOVESBLOCKS = 0x00000004, + EOPT_AUTOINDENT = 0x00000008, + EOPT_SAVEFILEPOSITION = 0x00000010, + EOPT_AUTODETECTCODEPAGE= 0x00000020, + EOPT_CURSORBEYONDEOL = 0x00000040, + EOPT_EXPANDONLYNEWTABS = 0x00000080, + EOPT_SHOWWHITESPACE = 0x00000100, + EOPT_BOM = 0x00000200, +}; + + +enum EDITOR_BLOCK_TYPES +{ + BTYPE_NONE, + BTYPE_STREAM, + BTYPE_COLUMN +}; + +enum EDITOR_CURRENTSTATE +{ + ECSTATE_MODIFIED = 0x00000001, + ECSTATE_SAVED = 0x00000002, + ECSTATE_LOCKED = 0x00000004, +}; + + +struct EditorInfo +{ + int EditorID; + int WindowSizeX; + int WindowSizeY; + int TotalLines; + int CurLine; + int CurPos; + int CurTabPos; + int TopScreenLine; + int LeftPos; + int Overtype; + int BlockType; + int BlockStartLine; + DWORD Options; + int TabSize; + int BookMarkCount; + DWORD CurState; + UINT CodePage; + DWORD Reserved[5]; +}; + +struct EditorBookMarks +{ + long *Line; + long *Cursor; + long *ScreenLine; + long *LeftPos; + DWORD Reserved[4]; +}; + +struct EditorSetPosition +{ + int CurLine; + int CurPos; + int CurTabPos; + int TopScreenLine; + int LeftPos; + int Overtype; +}; + + +struct EditorSelect +{ + int BlockType; + int BlockStartLine; + int BlockStartPos; + int BlockWidth; + int BlockHeight; +}; + + +struct EditorConvertPos +{ + int StringNumber; + int SrcPos; + int DestPos; +}; + + +enum EDITORCOLORFLAGS +{ + ECF_TAB1 = 0x10000, +}; + +struct EditorColor +{ + int StringNumber; + int ColorItem; + int StartPos; + int EndPos; + int Color; +}; + +struct EditorSaveFile +{ + const wchar_t *FileName; + const wchar_t *FileEOL; + UINT CodePage; +}; + +typedef int (WINAPI *FARAPIEDITORCONTROL)( + int Command, + void *Param +); + +enum INPUTBOXFLAGS +{ + FIB_ENABLEEMPTY = 0x00000001, + FIB_PASSWORD = 0x00000002, + FIB_EXPANDENV = 0x00000004, + FIB_NOUSELASTHISTORY = 0x00000008, + FIB_BUTTONS = 0x00000010, + FIB_NOAMPERSAND = 0x00000020, + FIB_EDITPATH = 0x01000000, +}; + +typedef int (WINAPI *FARAPIINPUTBOX)( + const wchar_t *Title, + const wchar_t *SubTitle, + const wchar_t *HistoryName, + const wchar_t *SrcText, + wchar_t *DestText, + int DestLength, + const wchar_t *HelpTopic, + DWORD Flags +); + +typedef int (WINAPI *FARAPIPLUGINSCONTROL)( + HANDLE hHandle, + int Command, + int Param1, + LONG_PTR Param2 +); + +typedef int (WINAPI *FARAPIFILEFILTERCONTROL)( + HANDLE hHandle, + int Command, + int Param1, + LONG_PTR Param2 +); + +typedef int (WINAPI *FARAPIREGEXPCONTROL)( + HANDLE hHandle, + int Command, + LONG_PTR Param +); + +// +typedef int (WINAPIV *FARSTDSPRINTF)(wchar_t *Buffer,const wchar_t *Format,...); +typedef int (WINAPIV *FARSTDSNPRINTF)(wchar_t *Buffer,size_t Sizebuf,const wchar_t *Format,...); +typedef int (WINAPIV *FARSTDSSCANF)(const wchar_t *Buffer, const wchar_t *Format,...); +// +typedef void (WINAPI *FARSTDQSORT)(void *base, size_t nelem, size_t width, int (__cdecl *fcmp)(const void *, const void *)); +typedef void (WINAPI *FARSTDQSORTEX)(void *base, size_t nelem, size_t width, int (__cdecl *fcmp)(const void *, const void *,void *userparam),void *userparam); +typedef void *(WINAPI *FARSTDBSEARCH)(const void *key, const void *base, size_t nelem, size_t width, int (__cdecl *fcmp)(const void *, const void *)); +typedef int (WINAPI *FARSTDGETFILEOWNER)(const wchar_t *Computer,const wchar_t *Name,wchar_t *Owner,int Size); +typedef int (WINAPI *FARSTDGETNUMBEROFLINKS)(const wchar_t *Name); +typedef int (WINAPI *FARSTDATOI)(const wchar_t *s); +typedef __int64(WINAPI *FARSTDATOI64)(const wchar_t *s); +typedef wchar_t *(WINAPI *FARSTDITOA64)(__int64 value, wchar_t *string, int radix); +typedef wchar_t *(WINAPI *FARSTDITOA)(int value, wchar_t *string, int radix); +typedef wchar_t *(WINAPI *FARSTDLTRIM)(wchar_t *Str); +typedef wchar_t *(WINAPI *FARSTDRTRIM)(wchar_t *Str); +typedef wchar_t *(WINAPI *FARSTDTRIM)(wchar_t *Str); +typedef wchar_t *(WINAPI *FARSTDTRUNCSTR)(wchar_t *Str,int MaxLength); +typedef wchar_t *(WINAPI *FARSTDTRUNCPATHSTR)(wchar_t *Str,int MaxLength); +typedef wchar_t *(WINAPI *FARSTDQUOTESPACEONLY)(wchar_t *Str); +typedef const wchar_t*(WINAPI *FARSTDPOINTTONAME)(const wchar_t *Path); +typedef int (WINAPI *FARSTDGETPATHROOT)(const wchar_t *Path,wchar_t *Root, int DestSize); +typedef BOOL (WINAPI *FARSTDADDENDSLASH)(wchar_t *Path); +typedef int (WINAPI *FARSTDCOPYTOCLIPBOARD)(const wchar_t *Data); +typedef wchar_t *(WINAPI *FARSTDPASTEFROMCLIPBOARD)(void); +typedef int (WINAPI *FARSTDINPUTRECORDTOKEY)(const INPUT_RECORD *r); +typedef int (WINAPI *FARSTDLOCALISLOWER)(wchar_t Ch); +typedef int (WINAPI *FARSTDLOCALISUPPER)(wchar_t Ch); +typedef int (WINAPI *FARSTDLOCALISALPHA)(wchar_t Ch); +typedef int (WINAPI *FARSTDLOCALISALPHANUM)(wchar_t Ch); +typedef wchar_t (WINAPI *FARSTDLOCALUPPER)(wchar_t LowerChar); +typedef wchar_t (WINAPI *FARSTDLOCALLOWER)(wchar_t UpperChar); +typedef void (WINAPI *FARSTDLOCALUPPERBUF)(wchar_t *Buf,int Length); +typedef void (WINAPI *FARSTDLOCALLOWERBUF)(wchar_t *Buf,int Length); +typedef void (WINAPI *FARSTDLOCALSTRUPR)(wchar_t *s1); +typedef void (WINAPI *FARSTDLOCALSTRLWR)(wchar_t *s1); +typedef int (WINAPI *FARSTDLOCALSTRICMP)(const wchar_t *s1,const wchar_t *s2); +typedef int (WINAPI *FARSTDLOCALSTRNICMP)(const wchar_t *s1,const wchar_t *s2,int n); + +enum PROCESSNAME_FLAGS +{ + PN_CMPNAME = 0x00000000UL, + PN_CMPNAMELIST = 0x00010000UL, + PN_GENERATENAME = 0x00020000UL, + PN_SKIPPATH = 0x01000000UL, +}; + +typedef int (WINAPI *FARSTDPROCESSNAME)(const wchar_t *param1, wchar_t *param2, DWORD size, DWORD flags); + +typedef void (WINAPI *FARSTDUNQUOTE)(wchar_t *Str); + +enum XLATMODE +{ + XLAT_SWITCHKEYBLAYOUT = 0x00000001UL, + XLAT_SWITCHKEYBBEEP = 0x00000002UL, + XLAT_USEKEYBLAYOUTNAME = 0x00000004UL, + XLAT_CONVERTALLCMDLINE = 0x00010000UL, +}; + +typedef size_t (WINAPI *FARSTDKEYTOKEYNAME)(int Key,wchar_t *KeyText,size_t Size); + +typedef wchar_t*(WINAPI *FARSTDXLAT)(wchar_t *Line,int StartPos,int EndPos,DWORD Flags); + +typedef int (WINAPI *FARSTDKEYNAMETOKEY)(const wchar_t *Name); + +typedef int (WINAPI *FRSUSERFUNC)( + const struct FAR_FIND_DATA *FData, + const wchar_t *FullName, + void *Param +); + +enum FRSMODE +{ + FRS_RETUPDIR = 0x01, + FRS_RECUR = 0x02, + FRS_SCANSYMLINK = 0x04, +}; + +typedef void (WINAPI *FARSTDRECURSIVESEARCH)(const wchar_t *InitDir,const wchar_t *Mask,FRSUSERFUNC Func,DWORD Flags,void *Param); +typedef int (WINAPI *FARSTDMKTEMP)(wchar_t *Dest, DWORD size, const wchar_t *Prefix); +typedef void (WINAPI *FARSTDDELETEBUFFER)(void *Buffer); + +enum MKLINKOP +{ + FLINK_HARDLINK = 1, + FLINK_JUNCTION = 2, + FLINK_VOLMOUNT = 3, + FLINK_SYMLINKFILE = 4, + FLINK_SYMLINKDIR = 5, + FLINK_SYMLINK = 6, + + FLINK_SHOWERRMSG = 0x10000, + FLINK_DONOTUPDATEPANEL = 0x20000, +}; +typedef int (WINAPI *FARSTDMKLINK)(const wchar_t *Src,const wchar_t *Dest,DWORD Flags); +typedef int (WINAPI *FARGETREPARSEPOINTINFO)(const wchar_t *Src, wchar_t *Dest,int DestSize); + +enum CONVERTPATHMODES +{ + CPM_FULL, + CPM_REAL, + CPM_NATIVE, +}; + +typedef int (WINAPI *FARCONVERTPATH)(enum CONVERTPATHMODES Mode, const wchar_t *Src, wchar_t *Dest, int DestSize); + +typedef DWORD (WINAPI *FARGETCURRENTDIRECTORY)(DWORD Size,wchar_t* Buffer); + +typedef struct FarStandardFunctions +{ + int StructSize; + + FARSTDATOI atoi; + FARSTDATOI64 atoi64; + FARSTDITOA itoa; + FARSTDITOA64 itoa64; + // + FARSTDSPRINTF sprintf; + FARSTDSSCANF sscanf; + // + FARSTDQSORT qsort; + FARSTDBSEARCH bsearch; + FARSTDQSORTEX qsortex; + // + FARSTDSNPRINTF snprintf; + // + + DWORD_PTR Reserved[8]; + + FARSTDLOCALISLOWER LIsLower; + FARSTDLOCALISUPPER LIsUpper; + FARSTDLOCALISALPHA LIsAlpha; + FARSTDLOCALISALPHANUM LIsAlphanum; + FARSTDLOCALUPPER LUpper; + FARSTDLOCALLOWER LLower; + FARSTDLOCALUPPERBUF LUpperBuf; + FARSTDLOCALLOWERBUF LLowerBuf; + FARSTDLOCALSTRUPR LStrupr; + FARSTDLOCALSTRLWR LStrlwr; + FARSTDLOCALSTRICMP LStricmp; + FARSTDLOCALSTRNICMP LStrnicmp; + + FARSTDUNQUOTE Unquote; + FARSTDLTRIM LTrim; + FARSTDRTRIM RTrim; + FARSTDTRIM Trim; + FARSTDTRUNCSTR TruncStr; + FARSTDTRUNCPATHSTR TruncPathStr; + FARSTDQUOTESPACEONLY QuoteSpaceOnly; + FARSTDPOINTTONAME PointToName; + FARSTDGETPATHROOT GetPathRoot; + FARSTDADDENDSLASH AddEndSlash; + FARSTDCOPYTOCLIPBOARD CopyToClipboard; + FARSTDPASTEFROMCLIPBOARD PasteFromClipboard; + FARSTDKEYTOKEYNAME FarKeyToName; + FARSTDKEYNAMETOKEY FarNameToKey; + FARSTDINPUTRECORDTOKEY FarInputRecordToKey; + FARSTDXLAT XLat; + FARSTDGETFILEOWNER GetFileOwner; + FARSTDGETNUMBEROFLINKS GetNumberOfLinks; + FARSTDRECURSIVESEARCH FarRecursiveSearch; + FARSTDMKTEMP MkTemp; + FARSTDDELETEBUFFER DeleteBuffer; + FARSTDPROCESSNAME ProcessName; + FARSTDMKLINK MkLink; + FARCONVERTPATH ConvertPath; + FARGETREPARSEPOINTINFO GetReparsePointInfo; + FARGETCURRENTDIRECTORY GetCurrentDirectory; +} FARSTANDARDFUNCTIONS; + +struct PluginStartupInfo +{ + int StructSize; + const wchar_t *ModuleName; + INT_PTR ModuleNumber; + const wchar_t *RootKey; + FARAPIMENU Menu; + FARAPIMESSAGE Message; + FARAPIGETMSG GetMsg; + FARAPICONTROL Control; + FARAPISAVESCREEN SaveScreen; + FARAPIRESTORESCREEN RestoreScreen; + FARAPIGETDIRLIST GetDirList; + FARAPIGETPLUGINDIRLIST GetPluginDirList; + FARAPIFREEDIRLIST FreeDirList; + FARAPIFREEPLUGINDIRLIST FreePluginDirList; + FARAPIVIEWER Viewer; + FARAPIEDITOR Editor; + FARAPICMPNAME CmpName; + FARAPITEXT Text; + FARAPIEDITORCONTROL EditorControl; + + FARSTANDARDFUNCTIONS *FSF; + + FARAPISHOWHELP ShowHelp; + FARAPIADVCONTROL AdvControl; + FARAPIINPUTBOX InputBox; + FARAPIDIALOGINIT DialogInit; + FARAPIDIALOGRUN DialogRun; + FARAPIDIALOGFREE DialogFree; + + FARAPISENDDLGMESSAGE SendDlgMessage; + FARAPIDEFDLGPROC DefDlgProc; + DWORD_PTR Reserved; + FARAPIVIEWERCONTROL ViewerControl; + FARAPIPLUGINSCONTROL PluginsControl; + FARAPIFILEFILTERCONTROL FileFilterControl; + FARAPIREGEXPCONTROL RegExpControl; +}; + + +enum PLUGIN_FLAGS +{ + PF_PRELOAD = 0x0001, + PF_DISABLEPANELS = 0x0002, + PF_EDITOR = 0x0004, + PF_VIEWER = 0x0008, + PF_FULLCMDLINE = 0x0010, + PF_DIALOG = 0x0020, +}; + +struct PluginInfo +{ + int StructSize; + DWORD Flags; + const wchar_t * const *DiskMenuStrings; + int *Reserved0; + int DiskMenuStringsNumber; + const wchar_t * const *PluginMenuStrings; + int PluginMenuStringsNumber; + const wchar_t * const *PluginConfigStrings; + int PluginConfigStringsNumber; + const wchar_t *CommandPrefix; + DWORD Reserved; +}; + + + +struct InfoPanelLine +{ + const wchar_t *Text; + const wchar_t *Data; + int Separator; +}; + +struct PanelMode +{ + const wchar_t *ColumnTypes; + const wchar_t *ColumnWidths; + const wchar_t * const *ColumnTitles; + int FullScreen; + int DetailedStatus; + int AlignExtensions; + int CaseConversion; + const wchar_t *StatusColumnTypes; + const wchar_t *StatusColumnWidths; + DWORD Reserved[2]; +}; + + +enum OPENPLUGININFO_FLAGS +{ + OPIF_USEFILTER = 0x00000001, + OPIF_USESORTGROUPS = 0x00000002, + OPIF_USEHIGHLIGHTING = 0x00000004, + OPIF_ADDDOTS = 0x00000008, + OPIF_RAWSELECTION = 0x00000010, + OPIF_REALNAMES = 0x00000020, + OPIF_SHOWNAMESONLY = 0x00000040, + OPIF_SHOWRIGHTALIGNNAMES = 0x00000080, + OPIF_SHOWPRESERVECASE = 0x00000100, + OPIF_COMPAREFATTIME = 0x00000400, + OPIF_EXTERNALGET = 0x00000800, + OPIF_EXTERNALPUT = 0x00001000, + OPIF_EXTERNALDELETE = 0x00002000, + OPIF_EXTERNALMKDIR = 0x00004000, + OPIF_USEATTRHIGHLIGHTING = 0x00008000, + OPIF_USECRC32 = 0x00010000, +}; + + +enum OPENPLUGININFO_SORTMODES +{ + SM_DEFAULT, + SM_UNSORTED, + SM_NAME, + SM_EXT, + SM_MTIME, + SM_CTIME, + SM_ATIME, + SM_SIZE, + SM_DESCR, + SM_OWNER, + SM_COMPRESSEDSIZE, + SM_NUMLINKS, + SM_NUMSTREAMS, + SM_STREAMSSIZE, + SM_FULLNAME, + SM_CHTIME, +}; + + +struct KeyBarTitles +{ + wchar_t *Titles[12]; + wchar_t *CtrlTitles[12]; + wchar_t *AltTitles[12]; + wchar_t *ShiftTitles[12]; + + wchar_t *CtrlShiftTitles[12]; + wchar_t *AltShiftTitles[12]; + wchar_t *CtrlAltTitles[12]; +}; + + +enum OPERATION_MODES +{ + OPM_SILENT =0x0001, + OPM_FIND =0x0002, + OPM_VIEW =0x0004, + OPM_EDIT =0x0008, + OPM_TOPLEVEL =0x0010, + OPM_DESCR =0x0020, + OPM_QUICKVIEW =0x0040, +}; + +struct OpenPluginInfo +{ + int StructSize; + DWORD Flags; + const wchar_t *HostFile; + const wchar_t *CurDir; + const wchar_t *Format; + const wchar_t *PanelTitle; + const struct InfoPanelLine *InfoLines; + int InfoLinesNumber; + const wchar_t * const *DescrFiles; + int DescrFilesNumber; + const struct PanelMode *PanelModesArray; + int PanelModesNumber; + int StartPanelMode; + int StartSortMode; + int StartSortOrder; + const struct KeyBarTitles *KeyBar; + const wchar_t *ShortcutData; + long Reserved; +}; + +enum OPENPLUGIN_OPENFROM +{ + OPEN_FROM_MASK = 0x000000FF, + + OPEN_DISKMENU = 0, + OPEN_PLUGINSMENU = 1, + OPEN_FINDLIST = 2, + OPEN_SHORTCUT = 3, + OPEN_COMMANDLINE = 4, + OPEN_EDITOR = 5, + OPEN_VIEWER = 6, + OPEN_FILEPANEL = 7, + OPEN_DIALOG = 8, + OPEN_ANALYSE = 9, + + OPEN_FROMMACRO_MASK = 0x000F0000, + + OPEN_FROMMACRO = 0x00010000, + OPEN_FROMMACROSTRING = 0x00020000, +}; + +enum FAR_PKF_FLAGS +{ + PKF_CONTROL = 0x00000001, + PKF_ALT = 0x00000002, + PKF_SHIFT = 0x00000004, + PKF_PREPROCESS = 0x00080000, // for "Key", function ProcessKey() +}; + +enum FAR_EVENTS +{ + FE_CHANGEVIEWMODE =0, + FE_REDRAW =1, + FE_IDLE =2, + FE_CLOSE =3, + FE_BREAK =4, + FE_COMMAND =5, + + FE_GOTFOCUS =6, + FE_KILLFOCUS =7, +}; + +enum FAR_PLUGINS_CONTROL_COMMANDS +{ + PCTL_LOADPLUGIN = 0, + PCTL_UNLOADPLUGIN = 1, + PCTL_FORCEDLOADPLUGIN = 2, +}; + +enum FAR_PLUGIN_LOAD_TYPE +{ + PLT_PATH = 0, +}; + +enum FAR_FILE_FILTER_CONTROL_COMMANDS +{ + FFCTL_CREATEFILEFILTER = 0, + FFCTL_FREEFILEFILTER, + FFCTL_OPENFILTERSMENU, + FFCTL_STARTINGTOFILTER, + FFCTL_ISFILEINFILTER, +}; + +enum FAR_FILE_FILTER_TYPE +{ + FFT_PANEL = 0, + FFT_FINDFILE, + FFT_COPY, + FFT_SELECT, + FFT_CUSTOM, +}; + +enum FAR_REGEXP_CONTROL_COMMANDS +{ + RECTL_CREATE=0, + RECTL_FREE, + RECTL_COMPILE, + RECTL_OPTIMIZE, + RECTL_MATCHEX, + RECTL_SEARCHEX, + RECTL_BRACKETSCOUNT +}; + +struct RegExpMatch +{ + int start,end; +}; + +struct RegExpSearch +{ + const wchar_t* Text; + int Position; + int Length; + struct RegExpMatch* Match; + int Count; + void* Reserved; +}; + + +#if defined(__BORLANDC__) || defined(_MSC_VER) || defined(__GNUC__) || defined(__WATCOMC__) +#ifdef __cplusplus +extern "C" +{ +#endif +// Exported Functions + + void WINAPI _export ClosePluginW(HANDLE hPlugin); + int WINAPI _export CompareW(HANDLE hPlugin,const struct PluginPanelItem *Item1,const struct PluginPanelItem *Item2,unsigned int Mode); + int WINAPI _export ConfigureW(int ItemNumber); + int WINAPI _export DeleteFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + void WINAPI _export ExitFARW(void); + void WINAPI _export FreeFindDataW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export FreeVirtualFindDataW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + int WINAPI _export GetFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,const wchar_t **DestPath,int OpMode); + int WINAPI _export GetFindDataW(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,int OpMode); + int WINAPI _export GetMinFarVersionW(void); + void WINAPI _export GetOpenPluginInfoW(HANDLE hPlugin,struct OpenPluginInfo *Info); + void WINAPI _export GetPluginInfoW(struct PluginInfo *Info); + int WINAPI _export GetVirtualFindDataW(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,const wchar_t *Path); + int WINAPI _export MakeDirectoryW(HANDLE hPlugin,const wchar_t **Name,int OpMode); + HANDLE WINAPI _export OpenFilePluginW(const wchar_t *Name,const unsigned char *Data,int DataSize,int OpMode); + HANDLE WINAPI _export OpenPluginW(int OpenFrom,INT_PTR Item); + int WINAPI _export ProcessDialogEventW(int Event,void *Param); + int WINAPI _export ProcessEditorEventW(int Event,void *Param); + int WINAPI _export ProcessEditorInputW(const INPUT_RECORD *Rec); + int WINAPI _export ProcessEventW(HANDLE hPlugin,int Event,void *Param); + int WINAPI _export ProcessHostFileW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + int WINAPI _export ProcessKeyW(HANDLE hPlugin,int Key,unsigned int ControlState); + int WINAPI _export ProcessSynchroEventW(int Event,void *Param); + int WINAPI _export ProcessViewerEventW(int Event,void *Param); + int WINAPI _export PutFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,const wchar_t *SrcPath,int OpMode); + int WINAPI _export SetDirectoryW(HANDLE hPlugin,const wchar_t *Dir,int OpMode); + int WINAPI _export SetFindListW(HANDLE hPlugin,const struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export SetStartupInfoW(const struct PluginStartupInfo *Info); + +#ifdef __cplusplus +}; +#endif +#endif + +#ifndef _WIN64 +#if defined(__BORLANDC__) +#pragma option -a. +#elif defined(__GNUC__) || (defined(__WATCOMC__) && (__WATCOMC__ < 1100)) || defined(__LCC__) +#pragma pack() +#else +#pragma pack(pop) +#endif +#endif + +#endif /* RC_INVOKED */ + +#endif /* __PLUGIN_HPP__ */ diff --git a/netbox/src/base/BaseDefs.hpp b/netbox/src/base/BaseDefs.hpp new file mode 100644 index 000000000..e4922dc75 --- /dev/null +++ b/netbox/src/base/BaseDefs.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#define DEFINE_CALLBACK_TYPE0(EVENT, R) \ + typedef fastdelegate::FastDelegate0 EVENT +#define DEFINE_CALLBACK_TYPE1(EVENT, R, T1) \ + typedef fastdelegate::FastDelegate1 EVENT +#define DEFINE_CALLBACK_TYPE2(EVENT, R, T1, T2) \ + typedef fastdelegate::FastDelegate2 EVENT +#define DEFINE_CALLBACK_TYPE3(EVENT, R, T1, T2, T3) \ + typedef fastdelegate::FastDelegate3 EVENT +#define DEFINE_CALLBACK_TYPE4(EVENT, R, T1, T2, T3, T4) \ + typedef fastdelegate::FastDelegate4 EVENT +#define DEFINE_CALLBACK_TYPE5(EVENT, R, T1, T2, T3, T4, T5) \ + typedef fastdelegate::FastDelegate5 EVENT +#define DEFINE_CALLBACK_TYPE6(EVENT, R, T1, T2, T3, T4, T5, T6) \ + typedef fastdelegate::FastDelegate6 EVENT +#define DEFINE_CALLBACK_TYPE7(EVENT, R, T1, T2, T3, T4, T5, T6, T7) \ + typedef fastdelegate::FastDelegate7 EVENT +#define DEFINE_CALLBACK_TYPE8(EVENT, R, T1, T2, T3, T4, T5, T6, T7, T8) \ + typedef fastdelegate::FastDelegate8 EVENT + +#define MAKE_CALLBACK(METHOD, OBJECT) \ + fastdelegate::bind(&METHOD, OBJECT) + +#define TShellExecuteInfoW _SHELLEXECUTEINFOW +#define TSHFileInfoW SHFILEINFOW +#ifndef __linux__ +#define TVSFixedFileInfo VS_FIXEDFILEINFO +#else +#define TVSFixedFileInfo char +#endif + diff --git a/netbox/src/base/Classes.cpp b/netbox/src/base/Classes.cpp new file mode 100644 index 000000000..440862270 --- /dev/null +++ b/netbox/src/base/Classes.cpp @@ -0,0 +1,2140 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef __linux__ +#include +#endif + +#if (_MSC_VER >= 1900) + +extern "C" { + FILE * __iob_func = nullptr; +} +#endif + +#ifdef __linux__ +#define REG_OPTION_NON_VOLATILE 0 +#endif + +void Abort() +{ + throw EAbort(L""); +} + +void Error(intptr_t Id, intptr_t ErrorId) +{ + UnicodeString Msg = FMTLOAD(Id, ErrorId); + throw ExtException(static_cast(nullptr), Msg); +} + +void ThrowNotImplemented(intptr_t ErrorId) +{ + Error(SNotImplemented, ErrorId); +} + +bool TObject::IsKindOf(TObjectClassId ClassId) const +{ + DebugAssert(this != nullptr); + + TClassInfo * thisInfo = this->GetClassInfo(); + DebugAssert(thisInfo != nullptr); + + const TClassInfo * classInfo = TClassInfo::FindClass(ClassId); + return thisInfo->IsKindOf(classInfo); +} + +TPersistent::TPersistent() +{} + +TPersistent::~TPersistent() +{} + +void TPersistent::Assign(const TPersistent * Source) +{ + if (Source != nullptr) + { + Source->AssignTo(this); + } + else + { + AssignError(nullptr); + } +} + +void TPersistent::AssignTo(TPersistent * Dest) const +{ + Dest->AssignError(this); +} + +TPersistent * TPersistent::GetOwner() +{ + return nullptr; +} + +void TPersistent::AssignError(const TPersistent * Source) +{ + (void)Source; + throw Exception(L"Cannot assign"); +} + +TList::TList() +{ +} + +TList::~TList() +{ + Clear(); +} + +intptr_t TList::GetCount() const +{ + return static_cast(FList.size()); +} + +void TList::SetCount(intptr_t NewCount) +{ + if (NewCount == NPOS) + { + Error(SListCountError, NewCount); + } + if (NewCount <= static_cast(FList.size())) + { + intptr_t sz = FList.size(); + for (intptr_t Index = sz - 1; (Index != NPOS) && (Index >= NewCount); Index--) + { + Delete(Index); + } + } + FList.resize(NewCount); +} + +void * TList::operator [](intptr_t Index) const +{ + return FList[Index]; +} + +void TList::SetItem(intptr_t Index, void * Item) +{ + if ((Index == NPOS) || (Index >= static_cast(FList.size()))) + { + Error(SListIndexError, Index); + } + FList[Index] = Item; +} + +intptr_t TList::Add(void * Value) +{ + intptr_t Result = static_cast(FList.size()); + FList.push_back(Value); + return Result; +} + +void * TList::Extract(void * Item) +{ + if (Remove(Item) != NPOS) + { + return Item; + } + else + { + return nullptr; + } +} + +intptr_t TList::Remove(void * Item) +{ + intptr_t Result = IndexOf(Item); + if (Result != NPOS) + { + Delete(Result); + } + return Result; +} + +void TList::Move(intptr_t CurIndex, intptr_t NewIndex) +{ + if (CurIndex != NewIndex) + { + if ((NewIndex == NPOS) || (NewIndex >= static_cast(FList.size()))) + { + Error(SListIndexError, NewIndex); + } + void * Item = GetItem(CurIndex); + FList[CurIndex] = nullptr; + Delete(CurIndex); + Insert(NewIndex, nullptr); + FList[NewIndex] = Item; + } +} + +void TList::Delete(intptr_t Index) +{ + if ((Index == NPOS) || (Index >= static_cast(FList.size()))) + { + Error(SListIndexError, Index); + } + void * Temp = GetItem(Index); + FList.erase(FList.begin() + Index); + if (Temp != nullptr) + { + Notify(Temp, lnDeleted); + } +} + +void TList::Insert(intptr_t Index, void * Item) +{ + if ((Index == NPOS) || (Index > static_cast(FList.size()))) + { + Error(SListIndexError, Index); + } + if (Index <= static_cast(FList.size())) + { + FList.insert(FList.begin() + Index, Item); + } + if (Item != nullptr) + { + Notify(Item, lnAdded); + } +} + +intptr_t TList::IndexOf(const void * Value) const +{ + intptr_t Result = 0; + while ((Result < static_cast(FList.size())) && (FList[Result] != Value)) + { + Result++; + } + if (Result == static_cast(FList.size())) + { + Result = NPOS; + } + return Result; +} + +void TList::Clear() +{ + SetCount(0); +} + +void QuickSort(std::vector & SortList, intptr_t L, intptr_t R, + CompareFunc SCompare) +{ + intptr_t Index; + do + { + Index = L; + intptr_t J = R; + void * P = SortList[(L + R) >> 1]; + do + { + while (SCompare(SortList[Index], P) < 0) + Index++; + while (SCompare(SortList[J], P) > 0) + J--; + if (Index <= J) + { + if (Index != J) + { + void * T = SortList[Index]; + SortList[Index] = SortList[J]; + SortList[J] = T; + } + Index--; + J--; + } + } + while (Index > J); + if (L < J) + QuickSort(SortList, L, J, SCompare); + L = Index; + } + while (Index >= R); +} + +void TList::Sort(CompareFunc Func) +{ + if (GetCount() > 1) + { + QuickSort(FList, 0, GetCount() - 1, Func); + } +} + +void TList::Notify(void * Ptr, TListNotification Action) +{ + (void)Ptr; + (void)Action; +} + +void TList::Sort() +{ + // if (FList.size() > 1) + // QuickSort(FList, 0, GetCount() - 1, Compare); + ThrowNotImplemented(15); +} + +TObjectList::TObjectList() : + FOwnsObjects(true) +{ +} + +TObjectList::~TObjectList() +{ + Clear(); +} + +TObject * TObjectList::operator [](intptr_t Index) const +{ + return static_cast(TList::operator[](Index)); +} + +TObject * TObjectList::GetObj(intptr_t Index) const +{ + return static_cast(TList::GetItem(Index)); +} + +void TObjectList::SetItem(intptr_t Index, TObject * Value) +{ + TList::SetItem(Index, Value); +} + +intptr_t TObjectList::Add(TObject * Value) +{ + return TList::Add(Value); +} + +intptr_t TObjectList::Remove(TObject * Value) +{ + return TList::Remove(Value); +} + +void TObjectList::Extract(TObject * Value) +{ + TList::Extract(Value); +} + +void TObjectList::Move(intptr_t Index, intptr_t To) +{ + TList::Move(Index, To); +} + +void TObjectList::Delete(intptr_t Index) +{ + TList::Delete(Index); +} + +void TObjectList::Insert(intptr_t Index, TObject * Value) +{ + TList::Insert(Index, Value); +} + +intptr_t TObjectList::IndexOf(const TObject * Value) const +{ + return TList::IndexOf(Value); +} + +void TObjectList::Clear() +{ + TList::Clear(); +} + +void TObjectList::Sort(CompareFunc func) +{ + TList::Sort(func); +} + +void TObjectList::Notify(void * Ptr, TListNotification Action) +{ + if (GetOwnsObjects()) + { + if (Action == lnDeleted) + { + delete static_cast(Ptr); + } + } + TList::Notify(Ptr, Action); +} + +const intptr_t MonthsPerYear = 12; +const intptr_t DaysPerWeek = 7; +const intptr_t MinsPerHour = 60; +const intptr_t SecsPerMin = 60; +const intptr_t HoursPerDay = 24; +const intptr_t MSecsPerSec = 1000; +const intptr_t OneSecond = MSecsPerSec; +const intptr_t SecsPerHour = MinsPerHour * SecsPerMin; +const intptr_t MinsPerDay = HoursPerDay * MinsPerHour; +const intptr_t SecsPerDay = MinsPerDay * SecsPerMin; +const intptr_t MSecsPerDay = SecsPerDay * MSecsPerSec; +// Days between 1/1/0001 and 12/31/1899 +const intptr_t DateDelta = 693594; +const intptr_t UnixDateDelta = 25569; +static const int MemoryDelta = 0x2000; + +TStrings::TStrings() : + FDuplicates(dupAccept), + FDelimiter(L','), + FQuoteChar(L'"'), + FUpdateCount(0) +{ +} + +TStrings::~TStrings() +{ +} + +void TStrings::SetTextStr(const UnicodeString & Text) +{ + BeginUpdate(); + SCOPE_EXIT + { + EndUpdate(); + }; + Clear(); + const wchar_t * P = Text.c_str(); + if (P != nullptr) + { + while (*P != 0x00) + { + const wchar_t * Start = P; + while (!((*P == 0x00) || (*P == 0x0A) || (*P == 0x0D))) + { + P++; + } + UnicodeString S; + S.SetLength(P - Start); + memmove(const_cast(S.c_str()), Start, (P - Start) * sizeof(wchar_t)); + Add(S); + if (*P == 0x0D) + { + P++; + } + if (*P == 0x0A) + { + P++; + } + } + } +} + +UnicodeString TStrings::GetCommaText() const +{ + wchar_t LOldDelimiter = GetDelimiter(); + wchar_t LOldQuoteChar = GetQuoteChar(); + FDelimiter = L','; + FQuoteChar = L'"'; + UnicodeString Result; + SCOPE_EXIT + { + FDelimiter = LOldDelimiter; + FQuoteChar = LOldQuoteChar; + }; + Result = GetDelimitedText(); + return Result; +} + +UnicodeString TStrings::GetDelimitedText() const +{ + UnicodeString Result; + intptr_t Count = GetCount(); + if ((Count == 1) && GetString(0).IsEmpty()) + { + Result = GetQuoteChar() + GetQuoteChar(); + } + else + { + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + UnicodeString Line = GetString(Index); + Result += GetQuoteChar() + Line + GetQuoteChar() + GetDelimiter(); + } + if (Result.Length() > 0) + Result.SetLength(Result.Length() - 1); + } + return Result; +} + +static void tokenize(const UnicodeString & str, std::vector & tokens, + const UnicodeString & delimiters = L" ", const bool trimEmpty = false) +{ + intptr_t lastPos = 0; + while (true) + { + intptr_t pos = str.FindFirstOf(delimiters.c_str(), lastPos); + if (pos == NPOS) + { + pos = str.Length(); + + if (pos != lastPos || !trimEmpty) + { + tokens.push_back( + UnicodeString(str.data() + lastPos, pos - lastPos)); + } + break; + } + else + { + if (pos != lastPos || !trimEmpty) + { + tokens.push_back( + UnicodeString(str.data() + lastPos, pos - lastPos)); + } + } + + lastPos = pos + 1; + } +} + +void TStrings::SetDelimitedText(const UnicodeString & Value) +{ + BeginUpdate(); + SCOPE_EXIT + { + EndUpdate(); + }; + Clear(); + std::vector Lines; + UnicodeString Delimiter(UnicodeString(GetDelimiter()) + L'\n'); + tokenize(Value, Lines, Delimiter, true); + for (size_t Index = 0; Index < Lines.size(); Index++) + { + Add(Lines[Index]); + } +} + +intptr_t TStrings::CompareStrings(const UnicodeString & S1, const UnicodeString & S2) const +{ + return static_cast(::AnsiCompareText(S1, S2)); +} + +void TStrings::Assign(const TPersistent * Source) +{ + const TStrings * Strings = NB_STATIC_DOWNCAST_CONST(TStrings, Source); + if (Strings != nullptr) + { + BeginUpdate(); + SCOPE_EXIT + { + EndUpdate(); + }; + Clear(); + DebugAssert(Strings); + FQuoteChar = Strings->FQuoteChar; + FDelimiter = Strings->FDelimiter; + AddStrings(Strings); + } + else + { + TPersistent::Assign(Source); + } +} + +intptr_t TStrings::Add(const UnicodeString & S, TObject * AObject) +{ + intptr_t Result = GetCount(); + Insert(Result, S, AObject); + return Result; +} + +UnicodeString TStrings::GetText() const +{ + return GetTextStr(); +} + +UnicodeString TStrings::GetTextStr() const +{ + UnicodeString Result; + intptr_t Count = GetCount(); + intptr_t Size = 0; + UnicodeString LB(L"\r\n"); + for (intptr_t Index = 0; Index < Count; ++Index) + { + Size += GetString(Index).Length() + LB.Length(); + } + Result.SetLength(Size); + wchar_t * P = const_cast(Result.c_str()); + for (intptr_t Index = 0; Index < Count; ++Index) + { + UnicodeString S = GetString(Index); + intptr_t L = S.Length() * sizeof(wchar_t); + if (L != 0) + { + memmove(P, S.c_str(), (size_t)L); + P += S.Length(); + } + L = LB.Length() * sizeof(wchar_t); + if (L != 0) + { + memmove(P, LB.c_str(), (size_t)L); + P += LB.Length(); + } + } + return Result; +} + +void TStrings::SetText(const UnicodeString & Text) +{ + SetTextStr(Text); +} + +void TStrings::SetCommaText(const UnicodeString & Value) +{ + SetDelimiter(L','); + SetQuoteChar(L'"'); + SetDelimitedText(Value); +} + +void TStrings::BeginUpdate() +{ + if (FUpdateCount == 0) + { + SetUpdateState(true); + } + FUpdateCount++; +} + +void TStrings::EndUpdate() +{ + FUpdateCount--; + if (FUpdateCount == 0) + { + SetUpdateState(false); + } +} + +void TStrings::SetUpdateState(bool Updating) +{ + (void)Updating; +} + +intptr_t TStrings::AddObject(const UnicodeString & S, TObject * AObject) +{ + intptr_t Result = Add(S, AObject); + return Result; +} + +void TStrings::InsertObject(intptr_t Index, const UnicodeString & Key, TObject * AObject) +{ + Insert(Index, Key, AObject); +} + +bool TStrings::Equals(const TStrings * Strings) const +{ + if (GetCount() != Strings->GetCount()) + { + return false; + } + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (GetString(Index) != Strings->GetString(Index)) + { + return false; + } + } + return true; +} + +void TStrings::SetString(intptr_t Index, const UnicodeString & S) +{ + TObject * TempObject = GetObj(Index); + Delete(Index); + InsertObject(Index, S, TempObject); +} + +void TStrings::SetDuplicates(TDuplicatesEnum Value) +{ + FDuplicates = Value; +} + +void TStrings::Move(intptr_t CurIndex, intptr_t NewIndex) +{ + if (CurIndex != NewIndex) + { + BeginUpdate(); + SCOPE_EXIT + { + EndUpdate(); + }; + UnicodeString TempString = GetString(CurIndex); + TObject * TempObject = GetObj(CurIndex); + Delete(CurIndex); + InsertObject(NewIndex, TempString, TempObject); + } +} + +intptr_t TStrings::IndexOf(const UnicodeString & S) const +{ + for (intptr_t Result = 0; Result < GetCount(); Result++) + { + if (CompareStrings(GetString(Result), S) == 0) + { + return Result; + } + } + return NPOS; +} + +intptr_t TStrings::IndexOfName(const UnicodeString & Name) const +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + UnicodeString S = GetString(Index); + intptr_t P = ::AnsiPos(S, L'='); + if ((P > 0) && (CompareStrings(S.SubStr(1, P - 1), Name) == 0)) + { + return Index; + } + } + return NPOS; +} + +const UnicodeString TStrings::GetName(intptr_t Index) const +{ + return ExtractName(GetString(Index)); +} + +void TStrings::SetName(intptr_t /*Index*/, const UnicodeString & /*Value*/) +{ + ThrowNotImplemented(2012); +} + +UnicodeString TStrings::ExtractName(const UnicodeString & S) const +{ + UnicodeString Result = S; + intptr_t P = ::AnsiPos(Result, L'='); + if (P > 0) + { + Result.SetLength(P - 1); + } + else + { + Result.SetLength(0); + } + return Result; +} + +const UnicodeString TStrings::GetValue(const UnicodeString & Name) const +{ + UnicodeString Result; + intptr_t Index = IndexOfName(Name); + if (Index >= 0) + { + Result = GetString(Index).SubStr(Name.Length() + 2, -1); + } + return Result; +} + +void TStrings::SetValue(const UnicodeString & Name, const UnicodeString & Value) +{ + intptr_t Index = IndexOfName(Name); + if (!Value.IsEmpty()) + { + if (Index < 0) + { + Index = Add(L""); + } + SetString(Index, Name + L'=' + Value); + } + else + { + if (Index >= 0) + { + Delete(Index); + } + } +} + +UnicodeString TStrings::GetValueFromIndex(intptr_t Index) const +{ + UnicodeString Name = GetName(Index); + UnicodeString Result = GetValue(Name); + return Result; +} + +void TStrings::AddStrings(const TStrings * Strings) +{ + BeginUpdate(); + SCOPE_EXIT + { + EndUpdate(); + }; + for (intptr_t Index = 0; Index < Strings->GetCount(); ++Index) + { + AddObject(Strings->GetString(Index), Strings->GetObj(Index)); + } +} + +void TStrings::Append(const UnicodeString & Value) +{ + Insert(GetCount(), Value); +} + +void TStrings::SaveToStream(TStream * /*Stream*/) const +{ + ThrowNotImplemented(12); +} + +intptr_t StringListCompareStrings(TStringList * List, intptr_t Index1, intptr_t Index2) +{ + intptr_t Result = List->CompareStrings(List->FStrings[Index1], + List->FStrings[Index2]); + return Result; +} + +TStringList::TStringList() : + FSorted(false), + FCaseSensitive(false) +{ +} + +TStringList::~TStringList() +{} + +void TStringList::Assign(const TPersistent * Source) +{ + TStrings::Assign(Source); +} + +intptr_t TStringList::GetCount() const +{ + DebugAssert(FStrings.size() == FObjects.size()); + return static_cast(FStrings.size()); +} + +void TStringList::Clear() +{ + FStrings.clear(); + FObjects.clear(); +} + +intptr_t TStringList::Add(const UnicodeString & S) +{ + return AddObject(S, nullptr); +} + +intptr_t TStringList::AddObject(const UnicodeString & S, TObject * AObject) +{ + intptr_t Result = 0; + if (!GetSorted()) + { + Result = GetCount(); + } + else + { + if (Find(S, Result)) + { + switch (FDuplicates) + { + case dupIgnore: + return Result; + break; + case dupError: + Error(SDuplicateString, 2); + break; + case dupAccept: + break; + } + } + else + { + Result = GetCount(); + } + } + InsertItem(Result, S, AObject); + return Result; +} + +bool TStringList::Find(const UnicodeString & S, intptr_t & Index) const +{ + bool Result = false; + intptr_t L = 0; + intptr_t H = GetCount() - 1; + while ((H != NPOS) && (L <= H)) + { + intptr_t Idx = (L + H) >> 1; + intptr_t C = CompareStrings(FStrings[Idx], S); + if (C < 0) + { + L = Idx + 1; + } + else + { + H = Idx - 1; + if (C == 0) + { + Result = true; + if (FDuplicates != dupAccept) + { + L = Idx; + } + } + } + } + Index = L; + return Result; +} + +intptr_t TStringList::IndexOf(const UnicodeString & S) const +{ + intptr_t Result = NPOS; + if (!GetSorted()) + { + Result = TStrings::IndexOf(S); + } + else + { + if (!Find(S, Result)) + { + Result = NPOS; + } + } + return Result; +} + +void TStringList::SetString(intptr_t Index, const UnicodeString & S) +{ + if (GetSorted()) + { + Error(SSortedListError, 0); + } + if ((Index == NPOS) || (Index > static_cast(FStrings.size()))) + { + Error(SListIndexError, Index); + } + Changing(); + if (Index < static_cast(FStrings.size())) + { + FStrings[Index] = S; + } + else + { + Insert(Index, S); + } + Changed(); +} + +void TStringList::Delete(intptr_t Index) +{ + if ((Index == NPOS) || (Index >= static_cast(FStrings.size()))) + { + Error(SListIndexError, Index); + } + Changing(); + FStrings.erase(FStrings.begin() + Index); + FObjects.erase(FObjects.begin() + Index); + Changed(); +} + +TObject * TStringList::GetObj(intptr_t Index) const +{ + if ((Index == NPOS) || (Index >= static_cast(FObjects.size()))) + { + Error(SListIndexError, Index); + } + return FObjects[Index]; +} + +void TStringList::InsertObject(intptr_t Index, const UnicodeString & Key, TObject * AObject) +{ + if (GetSorted()) + { + Error(SSortedListError, 0); + } + if ((Index == NPOS) || (Index > GetCount())) + { + Error(SListIndexError, Index); + } + InsertItem(Index, Key, AObject); +} + +void TStringList::InsertItem(intptr_t Index, const UnicodeString & S, TObject * AObject) +{ + if ((Index == NPOS) || (Index > GetCount())) + { + Error(SListIndexError, Index); + } + Changing(); + if (Index == GetCount()) + { + FStrings.push_back(S); + FObjects.push_back(AObject); + } + else + { + FStrings.insert(FStrings.begin() + Index, S); + FObjects.insert(FObjects.begin() + Index, AObject); + } + Changed(); +} + +const UnicodeString & TStringList::GetString(intptr_t Index) const +{ + if ((Index == NPOS) || (Index > static_cast(FStrings.size()))) + { + Error(SListIndexError, Index); + } + if (Index == static_cast(FStrings.size())) + { + const_cast(this)->InsertItem(Index, UnicodeString(), nullptr); + } + return FStrings[Index]; +} + +void TStringList::SetCaseSensitive(bool Value) +{ + if (Value != FCaseSensitive) + { + FCaseSensitive = Value; + if (GetSorted()) + { + Sort(); + } + } +} + +void TStringList::SetSorted(bool Value) +{ + if (Value != FSorted) + { + if (Value) + { + Sort(); + } + FSorted = Value; + } +} + +void TStringList::LoadFromFile(const UnicodeString & AFileName) +{ + HANDLE FileHandle = ::CreateFile(AFileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, 0); + if (FileHandle != INVALID_HANDLE_VALUE) + { + TSafeHandleStream Stream(FileHandle); + int64_t Size = Stream.GetSize(); + TFileBuffer FileBuffer; + FileBuffer.LoadStream(&Stream, Size, True); + bool ConvertToken = false; + FileBuffer.Convert(eolCRLF, eolCRLF, cpRemoveCtrlZ | cpRemoveBOM, ConvertToken); + ::CloseHandle(FileHandle); + UnicodeString Str(FileBuffer.GetData(), static_cast(FileBuffer.GetSize())); + SetTextStr(Str); + } +} + +void TStringList::SetObj(intptr_t Index, TObject * AObject) +{ + if ((Index == NPOS) || (Index >= static_cast(FObjects.size()))) + { + Error(SListIndexError, Index); + } + Changing(); + FObjects[Index] = AObject; + Changed(); +} + +void TStringList::SetUpdateState(bool Updating) +{ + if (Updating) + { + Changing(); + } + else + { + Changed(); + } +} + +void TStringList::Changing() +{ + if (GetUpdateCount() == 0 && FOnChanging) + { + FOnChanging(this); + } +} + +void TStringList::Changed() +{ + if (GetUpdateCount() == 0 && FOnChange) + { + FOnChange(this); + } +} + +void TStringList::Insert(intptr_t Index, const UnicodeString & S, TObject* AObject) +{ + InsertItem(Index, S, AObject); +} + +void TStringList::Sort() +{ + CustomSort(StringListCompareStrings); +} + +void TStringList::CustomSort(TStringListSortCompare ACompareFunc) +{ + if (!GetSorted() && (GetCount() > 1)) + { + Changing(); + QuickSort(0, GetCount() - 1, ACompareFunc); + Changed(); + } +} + +void TStringList::QuickSort(intptr_t L, intptr_t R, TStringListSortCompare SCompare) +{ + intptr_t Index; + do + { + Index = L; + intptr_t J = R; + intptr_t P = (L + R) >> 1; + do + { + while (SCompare(this, Index, P) < 0) + { + Index++; + } + while (SCompare(this, J, P) > 0) + { + J--; + } + if (Index <= J) + { + ExchangeItems(Index, J); + if (P == Index) + { + P = J; + } + else if (P == J) + { + P = Index; + } + Index++; + J--; + } + } + while (Index <= J); + if (L < J) + { + QuickSort(L, J, SCompare); + } + L = Index; + } + while (Index < R); +} + +void TStringList::ExchangeItems(intptr_t Index1, intptr_t Index2) +{ + UnicodeString SItem1 = FStrings[Index1]; + TObject * OItem1 = FObjects[Index1]; + FStrings[Index1] = FStrings[Index2]; + FObjects[Index1] = FObjects[Index2]; + FStrings[Index2] = SItem1; + FObjects[Index2] = OItem1; +} + +intptr_t TStringList::CompareStrings(const UnicodeString & S1, const UnicodeString & S2) const +{ + if (GetCaseSensitive()) + { + return ::AnsiCompareStr(S1, S2); + } + else + { + return ::AnsiCompareText(S1, S2); + } +} + +TDateTime::TDateTime(uint16_t Hour, + uint16_t Min, uint16_t Sec, uint16_t MSec) +{ + FValue = ::EncodeTimeVerbose(Hour, Min, Sec, MSec); +} + +bool TDateTime::operator ==(const TDateTime & rhs) +{ + return ::IsZero(FValue - rhs.FValue); +} + +UnicodeString TDateTime::DateString() const +{ + uint16_t Y, M, D; + DecodeDate(Y, M, D); + UnicodeString Result = FORMAT(L"%02d.%02d.%04d", D, M, Y); + return Result; +} + +UnicodeString TDateTime::TimeString(bool Short) const +{ + uint16_t H, N, S, MS; + DecodeTime(H, N, S, MS); + UnicodeString Result; + if (Short) + Result = FORMAT(L"%02d.%02d.%02d", H, N, S); + else + Result = FORMAT(L"%02d.%02d.%02d.%03d", H, N, S, MS); + return Result; +} + +UnicodeString TDateTime::FormatString(wchar_t * fmt) const +{ + (void)fmt; + uint16_t H, N, S, MS; + DecodeTime(H, N, S, MS); + UnicodeString Result = FORMAT(L"%02d.%02d.%02d.%03d", H, N, S, MS); + return Result; +} + +void TDateTime::DecodeDate(uint16_t & Y, + uint16_t & M, uint16_t & D) const +{ + ::DecodeDate(*this, Y, M, D); +} + +void TDateTime::DecodeTime(uint16_t & H, + uint16_t & N, uint16_t & S, uint16_t & MS) const +{ + ::DecodeTime(*this, H, N, S, MS); +} + +TDateTime Now() +{ + TDateTime Result(0.0); + SYSTEMTIME SystemTime; + ::GetLocalTime(&SystemTime); + Result = ::EncodeDate(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay) + + ::EncodeTime(SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds); + return Result; +} + +TDateTime SpanOfNowAndThen(const TDateTime & ANow, const TDateTime & AThen) +{ + TDateTime Result; + if (ANow < AThen) + Result = AThen - ANow; + else + Result = ANow - AThen; + return Result; +} + +double MilliSecondSpan(const TDateTime & ANow, const TDateTime & AThen) +{ + TDateTime Result; + Result = MSecsPerDay * SpanOfNowAndThen(ANow, AThen); + return Result; +} + +int64_t MilliSecondsBetween(const TDateTime & ANow, const TDateTime & AThen) +{ + TDateTime Result; + Result = floor(MilliSecondSpan(ANow, AThen)); + return static_cast(Result); +} + +int64_t SecondsBetween(const TDateTime & ANow, const TDateTime & AThen) +{ + return MilliSecondsBetween(ANow, AThen); +} + +#ifndef __linux__ +static TLibraryLoader SHFileInfoLoader; + +TSHFileInfo::TSHFileInfo() : + FGetFileInfo(nullptr) +{ + SHFileInfoLoader.Load(L"Shell32.dll"); + if (SHFileInfoLoader.Loaded()) + { + FGetFileInfo = reinterpret_cast(SHFileInfoLoader.GetProcAddress("SHGetFileInfo")); + } +} + +TSHFileInfo::~TSHFileInfo() +{ + SHFileInfoLoader.Unload(); +} + +int TSHFileInfo::GetFileIconIndex(const UnicodeString & StrFileName, BOOL bSmallIcon) const +{ + SHFILEINFO sfi; + + if (bSmallIcon) + { + if (FGetFileInfo) + FGetFileInfo( + static_cast(StrFileName.c_str()), + FILE_ATTRIBUTE_NORMAL, + &sfi, + sizeof(SHFILEINFO), + SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES); + } + else + { + if (FGetFileInfo) + FGetFileInfo( + static_cast(StrFileName.c_str()), + FILE_ATTRIBUTE_NORMAL, + &sfi, + sizeof(SHFILEINFO), + SHGFI_SYSICONINDEX | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES); + } + return sfi.iIcon; +} + +int TSHFileInfo::GetDirIconIndex(BOOL bSmallIcon) +{ + SHFILEINFO sfi; + if (bSmallIcon) + { + if (FGetFileInfo) + FGetFileInfo( + static_cast(L"Doesn't matter"), + FILE_ATTRIBUTE_DIRECTORY, + &sfi, + sizeof(SHFILEINFO), + SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES); + } + else + { + if (FGetFileInfo) + FGetFileInfo( + static_cast(L"Doesn't matter"), + FILE_ATTRIBUTE_DIRECTORY, + &sfi, + sizeof(SHFILEINFO), + SHGFI_SYSICONINDEX | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES); + } + return sfi.iIcon; +} + +UnicodeString TSHFileInfo::GetFileType(const UnicodeString & StrFileName) +{ + SHFILEINFO sfi; + + if (FGetFileInfo) + FGetFileInfo( + reinterpret_cast(StrFileName.c_str()), + FILE_ATTRIBUTE_NORMAL, + &sfi, + sizeof(SHFILEINFO), + SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES); + + return sfi.szTypeName; +} +#endif + +class EStreamError : public ExtException +{ +public: + explicit EStreamError(const UnicodeString & Msg) : + ExtException((Exception * )nullptr, Msg) + {} +}; + +TStream::TStream() +{ +} + +TStream::~TStream() +{ +} + +void TStream::ReadBuffer(void * Buffer, int64_t Count) +{ + if ((Count != 0) && (Read(Buffer, Count) != Count)) + { + throw Exception(FMTLOAD(SReadError)); + } +} + +void TStream::WriteBuffer(const void * Buffer, int64_t Count) +{ + if ((Count != 0) && (Write(Buffer, Count) != Count)) + { + throw Exception(FMTLOAD(SWriteError)); + } +} + +void ReadError(const UnicodeString & Name) +{ + throw Exception(FORMAT(L"InvalidRegType: %s", Name.c_str())); // FIXME ERegistryException.CreateResFmt(@SInvalidRegType, [Name]); +} + +THandleStream::THandleStream(HANDLE AHandle) : + FHandle(AHandle) +{ +} + +THandleStream::~THandleStream() +{ + // if (FHandle != INVALID_HANDLE_VALUE) + // ::CloseHandle(FHandle); +} + +int64_t THandleStream::Read(void * Buffer, int64_t Count) +{ + int64_t Result = ::FileRead(FHandle, Buffer, Count); + if (Result == -1) + { + Result = 0; + } + return Result; +} + +int64_t THandleStream::Write(const void * Buffer, int64_t Count) +{ + int64_t Result = ::FileWrite(FHandle, Buffer, Count); + if (Result == -1) + { + Result = 0; + } + return Result; +} + +int64_t THandleStream::Seek(int64_t Offset, int Origin) +{ + int64_t Result = ::FileSeek(FHandle, Offset, (DWORD)Origin); + return Result; +} + +int64_t THandleStream::Seek(const int64_t Offset, TSeekOrigin Origin) +{ + int origin = FILE_BEGIN; + switch (Origin) + { + case soFromBeginning: + origin = FILE_BEGIN; + break; + case soFromCurrent: + origin = FILE_CURRENT; + break; + case soFromEnd: + origin = FILE_END; + break; + } + return Seek(Offset, origin); +} + +void THandleStream::SetSize(const int64_t NewSize) +{ + Seek(NewSize, soFromBeginning); + // LARGE_INTEGER li; + // li.QuadPart = size; + // if (SetFilePointer(fh.get(), li.LowPart, &li.HighPart, FILE_BEGIN) == -1) + // handleLastErrorImpl(_path); + ::Win32Check(::SetEndOfFile(FHandle) > 0); +} + +TSafeHandleStream::TSafeHandleStream(THandle AHandle) : + THandleStream(AHandle) +{ +} + +int64_t TSafeHandleStream::Read(void * Buffer, int64_t Count) +{ + int64_t Result = ::FileRead(FHandle, Buffer, Count); + if (Result == static_cast(-1)) + { + ::RaiseLastOSError(); + } + return Result; +} + +int64_t TSafeHandleStream::Write(const void * Buffer, int64_t Count) +{ + int64_t Result = ::FileWrite(FHandle, Buffer, Count); + if (Result == -1) + { + ::RaiseLastOSError(); + } + return Result; +} + +TMemoryStream::TMemoryStream() : + FMemory(nullptr), + FSize(0), + FPosition(0), + FCapacity(0) +{ +} + +TMemoryStream::~TMemoryStream() +{ + Clear(); +} + +int64_t TMemoryStream::Read(void * Buffer, int64_t Count) +{ + int64_t Result = 0; + if ((FPosition >= 0) && (Count >= 0)) + { + Result = FSize - FPosition; + if (Result > 0) + { + if (Result > Count) + { + Result = Count; + } + memmove(Buffer, reinterpret_cast(FMemory) + FPosition, static_cast(Result)); + FPosition += Result; + return Result; + } + } + Result = 0; + return Result; +} + +int64_t TMemoryStream::Seek(int64_t Offset, int Origin) +{ + return Seek(Offset, static_cast(Origin)); +} + +int64_t TMemoryStream::Seek(const int64_t Offset, TSeekOrigin Origin) +{ + switch (Origin) + { + case soFromBeginning: + FPosition = Offset; + break; + case soFromCurrent: + FPosition += Offset; + break; + case soFromEnd: + FPosition = FSize + Offset; + break; + } + int64_t Result = FPosition; + return Result; +} + +void TMemoryStream::SaveToStream(TStream * Stream) +{ + if (FSize != 0) + { + Stream->WriteBuffer(FMemory, FSize); + } +} + +void TMemoryStream::SaveToFile(const UnicodeString & /*AFileName*/) +{ + // TFileStream Stream(FileName, fmCreate); + // SaveToStream(Stream); + ThrowNotImplemented(1203); +} + +void TMemoryStream::Clear() +{ + SetCapacity(0); + FSize = 0; + FPosition = 0; +} + +void TMemoryStream::SetSize(const int64_t NewSize) +{ + int64_t OldPosition = FPosition; + SetCapacity(NewSize); + FSize = NewSize; + if (OldPosition > NewSize) + { + Seek(0, soFromEnd); + } +} + +void TMemoryStream::SetCapacity(int64_t NewCapacity) +{ + SetPointer(Realloc(NewCapacity), FSize); + FCapacity = NewCapacity; +} + +void * TMemoryStream::Realloc(int64_t & NewCapacity) +{ + if ((NewCapacity > 0) && (NewCapacity != FSize)) + { + NewCapacity = (NewCapacity + (MemoryDelta - 1)) & ~(MemoryDelta - 1); + } + void * Result = FMemory; + if (NewCapacity != FCapacity) + { + if (NewCapacity == 0) + { + nb_free(FMemory); + FMemory = nullptr; + Result = nullptr; + } + else + { + if (FCapacity == 0) + { + Result = nb_malloc(static_cast(NewCapacity)); + } + else + { + Result = nb_realloc(FMemory, static_cast(NewCapacity)); + } + if (Result == nullptr) + { + throw EStreamError(FMTLOAD(SMemoryStreamError)); + } + } + } + return Result; +} + +void TMemoryStream::SetPointer(void * Ptr, int64_t Size) +{ + FMemory = Ptr; + FSize = Size; +} + +int64_t TMemoryStream::Write(const void * Buffer, int64_t Count) +{ + int64_t Result = 0; + if ((FPosition >= 0) && (Count >= 0)) + { + int64_t Pos = FPosition + Count; + if (Pos > 0) + { + if (Pos > FSize) + { + if (Pos > FCapacity) + { + SetCapacity(Pos); + } + FSize = Pos; + } + memmove(static_cast(FMemory) + FPosition, + Buffer, static_cast(Count)); + FPosition = Pos; + Result = Count; + } + } + return Result; +} + +bool IsRelative(const UnicodeString & Value) +{ + return !(!Value.IsEmpty() && (Value[1] == L'\\')); +} + +TRegDataType DataTypeToRegData(DWORD Value) +{ + TRegDataType Result; + if (Value == REG_SZ) + { + Result = rdString; + } + else if (Value == REG_EXPAND_SZ) + { + Result = rdExpandString; + } + else if (Value == REG_DWORD) + { + Result = rdInteger; + } + else if (Value == REG_BINARY) + { + Result = rdBinary; + } + else + { + Result = rdUnknown; + } + return Result; +} + +DWORD RegDataToDataType(TRegDataType Value) +{ + DWORD Result = 0; + switch (Value) + { + case rdString: + Result = REG_SZ; + break; + case rdExpandString: + Result = REG_EXPAND_SZ; + break; + case rdInteger: + Result = REG_DWORD; + break; + case rdBinary: + Result = REG_BINARY; + break; + default: + Result = REG_NONE; + break; + } + return Result; +} + +class ERegistryException : public std::exception +{ +}; + +TRegistry::TRegistry() : + FCurrentKey(0), + FRootKey(0), + FCloseRootKey(false), + FAccess(KEY_ALL_ACCESS) +{ + SetRootKey(HKEY_CURRENT_USER); + SetAccess(KEY_ALL_ACCESS); +} + +TRegistry::~TRegistry() +{ + CloseKey(); +} + +void TRegistry::SetAccess(uint32_t access) +{ + FAccess = access; +} + +void TRegistry::SetRootKey(HKEY ARootKey) +{ + if (FRootKey != ARootKey) + { + if (FCloseRootKey) + { + ::RegCloseKey(GetRootKey()); + FCloseRootKey = false; + } + FRootKey = ARootKey; + CloseKey(); + } +} + +void TRegistry::GetValueNames(TStrings * Strings) const +{ + Strings->Clear(); + TRegKeyInfo Info; + UnicodeString S; + if (GetKeyInfo(Info)) + { + S.SetLength(Info.MaxValueLen + 1); + for (DWORD Index = 0; Index < Info.NumValues; Index++) + { + DWORD Len = Info.MaxValueLen + 1; + RegEnumValue(GetCurrentKey(), Index, &S[1], &Len, nullptr, nullptr, nullptr, nullptr); + Strings->Add(S.c_str()); + } + } +} + +void TRegistry::GetKeyNames(TStrings * Strings) const +{ + Strings->Clear(); + TRegKeyInfo Info; + UnicodeString S; + if (GetKeyInfo(Info)) + { + S.SetLength(static_cast(Info.MaxSubKeyLen) + 1); + for (DWORD Index = 0; Index < Info.NumSubKeys; Index++) + { + DWORD Len = Info.MaxSubKeyLen + 1; + RegEnumKeyEx(GetCurrentKey(), static_cast(Index), &S[1], &Len, nullptr, nullptr, nullptr, nullptr); + Strings->Add(S.c_str()); + } + } +} + +HKEY TRegistry::GetCurrentKey() const { return FCurrentKey; } +HKEY TRegistry::GetRootKey() const { return FRootKey; } + +void TRegistry::CloseKey() +{ + if (GetCurrentKey() != 0) + { + ::RegCloseKey(GetCurrentKey()); + FCurrentKey = 0; + FCurrentPath.Clear(); + } +} + +bool TRegistry::OpenKey(const UnicodeString & Key, bool CanCreate) +{ + bool Result = false; + UnicodeString S = Key; + bool Relative = IsRelative(S); + + HKEY TempKey = 0; + if (!CanCreate || S.IsEmpty()) + { + Result = ::RegOpenKeyEx(GetBaseKey(Relative), S.c_str(), 0, + FAccess, &TempKey) == ERROR_SUCCESS; + } + else + { + Result = ::RegCreateKeyEx(GetBaseKey(Relative), S.c_str(), 0, nullptr, + REG_OPTION_NON_VOLATILE, FAccess, nullptr, &TempKey, nullptr) == ERROR_SUCCESS; + } + if (Result) + { + if ((GetCurrentKey() != 0) && Relative) + { + S = FCurrentPath + L'\\' + S; + } + ChangeKey(TempKey, S); + } + return Result; +} + +bool TRegistry::DeleteKey(const UnicodeString & Key) +{ + bool Result = false; + UnicodeString S = Key; + bool Relative = IsRelative(S); + HKEY OldKey = GetCurrentKey(); + HKEY DeleteKey = GetKey(Key); + if (DeleteKey != 0) + { + SCOPE_EXIT + { + SetCurrentKey(OldKey); + ::RegCloseKey(DeleteKey); + }; + SetCurrentKey(DeleteKey); + TRegKeyInfo Info; + if (GetKeyInfo(Info)) + { + UnicodeString KeyName; + KeyName.SetLength(Info.MaxSubKeyLen + 1); + for (intptr_t Index = static_cast(Info.NumSubKeys) - 1; Index >= 0; Index--) + { + DWORD Len = Info.MaxSubKeyLen + 1; + if (RegEnumKeyEx(DeleteKey, static_cast(Index), &KeyName[1], &Len, + nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) + { + this->DeleteKey(KeyName); + } + } + } + } + Result = RegDeleteKey(GetBaseKey(Relative), S.c_str()) == ERROR_SUCCESS; + return Result; +} + +bool TRegistry::DeleteValue(const UnicodeString & Name) const +{ + bool Result = RegDeleteValue(GetCurrentKey(), Name.c_str()) == ERROR_SUCCESS; + return Result; +} + +bool TRegistry::KeyExists(const UnicodeString & Key) const +{ + bool Result = false; + uint32_t OldAccess = FAccess; + SCOPE_EXIT + { + FAccess = OldAccess; + }; + FAccess = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS; + HKEY TempKey = GetKey(Key); + if (TempKey != 0) + { + ::RegCloseKey(TempKey); + } + Result = TempKey != 0; + return Result; +} + +bool TRegistry::ValueExists(const UnicodeString & Name) const +{ + TRegDataInfo Info; + bool Result = GetDataInfo(Name, Info); + return Result; +} + +bool TRegistry::GetDataInfo(const UnicodeString & ValueName, TRegDataInfo & Value) const +{ + DWORD DataType; + ClearStruct(Value); + bool Result = (::RegQueryValueEx(GetCurrentKey(), ValueName.c_str(), nullptr, &DataType, nullptr, + &Value.DataSize) == ERROR_SUCCESS); + Value.RegData = DataTypeToRegData(DataType); + return Result; +} + +TRegDataType TRegistry::GetDataType(const UnicodeString & ValueName) const +{ + TRegDataType Result; + TRegDataInfo Info; + if (GetDataInfo(ValueName, Info)) + { + Result = Info.RegData; + } + else + { + Result = rdUnknown; + } + return Result; +} + +DWORD TRegistry::GetDataSize(const UnicodeString & ValueName) const +{ + DWORD Result = 0; + TRegDataInfo Info; + if (GetDataInfo(ValueName, Info)) + { + Result = Info.DataSize; + } + else + { + Result = static_cast(-1); + } + return Result; +} + +bool TRegistry::ReadBool(const UnicodeString & Name) +{ + bool Result = ReadInteger(Name) != 0; + return Result; +} + +TDateTime TRegistry::ReadDateTime(const UnicodeString & Name) +{ + TDateTime Result = TDateTime(ReadFloat(Name)); + return Result; +} + +double TRegistry::ReadFloat(const UnicodeString & Name) const +{ + double Result = 0.0; + TRegDataType RegData = rdUnknown; + int Len = GetData(Name, &Result, sizeof(double), RegData); + if ((RegData != rdBinary) || (Len != sizeof(double))) + { + ReadError(Name); + } + return Result; +} + +intptr_t TRegistry::ReadInteger(const UnicodeString & Name) const +{ + DWORD Result = 0; + TRegDataType RegData = rdUnknown; + GetData(Name, &Result, sizeof(Result), RegData); + if (RegData != rdInteger) + { + ReadError(Name); + } + return Result; +} + +int64_t TRegistry::ReadInt64(const UnicodeString & Name) +{ + int64_t Result = 0; + ReadBinaryData(Name, &Result, sizeof(Result)); + return Result; +} + +UnicodeString TRegistry::ReadString(const UnicodeString & Name) +{ + UnicodeString Result; + intptr_t Len = GetDataSize(Name); + if (Len > 0) + { + TRegDataType RegData = rdUnknown; + Result.SetLength(Len); + GetData(Name, static_cast(const_cast(Result.c_str())), Len, RegData); + if ((RegData == rdString) || (RegData == rdExpandString)) + { + PackStr(Result); + } + else + { + ReadError(Name); + } + } + else + { + Result.Clear(); + } + return Result; +} + +UnicodeString TRegistry::ReadStringRaw(const UnicodeString & Name) +{ + UnicodeString Result = ReadString(Name); + return Result; +} + +size_t TRegistry::ReadBinaryData(const UnicodeString & Name, + void * Buffer, size_t BufSize) const +{ + size_t Result = 0; + TRegDataInfo Info; + if (GetDataInfo(Name, Info)) + { + Result = static_cast(Info.DataSize); + TRegDataType RegData = Info.RegData; + if (((RegData == rdBinary) || (RegData == rdUnknown)) && (Result <= BufSize)) + { + GetData(Name, Buffer, Result, RegData); + } + else + { + ReadError(Name); + } + } + else + { + Result = 0; + } + return Result; +} + +int TRegistry::GetData(const UnicodeString & Name, void * Buffer, + intptr_t BufSize, TRegDataType & RegData) const +{ + DWORD DataType = REG_NONE; + DWORD bufSize = static_cast(BufSize); + if (::RegQueryValueEx(GetCurrentKey(), Name.c_str(), nullptr, &DataType, + reinterpret_cast(Buffer), &bufSize) != ERROR_SUCCESS) + { + throw Exception(L"RegQueryValueEx failed"); // FIXME ERegistryException.CreateResFmt(@SRegGetDataFailed, [Name]); + } + RegData = DataTypeToRegData(DataType); + int Result = static_cast(BufSize); + return Result; +} + +void TRegistry::PutData(const UnicodeString & Name, const void * Buffer, + intptr_t BufSize, TRegDataType RegData) +{ + DWORD DataType = RegDataToDataType(RegData); + if (::RegSetValueEx(GetCurrentKey(), Name.c_str(), 0, DataType, + reinterpret_cast(Buffer), static_cast(BufSize)) != ERROR_SUCCESS) + { + throw Exception(L"RegSetValueEx failed"); // ERegistryException(); // FIXME .CreateResFmt(SRegSetDataFailed, Name.c_str()); + } +} + +void TRegistry::WriteBool(const UnicodeString & Name, bool Value) +{ + WriteInteger(Name, Value); +} + +void TRegistry::WriteDateTime(const UnicodeString & Name, const TDateTime & Value) +{ + double Val = Value.GetValue(); + PutData(Name, &Val, sizeof(double), rdBinary); +} + +void TRegistry::WriteFloat(const UnicodeString & Name, double Value) +{ + PutData(Name, &Value, sizeof(double), rdBinary); +} + +void TRegistry::WriteString(const UnicodeString & Name, const UnicodeString & Value) +{ + PutData(Name, static_cast(Value.c_str()), Value.Length() * sizeof(wchar_t) + 1, rdString); +} + +void TRegistry::WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value) +{ + PutData(Name, Value.c_str(), Value.Length() * sizeof(wchar_t) + 1, rdString); +} + +void TRegistry::WriteInteger(const UnicodeString & Name, intptr_t Value) +{ + DWORD Val = static_cast(Value); + PutData(Name, &Val, sizeof(Val), rdInteger); +} + +void TRegistry::WriteInt64(const UnicodeString & Name, int64_t Value) +{ + WriteBinaryData(Name, &Value, sizeof(Value)); +} + +void TRegistry::WriteBinaryData(const UnicodeString & Name, + const void * Buffer, size_t BufSize) +{ + PutData(Name, Buffer, BufSize, rdBinary); +} + +void TRegistry::ChangeKey(HKEY Value, const UnicodeString & APath) +{ + CloseKey(); + FCurrentKey = Value; + FCurrentPath = APath; +} + +HKEY TRegistry::GetBaseKey(bool Relative) const +{ + HKEY Result = 0; + if ((FCurrentKey == 0) || !Relative) + { + Result = GetRootKey(); + } + else + { + Result = FCurrentKey; + } + return Result; +} + +HKEY TRegistry::GetKey(const UnicodeString & Key) const +{ + UnicodeString S = Key; + bool Relative = IsRelative(S); + HKEY Result = 0; + if (::RegOpenKeyEx(GetBaseKey(Relative), S.c_str(), 0, FAccess, &Result) == ERROR_SUCCESS) + return Result; + else + return 0; +} + +bool TRegistry::GetKeyInfo(TRegKeyInfo & Value) const +{ + ClearStruct(Value); +#ifndef __linux__ + bool Result = ::RegQueryInfoKey(GetCurrentKey(), nullptr, nullptr, nullptr, &Value.NumSubKeys, + &Value.MaxSubKeyLen, nullptr, &Value.NumValues, &Value.MaxValueLen, + &Value.MaxDataLen, nullptr, &Value.FileTime) == ERROR_SUCCESS; + return Result; +#else + return false; +#endif +} + +TShortCut::TShortCut() : FValue(0) +{ +} + +TShortCut::TShortCut(intptr_t Value) : + FValue(Value) +{ +} + +TShortCut::operator intptr_t() const +{ + return FValue; +} + +bool TShortCut::operator < (const TShortCut & rhs) const +{ + return FValue < rhs.FValue; +} + +void GetLocaleFormatSettings(int LCID, TFormatSettings & FormatSettings) +{ + (void)LCID; + (void)FormatSettings; + ThrowNotImplemented(1204); +} + +NB_IMPLEMENT_CLASS(TObject, nullptr, nullptr) +NB_IMPLEMENT_CLASS(TPersistent, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TList, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TObjectList, NB_GET_CLASS_INFO(TList), nullptr) +NB_IMPLEMENT_CLASS(TStrings, NB_GET_CLASS_INFO(TPersistent), nullptr) +NB_IMPLEMENT_CLASS(TStringList, NB_GET_CLASS_INFO(TStrings), nullptr) + diff --git a/netbox/src/base/Classes.hpp b/netbox/src/base/Classes.hpp new file mode 100644 index 000000000..a15b8501e --- /dev/null +++ b/netbox/src/base/Classes.hpp @@ -0,0 +1,724 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 1) + +#include +#include +#include + +#pragma warning(pop) + +#define NPOS static_cast(-1) + +typedef HANDLE THandle; +typedef DWORD TThreadID; + +class Exception; + +extern const intptr_t MonthsPerYear; +extern const intptr_t DaysPerWeek; +extern const intptr_t MinsPerHour; +extern const intptr_t MinsPerDay; +extern const intptr_t SecsPerMin; +extern const intptr_t SecsPerHour; +extern const intptr_t HoursPerDay; +extern const intptr_t SecsPerDay; +extern const intptr_t MSecsPerDay; +extern const intptr_t MSecsPerSec; +extern const intptr_t OneSecond; +extern const intptr_t DateDelta; +extern const intptr_t UnixDateDelta; + +class TObject; +DEFINE_CALLBACK_TYPE0(TThreadMethod, void); + +DEFINE_CALLBACK_TYPE1(TNotifyEvent, void, TObject * /*Sender*/); + +void Abort(); +void Error(intptr_t Id, intptr_t ErrorId); +void ThrowNotImplemented(intptr_t ErrorId); + +class TObject +{ +CUSTOM_MEM_ALLOCATION_IMPL +NB_DECLARE_CLASS(TObject) +public: + TObject() {} + virtual ~TObject() {} + virtual void Changed() {} + + bool IsKindOf(TObjectClassId ClassId) const; +}; + +struct TPoint +{ + int x; + int y; + TPoint() : + x(0), + y(0) + {} + TPoint(int ax, int ay) : + x(ax), + y(ay) + {} +}; + +struct TRect +{ + int Left; + int Top; + int Right; + int Bottom; + int Width() const { return Right - Left; } + int Height() const { return Bottom - Top; } + TRect() : + Left(0), + Top(0), + Right(0), + Bottom(0) + {} + TRect(int left, int top, int right, int bottom) : + Left(left), + Top(top), + Right(right), + Bottom(bottom) + {} + bool operator == (const TRect & other) const + { + return + Left == other.Left && + Top == other.Top && + Right == other.Right && + Bottom == other.Bottom; + } + bool operator != (const TRect & other) const + { + return !(operator == (other)); + } + bool operator == (const RECT & other) const + { + return + Left == other.left && + Top == other.top && + Right == other.right && + Bottom == other.bottom; + } + bool operator != (const RECT & other) const + { + return !(operator == (other)); + } +}; + +class TPersistent : public TObject +{ +NB_DECLARE_CLASS(TPersistent) +public: + TPersistent(); + virtual ~TPersistent(); + virtual void Assign(const TPersistent * Source); + virtual TPersistent * GetOwner(); +protected: + virtual void AssignTo(TPersistent * Dest) const; +private: + void AssignError(const TPersistent * Source); +}; + +enum TListNotification +{ + lnAdded, + lnExtracted, + lnDeleted, +}; + +typedef intptr_t (CompareFunc)(const void * Item1, const void * Item2); + +class TList : public TObject +{ +NB_DECLARE_CLASS(TList) +public: + TList(); + virtual ~TList(); + void * operator [](intptr_t Index) const; + virtual void * GetItem(intptr_t Index) const { return FList[Index]; } + virtual void * GetItem(intptr_t Index) { return FList[Index]; } + void SetItem(intptr_t Index, void * Item); + intptr_t Add(void * Value); + void * Extract(void * Item); + intptr_t Remove(void * Item); + virtual void Move(intptr_t CurIndex, intptr_t NewIndex); + virtual void Delete(intptr_t Index); + void Insert(intptr_t Index, void * Item); + intptr_t IndexOf(const void * Value) const; + virtual void Clear(); + virtual void Sort(CompareFunc Func); + virtual void Notify(void * Ptr, TListNotification Action); + virtual void Sort(); + + intptr_t GetCount() const; + void SetCount(intptr_t Value); + +private: + std::vector FList; +}; + +class TObjectList : public TList +{ +NB_DECLARE_CLASS(TObjectList) +public: + TObjectList(); + virtual ~TObjectList(); + + TObject * operator [](intptr_t Index) const; + TObject * GetObj(intptr_t Index) const; + void SetItem(intptr_t Index, TObject * Value); + intptr_t Add(TObject * Value); + intptr_t Remove(TObject * Value); + void Extract(TObject * Value); + virtual void Move(intptr_t Index, intptr_t To); + virtual void Delete(intptr_t Index); + void Insert(intptr_t Index, TObject * Value); + intptr_t IndexOf(const TObject * Value) const; + virtual void Clear(); + bool GetOwnsObjects() const { return FOwnsObjects; } + void SetOwnsObjects(bool Value) { FOwnsObjects = Value; } + virtual void Sort(CompareFunc func); + virtual void Notify(void * Ptr, TListNotification Action); + +private: + bool FOwnsObjects; +}; + +enum TDuplicatesEnum +{ + dupAccept, + dupError, + dupIgnore +}; + +class TStream; + +class TStrings : public TPersistent +{ +NB_DECLARE_CLASS(TStrings) +public: + TStrings(); + virtual ~TStrings(); + intptr_t Add(const UnicodeString & S, TObject * AObject = nullptr); + virtual void Delete(intptr_t Index) = 0; + virtual UnicodeString GetTextStr() const; + virtual void SetTextStr(const UnicodeString & Text); + virtual void BeginUpdate(); + virtual void EndUpdate(); + virtual void SetUpdateState(bool Updating); + virtual intptr_t AddObject(const UnicodeString & S, TObject * AObject); + virtual void InsertObject(intptr_t Index, const UnicodeString & Key, TObject * AObject); + bool Equals(const TStrings * Value) const; + virtual void Clear() = 0; + void Move(intptr_t CurIndex, intptr_t NewIndex); + virtual intptr_t IndexOf(const UnicodeString & S) const; + virtual intptr_t IndexOfName(const UnicodeString & Name) const; + UnicodeString ExtractName(const UnicodeString & S) const; + void AddStrings(const TStrings * Strings); + void Append(const UnicodeString & Value); + virtual void Insert(intptr_t Index, const UnicodeString & AString, TObject * AObject = nullptr) = 0; + void SaveToStream(TStream * Stream) const; + wchar_t GetDelimiter() const { return FDelimiter; } + void SetDelimiter(wchar_t Value) { FDelimiter = Value; } + wchar_t GetQuoteChar() const { return FQuoteChar; } + void SetQuoteChar(wchar_t Value) { FQuoteChar = Value; } + UnicodeString GetDelimitedText() const; + void SetDelimitedText(const UnicodeString & Value); + virtual intptr_t CompareStrings(const UnicodeString & S1, const UnicodeString & S2) const; + intptr_t GetUpdateCount() const { return FUpdateCount; } + virtual void Assign(const TPersistent * Source); + virtual intptr_t GetCount() const = 0; + +public: + virtual TObject * GetObj(intptr_t Index) const = 0; + virtual void SetObj(intptr_t Index, TObject * AObject) = 0; + virtual bool GetSorted() const = 0; + virtual void SetSorted(bool Value) = 0; + virtual bool GetCaseSensitive() const = 0; + virtual void SetCaseSensitive(bool Value) = 0; + void SetDuplicates(TDuplicatesEnum Value); + UnicodeString GetCommaText() const; + void SetCommaText(const UnicodeString & Value); + virtual UnicodeString GetText() const; + virtual void SetText(const UnicodeString & Text); + virtual const UnicodeString & GetString(intptr_t Index) const = 0; + virtual void SetString(intptr_t Index, const UnicodeString & S) = 0; + const UnicodeString GetName(intptr_t Index) const; + void SetName(intptr_t Index, const UnicodeString & Value); + const UnicodeString GetValue(const UnicodeString & Name) const; + void SetValue(const UnicodeString & Name, const UnicodeString & Value); + UnicodeString GetValueFromIndex(intptr_t Index) const; + +protected: + TDuplicatesEnum FDuplicates; + mutable wchar_t FDelimiter; + mutable wchar_t FQuoteChar; + intptr_t FUpdateCount; +}; + +class TStringList; +typedef intptr_t (TStringListSortCompare)(TStringList * List, intptr_t Index1, intptr_t Index2); + +class TStringList : public TStrings +{ +friend intptr_t StringListCompareStrings(TStringList * List, intptr_t Index1, intptr_t Index2); +NB_DECLARE_CLASS(TStringList) +public: + TStringList(); + virtual ~TStringList(); + + intptr_t Add(const UnicodeString & S); + virtual intptr_t AddObject(const UnicodeString & S, TObject * AObject); + void LoadFromFile(const UnicodeString & AFileName); + TNotifyEvent & GetOnChange() { return FOnChange; } + void SetOnChange(TNotifyEvent OnChange) { FOnChange = OnChange; } + TNotifyEvent & GetOnChanging() { return FOnChanging; } + void SetOnChanging(TNotifyEvent OnChanging) { FOnChanging = OnChanging; } + void InsertItem(intptr_t Index, const UnicodeString & S, TObject * AObject); + void QuickSort(intptr_t L, intptr_t R, TStringListSortCompare SCompare); + + virtual void Assign(const TPersistent * Source); + virtual void Clear(); + virtual bool Find(const UnicodeString & S, intptr_t & Index) const; + virtual intptr_t IndexOf(const UnicodeString & S) const; + virtual void Delete(intptr_t Index); + virtual void InsertObject(intptr_t Index, const UnicodeString & Key, TObject * AObject); + virtual void Sort(); + virtual void CustomSort(TStringListSortCompare ACompareFunc); + + virtual void SetUpdateState(bool Updating); + virtual void Changing(); + virtual void Changed(); + virtual void Insert(intptr_t Index, const UnicodeString & S, TObject * AObject = nullptr); + virtual intptr_t CompareStrings(const UnicodeString & S1, const UnicodeString & S2) const; + virtual intptr_t GetCount() const; + +public: + virtual TObject * GetObj(intptr_t Index) const; + virtual void SetObj(intptr_t Index, TObject * AObject); + virtual bool GetSorted() const { return FSorted; } + virtual void SetSorted(bool Value); + virtual bool GetCaseSensitive() const { return FCaseSensitive; } + virtual void SetCaseSensitive(bool Value); + virtual const UnicodeString & GetString(intptr_t Index) const; + virtual void SetString(intptr_t Index, const UnicodeString & S); + +private: + TNotifyEvent FOnChange; + TNotifyEvent FOnChanging; + std::vector FStrings; + std::vector FObjects; + bool FSorted; + bool FCaseSensitive; + +private: + void ExchangeItems(intptr_t Index1, intptr_t Index2); + +private: + TStringList(const TStringList &); + TStringList & operator=(const TStringList &); +}; + +/// TDateTime: number of days since 12/30/1899 +class TDateTime : public TObject +{ +public: + TDateTime() : + FValue(0.0) + {} + explicit TDateTime(double Value) : + FValue(Value) + { + } + explicit TDateTime(uint16_t Hour, + uint16_t Min, uint16_t Sec, uint16_t MSec); + TDateTime(const TDateTime & rhs) : + FValue(rhs.FValue) + { + } + double GetValue() const { return operator double(); } + TDateTime & operator = (const TDateTime & rhs) + { + FValue = rhs.FValue; + return *this; + } + operator double() const + { + return FValue; + } + TDateTime & operator + (const TDateTime & rhs) + { + FValue += rhs.FValue; + return *this; + } + TDateTime & operator += (const TDateTime & rhs) + { + FValue += rhs.FValue; + return *this; + } + TDateTime & operator += (double val) + { + FValue += val; + return *this; + } + TDateTime & operator - (const TDateTime & rhs) + { + FValue -= rhs.FValue; + return *this; + } + TDateTime & operator -= (const TDateTime & rhs) + { + FValue -= rhs.FValue; + return *this; + } + TDateTime & operator -= (double val) + { + FValue -= val; + return *this; + } + TDateTime & operator = (double Value) + { + FValue = Value; + return *this; + } + bool operator == (const TDateTime & rhs); + bool operator != (const TDateTime & rhs) + { + return !(operator == (rhs)); + } + UnicodeString DateString() const; + UnicodeString TimeString(bool Short) const; + UnicodeString FormatString(wchar_t * fmt) const; + void DecodeDate(uint16_t & Y, + uint16_t & M, uint16_t & D) const; + void DecodeTime(uint16_t & H, + uint16_t & N, uint16_t & S, uint16_t & MS) const; +private: + double FValue; +}; + +#define MinDateTime TDateTime(-657434.0) + +TDateTime Now(); +TDateTime SpanOfNowAndThen(const TDateTime & ANow, const TDateTime & AThen); +double MilliSecondSpan(const TDateTime & ANow, const TDateTime & AThen); +int64_t MilliSecondsBetween(const TDateTime & ANow, const TDateTime & AThen); +int64_t SecondsBetween(const TDateTime & ANow, const TDateTime & AThen); + +#ifndef __linux__ +class TSHFileInfo : public TObject +{ + typedef DWORD_PTR (WINAPI * TGetFileInfo)( + _In_ LPCTSTR pszPath, + DWORD dwFileAttributes, + _Inout_ SHFILEINFO *psfi, + UINT cbFileInfo, + UINT uFlags); + +public: + TSHFileInfo(); + virtual ~TSHFileInfo(); + + //get the image's index in the system's image list + int GetFileIconIndex(const UnicodeString & StrFileName, BOOL bSmallIcon) const; + int GetDirIconIndex(BOOL bSmallIcon); + + //get file type + UnicodeString GetFileType(const UnicodeString & StrFileName); + +private: + TGetFileInfo FGetFileInfo; +}; +#endif + +enum TSeekOrigin +{ + soFromBeginning = 0, + soFromCurrent = 1, + soFromEnd = 2 +}; + +class TStream : public TObject +{ +public: + TStream(); + virtual ~TStream(); + virtual int64_t Read(void * Buffer, int64_t Count) = 0; + virtual int64_t Write(const void * Buffer, int64_t Count) = 0; + virtual int64_t Seek(int64_t Offset, int Origin) = 0; + virtual int64_t Seek(const int64_t Offset, TSeekOrigin Origin) = 0; + void ReadBuffer(void * Buffer, int64_t Count); + void WriteBuffer(const void * Buffer, int64_t Count); + int64_t CopyFrom(TStream * Source, int64_t Count); + +public: + int64_t GetPosition() + { + return Seek(0, soFromCurrent); + } + int64_t GetSize() + { + int64_t Pos = Seek(0, soFromCurrent); + int64_t Result = Seek(0, soFromEnd); + Seek(Pos, soFromBeginning); + return Result; + } + +public: + virtual void SetSize(const int64_t NewSize) = 0; + void SetPosition(const int64_t Pos) + { + Seek(Pos, soFromBeginning); + } +}; + +class THandleStream : public TStream +{ +NB_DISABLE_COPY(THandleStream) +public: + explicit THandleStream(HANDLE AHandle); + virtual ~THandleStream(); + virtual int64_t Read(void * Buffer, int64_t Count); + virtual int64_t Write(const void * Buffer, int64_t Count); + virtual int64_t Seek(int64_t Offset, int Origin); + virtual int64_t Seek(const int64_t Offset, TSeekOrigin Origin); + + HANDLE GetHandle() { return FHandle; } +protected: + virtual void SetSize(const int64_t NewSize); +protected: + HANDLE FHandle; +}; + +class TSafeHandleStream : public THandleStream +{ +public: + explicit TSafeHandleStream(THandle AHandle); + virtual ~TSafeHandleStream() {} + virtual int64_t Read(void * Buffer, int64_t Count); + virtual int64_t Write(const void * Buffer, int64_t Count); +}; + +class EReadError : public std::runtime_error +{ +public: + explicit EReadError(const char * Msg) : + std::runtime_error(Msg) + {} +}; + +class EWriteError : public std::runtime_error +{ +public: + explicit EWriteError(const char * Msg) : + std::runtime_error(Msg) + {} +}; + +class TMemoryStream : public TStream +{ +NB_DISABLE_COPY(TMemoryStream) +public: + TMemoryStream(); + virtual ~TMemoryStream(); + virtual int64_t Read(void * Buffer, int64_t Count); + virtual int64_t Seek(int64_t Offset, int Origin); + virtual int64_t Seek(const int64_t Offset, TSeekOrigin Origin); + void SaveToStream(TStream * Stream); + void SaveToFile(const UnicodeString & AFileName); + + void Clear(); + void LoadFromStream(TStream * Stream); + //void LoadFromFile(const UnicodeString & AFileName); + int64_t GetSize() const { return FSize; } + virtual void SetSize(const int64_t NewSize); + virtual int64_t Write(const void * Buffer, int64_t Count); + + void * GetMemory() const { return FMemory; } + +protected: + void SetPointer(void * Ptr, int64_t Size); + virtual void * Realloc(int64_t & NewCapacity); + int64_t GetCapacity() const { return FCapacity; } + +private: + void SetCapacity(int64_t NewCapacity); +private: + void * FMemory; + int64_t FSize; + int64_t FPosition; + int64_t FCapacity; +}; + +struct TRegKeyInfo +{ + DWORD NumSubKeys; + DWORD MaxSubKeyLen; + DWORD NumValues; + DWORD MaxValueLen; + DWORD MaxDataLen; + FILETIME FileTime; +}; + +enum TRegDataType +{ + rdUnknown, rdString, rdExpandString, rdInteger, rdBinary +}; + +struct TRegDataInfo +{ + TRegDataType RegData; + DWORD DataSize; +}; + +class TRegistry : public TObject +{ +NB_DISABLE_COPY(TRegistry) +public: + TRegistry(); + ~TRegistry(); + void GetValueNames(TStrings * Names) const; + void GetKeyNames(TStrings * Names) const; + void CloseKey(); + bool OpenKey(const UnicodeString & Key, bool CanCreate); + bool DeleteKey(const UnicodeString & Key); + bool DeleteValue(const UnicodeString & Value) const; + bool KeyExists(const UnicodeString & SubKey) const; + bool ValueExists(const UnicodeString & Value) const; + bool GetDataInfo(const UnicodeString & ValueName, TRegDataInfo & Value) const; + TRegDataType GetDataType(const UnicodeString & ValueName) const; + DWORD GetDataSize(const UnicodeString & Name) const; + bool ReadBool(const UnicodeString & Name); + TDateTime ReadDateTime(const UnicodeString & Name); + double ReadFloat(const UnicodeString & Name) const; + intptr_t ReadInteger(const UnicodeString & Name) const; + int64_t ReadInt64(const UnicodeString & Name); + UnicodeString ReadString(const UnicodeString & Name); + UnicodeString ReadStringRaw(const UnicodeString & Name); + size_t ReadBinaryData(const UnicodeString & Name, + void * Buffer, size_t Size) const; + + void WriteBool(const UnicodeString & Name, bool Value); + void WriteDateTime(const UnicodeString & Name, const TDateTime & Value); + void WriteFloat(const UnicodeString & Name, double Value); + void WriteString(const UnicodeString & Name, const UnicodeString & Value); + void WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value); + void WriteInteger(const UnicodeString & Name, intptr_t Value); + void WriteInt64(const UnicodeString & Name, int64_t Value); + void WriteBinaryData(const UnicodeString & Name, + const void * Buffer, size_t Size); +private: + void ChangeKey(HKEY Value, const UnicodeString & APath); + HKEY GetBaseKey(bool Relative) const; + HKEY GetKey(const UnicodeString & Key) const; + void SetCurrentKey(HKEY Value) { FCurrentKey = Value; } + bool GetKeyInfo(TRegKeyInfo & Value) const; + int GetData(const UnicodeString & Name, void * Buffer, + intptr_t BufSize, TRegDataType & RegData) const; + void PutData(const UnicodeString & Name, const void * Buffer, + intptr_t BufSize, TRegDataType RegData); + +public: + void SetAccess(uint32_t Value); + HKEY GetCurrentKey() const; + HKEY GetRootKey() const; + void SetRootKey(HKEY ARootKey); + +private: + HKEY FCurrentKey; + HKEY FRootKey; + // bool FLazyWrite; + UnicodeString FCurrentPath; + bool FCloseRootKey; + mutable uint32_t FAccess; +}; + +struct TTimeStamp +{ + int Time; // Number of milliseconds since midnight + int Date; // One plus number of days since 1/1/0001 +}; + +// FIXME +class TShortCut : public TObject +{ +public: + explicit TShortCut(); + explicit TShortCut(intptr_t Value); + operator intptr_t() const; + bool operator < (const TShortCut & rhs) const; + intptr_t Compare(const TShortCut & rhs) const { return FValue - rhs.FValue; } + +private: + intptr_t FValue; +}; + +enum TReplaceFlag +{ + rfReplaceAll, + rfIgnoreCase +}; + +enum TShiftStateFlag +{ + ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble, ssTouch, ssPen +}; + +inline double Trunc(double Value) { double intpart; modf(Value, &intpart); return intpart; } +inline double Frac(double Value) { double intpart; return modf(Value, &intpart); } +inline double Abs(double Value) { return fabs(Value); } + +// forms\InputDlg.cpp +struct TInputDialogData +{ +// TCustomEdit * Edit; + void * Edit; +}; + +//typedef void (__closure *TInputDialogInitialize) +// (TObject * Sender, TInputDialogData * Data); +DEFINE_CALLBACK_TYPE2(TInputDialogInitializeEvent, void, + TObject * /*Sender*/, TInputDialogData * /*Data*/); + +enum TQueryType +{ + qtConfirmation, + qtWarning, + qtError, + qtInformation, +}; + +struct TMessageParams; + +class TGlobalFunctionsIntf +{ +public: + virtual ~TGlobalFunctionsIntf() {} + + virtual HINSTANCE GetInstanceHandle() const = 0; + virtual UnicodeString GetMsg(intptr_t Id) const = 0; + virtual UnicodeString GetCurrDirectory() const = 0; + virtual UnicodeString GetStrVersionNumber() const = 0; + virtual bool InputDialog(const UnicodeString & ACaption, + const UnicodeString & APrompt, UnicodeString & Value, const UnicodeString & HelpKeyword, + TStrings * History, bool PathInput, + TInputDialogInitializeEvent OnInitialize, bool Echo) = 0; + virtual uintptr_t MoreMessageDialog(const UnicodeString & Message, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, + const TMessageParams * Params) = 0; +}; + +TGlobalFunctionsIntf * GetGlobalFunctions(); + diff --git a/netbox/src/base/Common.cpp b/netbox/src/base/Common.cpp new file mode 100644 index 000000000..d15fbbda5 --- /dev/null +++ b/netbox/src/base/Common.cpp @@ -0,0 +1,3197 @@ +#define NO_WIN32_LEAN_AND_MEAN +#include +#pragma hdrstop + +#ifndef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#ifndef __linux__ +#include +#include +#endif +#include +#include +#include + +#include "TextsCore.h" + +const wchar_t * DSTModeNames = L"Win;Unix;Keep"; + +const wchar_t EngShortMonthNames[12][4] = +{ + L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", + L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" +}; +const wchar_t TokenPrefix = L'%'; +const wchar_t NoReplacement = wchar_t(0); +const wchar_t TokenReplacement = wchar_t(1); + +UnicodeString ReplaceChar(const UnicodeString & Str, wchar_t A, wchar_t B) +{ + UnicodeString Result = Str; + for (wchar_t * Ch = const_cast(Result.c_str()); Ch && *Ch; ++Ch) + if (*Ch == A) + { + *Ch = B; + } + return Result; +} + +UnicodeString DeleteChar(const UnicodeString & Str, wchar_t C) +{ + UnicodeString Result = Str; + intptr_t P; + while ((P = Result.Pos(C)) > 0) + { + Result.Delete(P, 1); + } + return Result; +} + +template +void DoPackStr(T & Str) +{ + // Following will free unnecessary bytes + Str = Str.c_str(); +} + +void PackStr(UnicodeString & Str) +{ + DoPackStr(Str); +} + +void PackStr(RawByteString & Str) +{ + DoPackStr(Str); +} + +void PackStr(AnsiString & Str) +{ + DoPackStr(Str); +} + +template +void DoShred(T & Str) +{ + if (!Str.IsEmpty()) + { + Str.Unique(); + ::memset((void*)Str.c_str(), 0, Str.Length() * sizeof(*Str.c_str())); + Str = L""; + } +} + +void Shred(UnicodeString & Str) +{ + DoShred(Str); +} + +void Shred(UTF8String & Str) +{ + DoShred(Str); +} + +void Shred(AnsiString & Str) +{ + DoShred(Str); +} + +UnicodeString AnsiToString(const RawByteString & S) +{ + return UnicodeString(AnsiString(S)); +} + +UnicodeString AnsiToString(const char * S, size_t Len) +{ + return UnicodeString(AnsiString(S, Len)); +} + +UnicodeString MakeValidFileName(const UnicodeString & AFileName) +{ + UnicodeString Result = AFileName; + UnicodeString IllegalChars(L":;,=+<>|\"[] \\/?*"); + for (intptr_t Index = 0; Index < IllegalChars.Length(); ++Index) + { + Result = ReplaceChar(Result, IllegalChars[Index + 1], L'-'); + } + return Result; +} + +UnicodeString RootKeyToStr(HKEY RootKey) +{ + if (RootKey == HKEY_USERS) + return "HKU"; + else if (RootKey == HKEY_LOCAL_MACHINE) + return "HKLM"; + else if (RootKey == HKEY_CURRENT_USER) + return "HKCU"; + else if (RootKey == HKEY_CLASSES_ROOT) + return "HKCR"; +#ifndef __linux__ + else if (RootKey == HKEY_CURRENT_CONFIG) + return "HKCC"; + else if (RootKey == HKEY_DYN_DATA) + return "HKDD"; +#endif + else + { + Abort(); + return ""; + } +} + +UnicodeString BooleanToEngStr(bool B) +{ + if (B) + { + return "Yes"; + } + else + { + return "No"; + } +} + +UnicodeString BooleanToStr(bool B) +{ + if (B) + { + return LoadStr(YES_STR); + } + else + { + return LoadStr(NO_STR); + } +} + +UnicodeString DefaultStr(const UnicodeString & Str, const UnicodeString & Default) +{ + if (!Str.IsEmpty()) + { + return Str; + } + else + { + return Default; + } +} + +UnicodeString CutToChar(UnicodeString & Str, wchar_t Ch, bool Trim) +{ + intptr_t P = Str.Pos(Ch); + UnicodeString Result; + if (P) + { + Result = Str.SubString(1, P - 1); + Str.Delete(1, P); + } + else + { + Result = Str; + Str.Clear(); + } + if (Trim) + { + Result = Result.TrimRight(); + Str = Str.TrimLeft(); + } + return Result; +} + +UnicodeString CopyToChars(const UnicodeString & Str, intptr_t & From, const UnicodeString & Chs, bool Trim, + wchar_t * Delimiter, bool DoubleDelimiterEscapes) +{ + UnicodeString Result; + + intptr_t P; + for (P = From; P <= Str.Length(); P++) + { + if (::IsDelimiter(Chs, Str, P)) + { + if (DoubleDelimiterEscapes && + (P < Str.Length()) && + ::IsDelimiter(Chs, Str, P + 1)) + { + Result += Str[P]; + P++; + } + else + { + break; + } + } + else + { + Result += Str[P]; + } + } + + if (P <= Str.Length()) + { + if (Delimiter != nullptr) + { + *Delimiter = Str[P]; + } + } + else + { + if (Delimiter != nullptr) + { + *Delimiter = L'\0'; + } + } + // even if we reached the end, return index, as if there were the delimiter, + // so caller can easily find index of the end of the piece by subtracting + // 2 from From (as long as he did not asked for trimming) + From = P + 1; + if (Trim) + { + Result = Result.TrimRight(); + while ((From <= Str.Length()) && (Str[From] == L' ')) + { + From++; + } + } + return Result; +} + +UnicodeString CopyToChar(const UnicodeString & Str, wchar_t Ch, bool Trim) +{ + intptr_t From = 1; + return CopyToChars(Str, From, UnicodeString(Ch), Trim); +} + +UnicodeString DelimitStr(const UnicodeString & Str, const UnicodeString & Chars) +{ + UnicodeString Result = Str; + + for (intptr_t Index = 1; Index <= Result.Length(); ++Index) + { + if (Result.IsDelimiter(Chars, Index)) + { + Result.Insert(L"\\", Index); + ++Index; + } + } + return Result; +} + +UnicodeString ShellDelimitStr(const UnicodeString & Str, wchar_t Quote) +{ + UnicodeString Chars(L"$\\"); + if (Quote == L'"') + { + Chars += L"`\""; + } + return DelimitStr(Str, Chars); +} + +UnicodeString ExceptionLogString(Exception * E) +{ + DebugAssert(E); + if (NB_STATIC_DOWNCAST(Exception, E) != nullptr) + { + UnicodeString Msg; + Msg = FORMAT(L"%s", UnicodeString(E->what()).c_str()); + if (NB_STATIC_DOWNCAST(ExtException, E) != nullptr) + { + TStrings * MoreMessages = NB_STATIC_DOWNCAST(ExtException, E)->GetMoreMessages(); + if (MoreMessages) + { + Msg += L"\n" + + ReplaceStr(MoreMessages->GetText(), L"\r", L""); + } + } + return Msg; + } + else + { +#if defined(__BORLANDC__) + wchar_t Buffer[1024]; + ExceptionErrorMessage(ExceptObject(), ExceptAddr(), Buffer, _countof(Buffer)); + return UnicodeString(Buffer); +#else + return UnicodeString(E->what()); +#endif + } +} + +UnicodeString MainInstructions(const UnicodeString & S) +{ + UnicodeString MainMsgTag = LoadStr(MAIN_MSG_TAG); + return MainMsgTag + S + MainMsgTag; +} + +bool HasParagraphs(const UnicodeString & S) +{ + return (S.Pos(L"\n\n") > 0); +} + +UnicodeString MainInstructionsFirstParagraph(const UnicodeString & S) +{ + // WORKAROUND, we consider it bad practice, the highlighting should better + // be localized (but maybe we change our mind later) + UnicodeString Result; + intptr_t Pos = S.Pos(L"\n\n"); + // we would not be calling this on single paragraph message + if (DebugAlwaysTrue(Pos > 0)) + { + Result = + MainInstructions(S.SubString(1, Pos - 1)) + + S.SubString(Pos, S.Length() - Pos + 1); + } + else + { + Result = MainInstructions(S); + } + return Result; +} + +bool ExtractMainInstructions(UnicodeString & S, UnicodeString & MainInstructions) +{ + bool Result = false; + UnicodeString MainMsgTag = LoadStr(MAIN_MSG_TAG); + if (StartsStr(MainMsgTag, S)) + { + intptr_t EndTagPos = + S.SubString(MainMsgTag.Length() + 1, S.Length() - MainMsgTag.Length()).Pos(MainMsgTag); + if (EndTagPos > 0) + { + MainInstructions = S.SubString(MainMsgTag.Length() + 1, EndTagPos - 1); + S.Delete(1, EndTagPos + (2 * MainMsgTag.Length()) - 1); + Result = true; + } + } + + DebugAssert(MainInstructions.Pos(MainMsgTag) == 0); + DebugAssert(S.Pos(MainMsgTag) == 0); + + return Result; +} + +static intptr_t FindInteractiveMsgStart(const UnicodeString & S) +{ + intptr_t Result = 0; + UnicodeString InteractiveMsgTag = LoadStr(INTERACTIVE_MSG_TAG); + if (EndsStr(InteractiveMsgTag, S) && + (S.Length() >= 2 * InteractiveMsgTag.Length())) + { + Result = S.Length() - 2 * InteractiveMsgTag.Length() + 1; + while ((Result > 0) && (S.SubString(Result, InteractiveMsgTag.Length()) != InteractiveMsgTag)) + { + Result--; + } + } + return Result; +} + +UnicodeString RemoveMainInstructionsTag(const UnicodeString & S) +{ + UnicodeString Result = S; + + UnicodeString MainInstruction; + if (ExtractMainInstructions(Result, MainInstruction)) + { + Result = MainInstruction + Result; + } + return Result; +} + +UnicodeString UnformatMessage(const UnicodeString & S) +{ + UnicodeString Result = RemoveMainInstructionsTag(S); + + intptr_t InteractiveMsgStart = FindInteractiveMsgStart(Result); + if (InteractiveMsgStart > 0) + { + Result = Result.SubString(1, InteractiveMsgStart - 1); + } + return Result; +} + +UnicodeString RemoveInteractiveMsgTag(const UnicodeString & S) +{ + UnicodeString Result = S; + + intptr_t InteractiveMsgStart = FindInteractiveMsgStart(Result); + if (InteractiveMsgStart > 0) + { + UnicodeString InteractiveMsgTag = LoadStr(INTERACTIVE_MSG_TAG); + Result.Delete(InteractiveMsgStart, InteractiveMsgTag.Length()); + Result.Delete(Result.Length() - InteractiveMsgTag.Length() + 1, InteractiveMsgTag.Length()); + } + return Result; +} + +UnicodeString RemoveEmptyLines(const UnicodeString & S) +{ + return + ReplaceStr( + ReplaceStr(S.TrimRight(), L"\n\n", L"\n"), + L"\n \n", L"\n"); +} + +bool IsNumber(const UnicodeString & Str) +{ + int64_t Value = 0; + if (Str == L"0") + return true; + return TryStrToInt(Str, Value); +} + +UnicodeString GetSystemTemporaryDirectory() +{ +#ifndef __linux__ + UnicodeString TempDir; + TempDir.SetLength(NB_MAX_PATH); + TempDir.SetLength(::GetTempPath(NB_MAX_PATH, const_cast(TempDir.c_str()))); + return TempDir; +#else + return "/tmp"; +#endif +} + +UnicodeString GetShellFolderPath(int CSIdl) +{ + UnicodeString Result; + wchar_t Path[2 * MAX_PATH + 10] = L"\0"; +#ifndef __linux__ + if (SUCCEEDED(::SHGetFolderPath(nullptr, CSIdl, nullptr, SHGFP_TYPE_CURRENT, Path))) + { + Result = Path; + } +#endif + return Result; +} + +// Particularly needed when using file name selected by TFilenameEdit, +// as it wraps a path to double-quotes, when there is a space in the path. +UnicodeString StripPathQuotes(const UnicodeString & APath) +{ + if ((APath.Length() >= 2) && + (APath[1] == L'\"') && (APath[APath.Length()] == L'\"')) + { + return APath.SubString(2, APath.Length() - 2); + } + else + { + return APath; + } +} + +UnicodeString AddQuotes(const UnicodeString & AStr) +{ + UnicodeString Result = AStr; + if (Result.Pos(L" ") > 0) + { + Result = L"\"" + Result + L"\""; + } + return Result; +} + +UnicodeString AddPathQuotes(const UnicodeString & APath) +{ + UnicodeString Result = StripPathQuotes(APath); + return AddQuotes(Result); +} + +static wchar_t * ReplaceChar( + UnicodeString & AFileName, wchar_t * InvalidChar, wchar_t InvalidCharsReplacement) +{ + intptr_t Index = InvalidChar - AFileName.c_str() + 1; + if (InvalidCharsReplacement == TokenReplacement) + { + // currently we do not support unicode chars replacement + if (AFileName[Index] > 0xFF) + { + ThrowExtException(); + } + + AFileName.Insert(ByteToHex(static_cast(AFileName[Index])), Index + 1); + AFileName[Index] = TokenPrefix; + InvalidChar = const_cast(AFileName.c_str() + Index + 2); + } + else + { + AFileName[Index] = InvalidCharsReplacement; + InvalidChar = const_cast(AFileName.c_str() + Index); + } + return InvalidChar; +} + +UnicodeString ValidLocalFileName(const UnicodeString & AFileName) +{ + return ValidLocalFileName(AFileName, L'_', L"", LOCAL_INVALID_CHARS); +} + +UnicodeString ValidLocalFileName( + const UnicodeString & AFileName, wchar_t AInvalidCharsReplacement, + const UnicodeString & ATokenizibleChars, const UnicodeString & ALocalInvalidChars) +{ + UnicodeString Result = AFileName; + + if (AInvalidCharsReplacement != NoReplacement) + { + bool ATokenReplacement = (AInvalidCharsReplacement == TokenReplacement); + UnicodeString CharsStr = ATokenReplacement ? ATokenizibleChars : ALocalInvalidChars; + const wchar_t * Chars = + CharsStr.c_str(); + wchar_t * InvalidChar = const_cast(Result.c_str()); + while ((InvalidChar = wcspbrk(InvalidChar, Chars)) != nullptr) + { + intptr_t Pos = (InvalidChar - Result.c_str() + 1); + wchar_t Char; + if (ATokenReplacement && + (*InvalidChar == TokenPrefix) && + (((Result.Length() - Pos) <= 1) || + (((Char = static_cast(HexToByte(Result.SubString(Pos + 1, 2)))) == L'\0') || + (ATokenizibleChars.Pos(Char) == 0)))) + { + InvalidChar++; + } + else + { + InvalidChar = ReplaceChar(Result, InvalidChar, AInvalidCharsReplacement); + } + } + + // Windows trim trailing space or dot, hence we must encode it to preserve it + if (!Result.IsEmpty() && + ((Result[Result.Length()] == L' ') || + (Result[Result.Length()] == L'.'))) + { + ReplaceChar(Result, const_cast(Result.c_str() + Result.Length() - 1), AInvalidCharsReplacement); + } + + if (IsReservedName(Result)) + { + intptr_t P = Result.Pos(L"."); + if (P == 0) + { + P = Result.Length() + 1; + } + Result.Insert(L"%00", P); + } + } + return Result; +} + +void SplitCommand(const UnicodeString & Command, UnicodeString & Program, + UnicodeString & Params, UnicodeString & Dir) +{ + UnicodeString Cmd = Command.Trim(); + Params.Clear(); + Dir.Clear(); + if (!Cmd.IsEmpty() && (Cmd[1] == L'\"')) + { + Cmd.Delete(1, 1); + intptr_t P = Cmd.Pos(L'"'); + if (P) + { + Program = Cmd.SubString(1, P - 1).Trim(); + Params = Cmd.SubString(P + 1, Cmd.Length() - P).Trim(); + } + else + { + throw Exception(FMTLOAD(INVALID_SHELL_COMMAND, UnicodeString(L"\"" + Cmd).c_str())); + } + } + else + { + intptr_t P = Cmd.Pos(L" "); + if (P) + { + Program = Cmd.SubString(1, P).Trim(); + Params = Cmd.SubString(P + 1, Cmd.Length() - P).Trim(); + } + else + { + Program = Cmd; + } + } + intptr_t B = Program.LastDelimiter(L"\\/"); + if (B) + { + Dir = Program.SubString(1, B).Trim(); + } +} + +UnicodeString ExtractProgram(const UnicodeString & Command) +{ + UnicodeString Program; + UnicodeString Params; + UnicodeString Dir; + + SplitCommand(Command, Program, Params, Dir); + + return Program; +} + +UnicodeString ExtractProgramName(const UnicodeString & Command) +{ + UnicodeString Name = base::ExtractFileName(ExtractProgram(Command), false); + intptr_t Dot = Name.LastDelimiter(L"."); + if (Dot > 0) + { + Name = Name.SubString(1, Dot - 1); + } + return Name; +} + +UnicodeString FormatCommand(const UnicodeString & Program, const UnicodeString & AParams) +{ + UnicodeString Result = Program.Trim(); + UnicodeString Params = AParams.Trim(); + if (!Params.IsEmpty()) + Params = L" " + Params; + Result = AddQuotes(Result); + return Result + Params; +} + +const wchar_t ShellCommandFileNamePattern[] = L"!.!"; + +void ReformatFileNameCommand(UnicodeString & Command) +{ + if (!Command.IsEmpty()) + { + UnicodeString Program, Params, Dir; + SplitCommand(Command, Program, Params, Dir); + if (Params.Pos(ShellCommandFileNamePattern) == 0) + { + Params = Params + (Params.IsEmpty() ? L"" : L" ") + ShellCommandFileNamePattern; + } + Command = FormatCommand(Program, Params); + } +} + +UnicodeString ExpandFileNameCommand(const UnicodeString & Command, + const UnicodeString & AFileName) +{ + return AnsiReplaceStr(Command, ShellCommandFileNamePattern, + AddPathQuotes(AFileName)); +} + +UnicodeString EscapeParam(const UnicodeString & AParam) +{ + // Make sure this won't break RTF syntax + return ReplaceStr(AParam, L"\"", L"\"\""); +} + +UnicodeString EscapePuttyCommandParam(const UnicodeString & Param) +{ + UnicodeString Result = Param; + + bool Space = false; + + for (intptr_t Index = 1; Index <= Result.Length(); ++Index) + { + switch (Result[Index]) + { + case L'"': + Result.Insert(L"\\", Index); + ++Index; + break; + + case L' ': + Space = true; + break; + + case L'\\': + intptr_t I2 = Index; + while ((I2 <= Result.Length()) && (Result[I2] == L'\\')) + { + I2++; + } + if ((I2 <= Result.Length()) && (Result[I2] == L'"')) + { + while (Result[Index] == L'\\') + { + Result.Insert(L"\\", Index); + Index += 2; + } + Index--; + } + break; + } + } + + if (Space) + { + Result = L"\"" + Result + L'"'; + } + + return Result; +} + +UnicodeString ExpandEnvironmentVariables(const UnicodeString & Str) +{ +#ifndef __linux__ + UnicodeString Buf; + intptr_t Size = 1024; + + Buf.SetLength(Size); + intptr_t Len = ::ExpandEnvironmentStringsW(Str.c_str(), const_cast(Buf.c_str()), static_cast(Size)); + + if (Len > Size) + { + Buf.SetLength(Len); + ::ExpandEnvironmentStringsW(Str.c_str(), const_cast(Buf.c_str()), static_cast(Len)); + } + + PackStr(Buf); + + return Buf; +#else + return Str; +#endif +} + +bool CompareFileName(const UnicodeString & APath1, const UnicodeString & APath2) +{ + UnicodeString ShortPath1 = ExtractShortPathName(APath1); + UnicodeString ShortPath2 = ExtractShortPathName(APath2); + + bool Result; + // ExtractShortPathName returns empty string if file does not exist + if (ShortPath1.IsEmpty() || ShortPath2.IsEmpty()) + { + Result = AnsiSameText(APath1, APath2); + } + else + { + Result = AnsiSameText(ShortPath1, ShortPath2); + } + return Result; +} + +bool ComparePaths(const UnicodeString & APath1, const UnicodeString & APath2) +{ + TODO("ExpandUNCFileName"); + return AnsiSameText(::IncludeTrailingBackslash(APath1), ::IncludeTrailingBackslash(APath2)); +} + +intptr_t CompareLogicalText(const UnicodeString & S1, const UnicodeString & S2) +{ + if (S1.Length() > S2.Length()) + { + return 1; + } + else if (S1.Length() < S2.Length()) + { + return -1; + } + else + { +#if defined(_MSC_VER) + return ::StrCmpNCW(S1.c_str(), S2.c_str(), (int)S1.Length()); +#else + return S1.Compare(S2); +#endif + } +} + +bool IsReservedName(const UnicodeString & AFileName) +{ + UnicodeString FileName = AFileName; + + intptr_t P = FileName.Pos(L"."); + intptr_t Len = (P > 0) ? P - 1 : FileName.Length(); + if ((Len == 3) || (Len == 4)) + { + if (P > 0) + { + FileName.SetLength(P - 1); + } + static UnicodeString Reserved[] = + { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" + }; + for (intptr_t Index = 0; Index < static_cast(_countof(Reserved)); ++Index) + { + if (SameText(FileName, Reserved[Index])) + { + return true; + } + } + } + return false; +} + +// ApiPath support functions +// Inspired by +// http://stackoverflow.com/questions/18580945/need-clarification-for-converting-paths-into-long-unicode-paths-or-the-ones-star +// This can be reimplemented using PathCchCanonicalizeEx on Windows 8 and later +enum PATH_PREFIX_TYPE +{ + PPT_UNKNOWN, + PPT_ABSOLUTE, //Found absolute path that is none of the other types + PPT_UNC, //Found \\server\share\ prefix + PPT_LONG_UNICODE, //Found \\?\ prefix + PPT_LONG_UNICODE_UNC, //Found \\?\UNC\ prefix +}; + +static intptr_t PathRootLength(const UnicodeString & APath) +{ +#ifndef __linux__ + // Correction for PathSkipRoot API + + // Replace all /'s with \'s because PathSkipRoot can't handle /'s + UnicodeString Result = ReplaceChar(APath, L'/', L'\\'); + + // Now call the API + LPCTSTR Buffer = ::PathSkipRoot(Result.c_str()); + + return (Buffer != nullptr) ? (Buffer - Result.c_str()) : -1; +#else + return 0; +#endif +} + +static bool PathIsRelative_CorrectedForMicrosoftStupidity(const UnicodeString & APath) +{ +#ifndef __linux__ + // Correction for PathIsRelative API + + // Replace all /'s with \'s because PathIsRelative can't handle /'s + UnicodeString Result = ReplaceChar(APath, L'/', L'\\'); + + //Now call the API + return ::PathIsRelative(Result.c_str()) != FALSE; +#else + return false; +#endif +} + +static intptr_t GetOffsetAfterPathRoot(const UnicodeString & APath, PATH_PREFIX_TYPE & PrefixType) +{ + // Checks if 'pPath' begins with the drive, share, prefix, etc + // EXAMPLES: + // Path Return: Points at: PrefixType: + // Relative\Folder\File.txt 0 Relative\Folder\File.txt PPT_UNKNOWN + // \RelativeToRoot\Folder 1 RelativeToRoot\Folder PPT_ABSOLUTE + // C:\Windows\Folder 3 Windows\Folder PPT_ABSOLUTE + // \\server\share\Desktop 15 Desktop PPT_UNC + // \\?\C:\Windows\Folder 7 Windows\Folder PPT_LONG_UNICODE + // \\?\UNC\server\share\Desktop 21 Desktop PPT_LONG_UNICODE_UNC + // RETURN: + // = Index in 'pPath' after the root, or + // = 0 if no root was found + intptr_t Result = 0; + + PrefixType = PPT_UNKNOWN; + + if (!APath.IsEmpty()) + { + intptr_t Len = APath.Length(); + + bool WinXPOnly = !IsWinVista(); + + // The PathSkipRoot() API doesn't work correctly on Windows XP + if (!WinXPOnly) + { + // Works since Vista and up, but still needs correction :) + intptr_t RootLength = PathRootLength(APath); + if (RootLength >= 0) + { + Result = RootLength + 1; + } + } + + // Now determine the type of prefix + intptr_t IndCheckUNC = -1; + + if ((Len >= 8) && + (APath[1] == L'\\' || APath[1] == L'/') && + (APath[2] == L'\\' || APath[2] == L'/') && + (APath[3] == L'?') && + (APath[4] == L'\\' || APath[4] == L'/') && + (APath[5] == L'U' || APath[5] == L'u') && + (APath[6] == L'N' || APath[6] == L'n') && + (APath[7] == L'C' || APath[7] == L'c') && + (APath[8] == L'\\' || APath[8] == L'/')) + { + // Found \\?\UNC\ prefix + PrefixType = PPT_LONG_UNICODE_UNC; + + if (WinXPOnly) + { + //For older OS + Result += 8; + } + + //Check for UNC share later + IndCheckUNC = 8; + } + else if ((Len >= 4) && + (APath[1] == L'\\' || APath[1] == L'/') && + (APath[2] == L'\\' || APath[2] == L'/') && + (APath[3] == L'?') && + (APath[4] == L'\\' || APath[4] == L'/')) + { + // Found \\?\ prefix + PrefixType = PPT_LONG_UNICODE; + + if (WinXPOnly) + { + //For older OS + Result += 4; + } + } + else if ((Len >= 2) && + (APath[1] == L'\\' || APath[1] == L'/') && + (APath[2] == L'\\' || APath[2] == L'/')) + { + // Check for UNC share later + IndCheckUNC = 2; + } + + if (IndCheckUNC >= 0) + { + // Check for UNC, i.e. \\server\share\ part + intptr_t Index = IndCheckUNC; + for (int SkipSlashes = 2; SkipSlashes > 0; SkipSlashes--) + { + for(; Index <= Len; ++Index) + { + TCHAR z = APath[Index]; + if ((z == L'\\') || (z == L'/') || (Index >= Len)) + { + ++Index; + if (SkipSlashes == 1) + { + if (PrefixType == PPT_UNKNOWN) + { + PrefixType = PPT_UNC; + } + + if (WinXPOnly) + { + //For older OS + Result = Index; + } + } + + break; + } + } + } + } + + if (WinXPOnly) + { + // Only if we didn't determine any other type + if (PrefixType == PPT_UNKNOWN) + { + if (!PathIsRelative_CorrectedForMicrosoftStupidity(APath.SubString(Result, APath.Length() - Result + 1))) + { + PrefixType = PPT_ABSOLUTE; + } + } + + // For older OS only + intptr_t RootLength = PathRootLength(APath.SubString(Result, APath.Length() - Result + 1)); + if (RootLength >= 0) + { + Result = RootLength + 1; + } + } + else + { + // Only if we didn't determine any other type + if (PrefixType == PPT_UNKNOWN) + { + if (!PathIsRelative_CorrectedForMicrosoftStupidity(APath)) + { + PrefixType = PPT_ABSOLUTE; + } + } + } + } + + return Result; +} + +static UnicodeString MakeUnicodeLargePath(const UnicodeString & APath) +{ + // Convert path from 'into a larger Unicode path, that allows up to 32,767 character length + UnicodeString Result; + + if (!APath.IsEmpty()) + { + // Determine the type of the existing prefix + PATH_PREFIX_TYPE PrefixType; + GetOffsetAfterPathRoot(APath, PrefixType); + + // Assume path to be without change + Result = APath; + + switch (PrefixType) + { + case PPT_ABSOLUTE: + { + // First we need to check if its an absolute path relative to the root + bool AddPrefix = true; + if ((APath.Length() >= 1) && + ((APath[1] == L'\\') || (APath[1] == L'/'))) + { + AddPrefix = FALSE; + + // Get current root path + UnicodeString CurrentDir = GetCurrentDir(); + PATH_PREFIX_TYPE PrefixType2; // unused + intptr_t Following = GetOffsetAfterPathRoot(CurrentDir, PrefixType2); + if (Following > 0) + { + AddPrefix = true; + Result = CurrentDir.SubString(1, Following - 1) + Result.SubString(2, Result.Length() - 1); + } + } + + if (AddPrefix) + { + // Add \\?\ prefix + Result = L"\\\\?\\" + Result; + } + } + break; + + case PPT_UNC: + // First we need to remove the opening slashes for UNC share + if ((Result.Length() >= 2) && + ((Result[1] == L'\\') || (Result[1] == L'/')) && + ((Result[2] == L'\\') || (Result[2] == L'/'))) + { + Result = Result.SubString(3, Result.Length() - 2); + } + + // Add \\?\UNC\ prefix + Result = L"\\\\?\\UNC\\" + Result; + break; + + case PPT_LONG_UNICODE: + case PPT_LONG_UNICODE_UNC: + // nothing to do + break; + } + + } + + return Result; +} + +UnicodeString ApiPath(const UnicodeString & APath) +{ + UnicodeString Result = APath; + + if (IsWin7() || (Result.Length() >= MAX_PATH)) + { +// if (GetConfiguration() != nullptr) +// { +// GetConfiguration()->Usage->Inc(L"LongPath"); +// } + Result = MakeUnicodeLargePath(Result); + } + return Result; +} + +UnicodeString DisplayableStr(const RawByteString & Str) +{ + bool Displayable = true; + intptr_t Index1 = 1; + while ((Index1 <= Str.Length()) && Displayable) + { + if (((Str[Index1] < '\x20') || (static_cast(Str[Index1]) >= static_cast('\x80'))) && + (Str[Index1] != '\n') && (Str[Index1] != '\r') && (Str[Index1] != '\t') && (Str[Index1] != '\b')) + { + Displayable = false; + } + ++Index1; + } + + UnicodeString Result; + if (Displayable) + { + Result = L"\""; + for (intptr_t Index2 = 1; Index2 <= Str.Length(); ++Index2) + { + switch (Str[Index2]) + { + case '\n': + Result += L"\\n"; + break; + + case '\r': + Result += L"\\r"; + break; + + case '\t': + Result += L"\\t"; + break; + + case '\b': + Result += L"\\b"; + break; + + case '\\': + Result += L"\\\\"; + break; + + case '"': + Result += L"\\\""; + break; + + default: + Result += wchar_t(Str[Index2]); + break; + } + } + Result += L"\""; + } + else + { + Result = L"0x" + BytesToHex(Str); + } + return Result; +} + +UnicodeString ByteToHex(uint8_t B, bool UpperCase) +{ + static wchar_t UpperDigits[] = L"0123456789ABCDEF"; + static wchar_t LowerDigits[] = L"0123456789abcdef"; + + const wchar_t * Digits = (UpperCase ? UpperDigits : LowerDigits); + UnicodeString Result; + Result.SetLength(2); + Result[1] = Digits[(B & 0xF0) >> 4]; + Result[2] = Digits[(B & 0x0F) >> 0]; + return Result; +} + +UnicodeString BytesToHex(const uint8_t * B, uintptr_t Length, bool UpperCase, wchar_t Separator) +{ + UnicodeString Result; + for (uintptr_t Index = 0; Index < Length; ++Index) + { + Result += ByteToHex(B[Index], UpperCase); + if ((Separator != L'\0') && (Index < Length - 1)) + { + Result += Separator; + } + } + return Result; +} + +UnicodeString BytesToHex(const RawByteString & Str, bool UpperCase, wchar_t Separator) +{ + return BytesToHex(reinterpret_cast(Str.c_str()), Str.Length(), UpperCase, Separator); +} + +UnicodeString CharToHex(wchar_t Ch, bool UpperCase) +{ + return BytesToHex(reinterpret_cast(&Ch), sizeof(Ch), UpperCase); +} + +RawByteString HexToBytes(const UnicodeString & Hex) +{ + UnicodeString Digits = "0123456789ABCDEF"; + RawByteString Result; + intptr_t L = Hex.Length(); + if (L % 2 == 0) + { + for (intptr_t Index = 1; Index <= Hex.Length(); Index += 2) + { + intptr_t P1 = Digits.Pos(::UpCase(Hex[Index])); + intptr_t P2 = Digits.Pos(::UpCase(Hex[Index + 1])); + if (P1 <= 0 || P2 <= 0) + { + Result.Clear(); + break; + } + else + { + Result += static_cast((P1 - 1) * 16 + P2 - 1); + } + } + } + return Result; +} + +uint8_t HexToByte(const UnicodeString & Hex) +{ + static UnicodeString Digits = "0123456789ABCDEF"; + DebugAssert(Hex.Length() == 2); + intptr_t P1 = Digits.Pos(::UpCase(Hex[1])); + intptr_t P2 = Digits.Pos(::UpCase(Hex[2])); + + return + static_cast(((P1 <= 0) || (P2 <= 0)) ? 0 : (((P1 - 1) << 4) + (P2 - 1))); +} + +bool IsLowerCaseLetter(wchar_t Ch) +{ + return (Ch >= L'a') && (Ch <= L'z'); +} + +bool IsUpperCaseLetter(wchar_t Ch) +{ + return (Ch >= L'A') && (Ch <= L'Z'); +} + +bool IsLetter(wchar_t Ch) +{ + return IsLowerCaseLetter(Ch) || IsUpperCaseLetter(Ch); +} + +bool IsDigit(wchar_t Ch) +{ + return (Ch >= L'0') && (Ch <= L'9'); +} + +bool IsHex(wchar_t Ch) +{ + return + IsDigit(Ch) || + ((Ch >= L'A') && (Ch <= L'F')) || + ((Ch >= L'a') && (Ch <= L'f')); +} + +DWORD FindCheck(DWORD Result, const UnicodeString & APath) +{ + if ((Result != ERROR_SUCCESS) && + (Result != ERROR_FILE_NOT_FOUND) && + (Result != ERROR_NO_MORE_FILES)) + { + throw EOSExtException(FMTLOAD(FIND_FILE_ERROR, APath.c_str()), Result); + } + return Result; +} + +DWORD FindFirstUnchecked(const UnicodeString & APath, DWORD Attr, TSearchRecChecked & F) +{ + F.Path = APath; + return FindFirst(ApiPath(APath), Attr, F); +} + +DWORD FindFirstChecked(const UnicodeString & APath, DWORD LocalFileAttrs, TSearchRecChecked & F) +{ + // return FindCheck(FindFirst(Path, LocalFileAttrs, F)); + DWORD Result = FindFirstUnchecked(APath, LocalFileAttrs, F); + return FindCheck(Result, F.Path); +} + +// Equivalent to FindNext, just to complement to FindFirstUnchecked +DWORD FindNextUnchecked(TSearchRecChecked & F) +{ + return FindNext(F); +} + +// It can make sense to use FindNextChecked, even if unchecked FindFirst is used. +// I.e. even if we do not care that FindFirst failed, if FindNext +// fails after successful FindFirst, it mean some terrible problem +DWORD FindNextChecked(TSearchRecChecked & F) +{ + return FindCheck(FindNextUnchecked(F), F.Path); +} + +bool FileSearchRec(const UnicodeString & AFileName, TSearchRec & Rec) +{ + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + bool Result = (FindFirst(ApiPath(AFileName), FindAttrs, Rec) == 0); + if (Result) + { + FindClose(Rec); + } + return Result; +} + +void ProcessLocalDirectory(const UnicodeString & ADirName, + TProcessLocalFileEvent CallBackFunc, void * Param, + DWORD FindAttrs) +{ + DebugAssert(CallBackFunc); + if (FindAttrs == INVALID_FILE_ATTRIBUTES) + { + FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + } + TSearchRecChecked SearchRec; + + UnicodeString DirName = ApiPath(::IncludeTrailingBackslash(ADirName)); + if (FindFirstChecked(DirName + L"*.*", FindAttrs, SearchRec) == 0) + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + do + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + { + UnicodeString FileName = DirName + SearchRec.Name; + CallBackFunc(FileName, SearchRec, Param); + } + + } + while (FindNextChecked(SearchRec) == 0); + } +} + +DWORD FileGetAttrFix(const UnicodeString & FileName) +{ + // The default for FileGetAttr is to follow links + bool FollowLink = true; + // But the FileGetAttr whe called for link with FollowLink set will always fail + // as its calls InternalGetFileNameFromSymLink, which test for CheckWin32Version(6, 0) + if (!IsWinVista()) + { + FollowLink = false; + } + return ::FileGetAttr(FileName, FollowLink); +} + +TDateTime EncodeDateVerbose(Word Year, Word Month, Word Day) +{ + TDateTime Result; + try + { + Result = EncodeDate(Year, Month, Day); + } + catch (EConvertError & E) + { + throw EConvertError(FORMAT(L"%s [%04u-%02u-%02u]", E.Message.c_str(), int(Year), int(Month), int(Day))); + } + return Result; +} + +TDateTime EncodeTimeVerbose(Word Hour, Word Min, Word Sec, Word MSec) +{ + TDateTime Result; + try + { + Result = EncodeTime(Hour, Min, Sec, MSec); + } + catch (EConvertError & E) + { + throw EConvertError(FORMAT(L"%s [%02u:%02u:%02u.%04u]", E.Message.c_str(), int(Hour), int(Min), int(Sec), int(MSec))); + } + return Result; +} + +TDateTime SystemTimeToDateTimeVerbose(const SYSTEMTIME & SystemTime) +{ + try + { + TDateTime DateTime = SystemTimeToDateTime(SystemTime); + return DateTime; + } + catch (EConvertError & E) + { + throw EConvertError(FORMAT(L"%s [%d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%3.3d]", E.Message.c_str(), int(SystemTime.wYear), int(SystemTime.wMonth), int(SystemTime.wDay), int(SystemTime.wHour), int(SystemTime.wMinute), int(SystemTime.wSecond), int(SystemTime.wMilliseconds))); + } +} + +struct TDateTimeParams : public TObject +{ + TDateTimeParams() : + BaseDifference(0.0), + BaseDifferenceSec(0), + CurrentDaylightDifference(0.0), + CurrentDaylightDifferenceSec(0), + CurrentDifference(0.0), + CurrentDifferenceSec(0), + StandardDifference(0.0), + StandardDifferenceSec(0), + DaylightDifference(0.0), + DaylightDifferenceSec(0), + DaylightHack(false) + { + ClearStruct(SystemStandardDate); + ClearStruct(SystemDaylightDate); + } + TDateTime UnixEpoch; + double BaseDifference; + intptr_t BaseDifferenceSec; + // All Current* are actually global, not per-year + // are valid for Year 0 (current) only + double CurrentDaylightDifference; + intptr_t CurrentDaylightDifferenceSec; + double CurrentDifference; + intptr_t CurrentDifferenceSec; + double StandardDifference; + intptr_t StandardDifferenceSec; + double DaylightDifference; + intptr_t DaylightDifferenceSec; + SYSTEMTIME SystemStandardDate; + SYSTEMTIME SystemDaylightDate; + TDateTime StandardDate; + TDateTime DaylightDate; + UnicodeString StandardName; + UnicodeString DaylightName; + // This is actually global, not per-year + bool DaylightHack; + + bool HasDST() const + { + // On some systems it occurs that StandardDate is unset, while + // DaylightDate is set. MSDN states that this is invalid and + // should be treated as if there is no daylight saving. + // So check both. + return + (SystemStandardDate.wMonth != 0) && + (SystemDaylightDate.wMonth != 0); + } + + bool SummerDST() const + { + return HasDST() && (DaylightDate < StandardDate); + } +}; + +typedef std::map TYearlyDateTimeParams; +static TYearlyDateTimeParams YearlyDateTimeParams; +static TCriticalSection DateTimeParamsSection; +static void EncodeDSTMargin(const SYSTEMTIME & Date, uint16_t Year, + TDateTime & Result); + +static uint16_t DecodeYear(const TDateTime & DateTime) +{ + uint16_t Year, Month, Day; + DecodeDate(DateTime, Year, Month, Day); + return Year; +} + +static const TDateTimeParams * GetDateTimeParams(uint16_t Year) +{ + TGuard Guard(DateTimeParamsSection); + + TDateTimeParams * Result; + + TYearlyDateTimeParams::iterator it = YearlyDateTimeParams.find(Year); + if (it != YearlyDateTimeParams.end()) + { + Result = &(*it).second; + } + else + { + // creates new entry as a side effect + Result = &YearlyDateTimeParams[Year]; +#ifndef __linux__ + TIME_ZONE_INFORMATION TZI; + + uint32_t GTZI; + + HINSTANCE Kernel32 = ::GetModuleHandle(L"kernel32.dll"); + typedef BOOL (WINAPI * TGetTimeZoneInformationForYear)(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi, LPTIME_ZONE_INFORMATION ptzi); + TGetTimeZoneInformationForYear GetTimeZoneInformationForYear = + (TGetTimeZoneInformationForYear)::GetProcAddress(Kernel32, "GetTimeZoneInformationForYear"); + + if ((Year == 0) || (GetTimeZoneInformationForYear == nullptr)) + { + GTZI = GetTimeZoneInformation(&TZI); + } + else + { + GetTimeZoneInformationForYear(Year, nullptr, &TZI); + GTZI = TIME_ZONE_ID_UNKNOWN; + } + + switch (GTZI) + { + case TIME_ZONE_ID_UNKNOWN: + Result->CurrentDaylightDifferenceSec = 0; + break; + + case TIME_ZONE_ID_STANDARD: + Result->CurrentDaylightDifferenceSec = TZI.StandardBias; + break; + + case TIME_ZONE_ID_DAYLIGHT: + Result->CurrentDaylightDifferenceSec = TZI.DaylightBias; + break; + + case TIME_ZONE_ID_INVALID: + default: + throw Exception(FMTLOAD(TIMEZONE_ERROR)); + } + + Result->BaseDifferenceSec = TZI.Bias; + Result->BaseDifference = (double)(TZI.Bias) / MinsPerDay; + Result->BaseDifferenceSec *= SecsPerMin; + + Result->CurrentDifferenceSec = TZI.Bias + + Result->CurrentDaylightDifferenceSec; + Result->CurrentDifference = + (double)(Result->CurrentDifferenceSec) / MinsPerDay; + Result->CurrentDifferenceSec *= SecsPerMin; + + Result->CurrentDaylightDifference = + (double)(Result->CurrentDaylightDifferenceSec) / MinsPerDay; + Result->CurrentDaylightDifferenceSec *= SecsPerMin; + + Result->DaylightDifferenceSec = TZI.DaylightBias * SecsPerMin; + Result->DaylightDifference = (double)(TZI.DaylightBias) / MinsPerDay; + Result->StandardDifferenceSec = TZI.StandardBias * SecsPerMin; + Result->StandardDifference = (double)(TZI.StandardBias) / MinsPerDay; + + Result->SystemStandardDate = TZI.StandardDate; + Result->SystemDaylightDate = TZI.DaylightDate; + + uint16_t AYear = (Year != 0) ? Year : DecodeYear(Now()); + if (Result->HasDST()) + { + EncodeDSTMargin(Result->SystemStandardDate, AYear, Result->StandardDate); + EncodeDSTMargin(Result->SystemDaylightDate, AYear, Result->DaylightDate); + } + + Result->StandardName = TZI.StandardName; + Result->DaylightName = TZI.DaylightName; + + Result->DaylightHack = !IsWin7(); +#endif + } + + return Result; +} + +static void EncodeDSTMargin(const SYSTEMTIME & Date, uint16_t Year, + TDateTime & Result) +{ + if (Date.wYear == 0) + { + TDateTime Temp = EncodeDateVerbose(Year, Date.wMonth, 1); + Result = Temp + ((Date.wDayOfWeek - DayOfWeek(Temp) + 8) % 7) + + (7 * (Date.wDay - 1)); + // Day 5 means, the last occurrence of day-of-week in month + if (Date.wDay == 5) + { + uint16_t Month = static_cast(Date.wMonth + 1); + if (Month > 12) + { + Month = static_cast(Month - 12); + Year++; + } + + if (Result >= EncodeDateVerbose(Year, Month, 1)) + { + Result -= 7; + } + } + Result += EncodeTimeVerbose(Date.wHour, Date.wMinute, Date.wSecond, + Date.wMilliseconds); + } + else + { + Result = EncodeDateVerbose(Year, Date.wMonth, Date.wDay) + + EncodeTimeVerbose(Date.wHour, Date.wMinute, Date.wSecond, Date.wMilliseconds); + } +} + +static bool IsDateInDST(const TDateTime & DateTime) +{ + + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + + bool Result; + + // On some systems it occurs that StandardDate is unset, while + // DaylightDate is set. MSDN states that this is invalid and + // should be treated as if there is no daylight saving. + // So check both. + if (!Params->HasDST()) + { + Result = false; + } + else + { + + if (Params->SummerDST()) + { + Result = + (DateTime >= Params->DaylightDate) && + (DateTime < Params->StandardDate); + } + else + { + Result = + (DateTime < Params->StandardDate) || + (DateTime >= Params->DaylightDate); + } + } + return Result; +} + +bool UsesDaylightHack() +{ + return GetDateTimeParams(0)->DaylightHack; +} + +TDateTime UnixToDateTime(int64_t TimeStamp, TDSTMode DSTMode) +{ + DebugAssert(int(EncodeDateVerbose(1970, 1, 1)) == UnixDateDelta); + + TDateTime Result = TDateTime(UnixDateDelta + ((double)(TimeStamp) / SecsPerDay)); + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(Result)); + + if (Params->DaylightHack) + { + if ((DSTMode == dstmWin) || (DSTMode == dstmUnix)) + { + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + Result -= CurrentParams->CurrentDifference; + } + else if (DSTMode == dstmKeep) + { + Result -= Params->BaseDifference; + } + } + else + { + Result -= Params->BaseDifference; + } + + if ((DSTMode == dstmUnix) || (DSTMode == dstmKeep)) + { + Result -= DSTDifferenceForTime(Result); + } + + return Result; +} + +int64_t Round(double Number) +{ + double Floor = floor(Number); + double Ceil = ceil(Number); + return static_cast(((Number - Floor) > (Ceil - Number)) ? Ceil : Floor); +} + +bool TryRelativeStrToDateTime(const UnicodeString & AStr, TDateTime & DateTime) +{ + UnicodeString S = AStr.Trim(); + intptr_t Index = 1; + while ((Index <= S.Length()) && IsDigit(S[Index])) + { + ++Index; + } + UnicodeString NumberStr = S.SubString(1, Index - 1); + int64_t Number = 0; + bool Result = TryStrToInt(NumberStr, Number); + if (Result) + { + S.Delete(1, Index - 1); + S = S.Trim().UpperCase(); + DateTime = Now(); + // These may not overlap with ParseSize (K, M and G) + if (S == "S") + { + DateTime = IncSecond(DateTime, -Number); + } + else if (S == "N") + { + DateTime = IncMinute(DateTime, -Number); + } + else if (S == "H") + { + DateTime = IncHour(DateTime, -Number); + } + else if (S == "D") + { + DateTime = IncDay(DateTime, -Number); + } + else if (S == "Y") + { + DateTime = IncYear(DateTime, -Number); + } + else + { + Result = false; + } + } + return Result; +} + +static int64_t DateTimeToUnix(const TDateTime & DateTime) +{ + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + + DebugAssert(int(EncodeDateVerbose(1970, 1, 1)) == UnixDateDelta); + + return Round((double)(DateTime - UnixDateDelta) * SecsPerDay) + + CurrentParams->CurrentDifferenceSec; +} + +FILETIME DateTimeToFileTime(const TDateTime & DateTime, + TDSTMode /*DSTMode*/) +{ + int64_t UnixTimeStamp = ::DateTimeToUnix(DateTime); + + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + if (!Params->DaylightHack) + { + // We should probably use reversed code of FileTimeToDateTime here instead of custom implementation + + // We are incrementing and decrementing BaseDifferenceSec because it + // can actually change between years + // (as it did in Belarus from GMT+2 to GMT+3 between 2011 and 2012) + + UnixTimeStamp += (IsDateInDST(DateTime) ? + Params->DaylightDifferenceSec : Params->StandardDifferenceSec) + + Params->BaseDifferenceSec; + + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + UnixTimeStamp -= + CurrentParams->CurrentDaylightDifferenceSec + + CurrentParams->BaseDifferenceSec; + + } + + FILETIME Result; + (*(int64_t*)&(Result) = ((int64_t)(UnixTimeStamp) + 11644473600LL) * 10000000LL); + + return Result; +} + +TDateTime FileTimeToDateTime(const FILETIME & FileTime) +{ + // duplicated in DirView.pas + TDateTime Result; + // The 0xFFF... is sometime seen for invalid timestamps, + // it would cause failure in SystemTimeToDateTime below + if (FileTime.dwLowDateTime == DWORD(-1) / sizeof(DWORD)) //std::numeric_limits::max) // + { + Result = MinDateTime; + } + else + { + SYSTEMTIME SysTime; +#ifndef __linux__ + if (!UsesDaylightHack()) + { + SYSTEMTIME UniversalSysTime; + FileTimeToSystemTime(&FileTime, &UniversalSysTime); + SystemTimeToTzSpecificLocalTime(nullptr, &UniversalSysTime, &SysTime); + } + else +#endif + { + FILETIME LocalFileTime; + FileTimeToLocalFileTime(&FileTime, &LocalFileTime); + FileTimeToSystemTime(&LocalFileTime, &SysTime); + } + Result = SystemTimeToDateTimeVerbose(SysTime); + } +/* + SYSTEMTIME SysTime; + if (!UsesDaylightHack()) + { + SYSTEMTIME UniverzalSysTime; + FileTimeToSystemTime(&FileTime, &UniverzalSysTime); + SystemTimeToTzSpecificLocalTime(nullptr, &UniverzalSysTime, &SysTime); + } + else + { + FILETIME LocalFileTime; + FileTimeToLocalFileTime(&FileTime, &LocalFileTime); + FileTimeToSystemTime(&LocalFileTime, &SysTime); + } + TDateTime Result = SystemTimeToDateTime(SysTime); +*/ + return Result; +} + +int64_t ConvertTimestampToUnix(const FILETIME & FileTime, + TDSTMode DSTMode) +{ + int64_t Result = ((*(int64_t *) & (FileTime)) / 10000000LL - 11644473600LL); + if (UsesDaylightHack()) + { + if ((DSTMode == dstmUnix) || (DSTMode == dstmKeep)) + { + FILETIME LocalFileTime; + SYSTEMTIME SystemTime; + FileTimeToLocalFileTime(&FileTime, &LocalFileTime); + FileTimeToSystemTime(&LocalFileTime, &SystemTime); + TDateTime DateTime = SystemTimeToDateTimeVerbose(SystemTime); + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + Result += (IsDateInDST(DateTime) ? + Params->DaylightDifferenceSec : Params->StandardDifferenceSec); + + if (DSTMode == dstmKeep) + { + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + Result -= CurrentParams->CurrentDaylightDifferenceSec; + } + } + } + else + { + if (DSTMode == dstmWin) + { + FILETIME LocalFileTime; + SYSTEMTIME SystemTime; + FileTimeToLocalFileTime(&FileTime, &LocalFileTime); + FileTimeToSystemTime(&LocalFileTime, &SystemTime); + TDateTime DateTime = SystemTimeToDateTimeVerbose(SystemTime); + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + Result -= (IsDateInDST(DateTime) ? + Params->DaylightDifferenceSec : Params->StandardDifferenceSec); + } + } + + return Result; +} + +TDateTime ConvertTimestampToUTC(const TDateTime & DateTime) +{ + TDateTime Result = DateTime; + + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + Result += DSTDifferenceForTime(DateTime); + Result += Params->BaseDifference; + + if (Params->DaylightHack) + { + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + Result += CurrentParams->CurrentDaylightDifference; + } + + return Result; +} + +TDateTime ConvertTimestampFromUTC(const TDateTime & DateTime) +{ + TDateTime Result = DateTime; + + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(Result)); + Result -= DSTDifferenceForTime(DateTime); + Result -= Params->BaseDifference; + + if (Params->DaylightHack) + { + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + Result -= CurrentParams->CurrentDaylightDifference; + } + + return Result; +} + +int64_t ConvertTimestampToUnixSafe(const FILETIME & FileTime, + TDSTMode DSTMode) +{ + int64_t Result; + if ((FileTime.dwLowDateTime == 0) && + (FileTime.dwHighDateTime == 0)) + { + Result = ::DateTimeToUnix(Now()); + } + else + { + Result = ::ConvertTimestampToUnix(FileTime, DSTMode); + } + return Result; +} + +double DSTDifferenceForTime(const TDateTime & DateTime) +{ + double Result; + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime)); + if (IsDateInDST(DateTime)) + { + Result = Params->DaylightDifference; + } + else + { + Result = Params->StandardDifference; + } + return Result; +} + +TDateTime AdjustDateTimeFromUnix(const TDateTime & DateTime, TDSTMode DSTMode) +{ + TDateTime Result = DateTime; + + const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(Result)); + + if (Params->DaylightHack) + { + if ((DSTMode == dstmWin) || (DSTMode == dstmUnix)) + { + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + Result = Result - CurrentParams->CurrentDaylightDifference; + } + + if (!IsDateInDST(Result)) + { + if (DSTMode == dstmWin) + { + Result = Result - Params->DaylightDifference; + } + } + else + { + Result = Result - Params->StandardDifference; + } + } + else + { + if (DSTMode == dstmWin) + { + Result = Result + DSTDifferenceForTime(Result); + } + } + + return Result; +} + +UnicodeString FixedLenDateTimeFormat(const UnicodeString & Format) +{ + UnicodeString Result = Format; + bool AsIs = false; + + intptr_t Index = 1; + while (Index <= Result.Length()) + { + wchar_t F = Result[Index]; + if ((F == L'\'') || (F == L'\"')) + { + AsIs = !AsIs; + ++Index; + } + else if (!AsIs && ((F == L'a') || (F == L'A'))) + { + if (Result.SubString(Index, 5).LowerCase() == L"am/pm") + { + Index += 5; + } + else if (Result.SubString(Index, 3).LowerCase() == L"a/p") + { + Index += 3; + } + else if (Result.SubString(Index, 4).LowerCase() == L"ampm") + { + Index += 4; + } + else + { + ++Index; + } + } + else + { + if (!AsIs && (wcschr(L"dDeEmMhHnNsS", F) != nullptr) && + ((Index == Result.Length()) || (Result[Index + 1] != F))) + { + Result.Insert(F, Index); + } + + while ((Index <= Result.Length()) && (F == Result[Index])) + { + ++Index; + } + } + } + + return Result; +} + +UnicodeString FormatTimeZone(intptr_t Sec) +{ + UnicodeString Str; + TODO("implement class TTimeSpan"); + /* + TTimeSpan Span = TTimeSpan::FromSeconds(Sec); + if ((Span.Seconds == 0) && (Span.Minutes == 0)) + { + Str = FORMAT(L"%d", -Span.Hours); + } + else if (Span.Seconds == 0) + { + Str = FORMAT(L"%d:%2.2d", -Span.Hours, abs(Span.Minutes)); + } + else + { + Str = FORMAT(L"%d:%2.2d:%2.2d", -Span.Hours, abs(Span.Minutes), abs(Span.Seconds)); + } + Str = ((Span <= TTimeSpan::Zero) ? L"+" : L"") + Str;*/ + return Str; +} + +UnicodeString GetTimeZoneLogString() +{ + const TDateTimeParams * CurrentParams = GetDateTimeParams(0); + + UnicodeString Result = + FORMAT(L"Current: GMT%s", FormatTimeZone(CurrentParams->CurrentDifferenceSec).c_str()); + + if (!CurrentParams->HasDST()) + { + Result += FORMAT(L" (%s), No DST", CurrentParams->StandardName.c_str()); + } + else + { + Result += + FORMAT(L", Standard: GMT%s (%s), DST: GMT%s (%s), DST Start: %s, DST End: %s", + FormatTimeZone(CurrentParams->BaseDifferenceSec + CurrentParams->StandardDifferenceSec).c_str(), + CurrentParams->StandardName.c_str(), + FormatTimeZone(CurrentParams->BaseDifferenceSec + CurrentParams->DaylightDifferenceSec).c_str(), + CurrentParams->DaylightName.c_str(), + CurrentParams->DaylightDate.DateString().c_str(), + CurrentParams->StandardDate.DateString().c_str()); + } + + return Result; +} + +bool AdjustClockForDSTEnabled() +{ + // Windows XP deletes the DisableAutoDaylightTimeSet value when it is off + // (the later versions set it to DynamicDaylightTimeDisabled to 0) + bool DynamicDaylightTimeDisabled = false; + std::unique_ptr Registry(new TRegistry()); + Registry->SetAccess(KEY_READ); + try + { + Registry->SetRootKey(HKEY_LOCAL_MACHINE); + if (Registry->OpenKey("SYSTEM", false) && + Registry->OpenKey("CurrentControlSet", false) && + Registry->OpenKey("Control", false) && + Registry->OpenKey("TimeZoneInformation", false)) + { + if (Registry->ValueExists("DynamicDaylightTimeDisabled")) + { + DynamicDaylightTimeDisabled = Registry->ReadBool("DynamicDaylightTimeDisabled"); + } + // WORKAROUND + // Windows XP equivalent + else if (Registry->ValueExists("DisableAutoDaylightTimeSet")) + { + DynamicDaylightTimeDisabled = Registry->ReadBool("DisableAutoDaylightTimeSet"); + } + } + } + catch (...) + { + } + return !DynamicDaylightTimeDisabled; +} + +UnicodeString StandardDatestamp() +{ +#if defined(__BORLANDC__) + return FormatDateTime(L"yyyy'-'mm'-'dd", ConvertTimestampToUTC(Now())); +#else + TDateTime DT = ::ConvertTimestampToUTC(Now()); + uint16_t Y, M, D, H, N, S, MS; + DT.DecodeDate(Y, M, D); + DT.DecodeTime(H, N, S, MS); + UnicodeString Result = FORMAT(L"%04d-%02d-%02d", Y, M, D); + return Result; +#endif +} + +UnicodeString StandardTimestamp(const TDateTime & DateTime) +{ +#if defined(__BORLANDC__) + return FormatDateTime(L"yyyy'-'mm'-'dd'T'hh':'nn':'ss'.'zzz'Z'", ConvertTimestampToUTC(DateTime)); +#else + TDateTime DT = ::ConvertTimestampToUTC(DateTime); + uint16_t Y, M, D, H, N, S, MS; + DT.DecodeDate(Y, M, D); + DT.DecodeTime(H, N, S, MS); + UnicodeString Result = FORMAT(L"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", Y, M, D, H, N, S, MS); + return Result; +#endif +} + +UnicodeString StandardTimestamp() +{ + return StandardTimestamp(Now()); +} + +intptr_t CompareFileTime(const TDateTime & T1, const TDateTime & T2) +{ + TDateTime TwoSeconds(0, 0, 2, 0); + // "FAT" time precision + // (when one time is seconds-precision and other is millisecond-precision, + // we may have times like 12:00:00.000 and 12:00:01.999, which should + // be treated the same) + intptr_t Result; + if (T1 == T2) + { + // just optimization + Result = 0; + } + else if ((T1 < T2) && (T2 - T1 >= TwoSeconds)) + { + Result = -1; + } + else if ((T1 > T2) && (T1 - T2 >= TwoSeconds)) + { + Result = 1; + } + else + { + Result = 0; + } + return Result; +} + +intptr_t TimeToMSec(const TDateTime & T) +{ + return int(Round(double(T) * double(MSecsPerDay))); +} + +intptr_t TimeToSeconds(const TDateTime & T) +{ + return TimeToMSec(T) / MSecsPerSec; +} + +intptr_t TimeToMinutes(const TDateTime & T) +{ + return TimeToSeconds(T) / SecsPerMin; +} + +static bool DoRecursiveDeleteFile(const UnicodeString & AFileName, bool ToRecycleBin, UnicodeString & AErrorPath) +{ + bool Result; + + UnicodeString ErrorPath = AFileName; + +#ifndef __linux__ + if (!ToRecycleBin) +#endif + { + TSearchRecChecked SearchRec; + Result = FileSearchRec(AFileName, SearchRec); + if (Result) + { + if (FLAGCLEAR(SearchRec.Attr, faDirectory)) + { + Result = ::RemoveFile(AFileName); + } + else + { + Result = (FindFirstUnchecked(AFileName + L"\\*", faAnyFile, SearchRec) == 0); + + if (Result) + { + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + do + { + UnicodeString FileName2 = AFileName + L"\\" + SearchRec.Name; + if (FLAGSET(SearchRec.Attr, faDirectory)) + { + if ((SearchRec.Name != L".") && (SearchRec.Name != L"..")) + { + Result = DoRecursiveDeleteFile(FileName2, DebugAlwaysFalse(ToRecycleBin), ErrorPath); + } + } + else + { + Result = ::RemoveFile(FileName2); + if (!Result) + { + ErrorPath = FileName2; + } + } + } + while (Result && (FindNextUnchecked(SearchRec) == 0)); + } + + if (Result) + { + Result = ::RemoveDir(AFileName); + } + } + } + } + } +#ifndef __linux__ + else + { + SHFILEOPSTRUCT Data; + + ClearStruct(Data); + Data.hwnd = nullptr; + Data.wFunc = FO_DELETE; + // SHFileOperation does not support long paths anyway + UnicodeString FileList(ApiPath(AFileName)); + FileList.SetLength(FileList.Length() + 2); + FileList[FileList.Length() - 1] = L'\0'; + FileList[FileList.Length()] = L'\0'; + Data.pFrom = FileList.c_str(); + Data.pTo = L"\0\0"; // this will actually give one null more than needed + Data.fFlags = FOF_NOCONFIRMATION | FOF_RENAMEONCOLLISION | FOF_NOCONFIRMMKDIR | + FOF_NOERRORUI | FOF_SILENT; + if (DebugAlwaysTrue(ToRecycleBin)) + { + Data.fFlags |= FOF_ALLOWUNDO; + } + int ErrorCode = ::SHFileOperation(&Data); + Result = (ErrorCode == 0); + if (!Result) + { + // according to MSDN, SHFileOperation may return following non-Win32 + // error codes + if (((ErrorCode >= 0x71) && (ErrorCode <= 0x88)) || + (ErrorCode == 0xB7) || (ErrorCode == 0x402) || (ErrorCode == 0x10000) || + (ErrorCode == 0x10074)) + { + ErrorCode = 0; + } + ::SetLastError(ErrorCode); + } + } +#endif + + if (!Result) + { + AErrorPath = ErrorPath; + } + + return Result; +} + +bool RecursiveDeleteFile(const UnicodeString & AFileName, bool ToRecycleBin) +{ + UnicodeString ErrorPath; // unused + return DoRecursiveDeleteFile(AFileName, ToRecycleBin, ErrorPath); +} + +void RecursiveDeleteFileChecked(const UnicodeString & AFileName, bool ToRecycleBin) +{ + UnicodeString ErrorPath; + if (!DoRecursiveDeleteFile(AFileName, ToRecycleBin, ErrorPath)) + { + throw EOSExtException(FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, ErrorPath.c_str())); + } +} + +void DeleteFileChecked(const UnicodeString & AFileName) +{ + if (!::RemoveFile(AFileName)) + { + throw EOSExtException(FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, AFileName.c_str())); + } +} + +uintptr_t CancelAnswer(uintptr_t Answers) +{ + uintptr_t Result; + if ((Answers & qaCancel) != 0) + { + Result = qaCancel; + } + else if ((Answers & qaNo) != 0) + { + Result = qaNo; + } + else if ((Answers & qaAbort) != 0) + { + Result = qaAbort; + } + else if ((Answers & qaOK) != 0) + { + Result = qaOK; + } + else + { + DebugFail(); + Result = qaCancel; + } + return Result; +} + +uintptr_t AbortAnswer(uintptr_t Answers) +{ + uintptr_t Result; + if (FLAGSET(Answers, qaAbort)) + { + Result = qaAbort; + } + else + { + Result = CancelAnswer(Answers); + } + return Result; +} + +uintptr_t ContinueAnswer(uintptr_t Answers) +{ + uintptr_t Result; + if (FLAGSET(Answers, qaSkip)) + { + Result = qaSkip; + } + else if (FLAGSET(Answers, qaIgnore)) + { + Result = qaIgnore; + } + else if (FLAGSET(Answers, qaYes)) + { + Result = qaYes; + } + else if (FLAGSET(Answers, qaOK)) + { + Result = qaOK; + } + else if (FLAGSET(Answers, qaRetry)) + { + Result = qaRetry; + } + else + { + Result = CancelAnswer(Answers); + } + return Result; +} + +UnicodeString LoadStr(intptr_t Ident, uintptr_t /*MaxLength*/) +{ + UnicodeString Result = GetGlobalFunctions()->GetMsg(Ident); + return Result; +} + +UnicodeString LoadStrPart(intptr_t Ident, intptr_t Part) +{ + UnicodeString Result; + UnicodeString Str = LoadStr(Ident); + + while (Part > 0) + { + Result = CutToChar(Str, L'|', false); + Part--; + } + return Result; +} + +UnicodeString DecodeUrlChars(const UnicodeString & S) +{ + UnicodeString Result = S; + + intptr_t Index = 1; + while (Index <= Result.Length()) + { + switch (Result[Index]) + { + case L'+': + Result[Index] = L' '; + break; + + case L'%': + UnicodeString Hex; + while ((Index + 2 <= Result.Length()) && (Result[Index] == L'%') && + IsHex(Result[Index + 1]) && IsHex(Result[Index + 2])) + { + Hex += Result.SubString(Index + 1, 2); + Result.Delete(Index, 3); + } + + if (!Hex.IsEmpty()) + { + RawByteString Bytes = HexToBytes(Hex); + UTF8String UTF8(Bytes.c_str(), Bytes.Length()); + UnicodeString Chars(UTF8); + Result.Insert(Chars, Index); + Index += Chars.Length() - 1; + } + break; + } + ++Index; + } + return Result; +} + +UnicodeString DoEncodeUrl(const UnicodeString & S, bool EncodeSlash) +{ + UnicodeString Result = S; + + intptr_t Index = 1; + while (Index <= Result.Length()) + { + wchar_t C = Result[Index]; + if (IsLetter(C) || + IsDigit(C) || + (C == L'_') || (C == L'-') || (C == L'.') || + ((C == L'/') && !EncodeSlash)) + { + ++Index; + } + else + { + UTF8String UtfS(Result.SubString(Index, 1)); + UnicodeString H; + for (intptr_t Index2 = 1; Index2 <= UtfS.Length(); ++Index2) + { + H += L"%" + ByteToHex(static_cast(UtfS[Index2])); + } + Result.Delete(Index, 1); + Result.Insert(H, Index); + Index += H.Length(); + } +#if 0 + if (Chars.Pos(Result[Index]) > 0) + { + UnicodeString H = ByteToHex(AnsiString(UnicodeString(Result[Index]))[1]); + Result.Insert(H, Index + 1); + Result[Index] = L'%'; + Index += H.Length(); + } + ++Index; +#endif + } + return Result; +} + +UnicodeString EncodeUrlString(const UnicodeString & S) +{ + return DoEncodeUrl(S, true); +} + +UnicodeString EncodeUrlPath(const UnicodeString & S) +{ + return DoEncodeUrl(S, false); +} + +UnicodeString AppendUrlParams(const UnicodeString & AURL, const UnicodeString & Params) +{ + UnicodeString URL = AURL; + // see also TWebHelpSystem::ShowHelp + const wchar_t FragmentSeparator = L'#'; + UnicodeString Result = ::CutToChar(URL, FragmentSeparator, false); + + if (Result.Pos(L"?") == 0) + { + Result += L"?"; + } + else + { + Result += L"&"; + } + + Result += Params; + + AddToList(Result, URL, FragmentSeparator); + + return Result; +} + +UnicodeString ExtractFileNameFromUrl(const UnicodeString & Url) +{ + UnicodeString Result = Url; + intptr_t P = Result.Pos(L"?"); + if (P > 0) + { + Result.SetLength(P - 1); + } + P = Result.LastDelimiter("/"); + if (DebugAlwaysTrue(P > 0)) + { + Result.Delete(1, P); + } + return Result; +} + +UnicodeString EscapeHotkey(const UnicodeString & Caption) +{ + return ReplaceStr(Caption, L"&", L"&&"); +} + +// duplicated in console's Main.cpp +bool CutToken(UnicodeString & AStr, UnicodeString & AToken, + UnicodeString * ARawToken, UnicodeString * ASeparator) +{ + bool Result; + + AToken.Clear(); + + // inspired by Putty's sftp_getcmd() from PSFTP.C + intptr_t Index = 1; + while ((Index <= AStr.Length()) && + ((AStr[Index] == L' ') || (AStr[Index] == L'\t'))) + { + ++Index; + } + + if (Index <= AStr.Length()) + { + bool Quoting = false; + + while (Index <= AStr.Length()) + { + if (!Quoting && ((AStr[Index] == L' ') || (AStr[Index] == L'\t'))) + { + break; + } + // We should escape quotes only within quotes + // otherwise the "" means " (quote), but it should mean empty string. + // Or have a special case for bare "". + else if ((AStr[Index] == L'"') && (Index + 1 <= AStr.Length()) && + (AStr[Index + 1] == L'"')) + { + Index += 2; + AToken += L'"'; + } + else if (AStr[Index] == L'"') + { + ++Index; + Quoting = !Quoting; + } + else + { + AToken += AStr[Index]; + ++Index; + } + } + + if (ARawToken != nullptr) + { + (*ARawToken) = AStr.SubString(1, Index - 1); + } + + if (Index <= AStr.Length()) + { + if (ASeparator != nullptr) + { + *ASeparator = AStr.SubString(Index, 1); + } + ++Index; + } + else + { + if (ASeparator != nullptr) + { + *ASeparator = UnicodeString(); + } + } + + AStr = AStr.SubString(Index, AStr.Length()); + + Result = true; + } + else + { + Result = false; + AStr.Clear(); + } + + return Result; +} + +void AddToList(UnicodeString & List, const UnicodeString & Value, const UnicodeString & Delimiter) +{ + if (!Value.IsEmpty()) + { + if (!List.IsEmpty() && + ((List.Length() < Delimiter.Length()) || + (List.SubString(List.Length() - Delimiter.Length() + 1, Delimiter.Length()) != Delimiter))) + { + List += Delimiter; + } + List += Value; + } +} + +static bool CheckWin32Version(int Major, int Minor) +{ + return (Win32MajorVersion >= Major) && (Win32MinorVersion >= Minor); +} + +bool IsWinVista() +{ + // Vista is 6.0 + // Win XP is 5.1 + // There also 5.2, what is Windows 2003 or Windows XP 64bit + // (we consider it WinXP for now) + return CheckWin32Version(6, 0); +} + +bool IsWin7() +{ + return CheckWin32Version(6, 1); +} +//--------------------------------------------------------------------------- +bool IsWin8() +{ + return CheckWin32Version(6, 2); +} +//--------------------------------------------------------------------------- +bool IsWin10() +{ + return CheckWin32Version(10, 0); +} + +bool IsWine() +{ +#ifndef __linux__ + HMODULE NtDll = ::GetModuleHandle(L"ntdll.dll"); + return + DebugAlwaysTrue(NtDll != nullptr) && + (::GetProcAddress(NtDll, "wine_get_version") != nullptr); +#else + return true; +#endif +} + +LCID GetDefaultLCID() +{ +#ifndef __linux__ + return GetUserDefaultLCID(); +#else + return 0; +#endif +} + +UnicodeString DefaultEncodingName() +{ + static UnicodeString DefaultEncodingName; + if (DefaultEncodingName.IsEmpty()) + { + CPINFOEX Info; + GetCPInfoEx(CP_ACP, 0, &Info); + DefaultEncodingName = Info.CodePageName; + } + return DefaultEncodingName; +} + +bool GetWindowsProductType(DWORD & Type) +{ + bool Result = false; +#ifndef __linux__ + HINSTANCE Kernel32 = ::GetModuleHandle(L"kernel32.dll"); + typedef BOOL (WINAPI * TGetProductInfo)(DWORD, DWORD, DWORD, DWORD, PDWORD); + TGetProductInfo GetProductInfo = + (TGetProductInfo)::GetProcAddress(Kernel32, "GetProductInfo"); + if (GetProductInfo != nullptr) + { + GetProductInfo(Win32MajorVersion, Win32MinorVersion, 0, 0, &Type); + Result = true; + } +#endif + return Result; +} + +UnicodeString WindowsProductName() +{ + UnicodeString Result; + std::unique_ptr Registry(new TRegistry()); + Registry->SetAccess(KEY_READ); + try + { + Registry->SetRootKey(HKEY_LOCAL_MACHINE); + if (Registry->OpenKey("SOFTWARE", false) && + Registry->OpenKey("Microsoft", false) && + Registry->OpenKey("Windows NT", false) && + Registry->OpenKey("CurrentVersion", false)) + { + Result = Registry->ReadString("ProductName"); + } + } + catch (...) + { + } + return Result; +} + +bool IsDirectoryWriteable(const UnicodeString & APath) +{ + UnicodeString FileName = + ::IncludeTrailingPathDelimiter(APath) + + FORMAT(L"wscp_%s_%d.tmp", FormatDateTime(L"nnzzz", Now()).c_str(), int(GetCurrentProcessId())); + HANDLE LocalFileHandle = ::CreateFile(ApiPath(FileName).c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, 0); + bool Result = (LocalFileHandle != INVALID_HANDLE_VALUE); + if (Result) + { + ::CloseHandle(LocalFileHandle); + } + return Result; +} + +UnicodeString FormatNumber(int64_t Number) +{ +// return FormatFloat(L"#,##0", Number); + return FORMAT(L"%.0f", ToDouble(Number)); +} + +// simple alternative to FormatBytes +UnicodeString FormatSize(int64_t Size) +{ + return FormatNumber(Size); +} + +UnicodeString ExtractFileBaseName(const UnicodeString & APath) +{ + return ChangeFileExt(base::ExtractFileName(APath, false), L""); +} + +TStringList * TextToStringList(const UnicodeString & Text) +{ + std::unique_ptr List(new TStringList()); + List->SetText(Text); + return List.release(); +} + +UnicodeString StringsToText(TStrings * Strings) +{ + UnicodeString Result; + if (Strings->GetCount() == 1) + { + Result = Strings->GetString(0); + } + else + { + Result = Strings->GetText(); + } + return Result; +} + +TStrings * CloneStrings(TStrings * Strings) +{ + std::unique_ptr List(new TStringList()); + List->AddStrings(Strings); + return List.release(); +} + +UnicodeString TrimVersion(const UnicodeString & Version) +{ + UnicodeString Result = Version; + + while ((Result.Pos(L".") != Result.LastDelimiter(L".")) && + (Result.SubString(Result.Length() - 1, 2) == L".0")) + { + Result.SetLength(Result.Length() - 2); + } + return Result; +} + +UnicodeString FormatVersion(int MajorVersion, int MinorVersion, int Patch) +{ + return FORMAT(L"%d.%d.%d", MajorVersion, MinorVersion, Patch); +} + +TFormatSettings GetEngFormatSettings() +{ + return TFormatSettings::Create(1033); +} + +static int IndexStr(const UnicodeString & AStr) +{ + int Result = -1; + for (int Index = 0; Index < 12; ++Index) + { + if (AStr.CompareIC(EngShortMonthNames[Index]) == 0) + { + Result = Index; + break; + } + } + return Result; +} + +int ParseShortEngMonthName(const UnicodeString & MonthStr) +{ + // TFormatSettings FormatSettings = GetEngFormatSettings(); + // return IndexStr(MonthStr, FormatSettings.ShortMonthNames, FormatSettings.ShortMonthNames.size()) + 1; + return IndexStr(MonthStr) + 1; +} + +TStringList * CreateSortedStringList(bool CaseSensitive, TDuplicatesEnum Duplicates) +{ + TStringList * Result = new TStringList(); + Result->SetCaseSensitive(CaseSensitive); + Result->SetSorted(true); + Result->SetDuplicates(Duplicates); + return Result; +} + +static UnicodeString NormalizeIdent(const UnicodeString & Ident) +{ + UnicodeString Result = Ident; + intptr_t Index = 1; + while (Index <= Result.Length()) + { + if (Result[Index] == L'-') + { + Result.Delete(Index, 1); + } + else + { + Index++; + } + } + return Result; +} + +UnicodeString FindIdent(const UnicodeString & Ident, TStrings * Idents) +{ + UnicodeString NormalizedIdent(NormalizeIdent(Ident)); + for (intptr_t Index = 0; Index < Idents->GetCount(); Index++) + { + if (SameText(NormalizedIdent, NormalizeIdent(Idents->GetString(Index)))) + { + return Idents->GetString(Index); + } + } + return Ident; +} + +static UnicodeString GetTlsErrorStr(int Err) +{ + char * Buffer = new char[512]; + ERR_error_string(Err, Buffer); + // not sure about the UTF8 + return UnicodeString(UTF8String(Buffer)); +} +//--------------------------------------------------------------------------- +#ifdef __linux__ +FILE *_wfopen(const wchar_t *filename, const wchar_t *mode) +{ + return fopen(Wide2MB(filename).c_str(), Wide2MB(mode).c_str()); +} +#endif +static FILE * OpenCertificate(const UnicodeString & Path) +{ + FILE * Result = _wfopen(ApiPath(Path).c_str(), L"rb"); + if (Result == nullptr) + { + int Error = errno; + throw EOSExtException(MainInstructions(FMTLOAD(CERTIFICATE_OPEN_ERROR, Path.c_str())), Error); + } + + return Result; +} +//--------------------------------------------------------------------------- +struct TPemPasswordCallbackData +{ + UnicodeString * Passphrase; +}; +//--------------------------------------------------------------------------- +static int PemPasswordCallback(char * Buf, int Size, int /*RWFlag*/, void * UserData) +{ + TPemPasswordCallbackData & Data = *reinterpret_cast(UserData); + UTF8String UtfPassphrase = UTF8String(*Data.Passphrase); + strncpy(Buf, UtfPassphrase.c_str(), Size); + Shred(UtfPassphrase); + Buf[Size - 1] = '\0'; + return static_cast(strlen(Buf)); +} +//--------------------------------------------------------------------------- +static bool IsTlsPassphraseError(int Error, bool HasPassphrase) +{ + int ErrorLib = ERR_GET_LIB(Error); + int ErrorReason = ERR_GET_REASON(Error); + + bool Result = + ((ErrorLib == ERR_LIB_PKCS12) && + (ErrorReason == PKCS12_R_MAC_VERIFY_FAILURE)) || + ((ErrorLib == ERR_LIB_PEM) && + (ErrorReason == PEM_R_BAD_PASSWORD_READ)) || + (HasPassphrase && (ERR_LIB_EVP == ERR_LIB_EVP) && + ((ErrorReason == PEM_R_BAD_DECRYPT) || (ErrorReason == PEM_R_BAD_BASE64_DECODE))); + + return Result; +} +//--------------------------------------------------------------------------- +static void ThrowTlsCertificateErrorIgnorePassphraseErrors(const UnicodeString & Path, bool HasPassphrase) +{ + int Error = ERR_get_error(); + if (!IsTlsPassphraseError(Error, HasPassphrase)) + { + throw ExtException(MainInstructions(FMTLOAD(CERTIFICATE_READ_ERROR, Path.c_str())), GetTlsErrorStr(Error)); + } +} +//--------------------------------------------------------------------------- +void ParseCertificate(const UnicodeString & Path, + const UnicodeString & Passphrase, X509 *& Certificate, EVP_PKEY *& PrivateKey, + bool & WrongPassphrase) +{ + Certificate = nullptr; + PrivateKey = nullptr; + bool HasPassphrase = !Passphrase.IsEmpty(); + + FILE * File; + + // Inspired by neon's ne_ssl_clicert_read + File = OpenCertificate(Path); + // openssl pkcs12 -inkey cert.pem -in cert.crt -export -out cert.pfx + // Binary file + PKCS12 * Pkcs12 = d2i_PKCS12_fp(File, nullptr); + fclose(File); + + if (Pkcs12 != nullptr) + { + // Not sure about the UTF-8 encoding, but there's no wchar_t API + bool Result = + (PKCS12_parse(Pkcs12, UTF8String(Passphrase).c_str(), &PrivateKey, &Certificate, nullptr) == 1); + PKCS12_free(Pkcs12); + + if (!Result) + { + ThrowTlsCertificateErrorIgnorePassphraseErrors(Path, HasPassphrase); + WrongPassphrase = true; + } + } + else + { + ERR_clear_error(); + + TPemPasswordCallbackData CallbackUserData; + // PemPasswordCallback never writes to the .Passphrase + CallbackUserData.Passphrase = const_cast(&Passphrase); + + File = OpenCertificate(Path); + // Encrypted: + // openssl req -x509 -newkey rsa:2048 -keyout cert.pem -out cert.crt + // -----BEGIN ENCRYPTED PRIVATE KEY----- + // ... + // -----END ENCRYPTED PRIVATE KEY----- + + // Not encrypted (add -nodes): + // -----BEGIN PRIVATE KEY----- + // ... + // -----END PRIVATE KEY----- + // Or (openssl genrsa -out client.key 1024 # used for certificate signing request) + // -----BEGIN RSA PRIVATE KEY----- + // ... + // -----END RSA PRIVATE KEY----- + PrivateKey = PEM_read_PrivateKey(File, nullptr, PemPasswordCallback, &CallbackUserData); + fclose(File); + + try__finally + { + SCOPE_EXIT + { + // We loaded private key, but failed to load certificate, discard the certificate + // (either exception was thrown or WrongPassphrase) + if ((PrivateKey != nullptr) && (Certificate == nullptr)) + { + EVP_PKEY_free(PrivateKey); + PrivateKey = nullptr; + } + // Certificate was verified, but passphrase was wrong when loading private key, + // so discard the certificate + else if ((Certificate != nullptr) && (PrivateKey == nullptr)) + { + X509_free(Certificate); + Certificate = nullptr; + } + }; + if (PrivateKey == nullptr) + { + ThrowTlsCertificateErrorIgnorePassphraseErrors(Path, HasPassphrase); + WrongPassphrase = true; + } + + File = OpenCertificate(Path); + // The file can contain both private and public key + // (basically cert.pem and cert.crt appended one to each other) + // -----BEGIN ENCRYPTED PRIVATE KEY----- + // ... + // -----END ENCRYPTED PRIVATE KEY----- + // -----BEGIN CERTIFICATE----- + // ... + // -----END CERTIFICATE----- + Certificate = PEM_read_X509(File, nullptr, PemPasswordCallback, &CallbackUserData); + fclose(File); + + if (Certificate == nullptr) + { + int Error = ERR_get_error(); + // unlikely + if (IsTlsPassphraseError(Error, HasPassphrase)) + { + WrongPassphrase = true; + } + else + { + UnicodeString CertificatePath = ChangeFileExt(Path, L".cer"); + if (!FileExists(CertificatePath)) + { + CertificatePath = ChangeFileExt(Path, L".crt"); + } + + if (!FileExists(CertificatePath)) + { + throw Exception(MainInstructions(FMTLOAD(CERTIFICATE_PUBLIC_KEY_NOT_FOUND, Path.c_str()))); + } + else + { + File = OpenCertificate(CertificatePath); + // -----BEGIN CERTIFICATE----- + // ... + // -----END CERTIFICATE----- + Certificate = PEM_read_X509(File, nullptr, PemPasswordCallback, &CallbackUserData); + fclose(File); + + if (Certificate == nullptr) + { + int Base64Error = ERR_get_error(); + + File = OpenCertificate(CertificatePath); + // Binary DER-encoded certificate + // (as above, with BEGIN/END removed, and decoded from Base64 to binary) + // openssl x509 -in cert.crt -out client.der.crt -outform DER + Certificate = d2i_X509_fp(File, nullptr); + fclose(File); + + if (Certificate == nullptr) + { + int DERError = ERR_get_error(); + + UnicodeString Message = MainInstructions(FMTLOAD(CERTIFICATE_READ_ERROR, CertificatePath.c_str())); + UnicodeString MoreMessages = + FORMAT(L"Base64: %s\nDER: %s", GetTlsErrorStr(Base64Error).c_str(), GetTlsErrorStr(DERError).c_str()); + throw ExtException(Message, MoreMessages); + } + } + } + } + } + } + __finally + { + // We loaded private key, but failed to load certificate, discard the certificate + // (either exception was thrown or WrongPassphrase) + if ((PrivateKey != nullptr) && (Certificate == nullptr)) + { + EVP_PKEY_free(PrivateKey); + PrivateKey = nullptr; + } + // Certificate was verified, but passphrase was wrong when loading private key, + // so discard the certificate + else if ((Certificate != nullptr) && (PrivateKey == nullptr)) + { + X509_free(Certificate); + Certificate = nullptr; + } + }; + } +} +//--------------------------------------------------------------------------- +void CheckCertificate(const UnicodeString & Path) +{ + X509 * Certificate; + EVP_PKEY * PrivateKey; + bool WrongPassphrase; + + ParseCertificate(Path, L"", Certificate, PrivateKey, WrongPassphrase); + + if (PrivateKey != nullptr) + { + EVP_PKEY_free(PrivateKey); + } + if (Certificate != nullptr) + { + X509_free(Certificate); + } +} +//--------------------------------------------------------------------------- +const UnicodeString HttpProtocol(L"http"); +const UnicodeString HttpsProtocol(L"https"); +const UnicodeString ProtocolSeparator(L"://"); +//--------------------------------------------------------------------------- +bool IsHttpUrl(const UnicodeString & S) +{ + return StartsText(HttpProtocol + ProtocolSeparator, S); +} + +bool IsHttpOrHttpsUrl(const UnicodeString & S) +{ + return + IsHttpUrl(S) || + StartsText(HttpsProtocol + ProtocolSeparator, S); +} +//--------------------------------------------------------------------------- +UnicodeString ChangeUrlProtocol(const UnicodeString & S, const UnicodeString & Protocol) +{ + intptr_t P = S.Pos(ProtocolSeparator); + DebugAssert(P > 0); + return Protocol + ProtocolSeparator + RightStr(S, S.Length() - P - ProtocolSeparator.Length() + 1); +} + +const UnicodeString RtfPara = L"\\par\n"; +const UnicodeString RtfHyperlinkField = L"HYPERLINK"; +const UnicodeString RtfHyperlinkFieldPrefix = RtfHyperlinkField + L" \""; + +UnicodeString FormatBytes(int64_t Bytes, bool UseOrders) +{ + UnicodeString Result; + + if (!UseOrders || (Bytes < static_cast(100 * 1024))) + { + // Result = FormatFloat(L"#,##0 \"B\"", Bytes); + Result = FORMAT(L"%.0f B", ToDouble(Bytes)); + } + else if (Bytes < static_cast(100 * 1024 * 1024)) + { + // Result = FormatFloat(L"#,##0 \"KB\"", Bytes / 1024); + Result = FORMAT(L"%.0f KB", ToDouble(Bytes / 1024.0)); + } + else + { + // Result = FormatFloat(L"#,##0 \"MiB\"", Bytes / (1024*1024)); + Result = FORMAT(L"%.0f MiB", ToDouble(Bytes / (1024 * 1024.0))); + } + return Result; +} + +namespace base { + +UnicodeString UnixExtractFileName(const UnicodeString & APath) +{ + intptr_t Pos = APath.LastDelimiter(L'/'); + UnicodeString Result; + if (Pos > 0) + { + Result = APath.SubString(Pos + 1, APath.Length() - Pos); + } + else + { + Result = APath; + } + return Result; +} + +UnicodeString UnixExtractFileExt(const UnicodeString & APath) +{ + UnicodeString FileName = base::UnixExtractFileName(APath); + intptr_t Pos = FileName.LastDelimiter(L"."); + return (Pos > 0) ? APath.SubString(Pos, APath.Length() - Pos + 1) : UnicodeString(); +} + +UnicodeString ExtractFileName(const UnicodeString & APath, bool Unix) +{ + if (Unix) + { + return base::UnixExtractFileName(APath); + } + else + { + return ::ExtractFilename(APath, L'\\'); + } +} + +UnicodeString GetEnvironmentVariable(const UnicodeString & AEnvVarName) +{ + UnicodeString Result; + intptr_t Len = ::GetEnvironmentVariable(L"PATH", nullptr, 0); + if (Len > 0) + { + Result.SetLength(Len - 1); + ::GetEnvironmentVariable(AEnvVarName.c_str(), reinterpret_cast(const_cast(Result.c_str())), static_cast(Len)); + } + return Result; +} + +} // namespace base diff --git a/netbox/src/base/Common.h b/netbox/src/base/Common.h new file mode 100644 index 000000000..4e73d01d3 --- /dev/null +++ b/netbox/src/base/Common.h @@ -0,0 +1,358 @@ +#pragma once + +#include +#include + +extern const wchar_t EngShortMonthNames[12][4]; +#define CONST_BOM "\xEF\xBB\xBF" +extern const wchar_t TokenPrefix; +extern const wchar_t NoReplacement; +extern const wchar_t TokenReplacement; +#define LOCAL_INVALID_CHARS "/\\:*?\"<>|" +#define PASSWORD_MASK "***" +#define sLineBreak L"\n" + +// Order of the values also define order of the buttons/answers on the prompts +// MessageDlg relies on these to be <= 0x0000FFFF +const uint32_t qaYes = 0x00000001; +// MessageDlg relies that answer do not conflict with mrCancel (=0x2) +const uint32_t qaNo = 0x00000004; +const uint32_t qaOK = 0x00000008; +const uint32_t qaCancel = 0x00000010; +const uint32_t qaYesToAll = 0x00000020; +const uint32_t qaNoToAll = 0x00000040; +const uint32_t qaAbort = 0x00000080; +const uint32_t qaRetry = 0x00000100; +const uint32_t qaIgnore = 0x00000200; +const uint32_t qaSkip = 0x00000400; +const uint32_t qaAll = 0x00000800; +const uint32_t qaHelp = 0x00001000; +const uint32_t qaReport = 0x00002000; + +const uint32_t qaFirst = qaYes; +const uint32_t qaLast = qaReport; + +const uint32_t qaNeverAskAgain = 0x00010000; + +const int qpFatalAbort = 0x01; +const int qpNeverAskAgainCheck = 0x02; +const int qpAllowContinueOnError = 0x04; +const int qpIgnoreAbort = 0x08; +const int qpWaitInBatch = 0x10; + +inline void ThrowExtException() { throw ExtException(static_cast(nullptr), UnicodeString(L"")); } + +extern const UnicodeString HttpProtocol; +extern const UnicodeString HttpsProtocol; +extern const UnicodeString ProtocolSeparator; + +UnicodeString ReplaceChar(const UnicodeString & Str, wchar_t A, wchar_t B); +UnicodeString DeleteChar(const UnicodeString & Str, wchar_t C); +void PackStr(UnicodeString & Str); +void PackStr(RawByteString & Str); +void PackStr(AnsiString & Str); +void Shred(UnicodeString & Str); +void Shred(UTF8String & Str); +void Shred(AnsiString & Str); +UnicodeString AnsiToString(const RawByteString & S); +UnicodeString AnsiToString(const char * S, size_t Len); +UnicodeString MakeValidFileName(const UnicodeString & AFileName); +UnicodeString RootKeyToStr(HKEY RootKey); +UnicodeString BooleanToStr(bool B); +UnicodeString BooleanToEngStr(bool B); +UnicodeString DefaultStr(const UnicodeString & Str, const UnicodeString & Default); +UnicodeString CutToChar(UnicodeString & Str, wchar_t Ch, bool Trim); +UnicodeString CopyToChars(const UnicodeString & Str, intptr_t & From, const UnicodeString & Chs, bool Trim, + wchar_t * Delimiter = nullptr, bool DoubleDelimiterEscapes = false); +UnicodeString CopyToChar(const UnicodeString & Str, wchar_t Ch, bool Trim); +UnicodeString DelimitStr(const UnicodeString & Str, const UnicodeString & Chars); +UnicodeString ShellDelimitStr(const UnicodeString & Str, wchar_t Quote); +UnicodeString ExceptionLogString(Exception * E); +UnicodeString MainInstructions(const UnicodeString & S); +bool HasParagraphs(const UnicodeString & S); +UnicodeString MainInstructionsFirstParagraph(const UnicodeString & S); +bool ExtractMainInstructions(UnicodeString & S, UnicodeString & MainInstructions); +UnicodeString RemoveMainInstructionsTag(const UnicodeString & S); +UnicodeString UnformatMessage(const UnicodeString & S); +UnicodeString RemoveInteractiveMsgTag(const UnicodeString & S); +UnicodeString RemoveEmptyLines(const UnicodeString & S); +bool IsNumber(const UnicodeString & Str); +UnicodeString GetSystemTemporaryDirectory(); +UnicodeString GetShellFolderPath(int CSIdl); +UnicodeString StripPathQuotes(const UnicodeString & APath); +UnicodeString AddQuotes(const UnicodeString & Str); +UnicodeString AddPathQuotes(const UnicodeString & APath); +void SplitCommand(const UnicodeString & Command, UnicodeString & Program, + UnicodeString & Params, UnicodeString & Dir); +UnicodeString ValidLocalFileName(const UnicodeString & AFileName); +UnicodeString ValidLocalFileName( + const UnicodeString & AFileName, wchar_t AInvalidCharsReplacement, + const UnicodeString & ATokenizibleChars, const UnicodeString & ALocalInvalidChars); +UnicodeString ExtractProgram(const UnicodeString & Command); +UnicodeString ExtractProgramName(const UnicodeString & Command); +UnicodeString FormatCommand(const UnicodeString & Program, const UnicodeString & AParams); +UnicodeString ExpandFileNameCommand(const UnicodeString & Command, + const UnicodeString & AFileName); +void ReformatFileNameCommand(UnicodeString & Command); +UnicodeString EscapeParam(const UnicodeString & AParam); +UnicodeString EscapePuttyCommandParam(const UnicodeString & AParam); +UnicodeString ExpandEnvironmentVariables(const UnicodeString & Str); +bool ComparePaths(const UnicodeString & APath1, const UnicodeString & APath2); +bool CompareFileName(const UnicodeString & APath1, const UnicodeString & APath2); +intptr_t CompareLogicalText(const UnicodeString & S1, const UnicodeString & S2); +bool IsReservedName(const UnicodeString & AFileName); +UnicodeString ApiPath(const UnicodeString & APath); +UnicodeString DisplayableStr(const RawByteString & Str); +UnicodeString ByteToHex(uint8_t B, bool UpperCase = true); +UnicodeString BytesToHex(const uint8_t * B, uintptr_t Length, bool UpperCase = true, wchar_t Separator = L'\0'); +UnicodeString BytesToHex(const RawByteString & Str, bool UpperCase = true, wchar_t Separator = L'\0'); +UnicodeString CharToHex(wchar_t Ch, bool UpperCase = true); +RawByteString HexToBytes(const UnicodeString & Hex); +uint8_t HexToByte(const UnicodeString & Hex); +bool IsLowerCaseLetter(wchar_t Ch); +bool IsUpperCaseLetter(wchar_t Ch); +bool IsLetter(wchar_t Ch); +bool IsDigit(wchar_t Ch); +bool IsHex(wchar_t Ch); +UnicodeString DecodeUrlChars(const UnicodeString & S); +UnicodeString EncodeUrlString(const UnicodeString & S); +UnicodeString EncodeUrlPath(const UnicodeString & S); +UnicodeString AppendUrlParams(const UnicodeString & URL, const UnicodeString & Params); +UnicodeString ExtractFileNameFromUrl(const UnicodeString & Url); +bool RecursiveDeleteFile(const UnicodeString & AFileName, bool ToRecycleBin); +void RecursiveDeleteFileChecked(const UnicodeString & AFileName, bool ToRecycleBin); +void DeleteFileChecked(const UnicodeString & AFileName); +uintptr_t CancelAnswer(uintptr_t Answers); +uintptr_t AbortAnswer(uintptr_t Answers); +uintptr_t ContinueAnswer(uintptr_t Answers); +UnicodeString LoadStr(intptr_t Ident, uintptr_t MaxLength = 0); +UnicodeString LoadStrPart(intptr_t Ident, intptr_t Part); +UnicodeString EscapeHotkey(const UnicodeString & Caption); +bool CutToken(UnicodeString & AStr, UnicodeString & AToken, + UnicodeString * ARawToken = nullptr, UnicodeString * ASeparator = nullptr); +void AddToList(UnicodeString & List, const UnicodeString & Value, const UnicodeString & Delimiter); +bool IsWinVista(); +bool IsWin7(); +bool IsWin8(); +bool IsWin10(); +bool IsWine(); +int64_t Round(double Number); +bool TryRelativeStrToDateTime(const UnicodeString & AStr, TDateTime & DateTime); +LCID GetDefaultLCID(); +UnicodeString DefaultEncodingName(); +UnicodeString WindowsProductName(); +bool GetWindowsProductType(DWORD & Type); +bool IsDirectoryWriteable(const UnicodeString & APath); +UnicodeString FormatNumber(int64_t Size); +UnicodeString FormatSize(int64_t Size); +UnicodeString ExtractFileBaseName(const UnicodeString & APath); +TStringList * TextToStringList(const UnicodeString & Text); +UnicodeString StringsToText(TStrings * Strings); +TStrings * CloneStrings(TStrings * Strings); +UnicodeString TrimVersion(const UnicodeString & Version); +UnicodeString FormatVersion(int MajorVersion, int MinorVersion, int Patch); +TFormatSettings GetEngFormatSettings(); +int ParseShortEngMonthName(const UnicodeString & MonthStr); +// The defaults are equal to defaults of TStringList class (except for Sorted) +TStringList * CreateSortedStringList(bool CaseSensitive = false, TDuplicatesEnum Duplicates = dupIgnore); +UnicodeString FindIdent(const UnicodeString & Ident, TStrings * Idents); +void CheckCertificate(const UnicodeString & Path); +typedef struct x509_st X509; +typedef struct evp_pkey_st EVP_PKEY; +void ParseCertificate(const UnicodeString & Path, + const UnicodeString & Passphrase, X509 *& Certificate, EVP_PKEY *& PrivateKey, + bool & WrongPassphrase); +bool IsHttpUrl(const UnicodeString & S); +bool IsHttpOrHttpsUrl(const UnicodeString & S); +UnicodeString ChangeUrlProtocol(const UnicodeString & S, const UnicodeString & Protocol); +void LoadScriptFromFile(const UnicodeString & FileName, TStrings * Lines); +UnicodeString StripEllipsis(const UnicodeString & S); + +DEFINE_CALLBACK_TYPE3(TProcessLocalFileEvent, void, + const UnicodeString & /*FileName*/, const TSearchRec & /*Rec*/, void * /*Param*/); +bool FileSearchRec(const UnicodeString & AFileName, TSearchRec & Rec); + +struct TSearchRecChecked : public TSearchRec +{ + UnicodeString Path; +}; + +DWORD FindCheck(DWORD Result, const UnicodeString & APath); +DWORD FindFirstUnchecked(const UnicodeString & APath, DWORD LocalFileAttrs, TSearchRecChecked & F); +DWORD FindFirstChecked(const UnicodeString & APath, DWORD LocalFileAttrs, TSearchRecChecked & F); +DWORD FindNextChecked(TSearchRecChecked & F); +void ProcessLocalDirectory(const UnicodeString & ADirName, + TProcessLocalFileEvent CallBackFunc, void * Param = nullptr, DWORD FindAttrs = INVALID_FILE_ATTRIBUTES); +DWORD FileGetAttrFix(const UnicodeString & AFileName); + +extern const wchar_t * DSTModeNames; +enum TDSTMode +{ + dstmWin = 0, // + dstmUnix = 1, // adjust UTC time to Windows "bug" + dstmKeep = 2, +}; + +bool UsesDaylightHack(); +TDateTime EncodeDateVerbose(Word Year, Word Month, Word Day); +TDateTime EncodeTimeVerbose(Word Hour, Word Min, Word Sec, Word MSec); +double DSTDifferenceForTime(const TDateTime & DateTime); +TDateTime SystemTimeToDateTimeVerbose(const SYSTEMTIME & SystemTime); +TDateTime UnixToDateTime(int64_t TimeStamp, TDSTMode DSTMode); +TDateTime ConvertTimestampToUTC(const TDateTime & DateTime); +TDateTime ConvertTimestampFromUTC(const TDateTime & DateTime); +FILETIME DateTimeToFileTime(const TDateTime & DateTime, TDSTMode DSTMode); +TDateTime AdjustDateTimeFromUnix(const TDateTime & DateTime, TDSTMode DSTMode); +void UnifyDateTimePrecision(TDateTime & DateTime1, TDateTime & DateTime2); +TDateTime FileTimeToDateTime(const FILETIME & FileTime); +int64_t ConvertTimestampToUnix(const FILETIME & FileTime, + TDSTMode DSTMode); +int64_t ConvertTimestampToUnixSafe(const FILETIME & FileTime, + TDSTMode DSTMode); +UnicodeString FixedLenDateTimeFormat(const UnicodeString & Format); +UnicodeString StandardTimestamp(const TDateTime & DateTime); +UnicodeString StandardTimestamp(); +UnicodeString StandardDatestamp(); +UnicodeString FormatTimeZone(intptr_t Sec); +UnicodeString GetTimeZoneLogString(); +bool AdjustClockForDSTEnabled(); +intptr_t CompareFileTime(const TDateTime & T1, const TDateTime & T2); +intptr_t TimeToMSec(const TDateTime & T); +intptr_t TimeToSeconds(const TDateTime & T); +intptr_t TimeToMinutes(const TDateTime & T); + +#pragma warning(push) +#pragma warning(disable: 4512) // assignment operator could not be generated + +template +class TValueRestorer : public TObject +{ +public: + inline explicit TValueRestorer(T & Target, const T & Value) : + FTarget(Target), + FValue(Value), + FArmed(true) + { + } + + inline explicit TValueRestorer(T & Target) : + FTarget(Target), + FValue(Target), + FArmed(true) + { + } + + void Release() + { + if (FArmed) + { + FTarget = FValue; + FArmed = false; + } + } + + inline ~TValueRestorer() + { + Release(); + } + +protected: + T & FTarget; + T FValue; + bool FArmed; +}; + +class TAutoNestingCounter : TValueRestorer +{ +public: + inline explicit TAutoNestingCounter(int & Target) : + TValueRestorer(Target) + { + DebugAssert(Target >= 0); + ++Target; + } + + inline ~TAutoNestingCounter() + { + DebugAssert(!FArmed || (FTarget == (FValue + 1))); + } +}; + +class TAutoFlag : public TValueRestorer +{ +public: + TAutoFlag(bool & Target) : + TValueRestorer(Target) + { + DebugAssert(!Target); + Target = true; + } + + ~TAutoFlag() + { + DebugAssert(!FArmed || FTarget); + } +}; +#pragma warning(pop) + +#include + +template +class BiDiMap +{ +public: + typedef std::map TFirstToSecond; + typedef typename TFirstToSecond::const_iterator const_iterator; + + void Add(const T1 & Value1, const T2 & Value2) + { + FFirstToSecond.insert(std::make_pair(Value1, Value2)); + FSecondToFirst.insert(std::make_pair(Value2, Value1)); + } + + T1 LookupFirst(const T2 & Value2) const + { + typename TSecondToFirst::const_iterator Iterator = FSecondToFirst.find(Value2); + DebugAssert(Iterator != FSecondToFirst.end()); + return Iterator->second; + } + + T2 LookupSecond(const T1 & Value1) const + { + const_iterator Iterator = FFirstToSecond.find(Value1); + DebugAssert(Iterator != FFirstToSecond.end()); + return Iterator->second; + } + + const_iterator begin() + { + return FFirstToSecond.begin(); + } + + const_iterator end() + { + return FFirstToSecond.end(); + } + +private: + TFirstToSecond FFirstToSecond; + typedef std::map TSecondToFirst; + TSecondToFirst FSecondToFirst; +}; + +typedef std::vector TUnicodeStringVector; + +UnicodeString FormatBytes(int64_t Bytes, bool UseOrders = true); + +#ifdef __linux__ +FILE *_wfopen(const wchar_t *filename, const wchar_t *mode); +#endif + +namespace base { + +UnicodeString UnixExtractFileExt(const UnicodeString & APath); +UnicodeString UnixExtractFileName(const UnicodeString & APath); +UnicodeString ExtractFileName(const UnicodeString & APath, bool Unix); +UnicodeString GetEnvironmentVariable(const UnicodeString & AEnvVarName); + +} // namespace base diff --git a/netbox/src/base/CompThread.hpp b/netbox/src/base/CompThread.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/CompThread.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/CppProperties.h b/netbox/src/base/CppProperties.h new file mode 100644 index 000000000..59f2e1a1f --- /dev/null +++ b/netbox/src/base/CppProperties.h @@ -0,0 +1,318 @@ +#pragma once + + +// Some utility templates for emulating properties -- +// preferring a library solution to a new language feature +// Each property has three sets of redundant accessors: +// 1. function call syntax +// 2. get() and set() functions +// 3. overloaded operator = +// a read-write property with data store and +// automatically generated get/set functions. +// this is what C++/CLI calls a trivial scalar property +template +class Property +{ + T data; +public: + // access with function call syntax + Property() : data() { } + T operator()() const + { + return data; + } + T operator()(T Value) + { + data = Value; + return data; + } + // access with get()/set() syntax + T get() const + { + return data; + } + T set(T Value) + { + data = Value; + return data; + } + // access with '=' sign + // in an industrial-strength library, + // specializations for appropriate types might choose to + // add combined operators like +=, etc. + operator T() const + { + return data; + } + void operator = (T Value) + { + data = Value; + } + typedef T value_type; // might be useful for template deductions +}; + +// a read-only property calling a user-defined getter +template < + class T, + class Object, + T (Object::*real_getter)() + > +class ROProperty +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in a + // constructor, to initialize the ROProperty so it knows where its + // real implementation code can be found. obj is usually the containing + // class, but need not be; it could be a special implementation object. + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + T operator()() const + { + return (my_object->*real_getter)(); + } + // get/set syntax + T get() const + { + return (my_object->*real_getter)(); + } + void set(T Value); // reserved but not implemented, per C++/CLI + // use on rhs of '=' + operator T() const + { + return (my_object->*real_getter)(); + } + typedef T value_type; // might be useful for template deductions +}; + +// a write-only property calling a user-defined setter +template < + class T, + class Object, + void (Object::*real_setter)(T) + > +class WOProperty +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in + // a constructor, to initialize the WOProperty so it knows where its + // real implementation code can be found + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + T operator()(T Value) + { + return (my_object->*real_setter)(Value); + } + // get/set syntax + T get() const; // name reserved but not implemented per C++/CLI + void set(T Value) + { + void (my_object->*real_setter)(Value); + } + // access with '=' sign + void operator = (T Value) + { + (my_object->*real_setter)(Value); + } + typedef T value_type; // might be useful for template deductions +}; + +// a read-write property which invokes user-defined functions +template < + class T, + class Object, + T (Object::*real_getter)(), + void (Object::*real_setter)(T) + > +class RWProperty +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in a + // constructor, to initialize the ROProperty so it knows where its + // real implementation code can be found + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + T operator()() const + { + return (my_object->*real_getter)(); + } + void operator()(T Value) + { + (my_object->*real_setter)(Value); + } + // get/set syntax + T get() const + { + return (my_object->*real_getter)(); + } + void set(T Value) + { + return (my_object->*real_setter)(Value); + } + // access with '=' sign + operator T() const + { + return (my_object->*real_getter)(); + } + void operator = (T Value) + { + (my_object->*real_setter)(Value); + } + typedef T value_type; // might be useful for template deductions +}; + +// a read/write property providing indexed access. +// this class simply encapsulates a std::map and changes its interface +// to functions consistent with the other property<> classes. +// note that the interface combines certain limitations of std::map with +// some others from indexed properties as I understand them. +// an example of the first is that operator[] on a map will insert a +// key/Value pair if it isn't already there. A consequence of this is that +// it can't be a const member function (and therefore you cannot access +// a const map using operator [].) +// an example of the second is that indexed properties do not appear +// to have any facility for erasing key/value pairs from the container. +// C++/CLI properties can have multi-dimensional indexes: prop[2,3]. This is +// not allowed by the current rules of standard C++ + +template < + class Key, + class T, + class Object, + T (Object::*real_getter)(Key), + void (Object::*real_setter)(Key, T) + > +class IndexedProperty +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in a + // constructor, to initialize the ROProperty so it knows where its + // real implementation code can be found + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + T operator()(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void operator()(Key AKey, T AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // get/set syntax + T get_Item(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void set_Item(Key AKey, T AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // operator [] syntax + T operator[](Key AKey) + { + return (my_object->*real_getter)(AKey); + } +}; + +template < + class Key, + class T, + class Object, + T & (Object::*real_getter)(Key), + void (Object::*real_setter)(Key, T) + > +class IndexedProperty2 +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in a + // constructor, to initialize the ROProperty so it knows where its + // real implementation code can be found + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + T & operator()(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void operator()(Key AKey, T AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // get/set syntax + T & get_Item(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void set_Item(Key AKey, T AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // operator [] syntax + T & operator[](Key AKey) + { + return (my_object->*real_getter)(AKey); + } +}; + +template < + class Key, + class Object, + void *& (Object::*real_getter)(Key), + void (Object::*real_setter)(Key, void *) + > +class IndexedPropertyVoid +{ + Object * my_object; +public: + // this function must be called by the containing class, normally in a + // constructor, to initialize the ROProperty so it knows where its + // real implementation code can be found + void operator()(Object * obj) + { + my_object = obj; + } + // function call syntax + void *& operator()(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void operator()(Key AKey, void * AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // get/set syntax + void *& get_Item(Key AKey) + { + return (my_object->*real_getter)(AKey); + } + void set_Item(Key AKey, void * AValue) + { + (my_object->*real_setter)(AKey, AValue); + } + // operator [] syntax + void *& operator[](Key AKey) + { + return (my_object->*real_getter)(AKey); + } +}; + + + diff --git a/netbox/src/base/DateUtils.hpp b/netbox/src/base/DateUtils.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/DateUtils.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/DiscMon.hpp b/netbox/src/base/DiscMon.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/DiscMon.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/DynamicQueue.hpp b/netbox/src/base/DynamicQueue.hpp new file mode 100644 index 000000000..f38122b12 --- /dev/null +++ b/netbox/src/base/DynamicQueue.hpp @@ -0,0 +1,249 @@ +#pragma once +/************************* DynamicQueue.cpp ********************************* +* Author: Agner Fog +* Date created: 2008-06-12 +* Last modified: 2008-07-24 +* Description: +* Defines First-In-First-Out queue of dynamic size. +* +* The latest version of this file is available at: +* www.agner.org/optimize/cppexamples.zip +* (c) 2008 GNU General Public License www.gnu.org/copyleft/gpl.html +******************************************************************************* +* +* DynamicQueue is a container class defining a First-In-First-Out queue +* that can contain any number of objects of the same type. +* +* The objects stored can be of any type that does not require a constructor +* or destructor. +* +* DynamicQueue is not thread safe if shared between threads. If your program +* needs storage in multiple threads then each thread must have its own +* private instance of DynamicQueue, or you must prevent multiple threads from +* accessing DynamicQueue at the same time. +* +* Note that you should never store a pointer to an object in DynamicQueue +* because the pointer will become invalid in case a subsequent addition of +* another object causes the memory to be re-allocated. +* +* Attempts to read from an empty queue will cause an error message to the +* standard error output. You may change the DynamicQueue::Error function +* to produce a message box if the program has a graphical user interface. +* +* It is possible, but not necessary, to allocate a certain amount of +* memory before adding any objects. This can reduce the risk of having +* to re-allocate memory if the first allocated memory block turns out +* to be too small. Use Reserve(n) to reserve space for a dynamically +* growing queue. +* +* At the end of this file you will find a working example of how to use +* DynamicQueue. +* +* The first part of this file containing declarations may be placed in a +* header file. The second part containing examples should be removed from your +* final application. +* +******************************************************************************/ + +#include // For memcpy and memset +#include // For exit in Error function +#include // Needed for example only + +#include + + +// Class DynamicQueue makes a dynamic array which can grow as new data are added +template +class DynamicQueue +{ +public: + DynamicQueue(); // Constructor + ~DynamicQueue(); // Destructor + void Reserve(int num); // Allocate buffer for num objects + int GetNum(){return NumEntries;}; // Get number of objects stored + bool IsEmpty() {return GetNum() == 0; }; // Get number of objects stored + inline bool empty() { return IsEmpty(); } + int GetMaxNum(){return MaxNum;}; // Get number of objects that can be stored without re-allocating memory + void Put(TX const & obj); // Add object to head of queue + inline TX push_back(TX const & obj) { Put(obj); } + TX Get(); // Take object out from tail of queue + inline TX pop_front() { return Get(); } + TX & operator[] (int i); // Access object with index i from the tail + inline TX front() { return operator[](GetNum() - 1); } + // Define desired allocation size + enum DefineSize { + AllocateSpace = 4 * 1024 // Minimum size, in bytes, of automatic re-allocation done by Put + }; +private: + TX * Buffer; // Buffer containing data + TX * OldBuffer; // Old buffer before re-allocation + int head; + int tail; + int MaxNum; // Maximum number of objects that currently allocated buffer can contain + int NumEntries; // Number of objects stored + void ReAllocate(int num); // Increase size of memory buffer + void Error(int e, int n); // Make fatal error message + DynamicQueue(DynamicQueue const&){}; // Make private copy constructor to prevent copying + void operator = (DynamicQueue const&){}; // Make private assignment operator to prevent copying +}; + + +// Members of class DynamicQueue +template +DynamicQueue::DynamicQueue() { + // Constructor + Buffer = OldBuffer = 0; + MaxNum = NumEntries = head = tail = 0; +} + + +template +DynamicQueue::~DynamicQueue() { + // Destructor + Reserve(0); // De-allocate buffer +} + + +template +void DynamicQueue::Reserve(int num) { + // Allocate buffer of the specified size + // Setting num > current MaxNum will allocate a larger buffer and + // move all data to the new buffer. + // Setting num <= current MaxNum will do nothing. The buffer will + // only grow, not shrink. + // Setting num = 0 will discard all data and de-allocate the buffer. + if (num <= MaxNum) { + if (num <= 0) { + if (num < 0) Error(1, num); + // num = 0. Discard data and de-allocate buffer + if (Buffer) nb_free(Buffer); // De-allocate buffer + Buffer = 0; // Reset everything + MaxNum = NumEntries = head = tail = 0; + return; + } + // Request to reduce size. Ignore + return; + } + // num > MaxNum. Increase Buffer + ReAllocate(num); + // OldBuffer must be deleted after calling ReAllocate + if (OldBuffer) {nb_free(OldBuffer); OldBuffer = 0;} +} + + +template +void DynamicQueue::ReAllocate(int num) { + // Increase size of memory buffer. + // This function is used only internally. + // Note: ReAllocate leaves OldBuffer to be deleted by the calling function + if (OldBuffer) nb_free(OldBuffer); // Should not occur in single-threaded applications + + TX * Buffer2 = 0; // New buffer + Buffer2 = static_cast(nb_malloc(sizeof(TX) * num)); // Allocate new buffer + if (Buffer2 == 0) {Error(3,num); return;} // Error can't allocate + if (Buffer) { + // A smaller buffer is previously allocated + // Copy queue from old to new buffer + if (tail < head) { + // trail is unbroken, copy as one block + memcpy(Buffer2, Buffer + tail, NumEntries*sizeof(TX)); + } + else if (NumEntries) { + // trail is wrapping around or full, copy two blocks + memcpy(Buffer2, Buffer + tail, (MaxNum - tail)*sizeof(TX)); + if (head) { + memcpy(Buffer2 + (MaxNum - tail), Buffer, head*sizeof(TX)); + } + } + // Reset head and tail + tail = 0; head = NumEntries; + } + OldBuffer = Buffer; // Save old buffer. Deleted by calling function + Buffer = Buffer2; // Save pointer to buffer + MaxNum = num; // Save new size +} + + +template +void DynamicQueue::Put(const TX & obj) { + // Add object to buffer, return index + + if (NumEntries >= MaxNum) { + // buffer too small or no buffer. Allocate more memory + // Determine new size = 2 * current size + the number of objects that correspond to AllocateSpace + int NewSize = MaxNum * 2 + (AllocateSpace+sizeof(TX)-1)/sizeof(TX); + + ReAllocate(NewSize); + } + // Insert new object at head + Buffer[head] = obj; + // Delete old buffer after copying object, in case obj was in old buffer + if (OldBuffer) {nb_free(OldBuffer); OldBuffer = 0;} + // Make head point to next vacant slot + if (++head >= MaxNum) head = 0; + // Count entries + NumEntries++; +} + + +template +TX DynamicQueue::Get() { + // Remove last object and return it + if (NumEntries <= 0) { + // buffer is empty. Make error message + Error(2, 0); + // Return empty object + TX temp; + memset(&temp, 0, sizeof(temp)); + return temp; + } + // Pointer to object at tail + TX * p = Buffer + tail; + // Advance tail + if (++tail >= MaxNum) tail = 0; + // Count down entries + NumEntries--; + // Return object + return *p; +} + + +template +TX & DynamicQueue::operator[] (int i) { + // Access object at position i from tail + if ((unsigned int)i >= (unsigned int)NumEntries) { + // Index i does not exist + Error(1, i); i = 0; + } + // Calculate position + i += tail; + if (i >= MaxNum) i -= MaxNum; // Wrap around + return Buffer[i]; +} + + +// Produce fatal error message. Used internally. +// Note: If your program has a graphical user interface (GUI) then you +// must rewrite this function to produce a message box with the error message. +template +void DynamicQueue::Error(int e, int n) { + // Define error texts + static const char * ErrorTexts[] = { + "Unknown error", // 0 + "Index out of range", // 1 + "Queue is empty", // 2 + "Memory allocation failed" // 3 + }; + // Number of texts in ErrorTexts + const unsigned int NumErrorTexts = sizeof(ErrorTexts) / sizeof(*ErrorTexts); + + // check that index is within range + if ((unsigned int)e >= NumErrorTexts) e = 0; + + // Replace this with your own error routine, possibly with a message box: + // fprintf(stderr, "\nDynamicArray error: %s (%i)\n", ErrorTexts[e], n); + assert(false || ErrorTexts[e]); + + // Terminate execution + // exit(1); +} diff --git a/netbox/src/base/Exceptions.cpp b/netbox/src/base/Exceptions.cpp new file mode 100644 index 000000000..30587eb86 --- /dev/null +++ b/netbox/src/base/Exceptions.cpp @@ -0,0 +1,526 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#include "TextsCore.h" +#include "HelpCore.h" +#include "rtlconsts.h" + +static std::unique_ptr IgnoredExceptionsCriticalSection(new TCriticalSection()); +typedef std::set TIgnoredExceptions; +static TIgnoredExceptions IgnoredExceptions; + +static UnicodeString NormalizeClassName(const UnicodeString & ClassName) +{ + return ReplaceStr(ClassName, L".", L"::").LowerCase(); +} + +void IgnoreException(const std::type_info & ExceptionType) +{ + TGuard Guard(*IgnoredExceptionsCriticalSection.get()); + // We should better use type_index as a key, instead of a class name, + // but type_index is not available in 32-bit version of STL in XE6. + IgnoredExceptions.insert(NormalizeClassName(UnicodeString(AnsiString(ExceptionType.name())))); +} + +static bool WellKnownException( + const Exception * E, UnicodeString * AMessage, const wchar_t ** ACounterName, Exception ** AClone, bool Rethrow) +{ + UnicodeString Message; + const wchar_t * CounterName = nullptr; + std::unique_ptr Clone; + + + bool Result = true; + bool IgnoreException = false; + + if (!IgnoredExceptions.empty()) + { + TGuard Guard(*IgnoredExceptionsCriticalSection.get()); + UnicodeString ClassName = ""; // NormalizeClassName(E->QualifiedClassName()); + IgnoreException = (IgnoredExceptions.find(ClassName) != IgnoredExceptions.end()); + } + + if (IgnoreException) + { + Result = false; + } + // EAccessViolation is EExternal + else if (NB_STATIC_DOWNCAST_CONST(EAccessViolation, E) != nullptr) + { + if (Rethrow) + { + throw EAccessViolation(E->Message); + } + Message = MainInstructions(LoadStr(ACCESS_VIOLATION_ERROR3)); + CounterName = L"AccessViolations"; + Clone.reset(new EAccessViolation(E->Message)); + } + /* + // EIntError and EMathError are EExternal + // EClassNotFound is EFilerError + else if ((NB_STATIC_DOWNCAST(EListError, E) != nullptr) || + (NB_STATIC_DOWNCAST(EStringListError, E) != nullptr) || + (NB_STATIC_DOWNCAST(EIntError, E) != nullptr) || + (NB_STATIC_DOWNCAST(EMathError, E) != nullptr) || + (NB_STATIC_DOWNCAST(EVariantError, E) != nullptr) || + (NB_STATIC_DOWNCAST(EInvalidOperation, E) != nullptr)) + (dynamic_cast(E) != NULL)) + { + if (Rethrow) + { + throw EIntError(E->Message); + } + Message = MainInstructions(E->Message); + CounterName = L"InternalExceptions"; + Clone.reset(new EIntError(E->Message)); + } + else if (NB_STATIC_DOWNCAST(EExternal, E) != nullptr) + { + if (Rethrow) + { + throw EExternal(E->Message); + } + Message = MainInstructions(E->Message); + CounterName = L"ExternalExceptions"; + Clone.reset(new EExternal(E->Message)); + } + else if (NB_STATIC_DOWNCAST(EHeapException, E) != nullptr) + { + if (Rethrow) + { + throw EHeapException(E->Message); + } + Message = MainInstructions(E->Message); + CounterName = L"HeapExceptions"; + Clone.reset(new EHeapException(E->Message)); + } + */ + else + { + Result = false; + } + + if (Result) + { + if (AMessage != nullptr) + { + (*AMessage) = Message; + } + if (ACounterName != nullptr) + { + (*ACounterName) = CounterName; + } + if (AClone != nullptr) + { + (*AClone) = DebugNotNull(Clone.release()); + } + } + + return Result; +} + +static bool ExceptionMessage(const Exception * E, bool /*Count*/, + bool Formatted, UnicodeString & Message, bool & InternalError) +{ + bool Result = true; + const wchar_t * CounterName = nullptr; + InternalError = false; // see also IsInternalException + + // this list has to be in sync with CloneException + if (NB_STATIC_DOWNCAST_CONST(EAbort, E) != nullptr) + { + Result = false; + } + else if (WellKnownException(E, &Message, &CounterName, nullptr, false)) + { + InternalError = true; + } + else if (E && E->Message.IsEmpty()) + { + Result = false; + } + else if (E) + { + Message = E->Message; + } + + if (!Formatted) + { + Message = UnformatMessage(Message); + } + + if (InternalError) + { + Message = FMTLOAD(REPORT_ERROR, Message.c_str()); + } +/* + if (Count && (CounterName != nullptr) && (Configuration->Usage != nullptr)) + { + Configuration->Usage->Inc(CounterName); + UnicodeString ExceptionDebugInfo = + E->ClassName() + L":" + GetExceptionDebugInfo(); + Configuration->Usage->Set(L"LastInternalException", ExceptionDebugInfo); + } +*/ + return Result; +} + +bool IsInternalException(const Exception * E) +{ + // see also InternalError in ExceptionMessage + return WellKnownException(E, nullptr, nullptr, nullptr, false); +} + +bool ExceptionMessage(const Exception * E, UnicodeString & Message) +{ + bool InternalError; + return ExceptionMessage(E, true, false, Message, InternalError); +} + +bool ExceptionMessageFormatted(const Exception * E, UnicodeString & Message) +{ + bool InternalError; + return ExceptionMessage(E, true, true, Message, InternalError); +} + +bool ShouldDisplayException(Exception * E) +{ + UnicodeString Message; + return ExceptionMessageFormatted(E, Message); +} + +TStrings * ExceptionToMoreMessages(Exception * E) +{ + TStrings * Result = nullptr; + UnicodeString Message; + if (ExceptionMessage(E, Message)) + { + Result = new TStringList(); + Result->Add(Message); + ExtException * ExtE = NB_STATIC_DOWNCAST(ExtException, E); + if ((ExtE != nullptr) && (ExtE->GetMoreMessages() != nullptr)) + { + Result->AddStrings(ExtE->GetMoreMessages()); + } + } + return Result; +} + +UnicodeString GetExceptionHelpKeyword(const Exception * E) +{ + UnicodeString HelpKeyword; + const ExtException * ExtE = NB_STATIC_DOWNCAST_CONST(ExtException, E); + UnicodeString Message; // not used + bool InternalError = false; + if (ExtE != nullptr) + { + HelpKeyword = ExtE->GetHelpKeyword(); + } + else if ((E != nullptr) && ExceptionMessage(E, false, false, Message, InternalError) && + InternalError) + { + HelpKeyword = HELP_INTERNAL_ERROR; + } + return HelpKeyword; +} + +UnicodeString MergeHelpKeyword(const UnicodeString & PrimaryHelpKeyword, const UnicodeString & SecondaryHelpKeyword) +{ + if (!PrimaryHelpKeyword.IsEmpty() && + !IsInternalErrorHelpKeyword(SecondaryHelpKeyword)) + { + // we have to yet decide what we have both + // PrimaryHelpKeyword and SecondaryHelpKeyword + return PrimaryHelpKeyword; + } + else + { + return SecondaryHelpKeyword; + } +} + +bool IsInternalErrorHelpKeyword(const UnicodeString & HelpKeyword) +{ + return + (HelpKeyword == HELP_INTERNAL_ERROR); +} + +ExtException::ExtException(Exception * E) : + Exception(L""), + FMoreMessages(nullptr) +{ + AddMoreMessages(E); + FHelpKeyword = GetExceptionHelpKeyword(E); +} + +ExtException::ExtException(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) : + Exception(Msg), + FMoreMessages(nullptr) +{ + AddMoreMessages(E); + FHelpKeyword = MergeHelpKeyword(HelpKeyword, GetExceptionHelpKeyword(E)); +} +/*ExtException::ExtException(ExtException * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) : + Exception(Msg), + FMoreMessages(nullptr), + FHelpKeyword() +{ + AddMoreMessages(E); +}*/ + +ExtException::ExtException(Exception * E, int Ident) : + Exception(E, Ident), + FMoreMessages(nullptr), + FHelpKeyword() +{ +} + +ExtException::ExtException(const UnicodeString & Msg, const Exception * E, const UnicodeString & HelpKeyword) : + Exception(L""), + FMoreMessages(nullptr) +{ + // "copy exception" + AddMoreMessages(E); + // and append message to the end to more messages + if (!Msg.IsEmpty()) + { + if (Message.IsEmpty()) + { + Message = Msg; + } + else + { + if (FMoreMessages == nullptr) + { + FMoreMessages = new TStringList(); + } + FMoreMessages->Append(UnformatMessage(Msg)); + } + } + FHelpKeyword = MergeHelpKeyword(GetExceptionHelpKeyword(E), HelpKeyword); +} + +ExtException::ExtException(const UnicodeString & Msg, const UnicodeString & MoreMessages, + const UnicodeString & HelpKeyword) : + Exception(Msg), + FMoreMessages(nullptr), + FHelpKeyword(HelpKeyword) +{ + if (!MoreMessages.IsEmpty()) + { + FMoreMessages = TextToStringList(MoreMessages); + } +} + +ExtException::ExtException(const UnicodeString & Msg, TStrings * MoreMessages, + bool Own, const UnicodeString & HelpKeyword) : + Exception(Msg), + FMoreMessages(nullptr), + FHelpKeyword(HelpKeyword) +{ + if (Own) + { + FMoreMessages = MoreMessages; + } + else + { + FMoreMessages = new TStringList(); + FMoreMessages->Assign(MoreMessages); + } +} + +void ExtException::AddMoreMessages(const Exception * E) +{ + if (E != nullptr) + { + if (FMoreMessages == nullptr) + { + FMoreMessages = new TStringList(); + } + + const ExtException * ExtE = NB_STATIC_DOWNCAST_CONST(ExtException, E); + if (ExtE != nullptr) + { + if (ExtE->GetMoreMessages() != nullptr) + { + FMoreMessages->Assign(ExtE->GetMoreMessages()); + } + } + + UnicodeString Msg; + ExceptionMessageFormatted(E, Msg); + + // new exception does not have own message, this is in fact duplication of + // the exception data, but the exception class may being changed + if (Message.IsEmpty()) + { + Message = Msg; + } + else if (!Msg.IsEmpty()) + { + FMoreMessages->Insert(0, UnformatMessage(Msg)); + } + + if (IsInternalException(E)) + { + AppendExceptionStackTraceAndForget(FMoreMessages); + } + + if (FMoreMessages->GetCount() == 0) + { + SAFE_DESTROY(FMoreMessages); + } + } +} + +ExtException::~ExtException() noexcept +{ + SAFE_DESTROY(FMoreMessages); + FMoreMessages = nullptr; +} + +ExtException * ExtException::CloneFrom(const Exception * E) +{ + return new ExtException(E, L""); +} + +ExtException * ExtException::Clone() const +{ + return CloneFrom(this); +} + +UnicodeString SysErrorMessageForError(int LastError) +{ + UnicodeString Result; + if (LastError != 0) + { + //Result = FORMAT("System Error. Code: %d.\r\n%s", LastError, SysErrorMessage(LastError).c_str()); + Result = FMTLOAD(SOSError, LastError, ::SysErrorMessage(LastError).c_str(), L""); + } + return Result; +} + +UnicodeString LastSysErrorMessage() +{ + return SysErrorMessageForError(GetLastError()); +} + +EOSExtException::EOSExtException(const UnicodeString & Msg) : + ExtException(Msg, LastSysErrorMessage()) +{ +} + +EOSExtException::EOSExtException(const UnicodeString & Msg, int LastError) : + ExtException(Msg, SysErrorMessageForError(LastError)) +{ +} + +EFatal::EFatal(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) : + ExtException(Msg, E, HelpKeyword), + FReopenQueried(false) +{ + const EFatal * F = NB_STATIC_DOWNCAST_CONST(EFatal, E); + if (F != nullptr) + { + FReopenQueried = F->GetReopenQueried(); + } +} + +ECRTExtException::ECRTExtException(const UnicodeString & Msg) : + EOSExtException(Msg, errno) +{ +} + +ExtException * EFatal::Clone() const +{ + return new EFatal(this, L""); +} + +ExtException * ESshTerminate::Clone() const +{ + return new ESshTerminate(this, L"", Operation); +} + +ECallbackGuardAbort::ECallbackGuardAbort() : EAbort(L"callback abort") +{ +} + +Exception * CloneException(Exception * E) +{ + Exception * Result; + // this list has to be in sync with ExceptionMessage + ExtException * Ext = NB_STATIC_DOWNCAST(ExtException, E); + if (Ext != nullptr) + { + Result = Ext->Clone(); + } + else if (NB_STATIC_DOWNCAST(ECallbackGuardAbort, E) != nullptr) + { + Result = new ECallbackGuardAbort(); + } + else if (NB_STATIC_DOWNCAST(EAbort, E) != nullptr) + { + Result = new EAbort(E->Message); + } + else if (WellKnownException(E, nullptr, nullptr, &Result, false)) + { + // noop + } + else + { + // we do not expect this to happen + if (DebugAlwaysFalse(IsInternalException(E))) + { + // to save exception stack trace + Result = ExtException::CloneFrom(E); + } + else + { + Result = new Exception(E->Message); + } + } + return Result; +} + +void RethrowException(Exception * E) +{ + // this list has to be in sync with ExceptionMessage + if (NB_STATIC_DOWNCAST(EFatal, E) != nullptr) + { + throw EFatal(E, L""); + } + else if (NB_STATIC_DOWNCAST(ECallbackGuardAbort, E) != nullptr) + { + throw ECallbackGuardAbort(); + } + else if (NB_STATIC_DOWNCAST(EAbort, E) != nullptr) + { + throw EAbort(E->Message); + } + else if (WellKnownException(E, nullptr, nullptr, nullptr, true)) + { + // noop, should never get here + } + else + { + throw ExtException(E, L""); + } +} + +NB_IMPLEMENT_CLASS(ExtException, NB_GET_CLASS_INFO(Exception), nullptr) +NB_IMPLEMENT_CLASS(EFatal, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(ESshFatal, NB_GET_CLASS_INFO(EFatal), nullptr) +NB_IMPLEMENT_CLASS(EOSExtException, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(ESshTerminate, NB_GET_CLASS_INFO(EFatal), nullptr) +NB_IMPLEMENT_CLASS(ECallbackGuardAbort, NB_GET_CLASS_INFO(EAbort), nullptr) +NB_IMPLEMENT_CLASS(ESsh, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(ETerminal, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(ECommand, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(EScp, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(ESkipFile, NB_GET_CLASS_INFO(ExtException), nullptr) +NB_IMPLEMENT_CLASS(EFileSkipped, NB_GET_CLASS_INFO(ESkipFile), nullptr) + diff --git a/netbox/src/base/Exceptions.h b/netbox/src/base/Exceptions.h new file mode 100644 index 000000000..e05ac823e --- /dev/null +++ b/netbox/src/base/Exceptions.h @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include + +bool ShouldDisplayException(Exception * E); +bool ExceptionMessage(const Exception * E, UnicodeString & Message); +bool ExceptionMessageFormatted(const Exception * E, UnicodeString & Message); +UnicodeString SysErrorMessageForError(int LastError); +UnicodeString LastSysErrorMessage(); +TStrings * ExceptionToMoreMessages(Exception * E); +bool IsInternalException(const Exception * E); + +enum TOnceDoneOperation +{ + odoIdle, + odoDisconnect, + odoSuspend, + odoShutDown, +}; + +class ExtException : public Exception +{ +NB_DECLARE_CLASS(ExtException) +public: + explicit ExtException(Exception * E); + explicit ExtException(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + // explicit ExtException(const ExtException * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + explicit ExtException(Exception * E, int Ident); + // "copy the exception", just append message to the end + explicit ExtException(const UnicodeString & Msg, const Exception * E, const UnicodeString & HelpKeyword = L""); + explicit ExtException(const UnicodeString & Msg, const UnicodeString & MoreMessages, const UnicodeString & HelpKeyword = L""); + explicit ExtException(const UnicodeString & Msg, TStrings * MoreMessages, bool Own, const UnicodeString & HelpKeyword = L""); + virtual ~ExtException(void) noexcept; + TStrings * GetMoreMessages() const { return FMoreMessages; } + const UnicodeString & GetHelpKeyword() const { return FHelpKeyword; } + + explicit inline ExtException(const UnicodeString & Msg) : Exception(Msg), FMoreMessages(nullptr) {} + explicit inline ExtException(int Ident) : Exception(Ident), FMoreMessages(nullptr) {} + explicit inline ExtException(const UnicodeString & Msg, int AHelpContext) : Exception(Msg, AHelpContext), FMoreMessages(nullptr) {} + + ExtException(const ExtException & E) : Exception(L""), FMoreMessages(nullptr) + { + FHelpKeyword = E.FHelpKeyword; + AddMoreMessages(&E); + } + ExtException & operator = (const ExtException & rhs) + { + FHelpKeyword = rhs.FHelpKeyword; + Message = rhs.Message; + AddMoreMessages(&rhs); + return *this; + } + + static ExtException * CloneFrom(const Exception* E); + + virtual ExtException * Clone() const; + +protected: + void AddMoreMessages(const Exception * E); + +private: + TStrings * FMoreMessages; + UnicodeString FHelpKeyword; +}; + +#define DERIVE_EXT_EXCEPTION(NAME, BASE) \ + class NAME : public BASE \ + { \ + NB_DECLARE_CLASS(NAME) \ + public: \ + explicit inline NAME(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L"") : BASE(E, Msg, HelpKeyword) {} \ + virtual inline ~NAME(void) noexcept {} \ + explicit inline NAME(const UnicodeString & Msg, int AHelpContext) : BASE(Msg, AHelpContext) {} \ + virtual ExtException * Clone() const { return new NAME(this, L""); } \ + }; + +DERIVE_EXT_EXCEPTION(ESsh, ExtException) +DERIVE_EXT_EXCEPTION(ETerminal, ExtException) +DERIVE_EXT_EXCEPTION(ECommand, ExtException) +DERIVE_EXT_EXCEPTION(EScp, ExtException) // SCP protocol fatal error (non-fatal in application context) +DERIVE_EXT_EXCEPTION(ESkipFile, ExtException) +DERIVE_EXT_EXCEPTION(EFileSkipped, ESkipFile) + +class EOSExtException : public ExtException +{ +NB_DECLARE_CLASS(EOSExtException) +public: + explicit EOSExtException(); + explicit EOSExtException(const UnicodeString & Msg); + explicit EOSExtException(const UnicodeString & Msg, int LastError); +}; + +class ECRTExtException : public EOSExtException +{ +public: + ECRTExtException(); + explicit ECRTExtException(const UnicodeString & Msg); +}; + +class EFatal : public ExtException +{ +NB_DECLARE_CLASS(EFatal) +public: + // fatal errors are always copied, new message is only appended + explicit EFatal(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + + bool GetReopenQueried() const { return FReopenQueried; } + void SetReopenQueried(bool Value) { FReopenQueried = Value; } + + virtual ExtException * Clone() const; + +private: + bool FReopenQueried; +}; + +#define DERIVE_FATAL_EXCEPTION(NAME, BASE) \ + class NAME : public BASE \ + { \ + NB_DECLARE_CLASS(NAME) \ + public: \ + explicit inline NAME(const Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L"") : BASE(E, Msg, HelpKeyword) {} \ + virtual ExtException * Clone() const { return new NAME(this, L""); } \ + }; + +DERIVE_FATAL_EXCEPTION(ESshFatal, EFatal) + +// exception that closes application, but displays info message (not error message) +// = close on completion +class ESshTerminate : public EFatal +{ +NB_DECLARE_CLASS(ESshTerminate) +public: + explicit inline ESshTerminate(const Exception * E, const UnicodeString & Msg, TOnceDoneOperation AOperation) : + EFatal(E, Msg), + Operation(AOperation) + { + } + + virtual ExtException * Clone() const; + + TOnceDoneOperation Operation; +}; + +class ECallbackGuardAbort : public EAbort +{ +NB_DECLARE_CLASS(ECallbackGuardAbort) +public: + ECallbackGuardAbort(); +}; + +Exception * CloneException(Exception * Exception); +void RethrowException(Exception * E); +UnicodeString GetExceptionHelpKeyword(const Exception* E); +UnicodeString MergeHelpKeyword(const UnicodeString & PrimaryHelpKeyword, const UnicodeString & SecondaryHelpKeyword); +bool IsInternalErrorHelpKeyword(const UnicodeString & HelpKeyword); + diff --git a/netbox/src/base/FastDelegate.h b/netbox/src/base/FastDelegate.h new file mode 100644 index 000000000..31a2cc90d --- /dev/null +++ b/netbox/src/base/FastDelegate.h @@ -0,0 +1,2109 @@ +// FastDelegate.h +// Efficient delegates in C++ that generate only two lines of asm code! +// Documentation is found at http://www.codeproject.com/cpp/FastDelegate.asp +// +// - Don Clugston, Mar 2004. +// Major contributions were made by Jody Hagins. +// History: +// 24-Apr-04 1.0 * Submitted to CodeProject. +// 28-Apr-04 1.1 * Prevent most unsafe uses of evil static function hack. +// * Improved syntax for horrible_cast (thanks Paul Bludov). +// * Tested on Metrowerks MWCC and Intel ICL (IA32) +// * Compiled, but not run, on Comeau C++ and Intel Itanium ICL. +// 27-Jun-04 1.2 * Now works on Borland C++ Builder 5.5 +// * Now works on /clr "managed C++" code on VC7, VC7.1 +// * Comeau C++ now compiles without warnings. +// * Prevent the virtual inheritance case from being used on +// VC6 and earlier, which generate incorrect code. +// * Improved warning and error messages. Non-standard hacks +// now have compile-time checks to make them safer. +// * implicit_cast used instead of static_cast in many cases. +// * If calling a const member function, a const class pointer can be used. +// * MakeDelegate() global helper function added to simplify pass-by-value. +// * Added fastdelegate.clear() +// 16-Jul-04 1.2.1* Workaround for gcc bug (const member function pointers in templates) +// 30-Oct-04 1.3 * Support for (non-void) return values. +// * No more workarounds in client code! +// MSVC and Intel now use a clever hack invented by John Dlugosz: +// - The FASTDELEGATEDECLARE workaround is no longer necessary. +// - No more warning messages for VC6 +// * Less use of macros. Error messages should be more comprehensible. +// * Added include guards +// * Added FastDelegate::empty() to test if invocation is safe (Thanks Neville Franks). +// * Now tested on VS 2005 Express Beta, PGI C++ +// 24-Dec-04 1.4 * Added DelegateMemento, to allow collections of disparate delegates. +// * <,>,<=,>= comparison operators to allow storage in ordered containers. +// * Substantial reduction of code size, especially the 'Closure' class. +// * Standardised all the compiler-specific workarounds. +// * MFP conversion now works for CodePlay (but not yet supported in the full code). +// * Now compiles without warnings on _any_ supported compiler, including BCC 5.5.1 +// * New syntax: FastDelegate< int (char *, double) >. +// 14-Feb-05 1.4.1* Now treats =0 as equivalent to .clear(), ==0 as equivalent to .empty(). (Thanks elfric). +// * Now tested on Intel ICL for AMD64, VS2005 Beta for AMD64 and Itanium. +// 30-Mar-05 1.5 * Safebool idiom: "if (dg)" is now equivalent to "if (!dg.empty())" +// * Fully supported by CodePlay VectorC +// * Bugfix for Metrowerks: empty() was buggy because a valid MFP can be 0 on MWCC! +// * More optimal assignment,== and != operators for static function pointers. + +#ifndef FASTDELEGATE_H +#define FASTDELEGATE_H +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include // to allow <,> comparisons + +//////////////////////////////////////////////////////////////////////////////// +// Configuration options +// +//////////////////////////////////////////////////////////////////////////////// + +// Uncomment the following #define for optimally-sized delegates. +// In this case, the generated asm code is almost identical to the code you'd get +// if the compiler had native support for delegates. +// It will not work on systems where sizeof(dataptr) < sizeof(codeptr). +// Thus, it will not work for DOS compilers using the medium model. +// It will also probably fail on some DSP systems. +#define FASTDELEGATE_USESTATICFUNCTIONHACK + +// Uncomment the next line to allow function declarator syntax. +// It is automatically enabled for those compilers where it is known to work. +//#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +//////////////////////////////////////////////////////////////////////////////// +// Compiler identification for workarounds +// +//////////////////////////////////////////////////////////////////////////////// + +// Compiler identification. It's not easy to identify Visual C++ because +// many vendors fraudulently define Microsoft's identifiers. +#if defined(_MSC_VER) && !defined(__MWERKS__) && !defined(__VECTOR_C) && !defined(__ICL) && !defined(__BORLANDC__) +#define FASTDLGT_ISMSVC + +#if (_MSC_VER <1300) // Many workarounds are required for VC6. +#define FASTDLGT_VC6 +#pragma warning(disable:4786) // disable this ridiculous warning +#endif + +#endif + +// Does the compiler uses Microsoft's member function pointer structure? +// If so, it needs special treatment. +// Metrowerks CodeWarrior, Intel, and CodePlay fraudulently define Microsoft's +// identifier, _MSC_VER. We need to filter Metrowerks out. +#if defined(_MSC_VER) && !defined(__MWERKS__) +#define FASTDLGT_MICROSOFT_MFP + +#if !defined(__VECTOR_C) +// CodePlay doesn't have the __single/multi/virtual_inheritance keywords +#define FASTDLGT_HASINHERITANCE_KEYWORDS +#endif +#endif + +// Does it allow function declarator syntax? The following compilers are known to work: +#if defined(FASTDLGT_ISMSVC) && (_MSC_VER >=1310) // VC 7.1 +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +// Gcc(2.95+), and versions of Digital Mars, Intel and Comeau in common use. +#if defined (__DMC__) || defined(__GNUC__) || defined(__ICL) || defined(__COMO__) +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +// It works on Metrowerks MWCC 3.2.2. From boost.Config it should work on earlier ones too. +#if defined (__MWERKS__) +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +#ifdef __GNUC__ // Workaround GCC bug #8271 + // At present, GCC doesn't recognize constness of MFPs in templates +#define FASTDELEGATE_GCC_BUG_8271 +#endif + + + +//////////////////////////////////////////////////////////////////////////////// +// General tricks used in this code +// +// (a) Error messages are generated by typdefing an array of negative size to +// generate compile-time errors. +// (b) Warning messages on MSVC are generated by declaring unused variables, and +// enabling the "variable XXX is never used" warning. +// (c) Unions are used in a few compiler-specific cases to perform illegal casts. +// (d) For Microsoft and Intel, when adjusting the 'this' pointer, it's cast to +// (char *) first to ensure that the correct number of *bytes* are added. +// +//////////////////////////////////////////////////////////////////////////////// +// Helper templates +// +//////////////////////////////////////////////////////////////////////////////// + + +namespace fastdelegate { +namespace detail { // we'll hide the implementation details in a nested namespace. + +// implicit_cast< > +// I believe this was originally going to be in the C++ standard but +// was left out by accident. It's even milder than static_cast. +// I use it instead of static_cast<> to emphasize that I'm not doing +// anything nasty. +// Usage is identical to static_cast<> +template +inline OutputClass implicit_cast(InputClass input){ + return input; +} + +// horrible_cast< > +// This is truly evil. It completely subverts C++'s type system, allowing you +// to cast from any class to any other class. Technically, using a union +// to perform the cast is undefined behaviour (even in C). But we can see if +// it is OK by checking that the union is the same size as each of its members. +// horrible_cast<> should only be used for compiler-specific workarounds. +// Usage is identical to reinterpret_cast<>. + +// This union is declared outside the horrible_cast because BCC 5.5.1 +// can't inline a function with a nested class, and gives a warning. +template +union horrible_union{ + OutputClass out; + InputClass in; +}; + +template +inline OutputClass horrible_cast(const InputClass input){ + horrible_union u; + // Cause a compile-time error if in, out and u are not the same size. + // If the compile fails here, it means the compiler has peculiar + // unions which would prevent the cast from working. + typedef int ERROR_CantUseHorrible_cast[sizeof(InputClass)==sizeof(u) + && sizeof(InputClass)==sizeof(OutputClass) ? 1 : -1]; + u.in = input; + return u.out; +} + +//////////////////////////////////////////////////////////////////////////////// +// Workarounds +// +//////////////////////////////////////////////////////////////////////////////// + +// Backwards compatibility: This macro used to be necessary in the virtual inheritance +// case for Intel and Microsoft. Now it just forward-declares the class. +#define FASTDELEGATEDECLARE(CLASSNAME) class CLASSNAME; + +// Prevent use of the static function hack with the DOS medium model. +#ifdef __MEDIUM__ +#undef FASTDELEGATE_USESTATICFUNCTIONHACK +#endif + +// DefaultVoid - a workaround for 'void' templates in VC6. +// +// (1) VC6 and earlier do not allow 'void' as a default template argument. +// (2) They also doesn't allow you to return 'void' from a function. +// +// Workaround for (1): Declare a dummy type 'DefaultVoid' which we use +// when we'd like to use 'void'. We convert it into 'void' and back +// using the templates DefaultVoidToVoid<> and VoidToDefaultVoid<>. +// Workaround for (2): On VC6, the code for calling a void function is +// identical to the code for calling a non-void function in which the +// return value is never used, provided the return value is returned +// in the EAX register, rather than on the stack. +// This is true for most fundamental types such as int, enum, void *. +// Const void * is the safest option since it doesn't participate +// in any automatic conversions. But on a 16-bit compiler it might +// cause extra code to be generated, so we disable it for all compilers +// except for VC6 (and VC5). +#ifdef FASTDLGT_VC6 +// VC6 workaround +typedef const void * DefaultVoid; +#else +// On any other compiler, just use a normal void. +typedef void DefaultVoid; +#endif + +// Translate from 'DefaultVoid' to 'void'. +// Everything else is unchanged +template +struct DefaultVoidToVoid { typedef T type; }; + +template <> +struct DefaultVoidToVoid { typedef void type; }; + +// Translate from 'void' into 'DefaultVoid' +// Everything else is unchanged +template +struct VoidToDefaultVoid { typedef T type; }; + +template <> +struct VoidToDefaultVoid { typedef DefaultVoid type; }; + + + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 1: +// +// Conversion of member function pointer to a standard form +// +//////////////////////////////////////////////////////////////////////////////// + +// GenericClass is a fake class, ONLY used to provide a type. +// It is vitally important that it is never defined, so that the compiler doesn't +// think it can optimize the invocation. For example, Borland generates simpler +// code if it knows the class only uses single inheritance. + +// Compilers using Microsoft's structure need to be treated as a special case. +#ifdef FASTDLGT_MICROSOFT_MFP + +#ifdef FASTDLGT_HASINHERITANCE_KEYWORDS + // For Microsoft and Intel, we want to ensure that it's the most efficient type of MFP + // (4 bytes), even when the /vmg option is used. Declaring an empty class + // would give 16 byte pointers in this case.... + class __single_inheritance GenericClass; +#endif + // ...but for Codeplay, an empty class *always* gives 4 byte pointers. + // If compiled with the /clr option ("managed C++"), the JIT compiler thinks + // it needs to load GenericClass before it can call any of its functions, + // (compiles OK but crashes at runtime!), so we need to declare an + // empty class to make it happy. + // Codeplay and VC4 can't cope with the unknown_inheritance case either. + class GenericClass {}; +#else + class GenericClass; +#endif + +// The size of a single inheritance member function pointer. +const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (GenericClass::*)()); + +// SimplifyMemFunc< >::Convert() +// +// A template function that converts an arbitrary member function pointer into the +// simplest possible form of member function pointer, using a supplied 'this' pointer. +// According to the standard, this can be done legally with reinterpret_cast<>. +// For (non-standard) compilers which use member function pointers which vary in size +// depending on the class, we need to use knowledge of the internal structure of a +// member function pointer, as used by the compiler. Template specialization is used +// to distinguish between the sizes. Because some compilers don't support partial +// template specialisation, I use full specialisation of a wrapper struct. + +// general case -- don't know how to convert it. Force a compile failure +template +struct SimplifyMemFunc { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // Unsupported member function type -- force a compile failure. + // (it's illegal to have a array with negative size). + typedef char ERROR_Unsupported_member_function_pointer_on_this_compiler[N-100]; + return 0; + } +}; + +// For compilers where all member func ptrs are the same size, everything goes here. +// For non-standard compilers, only single_inheritance classes go here. +template <> +struct SimplifyMemFunc { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { +#if defined __DMC__ + // Digital Mars doesn't allow you to cast between arbitrary PMF's, + // even though the standard says you can. The 32-bit compiler lets you + // static_cast through an int, but the DOS compiler doesn't. + bound_func = horrible_cast(function_to_bind); +#else + bound_func = reinterpret_cast(function_to_bind); +#endif + return reinterpret_cast(pthis); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 1b: +// +// Workarounds for Microsoft and Intel +// +//////////////////////////////////////////////////////////////////////////////// + + +// Compilers with member function pointers which violate the standard (MSVC, Intel, Codeplay), +// need to be treated as a special case. +#ifdef FASTDLGT_MICROSOFT_MFP + +// We use unions to perform horrible_casts. I would like to use #pragma pack(push, 1) +// at the start of each function for extra safety, but VC6 seems to ICE +// intermittently if you do this inside a template. + +// __multiple_inheritance classes go here +// Nasty hack for Microsoft and Intel (IA32 and Itanium) +template<> +struct SimplifyMemFunc< SINGLE_MEMFUNCPTR_SIZE + sizeof(int) > { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // We need to use a horrible_cast to do this conversion. + // In MSVC, a multiple inheritance member pointer is internally defined as: + union { + XFuncType func; + struct { + GenericMemFuncType funcaddress; // points to the actual member function + int delta; // #BYTES to be added to the 'this' pointer + }s; + } u; + // Check that the horrible_cast will work + typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s)? 1 : -1]; + u.func = function_to_bind; + bound_func = u.s.funcaddress; + return reinterpret_cast(reinterpret_cast(pthis) + u.s.delta); + } +}; + +// virtual inheritance is a real nuisance. It's inefficient and complicated. +// On MSVC and Intel, there isn't enough information in the pointer itself to +// enable conversion to a closure pointer. Earlier versions of this code didn't +// work for all cases, and generated a compile-time error instead. +// But a very clever hack invented by John M. Dlugosz solves this problem. +// My code is somewhat different to his: I have no asm code, and I make no +// assumptions about the calling convention that is used. + +// In VC++ and ICL, a virtual_inheritance member pointer +// is internally defined as: +struct MicrosoftVirtualMFP { + void (GenericClass::*codeptr)(); // points to the actual member function + int delta; // #bytes to be added to the 'this' pointer + int vtable_index; // or 0 if no virtual inheritance +}; +// The CRUCIAL feature of Microsoft/Intel MFPs which we exploit is that the +// m_codeptr member is *always* called, regardless of the values of the other +// members. (This is *not* true for other compilers, eg GCC, which obtain the +// function address from the vtable if a virtual function is being called). +// Dlugosz's trick is to make the codeptr point to a probe function which +// returns the 'this' pointer that was used. + +// Define a generic class that uses virtual inheritance. +// It has a trival member function that returns the value of the 'this' pointer. +struct GenericVirtualClass : virtual public GenericClass +{ + typedef GenericVirtualClass * (GenericVirtualClass::*ProbePtrType)(); + GenericVirtualClass * GetThis() { return this; } +}; + +// __virtual_inheritance classes go here +template <> +struct SimplifyMemFunc +{ + + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + union { + XFuncType func; + GenericClass* (X::*ProbeFunc)(); + MicrosoftVirtualMFP s; + } u; + u.func = function_to_bind; + bound_func = reinterpret_cast(u.s.codeptr); + union { + GenericVirtualClass::ProbePtrType virtfunc; + MicrosoftVirtualMFP s; + } u2; + // Check that the horrible_cast<>s will work + typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s) + && sizeof(function_to_bind)==sizeof(u.ProbeFunc) + && sizeof(u2.virtfunc)==sizeof(u2.s) ? 1 : -1]; + // Unfortunately, taking the address of a MF prevents it from being inlined, so + // this next line can't be completely optimised away by the compiler. + u2.virtfunc = &GenericVirtualClass::GetThis; + u.s.codeptr = u2.s.codeptr; + return (pthis->*u.ProbeFunc)(); + } +}; + +#if (_MSC_VER <1300) + +// Nasty hack for Microsoft Visual C++ 6.0 +// unknown_inheritance classes go here +// There is a compiler bug in MSVC6 which generates incorrect code in this case!! +template <> +struct SimplifyMemFunc +{ + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // There is an appalling but obscure compiler bug in MSVC6 and earlier: + // vtable_index and 'vtordisp' are always set to 0 in the + // unknown_inheritance case! + // This means that an incorrect function could be called!!! + // Compiling with the /vmg option leads to potentially incorrect code. + // This is probably the reason that the IDE has a user interface for specifying + // the /vmg option, but it is disabled - you can only specify /vmg on + // the command line. In VC1.5 and earlier, the compiler would ICE if it ever + // encountered this situation. + // It is OK to use the /vmg option if /vmm or /vms is specified. + + // Fortunately, the wrong function is only called in very obscure cases. + // It only occurs when a derived class overrides a virtual function declared + // in a virtual base class, and the member function + // points to the *Derived* version of that function. The problem can be + // completely averted in 100% of cases by using the *Base class* for the + // member fpointer. Ie, if you use the base class as an interface, you'll + // stay out of trouble. + // Occasionally, you might want to point directly to a derived class function + // that isn't an override of a base class. In this case, both vtable_index + // and 'vtordisp' are zero, but a virtual_inheritance pointer will be generated. + // We can generate correct code in this case. To prevent an incorrect call from + // ever being made, on MSVC6 we generate a warning, and call a function to + // make the program crash instantly. + typedef char ERROR_VC6CompilerBug[-100]; + return 0; + } +}; + + +#else + +// Nasty hack for Microsoft and Intel (IA32 and Itanium) +// unknown_inheritance classes go here +// This is probably the ugliest bit of code I've ever written. Look at the casts! +// There is a compiler bug in MSVC6 which prevents it from using this code. +template <> +struct SimplifyMemFunc +{ + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // The member function pointer is 16 bytes long. We can't use a normal cast, but + // we can use a union to do the conversion. + union { + XFuncType func; + // In VC++ and ICL, an unknown_inheritance member pointer + // is internally defined as: + struct { + GenericMemFuncType m_funcaddress; // points to the actual member function + int delta; // #bytes to be added to the 'this' pointer + int vtordisp; // #bytes to add to 'this' to find the vtable + int vtable_index; // or 0 if no virtual inheritance + } s; + } u; + // Check that the horrible_cast will work + typedef int ERROR_CantUsehorrible_cast[sizeof(XFuncType)==sizeof(u.s)? 1 : -1]; + u.func = function_to_bind; + bound_func = u.s.funcaddress; + int virtual_delta = 0; + if (u.s.vtable_index) { // Virtual inheritance is used + // First, get to the vtable. + // It is 'vtordisp' bytes from the start of the class. + const int * vtable = *reinterpret_cast( + reinterpret_cast(pthis) + u.s.vtordisp ); + + // 'vtable_index' tells us where in the table we should be looking. + virtual_delta = u.s.vtordisp + *reinterpret_cast( + reinterpret_cast(vtable) + u.s.vtable_index); + } + // The int at 'virtual_delta' gives us the amount to add to 'this'. + // Finally we can add the three components together. Phew! + return reinterpret_cast( + reinterpret_cast(pthis) + u.s.delta + virtual_delta); + }; +}; +#endif // MSVC 7 and greater + +#endif // MS/Intel hacks + +} // namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 2: +// +// Define the delegate storage, and cope with static functions +// +//////////////////////////////////////////////////////////////////////////////// + +// DelegateMemento -- an opaque structure which can hold an arbitrary delegate. +// It knows nothing about the calling convention or number of arguments used by +// the function pointed to. +// It supplies comparison operators so that it can be stored in STL collections. +// It cannot be set to anything other than null, nor invoked directly: +// it must be converted to a specific delegate. + +// Implementation: +// There are two possible implementations: the Safe method and the Evil method. +// DelegateMemento - Safe version +// +// This implementation is standard-compliant, but a bit tricky. +// A static function pointer is stored inside the class. +// Here are the valid values: +// +-- Static pointer --+--pThis --+-- pMemFunc-+-- Meaning------+ +// | 0 | 0 | 0 | Empty | +// | !=0 |(dontcare)| Invoker | Static function| +// | 0 | !=0 | !=0* | Method call | +// +--------------------+----------+------------+----------------+ +// * For Metrowerks, this can be 0. (first virtual function in a +// single_inheritance class). +// When stored stored inside a specific delegate, the 'dontcare' entries are replaced +// with a reference to the delegate itself. This complicates the = and == operators +// for the delegate class. + +// DelegateMemento - Evil version +// +// For compilers where data pointers are at least as big as code pointers, it is +// possible to store the function pointer in the this pointer, using another +// horrible_cast. In this case the DelegateMemento implementation is simple: +// +--pThis --+-- pMemFunc-+-- Meaning---------------------+ +// | 0 | 0 | Empty | +// | !=0 | !=0* | Static function or method call| +// +----------+------------+-------------------------------+ +// * For Metrowerks, this can be 0. (first virtual function in a +// single_inheritance class). +// Note that the Sun C++ and MSVC documentation explicitly state that they +// support static_cast between void * and function pointers. + +class DelegateMemento { +protected: + // the data is protected, not private, because many + // compilers have problems with template friends. + typedef void (detail::GenericClass::*GenericMemFuncType)(); // arbitrary MFP. + GenericMemFuncType m_pFunction; + detail::GenericClass *m_pthis; + +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + typedef void (*GenericFuncPtr)(); // arbitrary code pointer + GenericFuncPtr m_pStaticFunction; +#endif + +public: +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + DelegateMemento() : m_pthis(0), m_pFunction(0), m_pStaticFunction(0) {}; + void clear() { + m_pthis=0; m_pFunction=0; m_pStaticFunction=0; + } +#else + DelegateMemento() : m_pFunction(0), m_pthis(0) {}; + void clear() { m_pthis=0; m_pFunction=0; } +#endif +public: +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + inline bool IsEqual (const DelegateMemento &x) const{ + // We have to cope with the static function pointers as a special case + if (m_pFunction!=x.m_pFunction) return false; + // the static function ptrs must either both be equal, or both be 0. + if (m_pStaticFunction!=x.m_pStaticFunction) return false; + if (m_pStaticFunction!=0) return m_pthis==x.m_pthis; + else return true; + } +#else // Evil Method + inline bool IsEqual (const DelegateMemento &x) const{ + return m_pthis==x.m_pthis && m_pFunction==x.m_pFunction; + } +#endif + // Provide a strict weak ordering for DelegateMementos. + inline bool IsLess(const DelegateMemento &right) const { + // deal with static function pointers first +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + if (m_pStaticFunction !=0 || right.m_pStaticFunction!=0) + return m_pStaticFunction < right.m_pStaticFunction; +#endif + if (m_pthis !=right.m_pthis) return m_pthis < right.m_pthis; + // There are no ordering operators for member function pointers, + // but we can fake one by comparing each byte. The resulting ordering is + // arbitrary (and compiler-dependent), but it permits storage in ordered STL containers. + return memcmp(&m_pFunction, &right.m_pFunction, sizeof(m_pFunction)) < 0; + + } + // BUGFIX (Mar 2005): + // We can't just compare m_pFunction because on Metrowerks, + // m_pFunction can be zero even if the delegate is not empty! + inline bool operator ! () const // Is it bound to anything? + { return m_pthis==0 && m_pFunction==0; } + inline bool empty() const // Is it bound to anything? + { return m_pthis==0 && m_pFunction==0; } +public: + DelegateMemento & operator = (const DelegateMemento &right) { + SetMementoFrom(right); + return *this; + } + inline bool operator <(const DelegateMemento &right) { + return IsLess(right); + } + inline bool operator >(const DelegateMemento &right) { + return right.IsLess(*this); + } + DelegateMemento (const DelegateMemento &right) : + m_pFunction(right.m_pFunction), m_pthis(right.m_pthis) +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + , m_pStaticFunction (right.m_pStaticFunction) +#endif + {} +protected: + void SetMementoFrom(const DelegateMemento &right) { + m_pFunction = right.m_pFunction; + m_pthis = right.m_pthis; +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = right.m_pStaticFunction; +#endif + } +}; + + +// ClosurePtr<> +// +// A private wrapper class that adds function signatures to DelegateMemento. +// It's the class that does most of the actual work. +// The signatures are specified by: +// GenericMemFunc: must be a type of GenericClass member function pointer. +// StaticFuncPtr: must be a type of function pointer with the same signature +// as GenericMemFunc. +// UnvoidStaticFuncPtr: is the same as StaticFuncPtr, except on VC6 +// where it never returns void (returns DefaultVoid instead). + +// An outer class, FastDelegateN<>, handles the invoking and creates the +// necessary typedefs. +// This class does everything else. + +namespace detail { + +template < class GenericMemFunc, class StaticFuncPtr, class UnvoidStaticFuncPtr> +class ClosurePtr : public DelegateMemento { +public: + // These functions are for setting the delegate to a member function. + + // Here's the clever bit: we convert an arbitrary member function into a + // standard form. XMemFunc should be a member function of class X, but I can't + // enforce that here. It needs to be enforced by the wrapper class. + template < class X, class XMemFunc > + inline void bindmemfunc(X *pthis, XMemFunc function_to_bind ) { + m_pthis = SimplifyMemFunc< sizeof(function_to_bind) > + ::Convert(pthis, function_to_bind, m_pFunction); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } + // For const member functions, we only need a const class pointer. + // Since we know that the member function is const, it's safe to + // remove the const qualifier from the 'this' pointer with a const_cast. + // VC6 has problems if we just overload 'bindmemfunc', so we give it a different name. + template < class X, class XMemFunc> + inline void bindconstmemfunc(const X *pthis, XMemFunc function_to_bind) { + m_pthis= SimplifyMemFunc< sizeof(function_to_bind) > + ::Convert(const_cast(pthis), function_to_bind, m_pFunction); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } +#ifdef FASTDELEGATE_GCC_BUG_8271 // At present, GCC doesn't recognize constness of MFPs in templates + template < class X, class XMemFunc> + inline void bindmemfunc(const X *pthis, XMemFunc function_to_bind) { + bindconstmemfunc(pthis, function_to_bind); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } +#endif + // These functions are required for invoking the stored function + inline GenericClass *GetClosureThis() const { return m_pthis; } + inline GenericMemFunc GetClosureMemPtr() const { return reinterpret_cast(m_pFunction); } + +// There are a few ways of dealing with static function pointers. +// There's a standard-compliant, but tricky method. +// There's also a straightforward hack, that won't work on DOS compilers using the +// medium memory model. It's so evil that I can't recommend it, but I've +// implemented it anyway because it produces very nice asm code. + +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + +// ClosurePtr<> - Safe version +// +// This implementation is standard-compliant, but a bit tricky. +// I store the function pointer inside the class, and the delegate then +// points to itself. Whenever the delegate is copied, these self-references +// must be transformed, and this complicates the = and == operators. +public: + // The next two functions are for operator ==, =, and the copy constructor. + // We may need to convert the m_pthis pointers, so that + // they remain as self-references. + template< class DerivedClass > + inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &x) { + SetMementoFrom(x); + if (m_pStaticFunction!=0) { + // transform self references... + m_pthis=reinterpret_cast(pParent); + } + } + // For static functions, the 'static_function_invoker' class in the parent + // will be called. The parent then needs to call GetStaticFunction() to find out + // the actual function to invoke. + template < class DerivedClass, class ParentInvokerSig > + inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, + StaticFuncPtr function_to_bind ) { + if (function_to_bind==0) { // cope with assignment to 0 + m_pFunction=0; + } else { + bindmemfunc(pParent, static_function_invoker); + } + m_pStaticFunction=reinterpret_cast(function_to_bind); + } + inline UnvoidStaticFuncPtr GetStaticFunction() const { + return reinterpret_cast(m_pStaticFunction); + } +#else + +// ClosurePtr<> - Evil version +// +// For compilers where data pointers are at least as big as code pointers, it is +// possible to store the function pointer in the this pointer, using another +// horrible_cast. Invocation isn't any faster, but it saves 4 bytes, and +// speeds up comparison and assignment. If C++ provided direct language support +// for delegates, they would produce asm code that was almost identical to this. +// Note that the Sun C++ and MSVC documentation explicitly state that they +// support static_cast between void * and function pointers. + + template< class DerivedClass > + inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &right) { + (void)pParent; + SetMementoFrom(right); + } + // For static functions, the 'static_function_invoker' class in the parent + // will be called. The parent then needs to call GetStaticFunction() to find out + // the actual function to invoke. + // ******** EVIL, EVIL CODE! ******* + template < class DerivedClass, class ParentInvokerSig> + inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, + StaticFuncPtr function_to_bind) { + if (function_to_bind==0) { // cope with assignment to 0 + m_pFunction=0; + } else { + // We'll be ignoring the 'this' pointer, but we need to make sure we pass + // a valid value to bindmemfunc(). + bindmemfunc(pParent, static_function_invoker); + } + + // WARNING! Evil hack. We store the function in the 'this' pointer! + // Ensure that there's a compilation failure if function pointers + // and data pointers have different sizes. + // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. + typedef int ERROR_CantUseEvilMethod[sizeof(GenericClass *)==sizeof(function_to_bind) ? 1 : -1]; + m_pthis = horrible_cast(function_to_bind); + // MSVC, SunC++ and DMC accept the following (non-standard) code: +// m_pthis = static_cast(static_cast(function_to_bind)); + // BCC32, Comeau and DMC accept this method. MSVC7.1 needs __int64 instead of long +// m_pthis = reinterpret_cast(reinterpret_cast(function_to_bind)); + } + // ******** EVIL, EVIL CODE! ******* + // This function will be called with an invalid 'this' pointer!! + // We're just returning the 'this' pointer, converted into + // a function pointer! + inline UnvoidStaticFuncPtr GetStaticFunction() const { + // Ensure that there's a compilation failure if function pointers + // and data pointers have different sizes. + // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. + typedef int ERROR_CantUseEvilMethod[sizeof(UnvoidStaticFuncPtr)==sizeof(this) ? 1 : -1]; + return horrible_cast(this); + } +#endif // !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + + // Does the closure contain this static function? + inline bool IsEqualToStaticFuncPtr(StaticFuncPtr funcptr){ + if (funcptr==0) return empty(); + // For the Evil method, if it doesn't actually contain a static function, this will return an arbitrary + // value that is not equal to any valid function pointer. + else return funcptr==reinterpret_cast(GetStaticFunction()); + } +}; + + +} // namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 3: +// +// Wrapper classes to ensure type safety +// +//////////////////////////////////////////////////////////////////////////////// + + +// Once we have the member function conversion templates, it's easy to make the +// wrapper classes. So that they will work with as many compilers as possible, +// the classes are of the form +// FastDelegate3 +// They can cope with any combination of parameters. The max number of parameters +// allowed is 8, but it is trivial to increase this limit. +// Note that we need to treat const member functions seperately. +// All this class does is to enforce type safety, and invoke the delegate with +// the correct list of parameters. + +// Because of the weird rule about the class of derived member function pointers, +// you sometimes need to apply a downcast to the 'this' pointer. +// This is the reason for the use of "implicit_cast(pthis)" in the code below. +// If CDerivedClass is derived from CBaseClass, but doesn't override SimpleVirtualFunction, +// without this trick you'd need to write: +// MyDelegate(static_cast(&d), &CDerivedClass::SimpleVirtualFunction); +// but with the trick you can write +// MyDelegate(&d, &CDerivedClass::SimpleVirtualFunction); + +// RetType is the type the compiler uses in compiling the template. For VC6, +// it cannot be void. DesiredRetType is the real type which is returned from +// all of the functions. It can be void. + +// Implicit conversion to "bool" is achieved using the safe_bool idiom, +// using member data pointers (MDP). This allows "if (dg)..." syntax +// Because some compilers (eg codeplay) don't have a unique value for a zero +// MDP, an extra padding member is added to the SafeBool struct. +// Some compilers (eg VC6) won't implicitly convert from 0 to an MDP, so +// in that case the static function constructor is not made explicit; this +// allows "if (dg==0) ..." to compile. + +//N=0 +template +class FastDelegate0 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(); + typedef RetType (*UnvoidStaticFunctionPtr)(); + typedef RetType (detail::GenericClass::*GenericMemFn)(); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate0 type; + + // Construction and comparison functions + FastDelegate0() { clear(); } + FastDelegate0(const FastDelegate0 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate0 & operator = (const FastDelegate0 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate0 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate0 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate0 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate0 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate0(Y *pthis, DesiredRetType (X::* function_to_bind)() ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)()) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate0(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate0(DesiredRetType (*function_to_bind)() ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate0 & operator = (DesiredRetType (*function_to_bind)() ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)()) { + m_Closure.bindstaticfunc(this, &FastDelegate0::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() () const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction() const { + return (*(m_Closure.GetStaticFunction()))(); } +}; + +//N=1 +template +class FastDelegate1 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate1 type; + + // Construction and comparison functions + FastDelegate1() { clear(); } + FastDelegate1(const FastDelegate1 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate1 & operator = (const FastDelegate1 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate1 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate1 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate1 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate1 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate1(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate1(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate1(DesiredRetType (*function_to_bind)(Param1 p1) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate1 & operator = (DesiredRetType (*function_to_bind)(Param1 p1) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1)) { + m_Closure.bindstaticfunc(this, &FastDelegate1::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1) const { + return (*(m_Closure.GetStaticFunction()))(p1); } +}; + +//N=2 +template +class FastDelegate2 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate2 type; + + // Construction and comparison functions + FastDelegate2() { clear(); } + FastDelegate2(const FastDelegate2 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate2 & operator = (const FastDelegate2 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate2 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate2 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate2 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate2 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate2(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate2(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate2(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate2 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2)) { + m_Closure.bindstaticfunc(this, &FastDelegate2::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2); } +}; + +//N=3 +template +class FastDelegate3 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate3 type; + + // Construction and comparison functions + FastDelegate3() { clear(); } + FastDelegate3(const FastDelegate3 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate3 & operator = (const FastDelegate3 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate3 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate3 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate3 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate3 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate3(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate3(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate3(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate3 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { + m_Closure.bindstaticfunc(this, &FastDelegate3::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3); } +}; + +//N=4 +template +class FastDelegate4 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate4 type; + + // Construction and comparison functions + FastDelegate4() { clear(); } + FastDelegate4(const FastDelegate4 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate4 & operator = (const FastDelegate4 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate4 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate4 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate4 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate4 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate4(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate4(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate4(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate4 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + m_Closure.bindstaticfunc(this, &FastDelegate4::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4); } +}; + +//N=5 +template +class FastDelegate5 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate5 type; + + // Construction and comparison functions + FastDelegate5() { clear(); } + FastDelegate5(const FastDelegate5 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate5 & operator = (const FastDelegate5 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate5 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate5 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate5 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate5 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate5(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate5(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate5(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate5 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + m_Closure.bindstaticfunc(this, &FastDelegate5::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5); } +}; + +//N=6 +template +class FastDelegate6 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate6 type; + + // Construction and comparison functions + FastDelegate6() { clear(); } + FastDelegate6(const FastDelegate6 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate6 & operator = (const FastDelegate6 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate6 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate6 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate6 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate6 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate6(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate6(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate6(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate6 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + m_Closure.bindstaticfunc(this, &FastDelegate6::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6); } +}; + +//N=7 +template +class FastDelegate7 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate7 type; + + // Construction and comparison functions + FastDelegate7() { clear(); } + FastDelegate7(const FastDelegate7 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate7 & operator = (const FastDelegate7 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate7 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate7 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate7 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate7 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate7(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate7(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate7(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate7 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + m_Closure.bindstaticfunc(this, &FastDelegate7::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7); } +}; + +//N=8 +template +class FastDelegate8 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate8 type; + + // Construction and comparison functions + FastDelegate8() { clear(); } + FastDelegate8(const FastDelegate8 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + FastDelegate8 & operator = (const FastDelegate8 &x) { + m_Closure.CopyFrom(this, x.m_Closure); return *this; } + bool operator ==(const FastDelegate8 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate8 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate8 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate8 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate8(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate8(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate8(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + FastDelegate8 & operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + bind(function_to_bind); return *this; } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + m_Closure.bindstaticfunc(this, &FastDelegate8::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7, p8); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7, p8); } +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 4: +// +// FastDelegate<> class (Original author: Jody Hagins) +// Allows boost::function style syntax like: +// FastDelegate< double (int, long) > +// instead of: +// FastDelegate2< int, long, double > +// +//////////////////////////////////////////////////////////////////////////////// + +#ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +// Declare FastDelegate as a class template. It will be specialized +// later for all number of arguments. +template +class FastDelegate; + +//N=0 +// Specialization to allow use of +// FastDelegate< R ( ) > +// instead of +// FastDelegate0 < R > +template +class FastDelegate< R ( ) > + // Inherit from FastDelegate0 so that it can be treated just like a FastDelegate0 + : public FastDelegate0 < R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate0 < R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=1 +// Specialization to allow use of +// FastDelegate< R ( Param1 ) > +// instead of +// FastDelegate1 < R, Param1 > +template +class FastDelegate< R ( Param1 ) > + // Inherit from FastDelegate1 so that it can be treated just like a FastDelegate1 + : public FastDelegate1 < R, Param1 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate1 < R, Param1 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=2 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2 ) > +// instead of +// FastDelegate2 < R, Param1, Param2 > +template +class FastDelegate< R ( Param1, Param2 ) > + // Inherit from FastDelegate2 so that it can be treated just like a FastDelegate2 + : public FastDelegate2 < R, Param1, Param2 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate2 < R, Param1, Param2 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=3 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3 ) > +// instead of +// FastDelegate3 < R, Param1, Param2, Param3 > +template +class FastDelegate< R ( Param1, Param2, Param3 ) > + // Inherit from FastDelegate3 so that it can be treated just like a FastDelegate3 + : public FastDelegate3 < R, Param1, Param2, Param3 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate3 < R, Param1, Param2, Param3 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=4 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4 ) > +// instead of +// FastDelegate4 < R, Param1, Param2, Param3, Param4 > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4 ) > + // Inherit from FastDelegate4 so that it can be treated just like a FastDelegate4 + : public FastDelegate4 < R, Param1, Param2, Param3, Param4 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate4 < R, Param1, Param2, Param3, Param4 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=5 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > +// instead of +// FastDelegate5 < R, Param1, Param2, Param3, Param4, Param5 > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > + // Inherit from FastDelegate5 so that it can be treated just like a FastDelegate5 + : public FastDelegate5 < R, Param1, Param2, Param3, Param4, Param5 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate5 < R, Param1, Param2, Param3, Param4, Param5 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=6 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > +// instead of +// FastDelegate6 < R, Param1, Param2, Param3, Param4, Param5, Param6 > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > + // Inherit from FastDelegate6 so that it can be treated just like a FastDelegate6 + : public FastDelegate6 < R, Param1, Param2, Param3, Param4, Param5, Param6 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate6 < R, Param1, Param2, Param3, Param4, Param5, Param6 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=7 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > +// instead of +// FastDelegate7 < R, Param1, Param2, Param3, Param4, Param5, Param6, Param7 > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > + // Inherit from FastDelegate7 so that it can be treated just like a FastDelegate7 + : public FastDelegate7 < R, Param1, Param2, Param3, Param4, Param5, Param6, Param7 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate7 < R, Param1, Param2, Param3, Param4, Param5, Param6, Param7 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + +//N=8 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > +// instead of +// FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > + // Inherit from FastDelegate8 so that it can be treated just like a FastDelegate8 + : public FastDelegate8 < R, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate8 < R, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) + : BaseType(function_to_bind) { } + FastDelegate & operator = (const BaseType &x) { + *static_cast(this) = x; return *this; } +}; + + +#endif //FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 5: +// +// MakeDelegate() helper function +// +// MakeDelegate(&x, &X::func) returns a fastdelegate of the type +// necessary for calling x.func() with the correct number of arguments. +// This makes it possible to eliminate many typedefs from user code. +// +//////////////////////////////////////////////////////////////////////////////// + +// Also declare overloads of a MakeDelegate() global function to +// reduce the need for typedefs. +// We need seperate overloads for const and non-const member functions. +// Also, because of the weird rule about the class of derived member function pointers, +// implicit downcasts may need to be applied later to the 'this' pointer. +// That's why two classes (X and Y) appear in the definitions. Y must be implicitly +// castable to X. + +// Workaround for VC6. VC6 needs void return types converted into DefaultVoid. +// GCC 3.2 and later won't compile this unless it's preceded by 'typename', +// but VC6 doesn't allow 'typename' in this context. +// So, I have to use a macro. + +#ifdef FASTDLGT_VC6 +#define FASTDLGT_RETTYPE detail::VoidToDefaultVoid::type +#else +#define FASTDLGT_RETTYPE RetType +#endif + +//N=0 +template +FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)()) { + return FastDelegate0(x, func); +} + +template +FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)() const) { + return FastDelegate0(x, func); +} + +//N=1 +template +FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1)) { + return FastDelegate1(x, func); +} + +template +FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1) const) { + return FastDelegate1(x, func); +} + +//N=2 +template +FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2)) { + return FastDelegate2(x, func); +} + +template +FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2) const) { + return FastDelegate2(x, func); +} + +//N=3 +template +FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3)) { + return FastDelegate3(x, func); +} + +template +FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3) const) { + return FastDelegate3(x, func); +} + +//N=4 +template +FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + return FastDelegate4(x, func); +} + +template +FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + return FastDelegate4(x, func); +} + +//N=5 +template +FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + return FastDelegate5(x, func); +} + +template +FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + return FastDelegate5(x, func); +} + +//N=6 +template +FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + return FastDelegate6(x, func); +} + +template +FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + return FastDelegate6(x, func); +} + +//N=7 +template +FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + return FastDelegate7(x, func); +} + +template +FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + return FastDelegate7(x, func); +} + +//N=8 +template +FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + return FastDelegate8(x, func); +} + +template +FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + return FastDelegate8(x, func); +} + + + // clean up after ourselves... +#undef FASTDLGT_RETTYPE + +} // namespace fastdelegate + +#endif // !defined(FASTDELEGATE_H) + diff --git a/netbox/src/base/FastDelegateBind.h b/netbox/src/base/FastDelegateBind.h new file mode 100644 index 000000000..cd9728893 --- /dev/null +++ b/netbox/src/base/FastDelegateBind.h @@ -0,0 +1,243 @@ +// FastDelegateBind.h +// Helper file for FastDelegates. Provides bind() function, enabling +// FastDelegates to be rapidly compared to programs using boost::function and fastdelegate::bind. +// +// Documentation is found at http://www.codeproject.com/cpp/FastDelegate.asp +// +// Original author: Jody Hagins. +// Minor changes by Don Clugston. +// +// Warning: The arguments to 'bind' are ignored! No actual binding is performed. +// The behaviour is equivalent to fastdelegate::bind only when the basic placeholder +// arguments _1, _2, _3, etc are used in order. +// +// HISTORY: +// 1.4 Dec 2004. Initial release as part of FastDelegate 1.4. + + +#ifndef FASTDELEGATEBIND_H +#define FASTDELEGATEBIND_H +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +//////////////////////////////////////////////////////////////////////////////// +// FastDelegate bind() +// +// bind() helper function for boost compatibility. +// (Original author: Jody Hagins). +// +// Add another helper, so FastDelegate can be a dropin replacement +// for fastdelegate::bind (in a fair number of cases). +// Note the elipses, because fastdelegate::bind() takes place holders +// but FastDelegate does not care about them. Getting the place holder +// mechanism to work, and play well with boost is a bit tricky, so +// we do the "easy" thing... +// Assume we have the following code... +// using fastdelegate::bind; +// bind(&Foo:func, &foo, _1, _2); +// we should be able to replace the "using" with... +// using fastdelegate::bind; +// and everything should work fine... +//////////////////////////////////////////////////////////////////////////////// + +#ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +namespace fastdelegate { + +//N=0 +template +FastDelegate< RetType ( ) > +bind( + RetType (X::*func)( ), + Y * y, + ...) +{ + return FastDelegate< RetType ( ) >(y, func); +} + +template +FastDelegate< RetType ( ) > +bind( + RetType (X::*func)( ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( ) >(y, func); +} + +//N=1 +template +FastDelegate< RetType ( Param1 p1 ) > +bind( + RetType (X::*func)( Param1 p1 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1 ) > +bind( + RetType (X::*func)( Param1 p1 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1 ) >(y, func); +} + +//N=2 +template +FastDelegate< RetType ( Param1 p1, Param2 p2 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2 ) >(y, func); +} + +//N=3 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) >(y, func); +} + +//N=4 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) >(y, func); +} + +//N=5 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) >(y, func); +} + +//N=6 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) >(y, func); +} + +//N=7 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) >(y, func); +} + +//N=8 +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ), + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) >(y, func); +} + +template +FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) > +bind( + RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) const, + Y * y, + ...) +{ + return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) >(y, func); +} + +} // namespace fastdelegate + +#endif //FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + + +#endif // !defined(FASTDELEGATEBIND_H) + diff --git a/netbox/src/base/FileBuffer.cpp b/netbox/src/base/FileBuffer.cpp new file mode 100644 index 000000000..86bebaec5 --- /dev/null +++ b/netbox/src/base/FileBuffer.cpp @@ -0,0 +1,242 @@ +#include +#pragma hdrstop + +#include +#include + +const wchar_t * EOLTypeNames = L"LF;CRLF;CR"; + +char * EOLToStr(TEOLType EOLType) +{ + switch (EOLType) + { + case eolLF: + return (char *)"\n"; + case eolCRLF: + return (char *)"\r\n"; + case eolCR: + return (char *)"\r"; + default: + DebugFail(); + return (char *)""; + } +} + +TFileBuffer::TFileBuffer() : + FMemory(new TMemoryStream()) +{ +} + +TFileBuffer::~TFileBuffer() +{ + SAFE_DESTROY(FMemory); +} + +void TFileBuffer::SetSize(int64_t Value) +{ + if (FMemory->GetSize() != Value) + { + FMemory->SetSize(Value); + } +} + +void TFileBuffer::SetPosition(int64_t Value) +{ + FMemory->SetPosition(Value); +} + +int64_t TFileBuffer::GetPosition() const +{ + return FMemory->GetPosition(); +} + +void TFileBuffer::SetMemory(TMemoryStream * Value) +{ + if (FMemory != Value) + { + if (FMemory) + { + SAFE_DESTROY(FMemory); + } + FMemory = Value; + } +} + +int64_t TFileBuffer::ReadStream(TStream * Stream, const int64_t Len, bool ForceLen) +{ + int64_t Result = 0; + try + { + SetSize(GetPosition() + Len); + // C++5 + // FMemory->SetSize(FMemory->Position + Len); + if (ForceLen) + { + Stream->ReadBuffer(GetData() + GetPosition(), Len); + Result = Len; + } + else + { + Result = Stream->Read(GetData() + GetPosition(), Len); + } + if (Result != Len) + { + SetSize(GetSize() - Len + Result); + } + FMemory->Seek(Len, soFromCurrent); + } + catch (EReadError &) + { + ::RaiseLastOSError(); + } + return Result; +} + +int64_t TFileBuffer::LoadStream(TStream * Stream, const int64_t Len, bool ForceLen) +{ + FMemory->Seek(0, soFromBeginning); + return ReadStream(Stream, Len, ForceLen); +} + +void TFileBuffer::Convert(char * Source, char * Dest, intptr_t Params, + bool & Token) +{ + DebugAssert(strlen(Source) <= 2); + DebugAssert(strlen(Dest) <= 2); + + const std::string Bom(CONST_BOM); + if (FLAGSET(Params, cpRemoveBOM) && (GetSize() >= 3) && + (memcmp(GetData(), Bom.c_str(), Bom.size()) == 0)) + { + Delete(0, 3); + } + + if (FLAGSET(Params, cpRemoveCtrlZ) && (GetSize() > 0) && ((*(GetData() + GetSize() - 1)) == '\x1A')) + { + Delete(GetSize() - 1, 1); + } + + if (strcmp(Source, Dest) == 0) + { + return; + } + + char * Ptr = GetData(); + + // one character source EOL + if (!Source[1]) + { + bool PrevToken = Token; + Token = false; + + for (intptr_t Index = 0; Index < GetSize(); ++Index) + { + // EOL already in destination format, make sure to pass it unmodified + if ((Index < GetSize() - 1) && (*Ptr == Dest[0]) && (*(Ptr+1) == Dest[1])) + { + ++Index; + Ptr++; + } + // last buffer ended with the first char of destination 2-char EOL format, + // which got expanded to full destination format. + // now we got the second char, so get rid of it. + else if ((Index == 0) && PrevToken && (*Ptr == Dest[1])) + { + Delete(Index, 1); + } + // we are ending with the first char of destination 2-char EOL format, + // append the second char and make sure we strip it from the next buffer, if any + else if ((*Ptr == Dest[0]) && (Index == GetSize() - 1) && Dest[1]) + { + Token = true; + Insert(Index+1, Dest+1, 1); + ++Index; + Ptr = GetData() + Index; + } + else if (*Ptr == Source[0]) + { + *Ptr = Dest[0]; + if (Dest[1]) + { + Insert(Index+1, Dest+1, 1); + ++Index; + Ptr = GetData() + Index; + } + } + Ptr++; + } + } + // two character source EOL + else + { + intptr_t Index; + for (Index = 0; Index < GetSize() - 1; ++Index) + { + if ((*Ptr == Source[0]) && (*(Ptr+1) == Source[1])) + { + *Ptr = Dest[0]; + if (Dest[1]) + { + *(Ptr+1) = Dest[1]; + ++Index; ++Ptr; + } + else + { + Delete(Index+1, 1); + Ptr = GetData() + Index; + } + } + Ptr++; + } + if ((Index < GetSize()) && (*Ptr == Source[0])) + { + Delete(Index, 1); + } + } +} + +void TFileBuffer::Convert(TEOLType Source, TEOLType Dest, intptr_t Params, + bool & Token) +{ + Convert(EOLToStr(Source), EOLToStr(Dest), Params, Token); +} + +void TFileBuffer::Convert(char * Source, TEOLType Dest, intptr_t Params, + bool & Token) +{ + Convert(Source, EOLToStr(Dest), Params, Token); +} + +void TFileBuffer::Convert(TEOLType Source, char * Dest, intptr_t Params, + bool & Token) +{ + Convert(EOLToStr(Source), Dest, Params, Token); +} + +void TFileBuffer::Insert(int64_t Index, const char * Buf, int64_t Len) +{ + SetSize(GetSize() + Len); + memmove(GetData() + Index + Len, GetData() + Index, static_cast(GetSize() - Index - Len)); + memmove(GetData() + Index, Buf, Len); +} + +void TFileBuffer::Delete(int64_t Index, int64_t Len) +{ + memmove(GetData() + Index, GetData() + Index + Len, static_cast(GetSize() - Index - Len)); + SetSize(GetSize() - Len); +} + +void TFileBuffer::WriteToStream(TStream * Stream, const int64_t Len) +{ + DebugAssert(Stream); + try + { + Stream->WriteBuffer(GetData() + GetPosition(), Len); + FMemory->Seek(Len, soFromCurrent); + } + catch (EWriteError &) + { + ::RaiseLastOSError(); + } +} + diff --git a/netbox/src/base/FileBuffer.h b/netbox/src/base/FileBuffer.h new file mode 100644 index 000000000..4cb2a666e --- /dev/null +++ b/netbox/src/base/FileBuffer.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +extern const wchar_t * EOLTypeNames; +enum TEOLType +{ + eolLF, // \n + eolCRLF, // \r\n + eolCR // \r +}; + +const int cpRemoveCtrlZ = 0x01; +const int cpRemoveBOM = 0x02; + +class TFileBuffer : public TObject +{ +NB_DISABLE_COPY(TFileBuffer) +public: + TFileBuffer(); + virtual ~TFileBuffer(); + void Convert(char * Source, char * Dest, intptr_t Params, bool & Token); + void Convert(TEOLType Source, TEOLType Dest, intptr_t Params, bool & Token); + void Convert(char * Source, TEOLType Dest, intptr_t Params, bool & Token); + void Convert(TEOLType Source, char * Dest, intptr_t Params, bool & Token); + void Insert(int64_t Index, const char * Buf, int64_t Len); + void Delete(int64_t Index, int64_t Len); + int64_t LoadStream(TStream * Stream, const int64_t Len, bool ForceLen); + int64_t ReadStream(TStream * Stream, const int64_t Len, bool ForceLen); + void WriteToStream(TStream * Stream, const int64_t Len); + +public: + TMemoryStream * GetMemory() const { return FMemory; } + void SetMemory(TMemoryStream * Value); + char * GetData() const { return static_cast(FMemory->GetMemory()); } + int64_t GetSize() const { return FMemory->GetSize(); } + void SetSize(int64_t Value); + void SetPosition(int64_t Value); + int64_t GetPosition() const; + +private: + TMemoryStream * FMemory; +}; + +char * EOLToStr(TEOLType EOLType); + diff --git a/netbox/src/base/FileCtrl.hpp b/netbox/src/base/FileCtrl.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/FileCtrl.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/Global.cpp b/netbox/src/base/Global.cpp new file mode 100644 index 000000000..469d8b775 --- /dev/null +++ b/netbox/src/base/Global.cpp @@ -0,0 +1,272 @@ +#include +#pragma hdrstop + +#ifdef _DEBUG +#include +#include +#include "Interface.h" +#endif // ifdef _DEBUG + +#include + +// TGuard + +TGuard::TGuard(const TCriticalSection & ACriticalSection) : + FCriticalSection(ACriticalSection) +{ + FCriticalSection.Enter(); +} + +TGuard::~TGuard() +{ + FCriticalSection.Leave(); +} + +// TUnguard + +TUnguard::TUnguard(TCriticalSection & ACriticalSection) : + FCriticalSection(ACriticalSection) +{ + FCriticalSection.Leave(); +} + +TUnguard::~TUnguard() +{ + FCriticalSection.Enter(); +} + +#ifdef _DEBUG + +static HANDLE TraceFile = nullptr; +BOOL IsTracing = false; +uintptr_t CallstackTls = CallstackTlsOff; +TCriticalSection * TracingCriticalSection = nullptr; + +void SetTraceFile(HANDLE ATraceFile) +{ + TraceFile = ATraceFile; + IsTracing = (TraceFile != 0); + if (TracingCriticalSection == nullptr) + { + TracingCriticalSection = new TCriticalSection(); + } +} + +void CleanupTracing() +{ + if (TracingCriticalSection != nullptr) + { + delete TracingCriticalSection; + TracingCriticalSection = nullptr; + } +} + +#ifdef TRACE_IN_MEMORY + +struct TTraceInMemory +{ +#ifdef TRACE_IN_MEMORY_NO_FORMATTING + DWORD Ticks; + DWORD Thread; + const wchar_t * SourceFile; + const wchar_t * Func; + int Line; + const wchar_t * Message; +#else + UTF8String Message; +#endif // TRACE_IN_MEMORY_NO_FORMATTING +}; +typedef std::vector TTracesInMemory; +TTracesInMemory TracesInMemory; + +int TraceThreadProc(void *) +{ + Trace(L">"); + try + { + do + { + Trace(L"2"); + TraceDumpToFile(); + Trace(L"3"); + ::Sleep(60000); + Trace(L"4"); + // if resuming from sleep causes the previous Sleep to immediately break, + // make sure we wait a little more before dumping + ::Sleep(60000); + Trace(L"5"); + } + while (true); + } + catch (...) + { + Trace(L"E"); + } + TraceExit(); + return 0; +} +#endif // TRACE_IN_MEMORY + +#ifdef TRACE_IN_MEMORY_NO_FORMATTING + +void DoTrace(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * Message) +{ + if (TracingCriticalSection != nullptr) + { + TTraceInMemory TraceInMemory; + TraceInMemory.Ticks = ::GetTickCount(); + TraceInMemory.Thread = ::GetCurrentThreadId(); + TraceInMemory.SourceFile = SourceFile; + TraceInMemory.Func = Func; + TraceInMemory.Line = Line; + TraceInMemory.Message = Message; + + TGuard Guard(TracingCriticalSection); + + if (TracesInMemory.capacity() == 0) + { + TracesInMemory.reserve(100000); + TThreadID ThreadID; + StartThread(nullptr, 0, TraceThreadProc, nullptr, 0, ThreadID); + } + + TracesInMemory.push_back(TraceInMemory); + } +} + +void DoTraceFmt(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * AFormat, TVarRec * /*Args*/, const int /*Args_Size*/) +{ + DoTrace(SourceFile, Func, Line, AFormat); +} + +#endif // TRACE_IN_MEMORY_NO_FORMATTING + +#ifdef TRACE_IN_MEMORY + +void TraceDumpToFile() +{ + if (TraceFile != nullptr) + { + TGuard Guard(TracingCriticalSection); + + DWORD Written; + + TDateTime N = Now(); + #ifdef TRACE_IN_MEMORY_NO_FORMATTING + DWORD Ticks = GetTickCount(); + #endif + + const UnicodeString TimestampFormat = L"hh:mm:ss.zzz"; + UnicodeString TimeString = FormatDateTime(TimestampFormat, N); + + UTF8String Buffer = UTF8String( + FORMAT("[%s] Dumping in-memory tracing =================================\n", + (TimeString))); + WriteFile(TraceFile, Buffer.c_str(), Buffer.Length(), &Written, nullptr); + + TTracesInMemory::const_iterator i = TracesInMemory.begin(); + while (i != TracesInMemory.end()) + { + #ifdef TRACE_IN_MEMORY_NO_FORMATTING + const wchar_t * SourceFile = i->SourceFile; + const wchar_t * Slash = wcsrchr(SourceFile, L'\\'); + if (Slash != nullptr) + { + SourceFile = Slash + 1; + } + + TimeString = + FormatDateTime(TimestampFormat, + IncMilliSecond(N, -static_cast(Ticks - i->Ticks))); + Buffer = UTF8String(FORMAT(L"[%s] [%.4X] [%s:%d:%s] %s\n", + (TimeString, int(i->Thread), SourceFile, + i->Line, i->Func, i->Message))); + WriteFile(TraceFile, Buffer.c_str(), Buffer.Length(), &Written, nullptr); + #else + WriteFile(TraceFile, i->Message.c_str(), i->Message.Length(), &Written, nullptr); + #endif + ++i; + } + TracesInMemory.clear(); + + TimeString = FormatDateTime(TimestampFormat, Now()); + Buffer = UTF8String( + FORMAT("[%s] Done in-memory tracing =================================\n", + (TimeString))); + WriteFile(TraceFile, Buffer.c_str(), Buffer.Length(), &Written, nullptr); + } +} + +void TraceInMemoryCallback(const wchar_t * Msg) +{ + if (IsTracing) + { + DoTrace(L"PAS", L"unk", ::GetCurrentThreadId(), Msg); + } +} +#endif // TRACE_IN_MEMORY + +#ifndef TRACE_IN_MEMORY_NO_FORMATTING + +void DoTrace(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * Message) +{ + DebugAssert(IsTracing); + + UnicodeString TimeString; + // DateTimeToString(TimeString, L"hh:mm:ss.zzz", Now()); + TODO("use Format"); + const wchar_t * Slash = wcsrchr(SourceFile, L'\\'); + if (Slash != nullptr) + { + SourceFile = Slash + 1; + } + UTF8String Buffer = UTF8String(FORMAT(L"[%s] [%.4X] [%s:%d:%s] %s\n", + TimeString.c_str(), int(::GetCurrentThreadId()), SourceFile, + Line, Func, Message)); +#ifdef TRACE_IN_MEMORY + if (TracingCriticalSection != nullptr) + { + TTraceInMemory TraceInMemory; + TraceInMemory.Message = Buffer; + + TGuard Guard(TracingCriticalSection); + + if (TracesInMemory.capacity() == 0) + { + TracesInMemory.reserve(100000); + TThreadID ThreadID; + StartThread(nullptr, 0, TraceThreadProc, nullptr, 0, ThreadID); + } + + TracesInMemory.push_back(TraceInMemory); + } +#else + DWORD Written; + WriteFile(TraceFile, Buffer.c_str(), static_cast(Buffer.Length()), &Written, nullptr); +#endif TRACE_IN_MEMORY +} + +void DoTraceFmt(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * AFormat, va_list Args) +{ + DebugAssert(IsTracing); + + UnicodeString Message = FormatV(AFormat, Args); + DoTrace(SourceFile, Func, Line, Message.c_str()); +} + +#endif // TRACE_IN_MEMORY_NO_FORMATTING + +void DoAssert(const wchar_t * Message, const wchar_t * Filename, uintptr_t LineNumber) +{ + if (IsTracing) + { + DoTrace(Filename, L"assert", LineNumber, Message); + } + _wassert(Message, Filename, (unsigned int)LineNumber); +} + +#endif // _DEBUG diff --git a/netbox/src/base/Global.h b/netbox/src/base/Global.h new file mode 100644 index 000000000..5b1bcb503 --- /dev/null +++ b/netbox/src/base/Global.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef __linux__ +typedef int64_t __int64; +#define HWND void * +#endif + +#define FORMAT(S, ...) ::Format(S, ##__VA_ARGS__) +#define FMTLOAD(Id, ...) ::FmtLoadStr(Id, ##__VA_ARGS__) +#ifndef LENOF +#define LENOF(x) (_countof(X)) +#endif +#define FLAGSET(SET, FLAG) (((SET) & (FLAG)) == (FLAG)) +#define FLAGCLEAR(SET, FLAG) (((SET) & (FLAG)) == 0) +#define FLAGMASK(ENABLE, FLAG) ((ENABLE) ? (FLAG) : 0) + +void ShowExtendedException(Exception * E); +bool AppendExceptionStackTraceAndForget(TStrings *& MoreMessages); + +class TGuard : public TObject +{ +NB_DISABLE_COPY(TGuard) +public: + explicit TGuard(const TCriticalSection & ACriticalSection); + ~TGuard(); + +private: + const TCriticalSection & FCriticalSection; +}; + +class TUnguard : public TObject +{ +NB_DISABLE_COPY(TUnguard) +public: + explicit TUnguard(TCriticalSection & ACriticalSection); + ~TUnguard(); + +private: + TCriticalSection & FCriticalSection; +}; + +#if !defined(_DEBUG) + +#define DebugAssert(p) (void)(p) +#define DebugCheck(p) (p) +#define DebugFail() (void)0 + +#define DebugAlwaysTrue(p) (p) +#define DebugAlwaysFalse(p) (p) +#define DebugNotNull(p) (p) + +#else // _DEBUG + +void SetTraceFile(HANDLE TraceFile); +void CleanupTracing(); +#define TRACEENV "WINSCPTRACE" +extern BOOL IsTracing; +const uintptr_t CallstackTlsOff = (uintptr_t)-1; +extern uintptr_t CallstackTls; +extern "C" void DoTrace(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * Message); +void DoTraceFmt(const wchar_t * SourceFile, const wchar_t * Func, + uintptr_t Line, const wchar_t * Format, va_list Args); + +#ifdef TRACE_IN_MEMORY +void TraceDumpToFile(); +void TraceInMemoryCallback(const wchar_t * Msg); + +#endif // TRACE_IN_MEMORY + +#define ACCESS_VIOLATION_TEST { (*((int*)nullptr)) = 0; } + +void DoAssert(const wchar_t * Message, const wchar_t * Filename, uintptr_t LineNumber); + +#define DebugAssert(p) ((p) ? (void)0 : DoAssert(TEXT(#p), TEXT(__FILE__), __LINE__)) +#define DebugCheck(p) { bool __CHECK_RESULT__ = (p); DebugAssert(__CHECK_RESULT__); } +#define DebugFail() DebugAssert(false) + +inline bool DoAlwaysTrue(bool Value, const wchar_t * Message, const wchar_t * Filename, uintptr_t LineNumber) +{ + if (!Value) + { + DoAssert(Message, Filename, LineNumber); + } + return Value; +} + +inline bool DoAlwaysFalse(bool Value, const wchar_t * Message, const wchar_t * Filename, uintptr_t LineNumber) +{ + if (Value) + { + DoAssert(Message, Filename, LineNumber); + } + return Value; +} + +template +inline typename T * DoCheckNotNull(T * p, const wchar_t * Message, const wchar_t * Filename, uintptr_t LineNumber) +{ + if (p == nullptr) + { + DoAssert(Message, Filename, LineNumber); + } + return p; +} + +#define DebugAlwaysTrue(p) DoAlwaysTrue((p), TEXT(#p), TEXT(__FILE__), __LINE__) +#define DebugAlwaysFalse(p) DoAlwaysFalse((p), TEXT(#p), TEXT(__FILE__), __LINE__) +#define DebugNotNull(p) DoCheckNotNull((p), TEXT(#p), TEXT(__FILE__), __LINE__) + +#endif // _DEBUG + +#define DebugUsedParam(p) (void)(p) + +#define MB_TEXT(x) const_cast(::MB2W(x).c_str()) diff --git a/netbox/src/base/LanguagesDEPfix.hpp b/netbox/src/base/LanguagesDEPfix.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/LanguagesDEPfix.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/LibraryLoader.cpp b/netbox/src/base/LibraryLoader.cpp new file mode 100644 index 000000000..7ec7a20dc --- /dev/null +++ b/netbox/src/base/LibraryLoader.cpp @@ -0,0 +1,55 @@ +#include "LibraryLoader.hpp" + +#ifndef __linux__ + +TLibraryLoader::TLibraryLoader(const UnicodeString & LibraryName, bool AllowFailure) : + FHModule(nullptr) +{ + Load(LibraryName, AllowFailure); +} + +TLibraryLoader::TLibraryLoader() : + FHModule(nullptr) +{ +} + +TLibraryLoader::~TLibraryLoader() +{ + Unload(); +} + +void TLibraryLoader::Load(const UnicodeString & LibraryName, bool AllowFailure) +{ + DebugAssert(FHModule == nullptr); + + // Loading library + FHModule = ::LoadLibrary(LibraryName.c_str()); + + if (!AllowFailure) + { + DebugAssert(FHModule != 0); + } +} + +void TLibraryLoader::Unload() +{ + if (FHModule != nullptr) + { + ::FreeLibrary(FHModule); + FHModule = nullptr; + } +} + +// Get procedure address from loaded library by name +FARPROC TLibraryLoader::GetProcAddress(const AnsiString & ProcedureName) +{ + return ::GetProcAddress(FHModule, ProcedureName.c_str()); +} + +// Get procedure address from loaded library by ordinal value +FARPROC TLibraryLoader::GetProcAddress(intptr_t ProcedureOrdinal) +{ + return ::GetProcAddress(FHModule, (LPCSTR)0 + ProcedureOrdinal); +} + +#endif diff --git a/netbox/src/base/LibraryLoader.hpp b/netbox/src/base/LibraryLoader.hpp new file mode 100644 index 000000000..bdb1ad260 --- /dev/null +++ b/netbox/src/base/LibraryLoader.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#ifndef __linux__ + +class TLibraryLoader : public TObject +{ +public: + explicit TLibraryLoader(const UnicodeString & libraryName, bool AllowFailure = false); + explicit TLibraryLoader(); + ~TLibraryLoader(); + + void Load(const UnicodeString & LibraryName, bool AllowFailure = false); + void Unload(); + FARPROC GetProcAddress(const AnsiString & ProcedureName); + FARPROC GetProcAddress(intptr_t ProcedureOrdinal); + bool Loaded() const { return FHModule != nullptr; } + +private: + HMODULE FHModule; + +private: + TLibraryLoader(const TLibraryLoader &); + TLibraryLoader & operator = (const TLibraryLoader &); +}; + +#endif diff --git a/netbox/src/base/Masks.cpp b/netbox/src/base/Masks.cpp new file mode 100644 index 000000000..6c06e09be --- /dev/null +++ b/netbox/src/base/Masks.cpp @@ -0,0 +1,141 @@ +#include + +namespace Masks { + +static int CmpName_Body(const wchar_t * pattern, const wchar_t * str, bool CmpNameSearchMode) +{ + for (;; ++str) + { + wchar_t stringc = Upper(*str); + wchar_t patternc = Upper(*pattern++); + + switch (patternc) + { + case 0: + return !stringc; + + case L'?': + if (!stringc) + return FALSE; + break; + + case L'*': + if (!*pattern) + return TRUE; + + if (*pattern == L'.') + { + if (pattern[1] == L'*' && !pattern[2]) + return TRUE; + + if (!wcspbrk(pattern, L"*?[")) + { + const wchar_t * dot = wcsrchr(str, L'.'); + + if (!pattern[1]) + return !dot || !dot[1]; + + const wchar_t * patdot = wcschr(pattern + 1, L'.'); + + if (patdot && !dot) + return FALSE; + + if (!patdot && dot ) + return !FarStrCmpI(pattern + 1, dot + 1); + } + } + + do + { + if (CmpName(pattern, str, CmpNameSearchMode)) + return TRUE; + } + while (*str++); + + return FALSE; + + case L'[': + { + if (!wcschr(pattern, L']')) + { + if (patternc != stringc) + return FALSE; + + break; + } + + if (*pattern && *(pattern + 1) == L']') + { + if (*pattern != *str) + return FALSE; + + pattern += 2; + break; + } + + int match = 0; + wchar_t rangec; + while ((rangec = Upper(*pattern++)) != 0) + { + if (rangec == L']') + { + if (match) + break; + else + return FALSE; + } + + if (match) + continue; + + if (rangec == L'-' && *(pattern - 2) != L'[' && *pattern != L']') + { + match = (stringc <= Upper(*pattern) && + Upper(*(pattern - 2)) <= stringc); + pattern++; + } + else + match = (stringc == rangec); + } + + if (!rangec) + return FALSE; + } + break; + + default: + if (patternc != stringc) + { + if (patternc == L'.' && !stringc && !CmpNameSearchMode) + return *pattern != L'.' && CmpName(pattern, str, CmpNameSearchMode); + else + return FALSE; + } + break; + } + } +} + +int CmpName(const wchar_t * pattern, const wchar_t * str, bool CmpNameSearchMode) +{ + if (!pattern || !str) + return FALSE; + + //if (skippath) + // str=PointToName(str); + + return CmpName_Body(pattern, str, CmpNameSearchMode); +} + +TMask::TMask(const UnicodeString & Mask) : + FMask(Mask) +{ +} + +bool TMask::Matches(const UnicodeString & Str) +{ + return CmpName(FMask.c_str(), Str.c_str()) == TRUE; +} + +} // namespace Masks + diff --git a/netbox/src/base/Masks.hpp b/netbox/src/base/Masks.hpp new file mode 100644 index 000000000..f6e6b1403 --- /dev/null +++ b/netbox/src/base/Masks.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Masks { + +class TMask : public TObject +{ +public: + explicit TMask(const UnicodeString & Mask); + bool Matches(const UnicodeString & Str); + +private: + UnicodeString FMask; +}; + +int CmpName(const wchar_t *pattern, const wchar_t *str, bool CmpNameSearchMode=false); + +} // namespace Masks + diff --git a/netbox/src/base/MsgIDs.h b/netbox/src/base/MsgIDs.h new file mode 100644 index 000000000..50180e06f --- /dev/null +++ b/netbox/src/base/MsgIDs.h @@ -0,0 +1,1272 @@ +#pragma once +enum MsgIDs { + + PLUGIN_NAME, + PLUGIN_TITLE, + + SESSION_NAME_COL_TITLE, + NB_STORED_SESSION_TITLE, + + NB_STATUS_CLOSED, + NB_STATUS_INITWINSOCK, + NB_STATUS_LOOKUPHOST, + NB_STATUS_CONNECT, + NB_STATUS_AUTHENTICATE, + NB_STATUS_AUTHENTICATED, + NB_STATUS_STARTUP, + NB_STATUS_OPEN_DIRECTORY, + NB_STATUS_READY, + + MSG_TITLE_CONFIRMATION, + MSG_TITLE_INFORMATION, + MSG_TITLE_ERROR, + MSG_TITLE_WARNING, + + MSG_BUTTON_Yes, + MSG_BUTTON_No, + MSG_BUTTON_OK, + MSG_BUTTON_Cancel, + MSG_BUTTON_Abort, + MSG_BUTTON_Retry, + MSG_BUTTON_Ignore, + MSG_BUTTON_All, + MSG_BUTTON_NoToAll, + MSG_BUTTON_YesToAll, + MSG_BUTTON_Help, + MSG_BUTTON_Skip, + MSG_BUTTON_Prev, + MSG_BUTTON_Next, + MSG_BUTTON_Append, + + MSG_BUTTON_CLOSE, + MSG_CHECK_NEVER_ASK_AGAIN, + MSG_CHECK_NEVER_SHOW_AGAIN, + MSG_BUTTON_TIMEOUT, + + GROUP_COL_TITLE, + RIGHTS_COL_TITLE, + RIGHTS_OCTAL_COL_TITLE, + LINK_TO_COL_TITLE, + + NEW_SESSION_KEYBAR, + NEW_SESSION_HINT, + EXPORT_SESSION_KEYBAR, + COPY_SESSION_KEYBAR, + RENAME_SESSION_KEYBAR, + OPEN_DIRECTORY_KEYBAR, + COPY_TO_FILE_KEYBAR, + MOVE_TO_FILE_KEYBAR, + RENAME_FILE_KEYBAR, + + WARN_FATAL_ERROR, + CREATE_LOCAL_DIR_ERROR, + CREATE_LOCAL_DIRECTORY, + CANCEL_OPERATION2, + EDIT_MASK_ERROR, + VIEW_FROM_FIND_NOT_SUPPORTED, + PENDING_QUEUE_ITEMS, + GSSAPI_NOT_INSTALLED, + WATCH_ERROR_DIRECTORY, + WATCH_ERROR_GENERAL, + PERFORM_ON_COMMAND_SESSION, + NO_FILES_SELECTED, + CREATE_TEMP_DIR_ERROR, + SAVE_PASSWORD, + OLD_FAR, + SAVE_SYNCHRONIZE_MODE, + SYNCHRONISE_BEFORE_KEEPUPTODATE, + SYNCHRONIZE_BROWSING_ON, + SYNCHRONIZE_BROWSING_OFF, + SYNCHRONIZE_BROWSING_LOCAL_PATH_REQUIRED, + SYNC_DIR_BROWSE_ERROR, + SYNC_DIR_BROWSE_CREATE, + DELETE_LOCAL_FILE_ERROR, + TOO_MANY_WATCH_DIRECTORIES, + DIRECTORY_READING_CANCELLED, + FTP_PASV_MODE_REQUIRED, + EDITOR_ALREADY_LOADED, + + COPY_PARAM_GROUP, + TRANSFER_SETTINGS_BUTTON, + + CREATE_FOLDER_TITLE, + CREATE_FOLDER_PROMPT, + CREATE_FOLDER_ATTRIBUTES, + CREATE_FOLDER_SET_RIGHTS, + CREATE_FOLDER_REUSE_SETTINGS, + + COPY_TITLE, + COPY_FILE_PROMPT, + COPY_FILES_PROMPT, + MOVE_TITLE, + MOVE_FILE_PROMPT, + MOVE_FILES_PROMPT, + + READING_DIRECTORY_TITLE, + CHANGING_DIRECTORY_TITLE, + + DELETE_FILE_CONFIRM, + DELETE_FILES_CONFIRM, + RECYCLE_FILE_CONFIRM, + RECYCLE_FILES_CONFIRM, + + PROGRESS_COPY, + PROGRESS_MOVE, + PROGRESS_DELETE, + PROGRESS_SETPROPERTIES, + PROGRESS_CALCULATE_SIZE, + PROGRESS_REMOTE_MOVE, + PROGRESS_REMOTE_COPY, + PROGRESS_GETPROPERTIES, + PROGRESS_CALCULATE_CHECKSUM, + + PROGRESS_FILE_LABEL, + TARGET_DIR_LABEL, + START_TIME_LABEL, + TIME_ELAPSED_LABEL, + BYTES_TRANSFERED_LABEL, + CPS_LABEL, + TIME_LEFT_LABEL, + + CANCEL_OPERATION_FATAL, + CANCEL_OPERATION, + + NOT_SUPPORTED, + OPERATION_NOT_SUPPORTED, + CANNOT_INIT_SESSION, + + SESSION_ALREADY_EXISTS_ERROR, + NEW_SESSION_NAME_TITLE, + NEW_SESSION_NAME_PROMPT, + DELETE_SESSIONS_CONFIRM, + EXPORT_SESSION_TITLE, + EXPORT_SESSION_PROMPT, + EXPORT_SESSIONS_PROMPT, + IMPORT_SESSIONS_PROMPT, + IMPORT_SESSIONS_EMPTY, + DUPLICATE_SESSION_TITLE, + DUPLICATE_SESSION_PROMPT, + RENAME_SESSION_TITLE, + RENAME_SESSION_PROMPT, + + CONFIG_INTERFACE, + CONFIG_CONFIRMATIONS, + CONFIG_TRANSFER, + CONFIG_BACKGROUND, + CONFIG_ENDURANCE, + CONFIG_TRANSFER_EDITOR, + CONFIG_LOGGING, + CONFIG_ABOUT, + CONFIG_INTEGRATION, + CONFIG_PANEL, + + CONFIG_DISKS_MENU, + CONFIG_PLUGINS_MENU, + CONFIG_PLUGINS_MENU_COMMANDS, + CONFIG_HOST_NAME_IN_TITLE, + CONFIG_COMAND_PREFIXES, + CONFIG_PANEL_MODE_GROUP, + CONFIG_PANEL_MODE_CHECK, + CONFIG_PANEL_MODE_TYPES, + CONFIG_PANEL_MODE_WIDTHS, + CONFIG_PANEL_MODE_STATUS_TYPES, + CONFIG_PANEL_MODE_STATUS_WIDTHS, + CONFIG_PANEL_MODE_FULL_SCREEN, + CONFIG_PANEL_MODE_HINT, + CONFIG_PANEL_MODE_HINT2, + CONFIG_AUTO_READ_DIRECTORY_AFTER_OP, + + ABOUT_VERSION, + ABOUT_PRODUCT_VERSION, + ABOUT_HOMEPAGE, + ABOUT_FORUM, + ABOUT_URL, + + LOGIN_EDIT, + LOGIN_ADD, + LOGIN_CONNECT, + LOGIN_CONNECT_BUTTON, + LOGIN_TAB_SESSION, + LOGIN_TAB_ENVIRONMENT, + LOGIN_TAB_DIRECTORIES, + LOGIN_TAB_SCP, + LOGIN_TAB_SFTP, + LOGIN_TAB_FTP, + LOGIN_TAB_FTPS, + LOGIN_TAB_SSH, + LOGIN_TAB_CONNECTION, + LOGIN_TAB_TUNNEL, + LOGIN_TAB_PROXY, + LOGIN_TAB_BUGS, + LOGIN_TAB_AUTH, + LOGIN_TAB_KEX, + LOGIN_TAB_WEBDAV, + LOGIN_GROUP_SESSION, + LOGIN_HOST_NAME, + LOGIN_PORT_NUMBER, + LOGIN_LOGIN_TYPE, + LOGIN_LOGIN_TYPE_ANONYMOUS, + LOGIN_LOGIN_TYPE_NORMAL, + LOGIN_USER_NAME, + LOGIN_PASSWORD, + LOGIN_PRIVATE_KEY, + LOGIN_GROUP_PROTOCOL, + LOGIN_TRANSFER_PROTOCOL, + LOGIN_SCP, + LOGIN_SFTP, + LOGIN_FTP, + LOGIN_WEBDAV, + LOGIN_ALLOW_SCP_FALLBACK, + LOGIN_INSECURE, + LOGIN_TAB_HINT1, + LOGIN_TAB_HINT2, + LOGIN_DIRECTORIES_GROUP, + LOGIN_UPDATE_DIRECTORIES, + LOGIN_DIRECTORY_OPTIONS_GROUP, + LOGIN_CACHE_DIRECTORIES, + LOGIN_CACHE_DIRECTORY_CHANGES, + LOGIN_PRESERVE_DIRECTORY_CHANGES, + LOGIN_RESOLVE_SYMLINKS, + LOGIN_REMOTE_DIRECTORY, + LOGIN_ENVIRONMENT_GROUP, + LOGIN_EOL_TYPE, + LOGIN_DST_MODE_GROUP, + LOGIN_DST_MODE_UNIX, + LOGIN_DST_MODE_WIN, + LOGIN_DST_MODE_KEEP, + LOGIN_SSH_GROUP, + LOGIN_COMPRESSION, + LOGIN_SSH_PROTOCOL_GROUP, + LOGIN_SSH1_ONLY, + LOGIN_SSH1, + LOGIN_SSH2, + LOGIN_SSH2_ONLY, + LOGIN_ENCRYPTION_GROUP, + LOGIN_CIPHER, + LOGIN_UP, + LOGIN_DOWN, + LOGIN_SSH2DES, + CIPHER_NAME_WARN, + CIPHER_NAME_3DES, + CIPHER_NAME_BLOWFISH, + CIPHER_NAME_AES, + CIPHER_NAME_DES, + CIPHER_NAME_ARCFOUR, + CIPHER_NAME_CHACHA20, + LOGIN_SHELL_GROUP, + LOGIN_SHELL_SHELL, + LOGIN_SHELL_SHELL_DEFAULT, + LOGIN_SHELL_RETURN_VAR, + LOGIN_SHELL_RETURN_VAR_AUTODETECT, + LOGIN_SCP_LS_OPTIONS_GROUP, + LOGIN_IGNORE_LS_WARNINGS, + LOGIN_LISTING_COMMAND, + LOGIN_SCP_LS_FULL_TIME_AUTO, + LOGIN_SCP_OPTIONS, + LOGIN_LOOKUP_USER_GROUPS, + LOGIN_CLEAR_NATIONAL_VARS, + LOGIN_CLEAR_ALIASES, + LOGIN_SCP1_COMPATIBILITY, + LOGIN_TIME_DIFFERENCE, + LOGIN_TIME_DIFFERENCE_HOURS, + LOGIN_TIME_DIFFERENCE_MINUTES, + LOGIN_TIMEOUTS_GROUP, + LOGIN_TIMEOUT, + LOGIN_TIMEOUT_SECONDS, + LOGIN_PING_GROUP, + LOGIN_PING_OFF, + LOGIN_PING_NULL_PACKET, + LOGIN_PING_DUMMY_COMMAND, + LOGIN_PING_INTERVAL, + LOGIN_IP_GROUP, + LOGIN_IP_AUTO, + LOGIN_IP_V4, + LOGIN_IP_V6, + LOGIN_CODE_PAGE, + LOGIN_PROXY_GROUP, + LOGIN_PROXY_METHOD, + LOGIN_PROXY_NONE, + LOGIN_PROXY_SOCKS4, + LOGIN_PROXY_SOCKS5, + LOGIN_PROXY_HTTP, + LOGIN_PROXY_TELNET, + LOGIN_PROXY_LOCAL, + LOGIN_PROXY_SYSTEM, + + LOGIN_PROXY_FTP_SITE, + LOGIN_PROXY_FTP_PROXYUSER_USERHOST, + LOGIN_PROXY_FTP_OPEN_HOST, + LOGIN_PROXY_FTP_PROXYUSER_USERUSER, + LOGIN_PROXY_FTP_USER_USERHOST, + LOGIN_PROXY_FTP_PROXYUSER_HOST, + LOGIN_PROXY_FTP_USERHOST_PROXYUSER, + LOGIN_PROXY_FTP_USER_USERPROXYUSERHOST, + + LOGIN_PROXY_HOST, + LOGIN_PROXY_PORT, + LOGIN_PROXY_USERNAME, + LOGIN_PROXY_PASSWORD, + LOGIN_PROXY_SETTINGS_GROUP, + LOGIN_PROXY_TELNET_COMMAND, + LOGIN_PROXY_LOCAL_COMMAND, + LOGIN_PROXY_LOCALHOST, + LOGIN_PROXY_DNS, + LOGIN_PROXY_DNS_NO, + LOGIN_PROXY_DNS_AUTO, + LOGIN_PROXY_DNS_YES, + LOGIN_BUGS_GROUP, + LOGIN_BUGS_IGNORE1, + LOGIN_BUGS_PLAIN_PW1, + LOGIN_BUGS_RSA1, + LOGIN_BUGS_HMAC2, + LOGIN_BUGS_DERIVE_KEY2, + LOGIN_BUGS_RSA_PAD2, + LOGIN_BUGS_PKSESSID2, + LOGIN_BUGS_REKEY2, + LOGIN_BUGS_AUTO, + LOGIN_BUGS_OFF, + LOGIN_BUGS_ON, + LOGIN_AUTH_SSH_NO_USER_AUTH, + LOGIN_AUTH_GROUP, + LOGIN_AUTH_TRY_AGENT, + LOGIN_AUTH_TIS, // "Attempt &TIS or CryptoCard authentication (SSH-1)" + LOGIN_AUTH_KI, + LOGIN_AUTH_KI_PASSWORD, + LOGIN_AUTH_AGENT_FWD, + LOGIN_AUTH_GSSAPI_PARAMS_GROUP, + LOGIN_AUTH_ATTEMPT_GSSAPI_AUTHENTICATION, + LOGIN_AUTH_ALLOW_GSSAPI_CREDENTIAL_DELEGATION, + LOGIN_AUTH_GSSAPI, + LOGIN_AUTH_GSSAPI_SERVER_REALM, + LOGIN_AUTH_PARAMS_GROUP, + LOGIN_RECYCLE_BIN_GROUP, + LOGIN_RECYCLE_BIN_DELETE, + LOGIN_RECYCLE_BIN_OVERWRITE, + LOGIN_RECYCLE_BIN_LABEL, + LOGIN_SFTP_PROTOCOL_GROUP, + LOGIN_SFTP_SERVER, + LOGIN_SFTP_SERVER_DEFAULT, + LOGIN_SFTP_MAX_VERSION, + LOGIN_UTF, + LOGIN_SFTP_BUGS_GROUP, + LOGIN_SFTP_BUGS_SYMLINK, + LOGIN_SFTP_BUGS_SIGNED_TS, + LOGIN_SFTP_MIN_PACKET_SIZE, + LOGIN_SFTP_MAX_PACKET_SIZE, + LOGIN_KEX_OPTIONS_GROUP, + LOGIN_KEX_LIST, + LOGIN_KEX_REEXCHANGE_GROUP, + LOGIN_KEX_REKEY_TIME, + LOGIN_KEX_REKEY_DATA, + KEX_NAME_WARN, + KEX_NAME_DHGROUP1, + KEX_NAME_DHGROUP14, + KEX_NAME_DHGEX, + KEX_NAME_RSA, + KEX_NAME_ECDH, + + LOGIN_TUNNEL_GROUP, + LOGIN_TUNNEL_TUNNEL, + LOGIN_TUNNEL_SESSION_GROUP, + LOGIN_TUNNEL_OPTIONS_GROUP, + LOGIN_TUNNEL_LOCAL_PORT_NUMBER, + LOGIN_TUNNEL_LOCAL_PORT_NUMBER_AUTOASSIGN, + LOGIN_ENVIRONMENT_UNIX, + LOGIN_ENVIRONMENT_WINDOWS, + LOGIN_CONNECTION_GROUP, + LOGIN_FTP_PASV_MODE, + LOGIN_SSH_OPTIMIZE_BUFFER_SIZE, + + LOGIN_FTP_ALLOW_EMPTY_PASSWORD, + LOGIN_FTP_USE_MLSD, + LOGIN_FTP_GROUP, + LOGIN_FTP_DUPFF, + LOGIN_FTP_UNDUPFF, + LOGIN_FTP_SSLSESSIONREUSE, + LOGIN_FTP_ENCRYPTION, + LOGIN_FTP_USE_PLAIN_FTP, + LOGIN_FTP_REQUIRE_IMPLICIT_FTP, + LOGIN_FTP_REQUIRE_EXPLICIT_FTP, + LOGIN_FTP_POST_LOGIN_COMMANDS, + + LOGIN_WEBDAV_GROUP, + + PROPERTIES_CAPTION, + PROPERTIES_PROMPT, + PROPERTIES_PROMPT_FILES, + PROPERTIES_OWNER_RIGHTS, + PROPERTIES_GROUP_RIGHTS, + PROPERTIES_OTHERS_RIGHTS, + PROPERTIES_READ_RIGHTS, + PROPERTIES_WRITE_RIGHTS, + PROPERTIES_EXECUTE_RIGHTS, + PROPERTIES_RIGHTS, + PROPERTIES_OCTAL, + PROPERTIES_DIRECTORIES_X, + PROPERTIES_OWNER, + PROPERTIES_GROUP, + PROPERTIES_RECURSIVE, + PROPERTIES_NONE_RIGHTS, + PROPERTIES_DEFAULT_RIGHTS, + PROPERTIES_ALL_RIGHTS, + PROPERTIES_SETUID_RIGHTS, + PROPERTIES_SETGID_RIGHTS, + PROPERTIES_STICKY_BIT_RIGHTS, + PROPERTIES_LINKTO, + + TRANSFER_MODE_TEXT, + TRANSFER_MODE_BINARY, + TRANSFER_MODE_AUTOMATIC, + TRANSFER_MODE_MASK, + TRANSFER_MODE, + TRANSFER_FILENAME_MODIFICATION, + TRANSFER_FILENAME_NOCHANGE, + TRANSFER_FILENAME_UPPERCASE, + TRANSFER_FILENAME_LOWERCASE, + TRANSFER_FILENAME_FIRSTUPPERCASE, + TRANSFER_FILENAME_LOWERCASESHORT, + TRANSFER_FILENAME_REPLACE_INVALID, + TRANSFER_PRESERVE_RIGHTS, + TRANSFER_PRESERVE_TIMESTAMP, + TRANSFER_PRESERVE_READONLY, + TRANSFER_REUSE_SETTINGS, + TRANSFER_QUEUE, + TRANSFER_QUEUE_NO_CONFIRMATION, + TRANSFER_NEWER_ONLY, + TRANSFER_CLEAR_ARCHIVE, + TRANSFER_OTHER, + TRANSFER_FILE_MASK, + TRANSFER_EXCLUDE, + TRANSFER_INCLUDE, + TRANSFER_UPLOAD_OPTIONS, + TRANSFER_DOWNLOAD_OPTIONS, + TRANSFER_COMMON_OPTIONS, + TRANSFER_PRESERVE_PERM_ERRORS, + TRANSFER_CALCULATE_SIZE, + TRANSFER_SPEED, + + STRING_LINK_EDIT_CAPTION, + STRING_LINK_ADD_CAPTION, + STRING_LINK_FILE, + STRING_LINK_POINT_TO, + STRING_LINK_SYMLINK, + + REMOTE_MOVE_TITLE, + REMOTE_MOVE_FILE, + REMOTE_MOVE_FILES, + + REMOTE_COPY_TITLE, + REMOTE_COPY_FILE, + REMOTE_COPY_FILES, + + RENAME_FILE_TITLE, + RENAME_FILE, + + SERVER_PASSWORD_HIDE_TYPING, + SERVER_PASSWORD_NOTE1, + SERVER_PASSWORD_NOTE2, + PASSWORD_SHOW_PROMPT, + PASSWORD_SAVE, + + LOGGING_ENABLE, + LOGGING_OPTIONS_GROUP, + LOGGING_LOG_PROTOCOL, + LOGGING_LOG_PROTOCOL_0, + LOGGING_LOG_PROTOCOL_1, + LOGGING_LOG_PROTOCOL_2, + LOGGING_LOG_TO_FILE, + LOGGING_LOG_FILE_APPEND, + LOGGING_LOG_FILE_OVERWRITE, + LOGGING_LOG_VIEW_GROUP, + LOGGING_LOG_VIEW_COMPLETE, + LOGGING_LOG_VIEW_LINES, + LOGGING_LOG_VIEW_LINES2, + LOGGING_LOG_FILE_HINT1, + LOGGING_LOG_FILE_HINT2, + + CONFIRMATIONS_CONFIRM_OVERWRITING, + CONFIRMATIONS_CONTINUE_ON_ERROR, + CONFIRMATIONS_CONFIRM_RESUME, + CONFIRMATIONS_OPEN_COMMAND_SESSION, + CONFIRMATIONS_SYNCHRONIZED_BROWSING, + + TRANSFER_RESUME, + TRANSFER_RESUME_ON, + TRANSFER_RESUME_SMART, + TRANSFER_RESUME_THRESHOLD_UNIT, + TRANSFER_RESUME_OFF, + TRANSFER_SESSION_REOPEN_GROUP, + TRANSFER_SESSION_REOPEN_AUTO_LABEL, + TRANSFER_SESSION_REOPEN_AUTO_LABEL2, + TRANSFER_SESSION_REOPEN_NUMBER_OF_RETRIES_LABEL, + TRANSFER_SESSION_REOPEN_NUMBER_OF_RETRIES_LABEL2, + + TRANSFER_EDITOR_DOWNLOAD, + TRANSFER_EDITOR_DOWNLOAD_DEFAULT, + TRANSFER_EDITOR_DOWNLOAD_OPTIONS, + + TRANSFER_EDITOR_UPLOAD, + TRANSFER_EDITOR_UPLOAD_SAME, + TRANSFER_EDITOR_UPLOAD_OPTIONS, + TRANSFER_EDITOR_UPLOAD_ON_SAVE, + TRANSFER_EDITOR_MULTIPLE, + + TRANSFER_QUEUE_LIMIT, + TRANSFER_QUEUE_DEFAULT, + TRANSFER_AUTO_POPUP, + TRANSFER_QUEUE_BEEP, + TRANSFER_REMEMBER_PASSWORD, + + MENU_COMMANDS, + MENU_COMMANDS_LOG, + MENU_COMMANDS_ATTRIBUTES, + MENU_COMMANDS_LINK, + MENU_COMMANDS_CONFIGURE, + MENU_COMMANDS_INFORMATION, + MENU_COMMANDS_PUTTY, + MENU_COMMANDS_PUTTYGEN, + MENU_COMMANDS_PAGEANT, + MENU_COMMANDS_OPEN_DIRECTORY, + MENU_COMMANDS_ADD_BOOKMARK, + MENU_COMMANDS_HOME_DIRECTORY, + MENU_COMMANDS_APPLY_COMMAND, + MENU_COMMANDS_CLEAR_CACHES, + MENU_COMMANDS_FULL_SYNCHRONIZE, + MENU_COMMANDS_QUEUE, + MENU_COMMANDS_SYNCHRONIZE, + MENU_COMMANDS_SYNCHRONIZE_BROWSING, + MENU_COMMANDS_EDIT_HISTORY, + + SERVER_PROTOCOL_INFORMATION, + SERVER_INFORMATION_GROUP, + SERVER_SSH_IMPLEMENTATION, + SERVER_CIPHER, + SERVER_COMPRESSION, + SERVER_FS_PROTOCOL, + SERVER_HOST_KEY, + PROTOCOL_INFORMATION_GROUP, + PROTOCOL_MODE_CHANGING, + PROTOCOL_OWNER_GROUP_CHANGING, + PROTOCOL_ANY_COMMAND, + PROTOCOL_SYMBOLIC_HARD_LINK, + PROTOCOL_USER_GROUP_LISTING, + PROTOCOL_REMOTE_COPY, + PROTOCOL_CHECKING_SPACE_AVAILABLE, + PROTOCOL_NATIVE_TEXT_MODE, + PROTOCOL_INFO_GROUP, + SPACE_AVAILABLE_GROUP, + SPACE_AVAILABLE_PATH, + SPACE_AVAILABLE_CHECK_SPACE, + SPACE_AVAILABLE_BYTES_UNKNOWN, + SPACE_AVAILABLE_BYTES_ON_DEVICE, + SPACE_AVAILABLE_UNUSED_BYTES_ON_DEVICE, + SPACE_AVAILABLE_BYTES_AVAILABLE_TO_USER, + SPACE_AVAILABLE_UNUSED_BYTES_AVAILABLE_TO_USER, + SPACE_AVAILABLE_BYTES_PER_ALLOCATION_UNIT, + SERVER_PROTOCOL_TAB_PROTOCOL, + SERVER_PROTOCOL_TAB_CAPABILITIES, + SERVER_PROTOCOL_TAB_SPACE_AVAILABLE, + SERVER_PROTOCOL_COPY_CLIPBOARD, + SERVER_REMOTE_SYSTEM, + SERVER_SESSION_PROTOCOL, + PROTOCOL_PROTOCOL_ANY_COMMAND, + PROTOCOL_CALCULATING_CHECKSUM, + + INTEGRATION_PUTTY, + INTEGRATION_PUTTY_PASSWORD, + INTEGRATION_PAGEANT, + INTEGRATION_PUTTYGEN, + INTEGRATION_TELNET_FOR_FTP_IN_PUTTY, + + OPEN_DIRECTORY_BROWSE_CAPTION, + OPEN_DIRECTORY_ADD_BOOMARK_ACTION, + OPEN_DIRECTORY_REMOVE, + OPEN_DIRECTORY_UP, + OPEN_DIRECTORY_DOWN, + OPEN_DIRECTORY_HELP, + + APPLY_COMMAND_TITLE, + APPLY_COMMAND_PROMPT, + APPLY_COMMAND_APPLY_TO_DIRECTORIES, + APPLY_COMMAND_RECURSIVE, + APPLY_COMMAND_HINT1, + APPLY_COMMAND_HINT2, + APPLY_COMMAND_HINT3, + APPLY_COMMAND_HINT4, + APPLY_COMMAND_HINT5, + APPLY_COMMAND_HINT_LOCAL, + APPLY_COMMAND_PARAM_TITLE, + APPLY_COMMAND_PARAM_PROMPT, + APPLY_COMMAND_REMOTE_COMMAND, + APPLY_COMMAND_LOCAL_COMMAND, + APPLY_COMMAND_SHOW_RESULTS, + APPLY_COMMAND_COPY_RESULTS, + CUSTOM_COMMAND_SELECTED_UNMATCH, + CUSTOM_COMMAND_SELECTED_UNMATCH1, + CUSTOM_COMMAND_PAIRS_DOWNLOAD_FAILED, + APPLY_COMMAND_LOCAL_PATH_REQUIRED, + + SYNCHRONIZE_LOCAL_PATH_REQUIRED, + COMPARE_NO_DIFFERENCES, + FULL_SYNCHRONIZE_TITLE, + FULL_SYNCHRONIZE_LOCAL_LABEL, + FULL_SYNCHRONIZE_REMOTE_LABEL, + FULL_SYNCHRONIZE_DIRECTION_GROUP, + FULL_SYNCHRONIZE_LOCAL, + FULL_SYNCHRONIZE_REMOTE, + FULL_SYNCHRONIZE_BOTH, + FULL_SYNCHRONIZE_MODE_GROUP, + FULL_SYNCHRONIZE_GROUP, + FULL_SYNCHRONIZE_CRITERIONS_GROUP, + SYNCHRONIZE_SYNCHRONIZE_FILES, + SYNCHRONIZE_MIRROR_FILES, + SYNCHRONIZE_SYNCHRONIZE_TIMESTAMPS, + SYNCHRONIZE_DELETE, + SYNCHRONIZE_NO_CONFIRMATION, + SYNCHRONIZE_EXISTING_ONLY, + SYNCHRONIZE_REUSE_SETTINGS, + SYNCHRONIZE_PREVIEW_CHANGES, + SYNCHRONIZE_SYNCHRONIZE, + SYNCHRONIZE_BY_TIME, + SYNCHRONIZE_BY_SIZE, + SYNCHRONIZE_SELECTED_ONLY, + SYNCHRONIZE_SAME_SIZE, + + SYNCHRONIZE_TITLE, + SYNCHRONIZE_SYCHRONIZING, + SYNCHRONIZE_LOCAL_LABEL, + SYNCHRONIZE_REMOTE_LABEL, + SYNCHRONIZE_RECURSIVE, + SYNCHRONIZE_GROUP, + SYNCHRONIZE_START_BUTTON, + SYNCHRONIZE_STOP_BUTTON, + + SYNCHRONIZE_PROGRESS_TITLE, + SYNCHRONIZE_PROGRESS_COMPARE_TITLE, + SYNCHRONIZE_PROGRESS_LOCAL, + SYNCHRONIZE_PROGRESS_REMOTE, + SYNCHRONIZE_PROGRESS_START_TIME, + SYNCHRONIZE_PROGRESS_ELAPSED, + + COPY_PARAM_CUSTOM_TITLE, + + QUEUE_TITLE, + QUEUE_HEADER, + QUEUE_SHOW, + QUEUE_EXECUTE, + QUEUE_DELETE, + QUEUE_MOVE_UP, + QUEUE_MOVE_DOWN, + QUEUE_CLOSE, + QUEUE_PAUSE, + QUEUE_RESUME, + QUEUE_COPY, + QUEUE_MOVE, + QUEUE_DOWNLOAD, + QUEUE_UPLOAD, + QUEUE_CONNECTING, + QUEUE_QUERY, + QUEUE_ERROR, + QUEUE_PROMPT, + QUEUE_PENDING, + QUEUE_PENDING_ITEMS, + QUEUE_CALCULATING_SIZE, + QUEUE_PAUSED, + + BANNER_TITLE, + BANNER_NEVER_SHOW_AGAIN, + BANNER_CONTINUE, + + CHECKLIST_TITLE, + CHECKLIST_HEADER, + CHECKLIST_ACTIONS, + CHECKLIST_CHECKED, + CHECKLIST_CHECK_ALL, + CHECKLIST_UNCHECK_ALL, + CHECKLIST_MAXIMIZE, + CHECKLIST_RESTORE, + + EDITOR_CURRENT, + EDITOR_NEW_INSTANCE, + EDITOR_NEW_INSTANCE_RO, + + CREATING_FOLDER, + + MENU_EDIT_HISTORY, + + IMPORTED_SESSIONS_INFO, + + StringTitle, + + StringOK, + StringCancel, + + StringSession, + StringProxy, + + //Edit link dialog + StringEdCrtTitle, + StringEdEdtTitle, + StringEdName, + StringEdURL, + StringEdCP, + StringEdAuth, + StringEdAuthUser, + StringEdAuthPsw, + StringEdAuthPromptPsw, + StringEdAuthShowPsw, + StringEdAuthCert, + StringEdErrURLEmpty, + StringEdErrURLInvalid, + StringEdErrNameEmpty, + StringEdErrNameInvalid, + + //Configure menu + StringSettingsMenuTitle, + StringMainSettingsMenuTitle, + StringProxySettingsMenuTitle, + StringLoggingSettingsMenuTitle, + StringAboutMenuTitle, + + //Main configure dialog + StringCfgAddToDM, + StringCfgAddToPM, + StringCfgUseOwnKey, + StringCfgPrefix, + StringCfgAltPrefixes, + StringCfgTimeout, + StringCfgSessionsPath, + + //Proxy configure dialog + StringProxySettingsDialogTitle, + StringProxySettingsProxyType, + proxyTypeItem1, + proxyTypeItem2, + proxyTypeItem3, + proxyTypeItem4, + + StringProxySettingsProxyHost, + StringProxySettingsProxyPort, + StringProxySettingsProxyLogin, + StringProxySettingsProxyPassword, + + //Logging configure dialog + StringLoggingDialogTitle, + StringLoggingDialogEnableLogging, + StringLoggingOptionsSeparatorTitle, + StringLoggingOptionsLevel, + StringLoggingOptionsLevelItem1, + StringLoggingOptionsLevelItem2, + StringLoggingDialogLogToFile, + StringLogFileName, + + //About menu + StringAboutDialogTitle, + StringPluginDescriptionText, + StringPluginVersion, + StringPluginDescriptionClose, + + //Prompt to crypto key + StringSessionPwd, + + //Create directory dialog + StringMKDirTitle, + StringMKDirName, + + //Delete items dialog + StringDelTitle, + StringDelQuestion, + StringDelQuestSession, + StringDelQuestFolder, + StringDelQuestFile, + StringDelSelected, + StringDelBtnDelete, + + //Copy dialog + StringCopyTitle, + StringCopyPath, + StringCopySelected, + StringCopyBtnCopy, + + //Move dialog + StringMoveTitle, + StringMovePath, + StringMoveSelected, + StringMoveBtnCopy, + + //Progress titles + StringPrgConnect, + StringPrgChangeDir, + StringPrgGetList, + StringPrgRcvFile, + StringPrgSendFile, + StringPrgTo, + StringPrgDelete, + + //Error messages + StringErrKeyFile, + StringErrEstablish, + StringErrCreateDir, + StringErrChangeDir, + StringErrListDir, + StringErrCopyFile, + StringErrRenameMove, + StringErrDeleteFile, + StringErrDeleteDir, + + StringOperationCanceledByUser, + + StringCreateNewSessionItem, + + StringSSLErrorContinue, + + // TextCore1.rc + MSG_CORE_ERROR_STRINGS, + MSG_KEY_NOT_VERIFIED, + MSG_CONNECTION_FAILED, + MSG_USER_TERMINATED, + MSG_LOST_CONNECTION, + MSG_CANT_DETECT_RETURN_CODE, + MSG_COMMAND_FAILED, + MSG_COMMAND_FAILED_CODEONLY, + MSG_INVALID_OUTPUT_ERROR, + MSG_READ_CURRENT_DIR_ERROR, + MSG_SKIP_STARTUP_MESSAGE_ERROR, + MSG_CHANGE_DIR_ERROR, + MSG_LIST_DIR_ERROR, + MSG_LIST_LINE_ERROR, + MSG_RIGHTS_ERROR, + MSG_CLEANUP_CONFIG_ERROR, + MSG_CLEANUP_HOSTKEYS_ERROR, + MSG_CLEANUP_SEEDFILE_ERROR, + MSG_CLEANUP_SESSIONS_ERROR, + MSG_DETECT_RETURNVAR_ERROR, + MSG_LOOKUP_GROUPS_ERROR, + MSG_FILE_NOT_EXISTS, + MSG_CANT_GET_ATTRS, + MSG_OPENFILE_ERROR, + MSG_READ_ERROR, + MSG_COPY_FATAL, + MSG_TOREMOTE_COPY_ERROR, + MSG_TOLOCAL_COPY_ERROR, + MSG_SCP_EMPTY_LINE, + MSG_SCP_ILLEGAL_TIME_FORMAT, + MSG_SCP_INVALID_CONTROL_RECORD, + MSG_COPY_ERROR, + MSG_SCP_ILLEGAL_FILE_DESCRIPTOR, + MSG_NOT_DIRECTORY_ERROR, + MSG_CREATE_DIR_ERROR, + MSG_CREATE_FILE_ERROR, + MSG_WRITE_ERROR, + MSG_CANT_SET_ATTRS, + MSG_REMOTE_ERROR, + MSG_DELETE_FILE_ERROR, + MSG_LOG_GEN_ERROR, + MSG_LOG_OPENERROR, + MSG_RENAME_FILE_ERROR, + MSG_RENAME_CREATE_FILE_EXISTS, + MSG_RENAME_CREATE_DIR_EXISTS, + MSG_CHANGE_HOMEDIR_ERROR, + MSG_UNALIAS_ALL_ERROR, + MSG_UNSET_NATIONAL_ERROR, + MSG_FIRST_LINE_EXPECTED, + MSG_CLEANUP_INIFILE_ERROR, + MSG_AUTHENTICATION_LOG, + MSG_AUTHENTICATION_FAILED, + MSG_NOT_CONNECTED, + MSG_SAVE_KEY_ERROR, + MSG_SSH_EXITCODE, + MSG_SFTP_INVALID_TYPE, + MSG_SFTP_VERSION_NOT_SUPPORTED, + MSG_SFTP_MESSAGE_NUMBER, + MSG_SFTP_STATUS_OK, + MSG_SFTP_STATUS_EOF, + MSG_SFTP_STATUS_NO_SUCH_FILE, + MSG_SFTP_STATUS_PERMISSION_DENIED, + MSG_SFTP_STATUS_FAILURE, + MSG_SFTP_STATUS_BAD_MESSAGE, + MSG_SFTP_STATUS_NO_CONNECTION, + MSG_SFTP_STATUS_CONNECTION_LOST, + MSG_SFTP_STATUS_OP_UNSUPPORTED, + MSG_SFTP_ERROR_FORMAT3, + MSG_SFTP_STATUS_UNKNOWN, + MSG_READ_SYMLINK_ERROR, + MSG_EMPTY_DIRECTORY, + MSG_SFTP_NON_ONE_FXP_NAME_PACKET, + MSG_SFTP_REALPATH_ERROR, + MSG_CHANGE_PROPERTIES_ERROR, + MSG_SFTP_INITIALIZE_ERROR, + MSG_TIMEZONE_ERROR, + MSG_SFTP_CREATE_FILE_ERROR, + MSG_SFTP_OPEN_FILE_ERROR, + MSG_SFTP_CLOSE_FILE_ERROR, + MSG_NOT_FILE_ERROR, + MSG_RENAME_AFTER_RESUME_ERROR, + MSG_CREATE_LINK_ERROR, + MSG_INVALID_SHELL_COMMAND, + MSG_SFTP_SERVER_MESSAGE_UNSUPPORTED, + MSG_INVALID_OCTAL_PERMISSIONS, + MSG_SFTP_INVALID_EOL, + MSG_SFTP_UNKNOWN_FILE_TYPE, + MSG_SFTP_STATUS_INVALID_HANDLE, + MSG_SFTP_STATUS_NO_SUCH_PATH, + MSG_SFTP_STATUS_FILE_ALREADY_EXISTS, + MSG_SFTP_STATUS_WRITE_PROTECT, + MSG_SFTP_STATUS_NO_MEDIA, + MSG_DECODE_UTF_ERROR, + MSG_CUSTOM_COMMAND_ERROR, + MSG_LOCALE_LOAD_ERROR, + MSG_SFTP_INCOMPLETE_BEFORE_EOF, + MSG_CALCULATE_SIZE_ERROR, + MSG_SFTP_PACKET_TOO_BIG, + MSG_SCP_INIT_ERROR, + MSG_DUPLICATE_BOOKMARK, + MSG_MOVE_FILE_ERROR, + MSG_SFTP_PACKET_TOO_BIG_INIT_EXPLAIN, + MSG_PRESERVE_TIME_PERM_ERROR3, + MSG_ACCESS_VIOLATION_ERROR3, + MSG_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM, + MSG_SFTP_STATUS_QUOTA_EXCEEDED, + MSG_SFTP_STATUS_UNKNOWN_PRINCIPAL, + MSG_COPY_FILE_ERROR, + MSG_CUSTOM_COMMAND_UNTERMINATED, + MSG_CUSTOM_COMMAND_UNKNOWN, + MSG_CUSTOM_COMMAND_FILELIST_ERROR, + + MSG_UNKNOWN_SOCKET_STATUS, + MSG_DELETE_ON_RESUME_ERROR, + MSG_SFTP_PACKET_ERROR, + MSG_ITEM_NAME_INVALID, + MSG_SFTP_STATUS_LOCK_CONFLICT, + MSG_SFTP_STATUS_DIR_NOT_EMPTY, + MSG_SFTP_STATUS_NOT_A_DIRECTORY, + MSG_SFTP_STATUS_INVALID_FILENAME, + MSG_SFTP_STATUS_LINK_LOOP, + MSG_SFTP_STATUS_CANNOT_DELETE, + MSG_SFTP_STATUS_INVALID_PARAMETER, + MSG_SFTP_STATUS_FILE_IS_A_DIRECTORY, + MSG_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT, + MSG_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED, + MSG_SFTP_STATUS_DELETE_PENDING, + MSG_SFTP_STATUS_FILE_CORRUPT, + MSG_KEY_TYPE_UNKNOWN2, + MSG_KEY_TYPE_UNSUPPORTED2, + MSG_KEY_TYPE_DIFFERENT_SSH, + MSG_SFTP_OVERWRITE_FILE_ERROR2, + MSG_SFTP_OVERWRITE_DELETE_BUTTON, + MSG_SPACE_AVAILABLE_ERROR, + MSG_TUNNEL_NO_FREE_PORT, + MSG_EVENT_SELECT_ERROR, + MSG_UNEXPECTED_CLOSE_ERROR, + MSG_TUNNEL_ERROR, + MSG_CHECKSUM_ERROR, + MSG_INTERNAL_ERROR, + MSG_FZ_NOTSUPPORTED, + MSG_FTP_ACCESS_DENIED, + MSG_FTP_CREDENTIAL_PROMPT, + MSG_FTP_RESPONSE_ERROR, + MSG_FTP_UNSUPPORTED, + + MSG_TRANSFER_ERROR, + MSG_EXECUTE_APP_ERROR, + MSG_FILE_NOT_FOUND, + MSG_DOCUMENT_WAIT_ERROR, + MSG_SPEED_INVALID, + MSG_CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, + MSG_CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, + MSG_CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, + MSG_CERT_ERR_INVALID_CA, + MSG_CERT_ERR_INVALID_PURPOSE, + MSG_CERT_ERR_KEYUSAGE_NO_CERTSIGN, + MSG_CERT_ERR_PATH_LENGTH_EXCEEDED, + MSG_CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN, + MSG_CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, + MSG_CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, + MSG_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT, + MSG_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + MSG_CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, + MSG_CERT_ERR_UNKNOWN, + MSG_CERT_ERRDEPTH, + MSG_MASK_ERROR, + MSG_FTP_CANNOT_OPEN_ACTIVE_CONNECTION2, + MSG_CORE_DELETE_LOCAL_FILE_ERROR, + MSG_URL_OPTION_BOOL_VALUE_ERROR, + MSG_FTP_ACCESS_DENIED_EMPTY_PASSWORD, + + MSG_NET_TRANSL_NO_ROUTE2, + MSG_NET_TRANSL_CONN_ABORTED, + MSG_NET_TRANSL_HOST_NOT_EXIST2, + MSG_NET_TRANSL_PACKET_GARBLED, + MSG_REPORT_ERROR, + MSG_TLS_CERT_DECODE_ERROR, + MSG_FIND_FILE_ERROR, + MSG_CERT_NAME_MISMATCH, + + MSG_CERT_ERR_BAD_CHAIN, + MSG_CERT_OK, + MSG_CERT_ERR_CERT_CHAIN_TOO_LONG, + MSG_CERT_ERR_CERT_HAS_EXPIRED, + MSG_CERT_ERR_CERT_NOT_YET_VALID, + MSG_CERT_ERR_CERT_REJECTED, + MSG_CERT_ERR_CERT_SIGNATURE_FAILURE, + MSG_CERT_ERR_CERT_UNTRUSTED, + MSG_REQUEST_REDIRECTED, + MSG_TOO_MANY_REDIRECTS, + MSG_REDIRECT_LOOP, + MSG_INVALID_URL, + MSG_PROXY_AUTHENTICATION_FAILED, + MSG_CONFIGURED_KEY_NOT_MATCH, + MSG_SFTP_STATUS_OWNER_INVALID, + MSG_SFTP_STATUS_GROUP_INVALID, + MSG_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK, + MSG_KEY_TYPE_UNOPENABLE, + MSG_UNKNOWN_CHECKSUM, + MSG_CIPHER_NOT_VERIFIED, + MSG_KEX_NOT_VERIFIED, + MSG_SFTP_STATUS_4, + MSG_CERTIFICATE_OPEN_ERROR, + MSG_CERTIFICATE_READ_ERROR, + MSG_CERTIFICATE_DECODE_ERROR_INFO, + MSG_CERTIFICATE_DECODE_ERROR, + MSG_CERTIFICATE_PUBLIC_KEY_NOT_FOUND, + MSG_LOCK_FILE_ERROR, + MSG_UNLOCK_FILE_ERROR, + MSG_NOT_LOCKED, + MSG_KEY_SAVE_ERROR, + MSG_NEON_INIT_FAILED, + MSG_SCRIPT_AMBIGUOUS_SLASH_IN_PATH, + MSG_CERT_IP_CANNOT_VERIFY, + MSG_HOSTKEY_NOT_CONFIGURED, + MSG_UNENCRYPTED_REDIRECT, + MSG_HTTP_ERROR2, + MSG_FILEZILLA_SITE_MANAGER_NOT_FOUND, + MSG_FILEZILLA_NO_SITES, + MSG_FILEZILLA_SITE_NOT_EXIST, + MSG_SFTP_AS_FTP_ERROR, + + MSG_CORE_CONFIRMATION_STRINGS, + MSG_CONFIRM_PROLONG_TIMEOUT3, + MSG_PROMPT_KEY_PASSPHRASE, + MSG_PROMPT_FILE_OVERWRITE, + MSG_DIRECTORY_OVERWRITE, + MSG_CIPHER_BELOW_TRESHOLD, + MSG_CIPHER_TYPE_BOTH, + MSG_CIPHER_TYPE_CS, + MSG_CIPHER_TYPE_SC, + MSG_RESUME_TRANSFER2, + MSG_PARTIAL_BIGGER_THAN_SOURCE, + MSG_APPEND_OR_RESUME2, + MSG_FILE_OVERWRITE_DETAILS, + MSG_READ_ONLY_OVERWRITE, + MSG_LOCAL_FILE_OVERWRITE2, + MSG_REMOTE_FILE_OVERWRITE2, + MSG_TIMEOUT_STILL_WAITING3, + MSG_KEX_BELOW_TRESHOLD, + MSG_RECONNECT_BUTTON, + MSG_RENAME_BUTTON, + MSG_TUNNEL_SESSION_NAME, + MSG_PASSWORD_TITLE, + MSG_PASSPHRASE_TITLE, + MSG_SERVER_PROMPT_TITLE, + MSG_USERNAME_TITLE, + MSG_USERNAME_PROMPT2, + MSG_SERVER_PROMPT_TITLE2, + MSG_NEW_PASSWORD_TITLE, + MSG_PROMPT_PROMPT, + MSG_TIS_INSTRUCTION, + MSG_CRYPTOCARD_INSTRUCTION, + MSG_PASSWORD_PROMPT, + MSG_KEYBINTER_INSTRUCTION, + MSG_NEW_PASSWORD_CURRENT_PROMPT, + MSG_NEW_PASSWORD_NEW_PROMPT, + MSG_NEW_PASSWORD_CONFIRM_PROMPT, + MSG_TUNNEL_INSTRUCTION, + MSG_RENAME_TITLE, + MSG_RENAME_PROMPT2, + MSG_VERIFY_CERT_PROMPT3, + MSG_VERIFY_CERT_CONTACT, + MSG_VERIFY_CERT_CONTACT_LIST, + MSG_CERT_TEXT, + MSG_CERTIFICATE_PASSPHRASE_PROMPT, + MSG_CERTIFICATE_PASSPHRASE_TITLE, + MSG_KEY_TYPE_CONVERT3, + MSG_MULTI_FILES_TO_ONE, + + MSG_CORE_INFORMATION_STRINGS, + MSG_YES_STR, + MSG_NO_STR, + MSG_SESSION_INFO_TIP2, + MSG_VERSION2, + MSG_CLOSED_ON_COMPLETION, + MSG_SFTP_PROTOCOL_NAME2, + MSG_FS_RENAME_NOT_SUPPORTED, + MSG_SFTP_NO_EXTENSION_INFO, + MSG_SFTP_EXTENSION_INFO, + MSG_APPEND_BUTTON, + MSG_YES_TO_NEWER_BUTTON, + + MSG_SKIP_ALL_BUTTON, + + MSG_COPY_PARAM_PRESET_ASCII, + MSG_COPY_PARAM_PRESET_BINARY, + MSG_COPY_PARAM_PRESET_EXCLUDE, + MSG_COPY_INFO_TRANSFER_TYPE2, + MSG_COPY_INFO_FILENAME, + MSG_COPY_INFO_PERMISSIONS, + MSG_COPY_INFO_ADD_X_TO_DIRS, + MSG_COPY_INFO_TIMESTAMP, + MSG_COPY_INFO_FILE_MASK, + MSG_COPY_INFO_CLEAR_ARCHIVE, + MSG_COPY_INFO_DONT_REPLACE_INV_CHARS, + MSG_COPY_INFO_DONT_PRESERVE_TIME, + MSG_COPY_INFO_DONT_CALCULATE_SIZE, + MSG_COPY_INFO_DEFAULT, + MSG_COPY_RULE_HOSTNAME, + MSG_COPY_RULE_USERNAME, + MSG_COPY_RULE_REMOTE_DIR, + MSG_COPY_RULE_LOCAL_DIR, + MSG_SYNCHRONIZE_SCAN, + MSG_SYNCHRONIZE_START, + MSG_SYNCHRONIZE_CHANGE, + MSG_SYNCHRONIZE_UPLOADED, + MSG_SYNCHRONIZE_DELETED, + MSG_COPY_INFO_NOT_USABLE, + MSG_COPY_INFO_IGNORE_PERM_ERRORS, + MSG_AUTH_TRANSL_USERNAME, + MSG_AUTH_TRANSL_KEYB_INTER, + MSG_AUTH_TRANSL_PUBLIC_KEY, + MSG_AUTH_TRANSL_WRONG_PASSPHRASE, + MSG_AUTH_TRANSL_ACCESS_DENIED, + MSG_AUTH_TRANSL_PUBLIC_KEY_AGENT, + MSG_AUTH_TRANSL_TRY_PUBLIC_KEY, + MSG_AUTH_PASSWORD, + MSG_OPEN_TUNNEL, + MSG_NETBOX_STATUS_CLOSED, + MSG_STATUS_LOOKUPHOST, + MSG_STATUS_CONNECT, + MSG_STATUS_AUTHENTICATE, + MSG_STATUS_AUTHENTICATED, + MSG_STATUS_STARTUP, + MSG_STATUS_READY, + MSG_STATUS_OPEN_DIRECTORY, + MSG_USING_TUNNEL, + MSG_AUTH_TRANSL_KEY_REFUSED, + MSG_PFWD_TRANSL_ADMIN, + MSG_PFWD_TRANSL_CONNECT, + MSG_NET_TRANSL_REFUSED2, + MSG_NET_TRANSL_RESET, + MSG_NET_TRANSL_TIMEOUT2, + MSG_SESSION_INFO_TIP_NO_SSH, + MSG_RESUME_BUTTON, + MSG_FTP_NO_FEATURE_INFO, + MSG_FTP_FEATURE_INFO, + MSG_COPY_INFO_CPS_LIMIT2, + MSG_COPY_KEY_BUTTON, + MSG_UPDATE_KEY_BUTTON, + MSG_ADD_KEY_BUTTON, + MSG_COPY_INFO_PRESERVE_READONLY, + + MSG_SPEED_UNLIMITED, + MSG_FTPS_IMPLICIT, + MSG_FTPS_EXPLICIT, + + MSG_HOSTKEY, + + MSG_COPY_PARAM_NEWER_ONLY, + MSG_FTP_SUGGESTION, + + MSG_ANY_HOSTKEY, + MSG_ANY_CERTIFICATE, + + MSG_COPY_INFO_REMOVE_CTRLZ, + MSG_COPY_INFO_REMOVE_BOM, + + MSG_VERSION_BUILD, + MSG_VERSION_DEV_BUILD, + MSG_VERSION_DEBUG_BUILD, + MSG_VERSION_DONT_DISTRIBUTE, + MSG_WEBDAV_EXTENSION_INFO, + MSG_COPY_PARAM_PRESET_EXCLUDE_ALL_DIR, + MSG_SCRIPT_CHECKSUM_DESC, + MSG_CLIENT_CERTIFICATE_LOADING, + MSG_NEED_CLIENT_CERTIFICATE, + MSG_LOCKED, + MSG_EXECUTABLE, + MSG_SCRIPT_CMDLINE_PARAMETERS, + MSG_SCRIPTING_USE_HOSTKEY, + MSG_SCRIPT_SITE_WARNING, + MSG_CODE_SESSION_OPTIONS, + MSG_CODE_CONNECT, + MSG_CODE_YOUR_CODE, + MSG_CODE_PS_ADD_TYPE, + MSG_COPY_INFO_PRESERVE_TIME_DIRS, + + MSG_CORE_VARIABLE_STRINGS, + MSG_PUTTY_BASED_ON, + MSG_PUTTY_VERSION, + MSG_PUTTY_COPYRIGHT, + MSG_PUTTY_URL, + MSG_FILEZILLA_BASED_ON2, + MSG_FILEZILLA_VERSION, + MSG_FILEZILLA_COPYRIGHT2, + MSG_FILEZILLA_URL, + MSG_OPENSSL_BASED_ON, + MSG_OPENSSL_COPYRIGHT2, + MSG_OPENSSL_VERSION, + MSG_OPENSSL_URL, + MSG_NEON_BASED_ON, + MSG_NEON_COPYRIGHT, + MSG_NEON_URL, + MSG_EXPAT_BASED_ON, + MSG_EXPAT_COPYRIGHT, + MSG_EXPAT_URL, + MSG_PUTTY_LICENSE_URL, + MSG_MAIN_MSG_TAG, + MSG_INTERACTIVE_MSG_TAG, + + MSG_WINSCPFAR_NAME, + MSG_WINSCP_VERSION, + MSG_WINSCPFAR_VERSION, + MSG_WINSCPFAR_BASED_ON, + MSG_WINSCPFAR_BASED_VERSION, + MSG_WINSCPFAR_BASED_COPYRIGHT, + + // TextsCore2.rc + MSG_UNKNOWN_KEY3, + MSG_DIFFERENT_KEY4, + MSG_OLD_KEY, + + // rtlconsts.rc + MSG_SDuplicateString, + + MSG_SListCountError, + MSG_SListIndexError, + + MSG_SMemoryStreamError, + + MSG_SReadError, + + MSG_SSortedListError, + + MSG_STimeEncodeError, + + MSG_SWriteError, + + MSG_SNotImplemented, + MSG_SOSError, + MSG_SUnkOSError, + + MSG_SDateEncodeError, + MSG_SCannotOpenClipboard, + + MSG_IDS_ERRORMSG_TIMEOUT, + MSG_IDS_STATUSMSG_DISCONNECTED, + + MSG_CONVERTKEY_SAVE_TITLE, // "Save converted private key" + MSG_CONVERTKEY_SAVE_FILTER, // "PuTTY Private Key Files (*.ppk)|*.ppk|All files (*.*)|*.*" + MSG_CONVERTKEY_SAVED, // "Private key was converted and saved to '%s'." + + MSG_STACK_TRACE, // "Stack trace:" + + MSG_NO_FILES_SELECTED, +}; diff --git a/netbox/src/base/StrUtils.cpp b/netbox/src/base/StrUtils.cpp new file mode 100644 index 000000000..daec9ffca --- /dev/null +++ b/netbox/src/base/StrUtils.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +UnicodeString ReplaceStr(const UnicodeString & Str, const UnicodeString & What, const UnicodeString & ByWhat) +{ + return ::StringReplaceAll(Str, What, ByWhat); +} + +bool StartsStr(const UnicodeString & SubStr, const UnicodeString & Str) +{ + return Str.Pos(SubStr) == 1; +} + +bool EndsStr(const UnicodeString & SubStr, const UnicodeString & Str) +{ + if (SubStr.Length() > Str.Length()) + return false; + return Str.SubStr(Str.Length() - SubStr.Length() + 1, SubStr.Length()) == SubStr; +} diff --git a/netbox/src/base/StrUtils.hpp b/netbox/src/base/StrUtils.hpp new file mode 100644 index 000000000..5c7150a42 --- /dev/null +++ b/netbox/src/base/StrUtils.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +UnicodeString ReplaceStr(const UnicodeString & Str, const UnicodeString & What, const UnicodeString & ByWhat); +bool StartsStr(const UnicodeString & SubStr, const UnicodeString & Str); +bool EndsStr(const UnicodeString & SubStr, const UnicodeString & Str); diff --git a/netbox/src/base/SysInit.hpp b/netbox/src/base/SysInit.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/SysInit.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/System.hpp b/netbox/src/base/System.hpp new file mode 100644 index 000000000..c5cc1e981 --- /dev/null +++ b/netbox/src/base/System.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include +#include + diff --git a/netbox/src/base/Sysutils.cpp b/netbox/src/base/Sysutils.cpp new file mode 100644 index 000000000..697e999ce --- /dev/null +++ b/netbox/src/base/Sysutils.cpp @@ -0,0 +1,1844 @@ +#include +#include + +#include +#include +#include +#include + +intptr_t __cdecl debug_printf(const wchar_t * format, ...) +{ + (void)format; + intptr_t len = 0; +#if !defined(NDEBUG) && !defined(__linux__) + va_list args; + va_start(args, format); + len = _vscwprintf(format, args); + std::wstring buf(len + 1, 0); + vswprintf(const_cast(buf.c_str()), buf.size(), format, args); + va_end(args); + OutputDebugStringW(buf.c_str()); +#endif + return len; +} + +intptr_t __cdecl debug_printf2(const char * format, ...) +{ + (void)format; + intptr_t len = 0; +#if !defined(NDEBUG) && !defined(__linux__) + va_list args; + va_start(args, format); + len = _vscprintf(format, args); + std::string buf(len + sizeof(char), 0); + vsprintf_s(&buf[0], buf.size(), format, args); + va_end(args); + OutputDebugStringA(buf.c_str()); +#endif + return len; +} + +UnicodeString MB2W(const char * src, const UINT cp) +{ + if (!src || !*src) + { + return UnicodeString(L""); + } + + intptr_t reqLength = ::MultiByteToWideChar(cp, 0, src, -1, nullptr, 0); + UnicodeString Result; + if (reqLength) + { + Result.SetLength(reqLength); + ::MultiByteToWideChar(cp, 0, src, -1, const_cast(Result.c_str()), static_cast(reqLength)); + Result.SetLength(Result.Length() - 1); //remove NULL character + } + return Result; +} + +AnsiString W2MB(const wchar_t * src, const UINT cp) +{ + if (!src || !*src) + { + return AnsiString(""); + } + + intptr_t reqLength = ::WideCharToMultiByte(cp, 0, src, -1, 0, 0, nullptr, nullptr); + AnsiString Result; + if (reqLength) + { + Result.SetLength(reqLength); + ::WideCharToMultiByte(cp, 0, src, -1, const_cast(Result.c_str()), + static_cast(reqLength), nullptr, nullptr); + Result.SetLength(Result.Length() - 1); //remove NULL character + } + return Result; +} + +int RandSeed = 0; +const TDayTable MonthDays[] = +{ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +Exception::Exception(Exception * E) : + std::runtime_error(E ? E->what() : ""), + Message(E ? E->Message : L"") +{ +} + +Exception::Exception(const UnicodeString & Msg) : + std::runtime_error(""), + Message(Msg) +{ +} + +Exception::Exception(const wchar_t * Msg) : + std::runtime_error(""), + Message(Msg) +{ +} + +Exception::Exception(std::exception * E) : + std::runtime_error(E ? E->what() : "") +{ +} + +Exception::Exception(const UnicodeString & Msg, int AHelpContext) : + std::runtime_error(""), + Message(Msg) +{ + TODO("FHelpContext = AHelpContext"); + (void)AHelpContext; +} + +Exception::Exception(Exception * E, int Ident) : + std::runtime_error(E ? E->what() : "") +{ + Message = FMTLOAD(Ident); +} + +Exception::Exception(int Ident) : + std::runtime_error("") +{ + Message = FMTLOAD(Ident); +} + +UnicodeString IntToStr(intptr_t Value) +{ + UnicodeString Result; + Result.sprintf(L"%d", Value); + return Result; +} + +UnicodeString Int64ToStr(int64_t Value) +{ + UnicodeString Result; + Result.sprintf(L"%lld", Value); + return Result; +} + +intptr_t StrToInt(const UnicodeString & Value) +{ + int64_t Result = 0; + if (TryStrToInt(Value, Result)) + { + return static_cast(Result); + } + else + { + return 0; + } +} + +int64_t ToInt(const UnicodeString & Value) +{ + int64_t Result = 0; + if (TryStrToInt(Value, Result)) + { + return Result; + } + else + { + return 0; + } +} + +intptr_t StrToIntDef(const UnicodeString & Value, intptr_t DefVal) +{ + int64_t Result = DefVal; + if (TryStrToInt(Value, Result)) + { + return static_cast(Result); + } + else + { + return DefVal; + } +} + +int64_t StrToInt64(const UnicodeString & Value) +{ + return ToInt(Value); +} + +int64_t StrToInt64Def(const UnicodeString & Value, int64_t DefVal) +{ + int64_t Result = DefVal; + if (TryStrToInt(Value, Result)) + { + return Result; + } + else + { + return DefVal; + } +} + +bool TryStrToInt(const UnicodeString & StrValue, int64_t & Value) +{ + bool Result = !StrValue.IsEmpty() && (StrValue.FindFirstNotOf(L"+-0123456789") == -1); + if (Result) + { + errno = 0; + Value = _wtoi64(StrValue.c_str()); + Result = (errno != EINVAL) && (errno != ERANGE); + } + return Result; +} + +UnicodeString Trim(const UnicodeString & Str) +{ + UnicodeString Result = TrimRight(TrimLeft(Str)); + return Result; +} + +UnicodeString TrimLeft(const UnicodeString & Str) +{ + UnicodeString Result = Str; + intptr_t Len = Result.Length(); + intptr_t Pos = 1; + while ((Pos <= Len) && (Result[Pos] == L' ')) + Pos++; + if (Pos > 1) + return Result.SubString(Pos, Len - Pos + 1); + else + return Result; +} + +UnicodeString TrimRight(const UnicodeString & Str) +{ + UnicodeString Result = Str; + intptr_t Len = Result.Length(); + while (Len > 0 && + ((Result[Len] == L' ') || (Result[Len] == L'\n'))) + { + Len--; + } + Result.SetLength(Len); + return Result; +} + +UnicodeString UpperCase(const UnicodeString & Str) +{ + UnicodeString Result(Str); + ::CharUpperBuff(const_cast(Result.c_str()), (DWORD)Result.Length()); + return Result; +} + +UnicodeString LowerCase(const UnicodeString & Str) +{ + UnicodeString Result(Str); + ::CharLowerBuff(const_cast(Result.c_str()), (DWORD)Result.Length()); + return Result; +} + +wchar_t UpCase(const wchar_t Ch) +{ + return static_cast(::towupper(Ch)); +} + +wchar_t LowCase(const wchar_t Ch) +{ + return static_cast(::towlower(Ch)); +} + +UnicodeString AnsiReplaceStr(const UnicodeString & Str, const UnicodeString & From, + const UnicodeString & To) +{ + UnicodeString Result = Str; + intptr_t Pos = 0; + while ((Pos = Result.Pos(From)) > 0) + { + Result.Replace(Pos, From.Length(), To); + } + return Result; +} + +intptr_t AnsiPos(const UnicodeString & Str, wchar_t Ch) +{ + intptr_t Result = Str.Pos(Ch); + return Result; +} + +intptr_t Pos(const UnicodeString & Str, const UnicodeString & Substr) +{ + intptr_t Result = Str.Pos(Substr.c_str()); + return Result; +} + +UnicodeString StringReplaceAll(const UnicodeString & Str, const UnicodeString & From, const UnicodeString & To) +{ + return AnsiReplaceStr(Str, From, To); +} + +bool IsDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str, intptr_t AIndex) +{ + if (AIndex <= Str.Length()) + { + wchar_t Ch = Str[AIndex]; + for (intptr_t Index = 1; Index <= Delimiters.Length(); ++Index) + { + if (Delimiters[Index] == Ch) + { + return true; + } + } + } + return false; +} + +intptr_t FirstDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str) +{ + if (Str.Length()) + { + for (intptr_t Index = 1; Index <= Str.Length(); ++Index) + { + if (Str.IsDelimiter(Delimiters, Index)) + { + return Index; + } + } + } + return 0; +} + +intptr_t LastDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str) +{ + if (Str.Length()) + { + for (intptr_t Index = Str.Length(); Index >= 1; --Index) + { + if (Str.IsDelimiter(Delimiters, Index)) + { + return Index; + } + } + } + return 0; +} + +int StringCmp(const wchar_t * S1, const wchar_t * S2) +{ + return ::CompareString(0, SORT_STRINGSORT, S1, -1, S2, -1) - 2; +} + +int StringCmpI(const wchar_t * S1, const wchar_t * S2) +{ + return ::CompareString(0, NORM_IGNORECASE | SORT_STRINGSORT, S1, -1, S2, -1) - 2; +} + +intptr_t CompareText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return StringCmp(Str1.c_str(), Str2.c_str()); +} + +intptr_t AnsiCompare(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return StringCmp(Str1.c_str(), Str2.c_str()); +} + +// Case-sensitive compare +intptr_t AnsiCompareStr(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return StringCmp(Str1.c_str(), Str2.c_str()); +} + +bool AnsiSameText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return StringCmpI(Str1.c_str(), Str2.c_str()) == 0; +} + +bool SameText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return AnsiSameText(Str1, Str2); +} + +intptr_t AnsiCompareText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return StringCmpI(Str1.c_str(), Str2.c_str()); +} + +intptr_t AnsiCompareIC(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return AnsiCompareText(Str1, Str2); +} + +bool AnsiSameStr(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return AnsiCompareIC(Str1, Str2) == 0; +} + +bool AnsiContainsText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return ::Pos(Str1, Str2) > 0; +} + +bool ContainsStr(const AnsiString & Str1, const AnsiString & Str2) +{ + return Str1.Pos(Str2) > 0; +} + +bool ContainsText(const UnicodeString & Str1, const UnicodeString & Str2) +{ + return AnsiContainsText(Str1, Str2); +} + +UnicodeString RightStr(const UnicodeString & Str, intptr_t ACount) +{ + UnicodeString Result = Str.SubString(Str.Length() - ACount, ACount); + return Result; +} + +intptr_t PosEx(const UnicodeString & SubStr, const UnicodeString & Str, intptr_t Offset) +{ + UnicodeString S = Str.SubString(Offset); + intptr_t Result = S.Pos(SubStr); + return Result; +} + +UnicodeString UTF8ToString(const RawByteString & Str) +{ + return MB2W(Str.c_str(), CP_UTF8); +} + +UnicodeString UTF8ToString(const char * Str, intptr_t Len) +{ + if (!Str || !*Str || !Len) + { + return UnicodeString(L""); + } + + intptr_t reqLength = ::MultiByteToWideChar(CP_UTF8, 0, Str, static_cast(Len), nullptr, 0); + UnicodeString Result; + if (reqLength) + { + Result.SetLength(reqLength); + ::MultiByteToWideChar(CP_UTF8, 0, Str, static_cast(Len), const_cast(Result.c_str()), static_cast(reqLength)); + Result.SetLength(Result.Length() - 1); //remove NULL character + } + return Result; +} + +void RaiseLastOSError(DWORD LastError) +{ + if (LastError == 0) + LastError = ::GetLastError(); + UnicodeString ErrorMsg; + if (LastError != 0) + { + ErrorMsg = FMTLOAD(SOSError, LastError, ::SysErrorMessage(LastError).c_str()); + } + else + { + ErrorMsg = FMTLOAD(SUnkOSError); + } + throw EOSError(ErrorMsg, LastError); +} + +double StrToFloat(const UnicodeString & Value) +{ + return StrToFloatDef(Value, 0.0); +} + +double StrToFloatDef(const UnicodeString & Value, double DefVal) +{ + double Result = 0.0; + try + { + Result = atof(Wide2MB(Value.c_str()).c_str()); + } + catch (...) + { + Result = DefVal; + } + return Result; +} + +UnicodeString FormatFloat(const UnicodeString & /*Format*/, double Value) +{ + UnicodeString Result(20, L'\0'); +#ifndef __linux__ + swprintf(&Result[1], L"%.2f", Value); +#else + swprintf(&Result[1], 20, L"%.2f", Value); +#endif + return Result.c_str(); +} + +bool IsZero(double Value) +{ + return fabs(Value) < std::numeric_limits::epsilon(); +} + +TTimeStamp DateTimeToTimeStamp(const TDateTime & DateTime) +{ + TTimeStamp Result = {0, 0}; + double fractpart, intpart; + fractpart = modf(DateTime, &intpart); + Result.Time = static_cast(fractpart * MSecsPerDay + 0.5); + Result.Date = static_cast(intpart + DateDelta); + return Result; +} + +int64_t FileRead(HANDLE AHandle, void * Buffer, int64_t Count) +{ + int64_t Result = -1; + DWORD Res = 0; + if (::ReadFile(AHandle, reinterpret_cast(Buffer), static_cast(Count), &Res, nullptr)) + { + Result = Res; + } + else + { + Result = -1; + } + return Result; +} + +int64_t FileWrite(HANDLE AHandle, const void * Buffer, int64_t Count) +{ + int64_t Result = -1; + DWORD Res = 0; + if (::WriteFile(AHandle, Buffer, static_cast(Count), &Res, nullptr)) + { + Result = Res; + } + else + { + Result = -1; + } + return Result; +} + +int64_t FileSeek(HANDLE AHandle, int64_t Offset, DWORD Origin) +{ + LONG low = Offset & 0xFFFFFFFF; + LONG high = Offset >> 32; + low = ::SetFilePointer(AHandle, low, &high, Origin); + return ((int64_t)high << 32) + low; +} + +bool FileExists(const UnicodeString & AFileName) +{ + return FileGetAttr(AFileName) != INVALID_FILE_ATTRIBUTES; +} + +bool RenameFile(const UnicodeString & From, const UnicodeString & To) +{ + bool Result = ::MoveFile(From.c_str(), To.c_str()) != 0; + return Result; +} + +bool DirectoryExists(const UnicodeString & ADir) +{ + if ((ADir == THISDIRECTORY) || (ADir == PARENTDIRECTORY)) + { + return true; + } + + DWORD LocalFileAttrs = FileGetAttr(ADir); + + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && FLAGSET(LocalFileAttrs, FILE_ATTRIBUTE_DIRECTORY)) + { + return true; + } + return false; +} + +UnicodeString FileSearch(const UnicodeString & AFileName, const UnicodeString & DirectoryList) +{ + UnicodeString Temp; + UnicodeString Result; + Temp = DirectoryList; + UnicodeString PathSeparators = L"/\\"; + do + { + intptr_t Index = ::Pos(Temp, PathSeparators); + while ((Temp.Length() > 0) && (Index == 0)) + { + Temp.Delete(1, 1); + Index = ::Pos(Temp, PathSeparators); + } + Index = ::Pos(Temp, PathSeparators); + if (Index > 0) + { + Result = Temp.SubString(1, Index - 1); + Temp.Delete(1, Index); + } + else + { + Result = Temp; + Temp.Clear(); + } + Result = ::IncludeTrailingBackslash(Result); + Result = Result + AFileName; + if (!::FileExists(Result)) + { + Result.Clear(); + } + } + while (!(Temp.Length() == 0) || (Result.Length() != 0)); + return Result; +} + +void FileAge(const UnicodeString & AFileName, TDateTime & ATimestamp) +{ + WIN32_FIND_DATA FindData; + HANDLE LocalFileHandle = ::FindFirstFile(ApiPath(AFileName).c_str(), &FindData); + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ATimestamp = + UnixToDateTime( + ConvertTimestampToUnixSafe(FindData.ftLastWriteTime, dstmUnix), + dstmUnix); + ::FindClose(LocalFileHandle); + } +} + +DWORD FileGetAttr(const UnicodeString & AFileName, bool FollowLink) +{ + TODO("FollowLink"); + DWORD LocalFileAttrs = ::GetFileAttributes(ApiPath(AFileName).c_str()); + return LocalFileAttrs; +} + +DWORD FileSetAttr(const UnicodeString & AFileName, DWORD LocalFileAttrs) +{ + DWORD Result = ::SetFileAttributes(ApiPath(AFileName).c_str(), LocalFileAttrs); + return Result; +} + +bool CreateDir(const UnicodeString & ADir, LPSECURITY_ATTRIBUTES SecurityAttributes) +{ + return ::CreateDirectory(ApiPath(ADir).c_str(), SecurityAttributes) != 0; +} + +bool RemoveDir(const UnicodeString & ADir) +{ + return ::RemoveDirectory(ApiPath(ADir).c_str()) != 0; +} + +bool ForceDirectories(const UnicodeString & ADir) +{ + bool Result = true; + if (ADir.IsEmpty()) + { + return false; + } + UnicodeString Dir2 = ::ExcludeTrailingBackslash(ADir); + if ((Dir2.Length() < 3) || ::DirectoryExists(Dir2)) + { + return Result; + } + if (::ExtractFilePath(Dir2).IsEmpty()) + { + return ::CreateDir(Dir2); + } + Result = ::ForceDirectories(::ExtractFilePath(Dir2)) && CreateDir(Dir2); + return Result; +} + +bool RemoveFile(const UnicodeString & AFileName) +{ + ::DeleteFile(ApiPath(AFileName).c_str()); + return !::FileExists(AFileName); +} + +UnicodeString Format(const wchar_t * Format, ...) +{ + va_list Args; + va_start(Args, Format); + UnicodeString Result = ::FormatV(Format, Args); + va_end(Args); + return Result.c_str(); +} + +UnicodeString FormatV(const wchar_t * Format, va_list Args) +{ + UnicodeString Result; + if (Format && *Format) + { +#ifndef __linux__ + intptr_t Len = _vscwprintf(Format, Args); + Result.SetLength(Len + 1); + vswprintf(const_cast(Result.c_str()), Len + 1, Format, Args); +#else + Result.SetLength(1024); + int Len = vswprintf(const_cast(Result.c_str()), 1024, Format, Args); + Result.SetLength(Len + 1); +#endif + } + return Result.c_str(); +} + +AnsiString FormatA(const char * Format, ...) +{ + AnsiString Result(64, 0); + va_list Args; + va_start(Args, Format); + Result = ::FormatA(Format, Args); + va_end(Args); + return Result; +} + +AnsiString FormatA(const char * Format, va_list Args) +{ + AnsiString Result(64, 0); + if (Format && *Format) + { +#ifndef __linux__ + intptr_t Len = _vscprintf(Format, Args); + Result.SetLength(Len + 1); + vsprintf_s(&Result[1], Len + 1, Format, Args); +#else + Result.SetLength(1024); + int Len = vsnprintf(&Result[1], 1024, Format, Args); + Result.SetLength(Len + 1); +#endif + } + return Result.c_str(); +} + +UnicodeString FmtLoadStr(intptr_t Id, ...) +{ + UnicodeString Result; +// HINSTANCE hInstance = GetGlobalFunctions()->GetInstanceHandle(); +// intptr_t Length = ::LoadString(hInstance, static_cast(Id), +// const_cast(Fmt.c_str()), static_cast(Fmt.GetLength())); +// if (!Length) +// { +// DEBUG_PRINTF(L"Unknown resource string id: %d\n", Id); +// } +// else + UnicodeString Fmt = GetGlobalFunctions()->GetMsg(Id); + if (!Fmt.IsEmpty()) + { + va_list Args; + va_start(Args, Id); +#ifndef __linux__ + intptr_t Len = _vscwprintf(Fmt.c_str(), Args); + Result.SetLength(Len + sizeof(wchar_t)); + vswprintf_s(&Result[1], Result.Length(), Fmt.c_str(), Args); +#else + Result.SetLength(1024); + int Len = vswprintf(&Result[1], Result.Length(), Fmt.c_str(), Args); + Result.SetLength(Len + 1); +#endif + va_end(Args); + } + else + { + DEBUG_PRINTF("Unknown resource string id: %d\n", Id); + } + return Result; +} + +// Returns the next available word, ignoring whitespace +static const wchar_t * +NextWord(const wchar_t * Input) +{ + static wchar_t buffer[1024]; + static const wchar_t * text = nullptr; + + wchar_t * endOfBuffer = buffer + _countof(buffer) - 1; + wchar_t * pBuffer = buffer; + + if (Input) + { + text = Input; + } + + if (text) + { + /* add leading spaces */ + while (iswspace(*text)) + { + *(pBuffer++) = *(text++); + } + + /* copy the word to our static buffer */ + while (*text && !iswspace(*text) && pBuffer < endOfBuffer) + { + *(pBuffer++) = *(text++); + } + } + + *pBuffer = 0; + + return buffer; +} + +UnicodeString WrapText(const UnicodeString & Line, intptr_t MaxWidth) +{ + UnicodeString Result; + + intptr_t LenBuffer = 0; + intptr_t SpaceLeft = MaxWidth; + + if (MaxWidth == 0) + { + MaxWidth = 78; + } + if (MaxWidth < 5) + { + MaxWidth = 5; + } + + /* two passes through the input. the first pass updates the buffer length. + * the second pass creates and populates the buffer + */ + while (Result.Length() == 0) + { + intptr_t LineCount = 0; + + if (LenBuffer) + { + /* second pass, so create the wrapped buffer */ + Result.SetLength(LenBuffer + 1); + if (Result.Length() == 0) + { + break; + } + } + wchar_t * w = const_cast(Result.c_str()); + + /* for each Word in Text + if Width(Word) > SpaceLeft + insert line break before Word in Text + SpaceLeft := LineWidth - Width(Word) + else + SpaceLeft := SpaceLeft - Width(Word) + SpaceWidth + */ + const wchar_t * s = NextWord(Line.c_str()); + while (*s) + { + SpaceLeft = MaxWidth; + + /* force the first word to always be completely copied */ + while (*s) + { + if (Result.Length() == 0) + { + ++LenBuffer; + } + else + { + *(w++) = *s; + } + --SpaceLeft; + ++s; + } + if (!*s) + { + s = NextWord(nullptr); + } + + /* copy as many words as will fit onto the current line */ + while (*s && static_cast(wcslen(s) + 1) <= SpaceLeft) + { + if (Result.Length() == 0) + { + ++LenBuffer; + } + --SpaceLeft; + + /* then copy the word */ + while (*s) + { + if (Result.Length() == 0) + { + ++LenBuffer; + } + else + { + *(w++) = *s; + } + --SpaceLeft; + ++s; + } + if (!*s) + { + s = NextWord(nullptr); + } + } + if (!*s) + { + s = NextWord(nullptr); + } + + if (*s) + { + /* add a new line here */ + if (Result.Length() == 0) + { + ++LenBuffer; + } + else + { + *(w++) = L'\n'; + } + // Skip whitespace before first word on new line + while (iswspace(*s)) + { + ++s; + } + } + + ++LineCount; + } + + LenBuffer += 2; + + if (w) + { + *w = 0; + } + } + + return Result; +} + +UnicodeString TranslateExceptionMessage(Exception * E) +{ + if (E) + { + if (NB_STATIC_DOWNCAST(Exception, E) != nullptr) + { + return NB_STATIC_DOWNCAST(Exception, E)->Message; + } + else + { + return E->what(); + } + } + else + { + return UnicodeString(); + } +} + +void AppendWChar(UnicodeString & Str, const wchar_t Ch) +{ + if (!Str.IsEmpty() && Str[Str.Length()] != Ch) + { + Str += Ch; + } +} + +void AppendChar(std::string & Str, const char Ch) +{ + if (!Str.empty() && Str[Str.length() - 1] != Ch) + { + Str += Ch; + } +} + +void AppendPathDelimiterW(UnicodeString & Str) +{ + if (!Str.IsEmpty() && Str[Str.Length()] != L'/' && Str[Str.Length()] != L'\\') + { + Str += L"\\"; + } +} + +UnicodeString ExpandEnvVars(const UnicodeString & Str) +{ +#ifndef __linux__ + UnicodeString Buf(32 * 1024, 0); + intptr_t size = ::ExpandEnvironmentStringsW(Str.c_str(), (wchar_t *)Buf.c_str(), static_cast(32 * 1024 - 1)); + UnicodeString Result = UnicodeString(Buf.c_str(), size - 1); + return Result; +#else + return Str; +#endif +} + +UnicodeString StringOfChar(const wchar_t Ch, intptr_t Len) +{ + UnicodeString Result; + if (Len < 0) + Len = 0; + Result.SetLength(Len); + for (intptr_t Index = 1; Index <= Len; ++Index) + { + Result[Index] = Ch; + } + return Result; +} + +UnicodeString ChangeFileExt(const UnicodeString & AFileName, const UnicodeString & AExt, + wchar_t Delimiter) +{ + UnicodeString Result = ::ChangeFileExtension(AFileName, AExt, Delimiter); + return Result; +} + +UnicodeString ExtractFileExt(const UnicodeString & AFileName) +{ + UnicodeString Result = ::ExtractFileExtension(AFileName, L'.'); + return Result; +} + +static UnicodeString ExpandFileName(const UnicodeString & AFileName) +{ +#ifndef __linux__ + UnicodeString Result; + UnicodeString Buf(NB_MAX_PATH, 0); + intptr_t Size = ::GetFullPathNameW(AFileName.c_str(), static_cast(Buf.Length() - 1), + reinterpret_cast(const_cast(Buf.c_str())), nullptr); + if (Size > Buf.Length()) + { + Buf.SetLength(Size); + Size = ::GetFullPathNameW(AFileName.c_str(), static_cast(Buf.Length() - 1), + reinterpret_cast(const_cast(Buf.c_str())), nullptr); + } + return UnicodeString(Buf.c_str(), Size); +#else + return AFileName; +#endif +} + +static UnicodeString GetUniversalName(UnicodeString & AFileName) +{ + UnicodeString Result = AFileName; + return Result; +} + +UnicodeString ExpandUNCFileName(const UnicodeString & AFileName) +{ + UnicodeString Result = ExpandFileName(AFileName); + if ((Result.Length() >= 3) && (Result[1] == L':') && (::UpCase(Result[1]) >= L'A') && + (::UpCase(Result[1]) <= L'Z')) + { + Result = GetUniversalName(Result); + } + return Result; +} + +static DWORD FindMatchingFile(TSearchRec & Rec) +{ + TFileTime LocalFileTime = {0}; + DWORD Result = ERROR_SUCCESS; + while ((Rec.FindData.dwFileAttributes && Rec.ExcludeAttr) != 0) + { + if (!::FindNextFile(Rec.FindHandle, &Rec.FindData)) + { + Result = ::GetLastError(); + return Result; + } + } + FileTimeToLocalFileTime(&Rec.FindData.ftLastWriteTime, reinterpret_cast(&LocalFileTime)); + WORD Hi = (Rec.Time & 0xFFFF0000) >> 16; + WORD Lo = Rec.Time & 0xFFFF; + FileTimeToDosDateTime(reinterpret_cast(&LocalFileTime), &Hi, &Lo); + Rec.Time = (Hi << 16) + Lo; + Rec.Size = Rec.FindData.nFileSizeLow || static_cast(Rec.FindData.nFileSizeHigh) << 32; + Rec.Attr = Rec.FindData.dwFileAttributes; + Rec.Name = Rec.FindData.cFileName; + Result = ERROR_SUCCESS; + return Result; +} + +DWORD FindFirst(const UnicodeString & AFileName, DWORD LocalFileAttrs, TSearchRec & Rec) +{ + const DWORD faSpecial = faHidden | faSysFile | faDirectory; + Rec.ExcludeAttr = (~LocalFileAttrs) & faSpecial; + Rec.FindHandle = ::FindFirstFile(ApiPath(AFileName).c_str(), &Rec.FindData); + DWORD Result = ERROR_SUCCESS; + if (Rec.FindHandle != INVALID_HANDLE_VALUE) + { + Result = FindMatchingFile(Rec); + if (Result != ERROR_SUCCESS) + { + FindClose(Rec); + } + } + else + { + Result = ::GetLastError(); + } + return Result; +} + +DWORD FindNext(TSearchRec & Rec) +{ + DWORD Result = 0; + if (::FindNextFile(Rec.FindHandle, &Rec.FindData)) + Result = FindMatchingFile(Rec); + else + Result = ::GetLastError(); + return Result; +} + +DWORD FindClose(TSearchRec & Rec) +{ + DWORD Result = 0; + if (Rec.FindHandle != INVALID_HANDLE_VALUE) + { + ::FindClose(Rec.FindHandle); + Rec.FindHandle = INVALID_HANDLE_VALUE; + } + return Result; +} + +void InitPlatformId() +{ +#ifndef __linux__ + OSVERSIONINFO OSVersionInfo; + OSVersionInfo.dwOSVersionInfoSize = sizeof(OSVersionInfo); + if (::GetVersionEx(&OSVersionInfo) != 0) + { + Win32Platform = OSVersionInfo.dwPlatformId; + Win32MajorVersion = OSVersionInfo.dwMajorVersion; + Win32MinorVersion = OSVersionInfo.dwMinorVersion; + Win32BuildNumber = OSVersionInfo.dwBuildNumber; + // Win32CSDVersion = OSVersionInfo.szCSDVersion; + } +#endif +} + +bool Win32Check(bool RetVal) +{ + if (!RetVal) + { + RaiseLastOSError(); + } + return RetVal; +} + +UnicodeString SysErrorMessage(int ErrorCode) +{ + UnicodeString Result; +#ifndef __linux__ + wchar_t Buffer[255]; + intptr_t Len = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ARGUMENT_ARRAY, nullptr, ErrorCode, 0, + static_cast(Buffer), + sizeof(Buffer), nullptr); + while ((Len > 0) && ((Buffer[Len - 1] != 0) && + ((Buffer[Len - 1] <= 32) || (Buffer[Len - 1] == L'.')))) + { + Len--; + } + Result = UnicodeString(Buffer, Len); +#endif + return Result; +} + +UnicodeString ReplaceStrAll(const UnicodeString & Str, const UnicodeString & What, const UnicodeString & ByWhat) +{ + UnicodeString Result = Str; + intptr_t Pos = Result.Pos(What); + while (Pos > 0) + { + Result.Replace(Pos, What.Length(), ByWhat.c_str(), ByWhat.Length()); + Pos = Result.Pos(What); + } + return Result; +} + +UnicodeString ExtractShortPathName(const UnicodeString & APath) +{ + // FIXME + return APath; +} + +// +// Returns everything, including the trailing Path separator, except the Filename +// part of the Path. +// +// "/foo/bar/baz.txt" --> "/foo/bar/" +UnicodeString ExtractDirectory(const UnicodeString & APath, wchar_t Delimiter) +{ + UnicodeString Result = APath.SubString(1, APath.RPos(Delimiter)); + return Result; +} + +// +// Returns only the Filename part of the Path. +// +// "/foo/bar/baz.txt" --> "baz.txt" +UnicodeString ExtractFilename(const UnicodeString & APath, wchar_t Delimiter) +{ + return APath.SubString(APath.RPos(Delimiter) + 1); +} + +// +// Returns the file's extension, if any. The period is considered part +// of the extension. +// +// "/foo/bar/baz.txt" --> ".txt" +// "/foo/bar/baz" --> "" +UnicodeString ExtractFileExtension(const UnicodeString & APath, wchar_t Delimiter) +{ + UnicodeString FileName = ::ExtractFilename(APath, Delimiter); + intptr_t N = FileName.RPos(L'.'); + if (N > 0) + { + return FileName.SubString(N); + } + return UnicodeString(); +} + +// +// Modifies the Filename's extension. The period is considered part +// of the extension. +// +// "/foo/bar/baz.txt", ".dat" --> "/foo/bar/baz.dat" +// "/foo/bar/baz.txt", "" --> "/foo/bar/baz" +// "/foo/bar/baz", ".txt" --> "/foo/bar/baz.txt" +// +UnicodeString ChangeFileExtension(const UnicodeString & APath, const UnicodeString & Ext, wchar_t Delimiter) +{ + UnicodeString FileName = ::ExtractFilename(APath, Delimiter); + if (FileName.RPos(L'.') > 1) + { + return ExtractDirectory(APath, Delimiter) + + FileName.SubString(1, FileName.RPos(L'.') - 1) + + Ext; + } + else + { + return ExtractDirectory(APath, Delimiter) + + FileName + + Ext; + } +} + +UnicodeString ExcludeTrailingBackslash(const UnicodeString & Str) +{ + UnicodeString Result = Str; + if ((Result.Length() > 0) && ((Result[Result.Length()] == L'/') || + (Result[Result.Length()] == L'\\'))) + { + Result.SetLength(Result.Length() - 1); + } + return Result; +} + +UnicodeString IncludeTrailingBackslash(const UnicodeString & Str) +{ + UnicodeString Result = Str; + intptr_t L = Result.Length(); + if ((L == 0) || ((Result[L] != L'/') && (Result[L] != L'\\'))) + { + Result += L'\\'; + } + return Result; +} + +UnicodeString IncludeTrailingPathDelimiter(const UnicodeString & Str) +{ + return IncludeTrailingBackslash(Str); +} + +UnicodeString ExtractFileDir(const UnicodeString & Str) +{ + UnicodeString Result; + intptr_t Pos = Str.LastDelimiter(L"/\\"); + // it used to return Path when no slash was found + if (Pos > 1) + { + Result = Str.SubString(1, Pos); + } + else + { + Result = (Pos == 1) ? UnicodeString(ROOTDIRECTORY) : UnicodeString(); + } + return Result; +} + +UnicodeString ExtractFilePath(const UnicodeString & Str) +{ + UnicodeString Result = ::ExtractFileDir(Str); + return Result; +} + +UnicodeString GetCurrentDir() +{ + UnicodeString Result = GetGlobalFunctions()->GetCurrDirectory(); + return Result; +} + +UnicodeString StrToHex(const UnicodeString & Str, bool UpperCase, wchar_t Separator) +{ + UnicodeString Result; + for (intptr_t Index = 1; Index <= Str.Length(); ++Index) + { + Result += CharToHex(Str[Index], UpperCase); + if ((Separator != L'\0') && (Index <= Str.Length())) + { + Result += Separator; + } + } + return Result; +} + +UnicodeString HexToStr(const UnicodeString & Hex) +{ + UnicodeString Digits = "0123456789ABCDEF"; + UnicodeString Result; + intptr_t L = Hex.Length() - 1; + if (L % 2 == 0) + { + for (intptr_t Index = 1; Index <= Hex.Length(); Index += 2) + { + intptr_t P1 = Digits.FindFirstOf(::UpCase(Hex[Index])); + intptr_t P2 = Digits.FindFirstOf(::UpCase(Hex[Index + 1])); + if ((P1 == NPOS) || (P2 == NPOS)) + { + Result.Clear(); + break; + } + else + { + Result += static_cast((P1 - 1) * 16 + P2 - 1); + } + } + } + return Result; +} + +uintptr_t HexToInt(const UnicodeString & Hex, uintptr_t MinChars) +{ + UnicodeString Digits = "0123456789ABCDEF"; + uintptr_t Result = 0; + intptr_t Index = 1; + while (Index <= Hex.Length()) + { + intptr_t A = Digits.FindFirstOf(UpCase(Hex[Index])); + if (A == NPOS) + { + if ((MinChars == NPOS) || (Index <= static_cast(MinChars))) + { + Result = 0; + } + break; + } + + Result = (Result * 16) + (static_cast(A) - 1); + + ++Index; + } + return Result; +} + +UnicodeString IntToHex(uintptr_t Int, uintptr_t MinChars) +{ + UnicodeString Result; + Result.sprintf(L"%X", Int); + intptr_t Pad = MinChars - Result.Length(); + if (Pad > 0) + { + for (intptr_t Index = 0; Index < Pad; ++Index) + { + Result.Insert(L'0', 1); + } + } + return Result; +} + +char HexToChar(const UnicodeString & Hex, uintptr_t MinChars) +{ + return static_cast(HexToInt(Hex, MinChars)); +} + +static void ConvertError(intptr_t ErrorID) +{ + UnicodeString Msg = FMTLOAD(ErrorID, 0); + throw EConvertError(Msg); +} + +static void DivMod(const uintptr_t Dividend, uintptr_t Divisor, + uintptr_t & Result, uintptr_t & Remainder) +{ + Result = Dividend / Divisor; + Remainder = Dividend % Divisor; +} + +static bool DecodeDateFully(const TDateTime & DateTime, + uint16_t & Year, uint16_t & Month, uint16_t & Day, + uint16_t & DOW) +{ + static const int D1 = 365; + static const int D4 = D1 * 4 + 1; + static const int D100 = D4 * 25 - 1; + static const int D400 = D100 * 4 + 1; + bool Result = false; + int T = DateTimeToTimeStamp(DateTime).Date; + if (T <= 0) + { + Year = 0; + Month = 0; + Day = 0; + DOW = 0; + return false; + } + else + { + DOW = T % 7 + 1; + T--; + uintptr_t Y = 1; + while (T >= D400) + { + T -= D400; + Y += 400; + } + uintptr_t D = 0; + uintptr_t I = 0; + DivMod(T, D100, I, D); + if (I == 4) + { + I--; + D += D100; + } + Y += I * 100; + DivMod(D, D4, I, D); + Y += I * 4; + DivMod(D, D1, I, D); + if (I == 4) + { + I--; + D += ToWord(D1); + } + Y += I; + Result = IsLeapYear(ToWord(Y)); + const TDayTable * DayTable = &MonthDays[Result]; + uintptr_t M = 1; + while (true) + { + I = (*DayTable)[M - 1]; + if (D < I) + { + break; + } + D -= I; + M++; + } + Year = static_cast(Y); + Month = static_cast(M); + Day = static_cast(D + 1); + } + return Result; +} + +void DecodeDate(const TDateTime & DateTime, uint16_t & Year, + uint16_t & Month, uint16_t & Day) +{ + uint16_t Dummy = 0; + DecodeDateFully(DateTime, Year, Month, Day, Dummy); +} + +void DecodeTime(const TDateTime & DateTime, uint16_t & Hour, + uint16_t & Min, uint16_t & Sec, uint16_t & MSec) +{ + uintptr_t MinCount, MSecCount; + DivMod(DateTimeToTimeStamp(DateTime).Time, 60000, MinCount, MSecCount); + uintptr_t H, M, S, MS; + DivMod(MinCount, 60, H, M); + DivMod(MSecCount, 1000, S, MS); + Hour = static_cast(H); + Min = static_cast(M); + Sec = static_cast(S); + MSec = static_cast(MS); +} + +static bool TryEncodeDate(int Year, int Month, int Day, TDateTime & Date) +{ + const TDayTable * DayTable = &MonthDays[IsLeapYear(ToWord(Year))]; + if ((Year >= 1) && (Year <= 9999) && (Month >= 1) && (Month <= 12) && + (Day >= 1) && (Day <= (*DayTable)[Month - 1])) + { + for (int Index = 1; Index <= Month - 1; Index++) + { + Day += (*DayTable)[Index - 1]; + } + int Idx = Year - 1; + Date = TDateTime((double)(Idx * 365 + Idx / 4 - Idx / 100 + Idx / 400 + Day - DateDelta)); + return true; + } + return false; +} + +TDateTime EncodeDate(int Year, int Month, int Day) +{ + TDateTime Result; + if (!TryEncodeDate(Year, Month, Day, Result)) + { + ::ConvertError(SDateEncodeError); + } + return Result; +} + +static bool TryEncodeTime(uint32_t Hour, uint32_t Min, uint32_t Sec, uint32_t MSec, + TDateTime & Time) +{ + bool Result = false; + if ((Hour < 24) && (Min < 60) && (Sec < 60) && (MSec < 1000)) + { + Time = (Hour * 3600000 + Min * 60000 + Sec * 1000 + MSec) / ToDouble(MSecsPerDay); + Result = true; + } + return Result; +} + +TDateTime EncodeTime(uint32_t Hour, uint32_t Min, uint32_t Sec, uint32_t MSec) +{ + TDateTime Result; + if (!TryEncodeTime(Hour, Min, Sec, MSec, Result)) + { + ::ConvertError(STimeEncodeError); + } + return Result; +} + +TDateTime StrToDateTime(const UnicodeString & Value) +{ + (void)Value; + ThrowNotImplemented(145); + return TDateTime(); +} + +bool TryStrToDateTime(const UnicodeString & StrValue, TDateTime & Value, + TFormatSettings & FormatSettings) +{ + (void)StrValue; + (void)Value; + (void)FormatSettings; + ThrowNotImplemented(147); + return false; +} + +UnicodeString DateTimeToStr(UnicodeString & Result, const UnicodeString & Format, + const TDateTime & DateTime) +{ + (void)Result; + (void)Format; + return DateTime.FormatString((wchar_t *)L""); +} + +UnicodeString DateTimeToString(const TDateTime & DateTime) +{ + return DateTime.FormatString((wchar_t *)L""); +} + +// DayOfWeek returns the day of the week of the given date. The Result is an +// integer between 1 and 7, corresponding to Sunday through Saturday. +// This function is not ISO 8601 compliant, for that see the DateUtils unit. +uint32_t DayOfWeek(const TDateTime & DateTime) +{ + return ::DateTimeToTimeStamp(DateTime).Date % 7 + 1; +} + +TDateTime Date() +{ + SYSTEMTIME t; + ::GetLocalTime(&t); + TDateTime Result = ::EncodeDate(t.wYear, t.wMonth, t.wDay); + return Result; +} + +UnicodeString FormatDateTime(const UnicodeString & Fmt, const TDateTime & ADateTime) +{ + (void)Fmt; + UnicodeString Result; + if (Fmt == L"ddddd tt") + { + /* + return FormatDateTime(L"ddddd tt", + EncodeDateVerbose( + static_cast(ValidityTime.Year), static_cast(ValidityTime.Month), + static_cast(ValidityTime.Day)) + + EncodeTimeVerbose( + static_cast(ValidityTime.Hour), static_cast(ValidityTime.Min), + static_cast(ValidityTime.Sec), 0)); + */ + uint16_t Year; + uint16_t Month; + uint16_t Day; + uint16_t Hour; + uint16_t Minutes; + uint16_t Seconds; + uint16_t Milliseconds; + + ADateTime.DecodeDate(Year, Month, Day); + ADateTime.DecodeTime(Hour, Minutes, Seconds, Milliseconds); + + uint16_t Y, M, D, H, Mm, S, MS; + TDateTime DateTime = + EncodeDateVerbose(Year, Month, Day) + + EncodeTimeVerbose(Hour, Minutes, Seconds, Milliseconds); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, Mm, S, MS); + Result = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d ", D, M, Y, H, Mm, S); + } + else + { + ThrowNotImplemented(150); + } + return Result; +} + +static TDateTime ComposeDateTime(const TDateTime & Date, const TDateTime & Time) +{ + TDateTime Result = TDateTime(Date); + Result += Time; + return Result; +} + +TDateTime SystemTimeToDateTime(const SYSTEMTIME & SystemTime) +{ + TDateTime Result(0.0); + Result = ComposeDateTime(EncodeDate(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay), + EncodeTime(SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds)); + return Result; +} + +UnicodeString UnixExcludeLeadingBackslash(const UnicodeString & APath) +{ + UnicodeString Result = APath; + while (!Result.IsEmpty() && Result[1] == L'/') + { + Result.Delete(1, 1); + } + return Result; +} + +void Randomize() +{ + srand(static_cast(time(nullptr))); +} + +static void IncAMonth(Word & Year, Word & Month, Word & Day, Int64 NumberOfMonths = 1) +{ + Integer Sign; + if (NumberOfMonths >= 0) + Sign = 1; + else + Sign = -1; + Year = Year + (NumberOfMonths % 12); + NumberOfMonths = NumberOfMonths / 12; + Month += ToWord(NumberOfMonths); + if (ToWord(Month-1) > 11) // if Month <= 0, word(Month-1) > 11) + { + Year += ToWord(Sign); + Month += -12 * ToWord(Sign); + } + const TDayTable * DayTable = &MonthDays[IsLeapYear(Year)]; + if (Day > (*DayTable)[Month]) + Day = ToWord(*DayTable[Month]); +} + +static void ReplaceTime(TDateTime & DateTime, const TDateTime & NewTime) +{ + DateTime = Trunc(DateTime); + if (DateTime >= 0) + DateTime = DateTime + Abs(Frac(NewTime)); + else + DateTime = DateTime - Abs(Frac(NewTime)); +} + +TDateTime IncYear(const TDateTime & AValue, const Int64 ANumberOfYears) +{ + TDateTime Result; + Result = IncMonth(AValue, ANumberOfYears * MonthsPerYear); + return Result; +} + +TDateTime IncMonth(const TDateTime & AValue, const Int64 NumberOfMonths) +{ + TDateTime Result; + Word Year, Month, Day; + DecodeDate(AValue, Year, Month, Day); + IncAMonth(Year, Month, Day, NumberOfMonths); + Result = EncodeDate(Year, Month, Day); + ReplaceTime(Result, AValue); + return Result; +} + +TDateTime IncWeek(const TDateTime & AValue, const Int64 ANumberOfWeeks) +{ + TDateTime Result; + Result = AValue + ANumberOfWeeks * DaysPerWeek; + return Result; +} + +TDateTime IncDay(const TDateTime & AValue, const Int64 ANumberOfDays) +{ + TDateTime Result; + Result = AValue + ANumberOfDays; + return Result; +} + +TDateTime IncHour(const TDateTime & AValue, const Int64 ANumberOfHours) +{ + TDateTime Result; + if (AValue > 0) + Result = ((AValue * HoursPerDay) + ANumberOfHours) / HoursPerDay; + else + Result = ((AValue * HoursPerDay) - ANumberOfHours) / HoursPerDay; + return Result; +} + +TDateTime IncMinute(const TDateTime & AValue, const Int64 ANumberOfMinutes) +{ + TDateTime Result; + if (AValue > 0) + Result = ((AValue * MinsPerDay) + ANumberOfMinutes) / MinsPerDay; + else + Result = ((AValue * MinsPerDay) - ANumberOfMinutes) / MinsPerDay; + return Result; +} + +TDateTime IncSecond(const TDateTime & AValue, const Int64 ANumberOfSeconds) +{ + TDateTime Result; + if (AValue > 0) + Result = ((AValue * SecsPerDay) + ANumberOfSeconds) / SecsPerDay; + else + Result = ((AValue * SecsPerDay) - ANumberOfSeconds) / SecsPerDay; + return Result; +} + +TDateTime IncMilliSecond(const TDateTime & AValue, const Int64 ANumberOfMilliSeconds) +{ + TDateTime Result; + if (AValue > 0) + Result = ((AValue * MSecsPerDay) + ANumberOfMilliSeconds) / MSecsPerDay; + else + Result = ((AValue * MSecsPerDay) - ANumberOfMilliSeconds) / MSecsPerDay; + return Result; +} + +Boolean IsLeapYear(Word Year) +{ + return (Year % 4 == 0) && ((Year % 100 != 0) || (Year % 400 == 0)); +} + +// TCriticalSection + +TCriticalSection::TCriticalSection() : + FAcquired(0) +{ +#ifndef __linux__ + InitializeCriticalSection(&FSection); +#endif +} + +TCriticalSection::~TCriticalSection() +{ + DebugAssert(FAcquired == 0); +#ifndef __linux__ + DeleteCriticalSection(&FSection); +#endif +} + +void TCriticalSection::Enter() const +{ +#ifndef __linux__ + ::EnterCriticalSection(&FSection); +#else + FSection.lock(); +#endif + FAcquired++; +} + +void TCriticalSection::Leave() const +{ + FAcquired--; +#ifndef __linux__ + ::LeaveCriticalSection(&FSection); +#else + FSection.unlock(); +#endif +} + +UnicodeString StripHotkey(const UnicodeString & AText) +{ + UnicodeString Result = AText; + intptr_t Len = Result.Length(); + intptr_t Pos = 1; + while (Pos <= Len) + { + if (Result[Pos] == L'&') + { + Result.Delete(Pos, 1); + Len--; + } + else + { + Pos++; + } + } + return Result; +} + +bool StartsText(const UnicodeString & ASubText, const UnicodeString & AText) +{ + return AText.Pos(ASubText) == 1; +} + +uintptr_t StrToVersionNumber(const UnicodeString & VersionMumberStr) +{ + uintptr_t Result = 0; + UnicodeString Version = VersionMumberStr; + int Shift = 16; + while (!Version.IsEmpty()) + { + UnicodeString Num = CutToChar(Version, L'.', true); + Result += static_cast(Num.ToInt()) << Shift; + if (Shift >= 8) + Shift -= 8; + } + return Result; +} + +UnicodeString VersionNumberToStr(uintptr_t VersionNumber) +{ + DWORD Major = (VersionNumber>>16) & 0xFF; + DWORD Minor = (VersionNumber>>8) & 0xFF; + DWORD Revision = (VersionNumber & 0xFF); + UnicodeString Result = FORMAT(L"%d.%d.%d", Major, Minor, Revision); + return Result; +} + +TFormatSettings::TFormatSettings(int) : + CurrencyFormat(0), + NegCurrFormat(0), + ThousandSeparator(0), + DecimalSeparator(0), + CurrencyDecimals(0), + DateSeparator(0), + TimeSeparator(0), + ListSeparator(0), + TwoDigitYearCenturyWindow(0) +{ +} + +NB_IMPLEMENT_CLASS(Exception, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(EAccessViolation, NB_GET_CLASS_INFO(Exception), nullptr) +NB_IMPLEMENT_CLASS(EAbort, NB_GET_CLASS_INFO(Exception), nullptr) +NB_IMPLEMENT_CLASS(EFileNotFoundError, NB_GET_CLASS_INFO(Exception), nullptr) +NB_IMPLEMENT_CLASS(EOSError, NB_GET_CLASS_INFO(Exception), nullptr) diff --git a/netbox/src/base/Sysutils.hpp b/netbox/src/base/Sysutils.hpp new file mode 100644 index 000000000..ca3acc136 --- /dev/null +++ b/netbox/src/base/Sysutils.hpp @@ -0,0 +1,490 @@ +#pragma once + +#include +#include + +//#define EXCEPTION throw ExtException(nullptr, L"") +#define THROWOSIFFALSE(C) { if (!(C)) ::RaiseLastOSError(); } +#define SAFE_DESTROY_EX(CLASS, OBJ) { CLASS * PObj = OBJ; OBJ = nullptr; delete PObj; } +#define SAFE_DESTROY(OBJ) SAFE_DESTROY_EX(TObject, OBJ) +#define NULL_TERMINATE(S) S[LENOF(S) - 1] = L'\0' + +#define FORMAT(S, ...) ::Format(S, ##__VA_ARGS__) +#define FMTLOAD(Id, ...) ::FmtLoadStr(Id, ##__VA_ARGS__) + +#define FLAGSET(SET, FLAG) (((SET) & (FLAG)) == (FLAG)) +#define FLAGCLEAR(SET, FLAG) (((SET) & (FLAG)) == 0) +#define FLAGMASK(ENABLE, FLAG) ((ENABLE) ? (FLAG) : 0) +#define SWAP(TYPE, FIRST, SECOND) \ + { TYPE __Backup = FIRST; FIRST = SECOND; SECOND = __Backup; } + +#if !defined(_MSC_VER) + +#define TODO(s) +#define WARNING(s) +#define PRAGMA_ERROR(s) + +#else + +#define STRING2(x) #x +#define STRING(x) STRING2(x) +#define FILE_LINE __FILE__ "(" STRING(__LINE__) "): " +#ifdef HIDE_TODO +#define TODO(s) +#define WARNING(s) +#else +#define TODO(s) __pragma(message (FILE_LINE /*"warning: "*/ "TODO: " s)) +#define WARNING(s) __pragma(message (FILE_LINE /*"warning: "*/ "WARN: " s)) +#endif +#define PRAGMA_ERROR(s) __pragma(message (FILE_LINE "error: " s)) + +#endif + +#define PARENTDIRECTORY L".." +#define THISDIRECTORY L"." +#define ROOTDIRECTORY L"/" +#define SLASH L"/" +#define BACKSLASH L"\\" +#define QUOTE L"\'" +#define DOUBLEQUOTE L"\"" + +enum FileAttributesEnum +{ + faReadOnly = 0x00000001, + faHidden = 0x00000002, + faSysFile = 0x00000004, + faVolumeId = 0x00000008, + faDirectory = 0x00000010, + faArchive = 0x00000020, + faSymLink = 0x00000040, + faAnyFile = 0x0000003f, +}; + +intptr_t __cdecl debug_printf(const wchar_t * format, ...); +intptr_t __cdecl debug_printf2(const char * format, ...); + +#define NB_TEXT(T) L#T +#ifndef NDEBUG +#if defined(_MSC_VER) +#if (_MSC_VER >= 1900) +#define DEBUG_PRINTF(format, ...) do { ::debug_printf(L"Plugin: [%s:%d] %s: " format L"\n", ::ExtractFilename(__FILEW__, L'\\').c_str(), __LINE__, ::MB2W(__FUNCTION__).c_str(), __VA_ARGS__); } while (0) +#define DEBUG_PRINTF2(format, ...) do { ::debug_printf2("Plugin: [%s:%d] %s: " format "\n", W2MB(::ExtractFilename(__FILEW__, '\\').c_str()).c_str(), __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) +#else +#define DEBUG_PRINTF(format, ...) do { ::debug_printf(L"Plugin: [%s:%d] %s: "NB_TEXT(format) L"\n", ::ExtractFilename(__FILEW__, L'\\').c_str(), __LINE__, ::MB2W(__FUNCTION__).c_str(), __VA_ARGS__); } while (0) +#define DEBUG_PRINTF2(format, ...) do { ::debug_printf2("Plugin: [%s:%d] %s: "format "\n", W2MB(::ExtractFilename(__FILEW__, '\\').c_str()).c_str(), __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) +#endif +#else +#define DEBUG_PRINTF(format, ...) do { ::debug_printf(L"Plugin: [%s:%d] %s: " format L"\n", ::ExtractFilename(MB2W(__FILE__).c_str(), L'\\').c_str(), __LINE__, ::MB2W(__FUNCTION__).c_str(), ##__VA_ARGS__); } while (0) +#define DEBUG_PRINTF2(format, ...) do { ::debug_printf2("Plugin: [%s:%d] %s: " format "\n", W2MB(::ExtractFilename(MB2W(__FILE__).c_str(), '\\').c_str()).c_str(), __LINE__, __FUNCTION__, ##__VA_ARGS__); } while (0) +#endif +#else +#define DEBUG_PRINTF(format, ...) +#define DEBUG_PRINTF2(format, ...) +#endif + +UnicodeString MB2W(const char * src, const UINT cp = CP_ACP); +AnsiString W2MB(const wchar_t * src, const UINT cp = CP_ACP); + +typedef int TDayTable[12]; +extern const TDayTable MonthDays[]; + +class Exception : public std::runtime_error, public TObject +{ +NB_DECLARE_CLASS(Exception) +public: + explicit Exception(const wchar_t * Msg); + explicit Exception(const UnicodeString & Msg); + explicit Exception(Exception * E); + explicit Exception(std::exception * E); + explicit Exception(const UnicodeString & Msg, int AHelpContext); + explicit Exception(Exception * E, int Ident); + explicit Exception(int Ident); + ~Exception() throw() {} + +public: + UnicodeString Message; + +protected: + // UnicodeString FHelpKeyword; +}; + +class EAbort : public Exception +{ +NB_DECLARE_CLASS(EAbort) +public: + explicit EAbort(const UnicodeString & what) : Exception(what) + {} +}; + +class EAccessViolation : public Exception +{ +NB_DECLARE_CLASS(EAccessViolation) +public: + explicit EAccessViolation(const UnicodeString & what) : Exception(what) + {} +}; + +class EFileNotFoundError : public Exception +{ +NB_DECLARE_CLASS(EFileNotFoundError) +public: + EFileNotFoundError() : Exception(L"") + {} +}; + +class EOSError : public Exception +{ +NB_DECLARE_CLASS(EOSError) +public: + explicit EOSError(const UnicodeString & Msg, DWORD code) : Exception(Msg), + ErrorCode(code) + { + } + DWORD ErrorCode; +}; + +void RaiseLastOSError(DWORD Result = 0); + +struct TFormatSettings : public TObject +{ +public: + explicit TFormatSettings(int /*LCID*/); + static TFormatSettings Create(int LCID ) { return TFormatSettings(LCID); } + uint8_t CurrencyFormat; + uint8_t NegCurrFormat; + wchar_t ThousandSeparator; + wchar_t DecimalSeparator; + uint8_t CurrencyDecimals; + wchar_t DateSeparator; + wchar_t TimeSeparator; + wchar_t ListSeparator; + UnicodeString CurrencyString; + UnicodeString ShortDateFormat; + UnicodeString LongDateFormat; + UnicodeString TimeAMString; + UnicodeString TimePMString; + UnicodeString ShortTimeFormat; + UnicodeString LongTimeFormat; + uint16_t TwoDigitYearCenturyWindow; +}; + +void GetLocaleFormatSettings(int LCID, TFormatSettings & FormatSettings); + +UnicodeString ExtractShortPathName(const UnicodeString & APath); +UnicodeString ExtractDirectory(const UnicodeString & APath, wchar_t Delimiter = L'/'); +UnicodeString ExtractFilename(const UnicodeString & APath, wchar_t Delimiter = L'/'); +UnicodeString ExtractFileExtension(const UnicodeString & APath, wchar_t Delimiter = L'/'); +UnicodeString ChangeFileExtension(const UnicodeString & APath, const UnicodeString & Ext, wchar_t Delimiter = L'/'); + +UnicodeString IncludeTrailingBackslash(const UnicodeString & Str); +UnicodeString ExcludeTrailingBackslash(const UnicodeString & Str); +UnicodeString ExtractFileDir(const UnicodeString & Str); +UnicodeString ExtractFilePath(const UnicodeString & Str); +UnicodeString GetCurrentDir(); + +UnicodeString IncludeTrailingPathDelimiter(const UnicodeString & Str); + +UnicodeString StrToHex(const UnicodeString & Str, bool UpperCase = true, wchar_t Separator = L'\0'); +UnicodeString HexToStr(const UnicodeString & Hex); +uintptr_t HexToInt(const UnicodeString & Hex, uintptr_t MinChars = 0); +UnicodeString IntToHex(uintptr_t Int, uintptr_t MinChars = 0); +char HexToChar(const UnicodeString & Hex, uintptr_t MinChars = 0); + +UnicodeString ReplaceStrAll(const UnicodeString & Str, const UnicodeString & What, const UnicodeString & ByWhat); +UnicodeString SysErrorMessage(int Code); + +bool TryStrToDateTime(const UnicodeString & StrValue, TDateTime & Value, TFormatSettings & FormatSettings); +UnicodeString DateTimeToStr(UnicodeString & Result, const UnicodeString & Format, + const TDateTime & DateTime); +UnicodeString DateTimeToString(const TDateTime & DateTime); +uint32_t DayOfWeek(const TDateTime & DateTime); + +TDateTime Date(); +void DecodeDate(const TDateTime & DateTime, uint16_t & Y, + uint16_t & M, uint16_t & D); +void DecodeTime(const TDateTime & DateTime, uint16_t & H, + uint16_t & N, uint16_t & S, uint16_t & MS); + +UnicodeString FormatDateTime(const UnicodeString & Fmt, const TDateTime & ADateTime); +TDateTime SystemTimeToDateTime(const SYSTEMTIME & SystemTime); + +TDateTime EncodeDate(int Year, int Month, int Day); +TDateTime EncodeTime(uint32_t Hour, uint32_t Min, uint32_t Sec, uint32_t MSec); + +UnicodeString Trim(const UnicodeString & Str); +UnicodeString TrimLeft(const UnicodeString & Str); +UnicodeString TrimRight(const UnicodeString & Str); +UnicodeString UpperCase(const UnicodeString & Str); +UnicodeString LowerCase(const UnicodeString & Str); +wchar_t UpCase(const wchar_t Ch); +wchar_t LowCase(const wchar_t Ch); +UnicodeString AnsiReplaceStr(const UnicodeString & Str, const UnicodeString & From, const UnicodeString & To); +intptr_t AnsiPos(const UnicodeString & Str2, wchar_t Ch); +intptr_t Pos(const UnicodeString & Str2, const UnicodeString & Substr); +UnicodeString StringReplaceAll(const UnicodeString & Str, const UnicodeString & From, const UnicodeString & To); +bool IsDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str, intptr_t AIndex); +intptr_t FirstDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str); +intptr_t LastDelimiter(const UnicodeString & Delimiters, const UnicodeString & Str); + +intptr_t CompareText(const UnicodeString & Str1, const UnicodeString & Str2); +intptr_t AnsiCompare(const UnicodeString & Str1, const UnicodeString & Str2); +intptr_t AnsiCompareStr(const UnicodeString & Str1, const UnicodeString & Str2); +bool AnsiSameText(const UnicodeString & Str1, const UnicodeString & Str2); +bool SameText(const UnicodeString & Str1, const UnicodeString & Str2); +intptr_t AnsiCompareText(const UnicodeString & Str1, const UnicodeString & Str2); +intptr_t AnsiCompareIC(const UnicodeString & Str1, const UnicodeString & Str2); +bool AnsiSameStr(const UnicodeString & Str1, const UnicodeString & Str2); +bool AnsiContainsText(const UnicodeString & Str1, const UnicodeString & Str2); +bool ContainsStr(const AnsiString & Str1, const AnsiString & Str2); +bool ContainsText(const UnicodeString & Str1, const UnicodeString & Str2); +UnicodeString RightStr(const UnicodeString & Str, intptr_t ACount); +intptr_t PosEx(const UnicodeString & SubStr, const UnicodeString & Str, intptr_t Offset = 1); + +UnicodeString UTF8ToString(const RawByteString & Str); +UnicodeString UTF8ToString(const char * Str, intptr_t Len); + +int StringCmp(const wchar_t * S1, const wchar_t * S2); +int StringCmpI(const wchar_t * S1, const wchar_t * S2); + +UnicodeString IntToStr(intptr_t Value); +UnicodeString Int64ToStr(int64_t Value); +intptr_t StrToInt(const UnicodeString & Value); +int64_t ToInt(const UnicodeString & Value); +intptr_t StrToIntDef(const UnicodeString & Value, intptr_t DefVal); +int64_t StrToInt64(const UnicodeString & Value); +int64_t StrToInt64Def(const UnicodeString & Value, int64_t DefVal); +bool TryStrToInt(const UnicodeString & StrValue, int64_t & Value); + +double StrToFloat(const UnicodeString & Value); +double StrToFloatDef(const UnicodeString & Value, double DefVal); +UnicodeString FormatFloat(const UnicodeString & Format, double Value); +bool IsZero(double Value); + +TTimeStamp DateTimeToTimeStamp(const TDateTime & DateTime); + +int64_t FileRead(HANDLE AHandle, void * Buffer, int64_t Count); +int64_t FileWrite(HANDLE AHandle, const void * Buffer, int64_t Count); +int64_t FileSeek(HANDLE AHandle, int64_t Offset, DWORD Origin); + +bool FileExists(const UnicodeString & AFileName); +bool RenameFile(const UnicodeString & From, const UnicodeString & To); +bool DirectoryExists(const UnicodeString & ADir); +UnicodeString FileSearch(const UnicodeString & AFileName, const UnicodeString & DirectoryList); +void FileAge(const UnicodeString & AFileName, TDateTime & ATimestamp); + +DWORD FileGetAttr(const UnicodeString & AFileName, bool FollowLink = true); +DWORD FileSetAttr(const UnicodeString & AFileName, DWORD LocalFileAttrs); + +bool ForceDirectories(const UnicodeString & ADir); +bool RemoveFile(const UnicodeString & AFileName); +bool CreateDir(const UnicodeString & ADir, LPSECURITY_ATTRIBUTES SecurityAttributes = nullptr); +bool RemoveDir(const UnicodeString & ADir); + +UnicodeString Format(const wchar_t * Format, ...); +UnicodeString FormatV(const wchar_t * Format, va_list Args); +AnsiString FormatA(const char * Format, ...); +AnsiString FormatA(const char * Format, va_list Args); +UnicodeString FmtLoadStr(intptr_t Id, ...); + +UnicodeString WrapText(const UnicodeString & Line, intptr_t MaxWidth = 40); + +UnicodeString TranslateExceptionMessage(Exception * E); + +void AppendWChar(UnicodeString & Str2, const wchar_t Ch); +void AppendChar(std::string & Str2, const char Ch); + +void AppendPathDelimiterW(UnicodeString & Str); + +UnicodeString ExpandEnvVars(const UnicodeString & Str); + +UnicodeString StringOfChar(const wchar_t Ch, intptr_t Len); + +UnicodeString ChangeFileExt(const UnicodeString & AFileName, const UnicodeString & AExt, + wchar_t Delimiter = L'/'); +UnicodeString ExtractFileExt(const UnicodeString & AFileName); +UnicodeString ExpandUNCFileName(const UnicodeString & AFileName); + +typedef WIN32_FIND_DATA TWin32FindData; +typedef UnicodeString TFileName; + +struct TSystemTime +{ + Word wYear; + Word wMonth; + Word wDayOfWeek; + Word wDay; + Word wHour; + Word wMinute; + Word wSecond; + Word wMilliseconds; +}; + +struct TFileTime +{ + Integer LowTime; + Integer HighTime; +}; + +struct TSearchRec : public TObject +{ +NB_DISABLE_COPY(TSearchRec) +public: + TSearchRec() : + Time(0), + Size(0), + Attr(0), + ExcludeAttr(0), + FindHandle(INVALID_HANDLE_VALUE) + { + ClearStruct(FindData); + } + Integer Time; + Int64 Size; + Integer Attr; + TFileName Name; + Integer ExcludeAttr; + THandle FindHandle; + TWin32FindData FindData; +}; + +DWORD FindFirst(const UnicodeString & AFileName, DWORD LocalFileAttrs, TSearchRec & Rec); +DWORD FindNext(TSearchRec & Rec); +DWORD FindClose(TSearchRec & Rec); + +void InitPlatformId(); +bool Win32Check(bool RetVal); + +class EConvertError : public Exception +{ +public: + explicit EConvertError(const UnicodeString & Msg) : + Exception(Msg) + {} +}; + +UnicodeString UnixExcludeLeadingBackslash(const UnicodeString & APath); + +extern int RandSeed; +extern void Randomize(); + +TDateTime IncYear(const TDateTime & AValue, const Int64 ANumberOfYears = 1); +TDateTime IncMonth(const TDateTime & AValue, const Int64 NumberOfMonths = 1); +TDateTime IncWeek(const TDateTime & AValue, const Int64 ANumberOfWeeks = 1); +TDateTime IncDay(const TDateTime & AValue, const Int64 ANumberOfDays = 1); +TDateTime IncHour(const TDateTime & AValue, const Int64 ANumberOfHours = 1); +TDateTime IncMinute(const TDateTime & AValue, const Int64 ANumberOfMinutes = 1); +TDateTime IncSecond(const TDateTime & AValue, const Int64 ANumberOfSeconds = 1); +TDateTime IncMilliSecond(const TDateTime & AValue, const Int64 ANumberOfMilliSeconds = 1); + +Boolean IsLeapYear(Word Year); + +class TCriticalSection : public TObject +{ +public: + TCriticalSection(); + ~TCriticalSection(); + + void Enter() const; + void Leave() const; + + int GetAcquired() const { return FAcquired; } + +private: + mutable std::recursive_mutex FSection; + mutable int FAcquired; +}; + +UnicodeString StripHotkey(const UnicodeString & AText); +bool StartsText(const UnicodeString & ASubText, const UnicodeString & AText); + +struct TVersionInfo +{ + DWORD Major; + DWORD Minor; + DWORD Revision; + DWORD Build; +}; + +#define MAKEVERSIONNUMBER(major, minor, revision) ( ((major)<<16) | ((minor)<<8) | (revision)) +uintptr_t StrToVersionNumber(const UnicodeString & VersionMumberStr); +UnicodeString VersionNumberToStr(uintptr_t VersionNumber); +uintptr_t inline GetVersionNumber219() { return MAKEVERSIONNUMBER(2,1,9); } +uintptr_t inline GetVersionNumber2110() { return MAKEVERSIONNUMBER(2,1,10); } +uintptr_t inline GetVersionNumber2121() { return MAKEVERSIONNUMBER(2,1,21); } +uintptr_t inline GetCurrentVersionNumber() { return StrToVersionNumber(GetGlobalFunctions()->GetStrVersionNumber()); } + +#if defined(__MINGW32__) && (__MINGW_GCC_VERSION < 50100) +typedef struct _TIME_DYNAMIC_ZONE_INFORMATION +{ + LONG Bias; + WCHAR StandardName[32]; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR DaylightName[32]; + SYSTEMTIME DaylightDate; + LONG DaylightBias; + WCHAR TimeZoneKeyName[128]; + BOOLEAN DynamicDaylightTimeDisabled; +} DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION; +#endif + +class ScopeExit +{ +public: + ScopeExit(const std::function & f) : m_f(f) {} + ~ScopeExit() { m_f(); } + +private: + std::function m_f; +}; + +#define DETAIL_CONCATENATE_IMPL(s1, s2) s1 ## s2 +#define CONCATENATE(s1, s2) DETAIL_CONCATENATE_IMPL(s1, s2) + +#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__) + +#define SCOPED_ACTION(RAII_type) \ +const RAII_type ANONYMOUS_VARIABLE(scoped_object_) + +//#define STR(x) #x +#define WSTR(x) L###x + +namespace detail +{ + template + class scope_guard + { + public: + scope_guard(F&& f) : m_f(std::move(f)) {} + ~scope_guard() { m_f(); } + + private: + const F m_f; + }; + + class make_scope_guard + { + public: + template + scope_guard operator << (F&& f) { return scope_guard(std::move(f)); } + }; + +}; + +#define SCOPE_EXIT \ + const auto ANONYMOUS_VARIABLE(scope_exit_guard) = detail::make_scope_guard() << [&]() /* lambda body here */ + +class NullFunc +{ +public: + NullFunc(const std::function & f) { (void)(f); } + ~NullFunc() { } +}; + +#define try__catch (void)0; +#define try__finally (void)0; + +#define __finally \ + std::function CONCATENATE(null_func_, __LINE__); \ + NullFunc ANONYMOUS_VARIABLE(null_) = CONCATENATE(null_func_, __LINE__) = [&]() /* lambda body here */ + diff --git a/netbox/src/base/UnicodeString.cpp b/netbox/src/base/UnicodeString.cpp new file mode 100644 index 000000000..3291b415c --- /dev/null +++ b/netbox/src/base/UnicodeString.cpp @@ -0,0 +1,794 @@ +#include + +#include "UnicodeString.hpp" +#pragma hdrstop + +AnsiString::AnsiString(const AnsiString & rht) +{ + Init(rht.c_str(), rht.Length()); +} + +AnsiString::AnsiString(const wchar_t * Str) +{ + Init(Str, ::StrLength(Str)); +} + +AnsiString::AnsiString(const wchar_t * Str, intptr_t Size) +{ + Init(Str, Size); +} + +AnsiString::AnsiString(const char* Str) +{ + Init(Str, Str ? strlen(Str) : 0); +} + +AnsiString::AnsiString(const char * Str, intptr_t Size) +{ + Init(Str, Size); +} + +AnsiString::AnsiString(const uint8_t * Str) +{ + Init(Str, Str ? strlen(reinterpret_cast(Str)) : 0); +} + +AnsiString::AnsiString(const uint8_t * Str, intptr_t Size) +{ + Init(Str, Size); +} + +AnsiString::AnsiString(const UnicodeString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +AnsiString::AnsiString(const UTF8String & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +AnsiString::AnsiString(const RawByteString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +void AnsiString::Init(const wchar_t * Str, intptr_t Length) +{ + intptr_t Size = ::WideCharToMultiByte(CP_UTF8, 0, Str, static_cast(Length > 0 ? Length : -1), nullptr, 0, nullptr, nullptr); + if (Length > 0) + { + Data.resize(Size + 1); + ::WideCharToMultiByte(CP_UTF8, 0, Str, static_cast(Length > 0 ? Length : -1), + reinterpret_cast(const_cast(Data.c_str())), static_cast(Size), nullptr, nullptr); + Data[Size] = 0; + Data = Data.c_str(); + } + else + { + Data.clear(); + } +} + +void AnsiString::Init(const char * Str, intptr_t Length) +{ + Data.resize(Length); + if (Length > 0) + { + memmove(const_cast(Data.c_str()), Str, Length); + } + Data = Data.c_str(); +} + +void AnsiString::Init(const uint8_t * Str, intptr_t Length) +{ + Data.resize(Length); + if (Length > 0) + { + memmove(const_cast(Data.c_str()), Str, Length); + } + Data = Data.c_str(); +} + +intptr_t AnsiString::Pos(const AnsiString & Str) const +{ + return static_cast(Data.find(Str.c_str(), 0, 1)) + 1; +} + +intptr_t AnsiString::Pos(wchar_t Ch) const +{ + AnsiString Str(&Ch, 1); + return static_cast(Data.find(Str.c_str(), 0, 1)) + 1; +} + +char AnsiString::operator [](intptr_t Idx) const +{ + ThrowIfOutOfRange(Idx); // Should Range-checking be optional to avoid overhead ?? + return Data[Idx-1]; +} + +char &AnsiString::operator [](intptr_t Idx) +{ + ThrowIfOutOfRange(Idx); // Should Range-checking be optional to avoid overhead ?? + return Data[Idx-1]; +} + +AnsiString & AnsiString::Insert(const char * Str, intptr_t Pos) +{ + Data.insert(Pos - 1, Str); + return *this; +} + +AnsiString AnsiString::SubString(intptr_t Pos, intptr_t Len) const +{ + // std::string S = std::string(Data.substr(Pos - 1, Len).c_str(), Len); + // AnsiString Result(S.c_str(), S.size()); + AnsiString Result(Data.substr(Pos - 1, Len).c_str(), Len); + return Result; +} + +AnsiString & AnsiString::operator=(const UnicodeString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +AnsiString & AnsiString::operator=(const AnsiString & StrCopy) +{ + if (*this != StrCopy) + { + Init(StrCopy.c_str(), StrCopy.Length()); + } + return *this; +} + +AnsiString & AnsiString::operator=(const UTF8String & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +AnsiString & AnsiString::operator=(const char * lpszData) +{ + if (lpszData) + { + Init(lpszData, strlen(lpszData ? lpszData : "")); + } + return *this; +} + +AnsiString & AnsiString::operator=(const wchar_t * lpwszData) +{ + Init(lpwszData, wcslen(NullToEmpty(lpwszData))); + return *this; +} + +AnsiString & AnsiString::operator +=(const AnsiString & rhs) +{ + Data.append(rhs.c_str(), rhs.Length()); + return *this; +} + +AnsiString & AnsiString::operator +=(const char Ch) +{ + Data.append(1, Ch); + return *this; +} + +AnsiString & AnsiString::operator +=(const char * rhs) +{ + Data.append(rhs); + return *this; +} + +void AnsiString::ThrowIfOutOfRange(intptr_t Idx) const +{ + if (Idx < 1 || Idx > Length()) // NOTE: UnicodeString is 1-based !! + throw std::runtime_error("Index is out of range"); // ERangeError(Sysconst_SRangeError); +} + +void RawByteString::Init(const wchar_t * Str, intptr_t Length) +{ + intptr_t Size = ::WideCharToMultiByte(CP_ACP, 0, Str, static_cast(Length > 0 ? Length : -1), nullptr, 0, nullptr, nullptr); + if (Length > 0) + { + Data.resize(Size + 1); + ::WideCharToMultiByte(CP_ACP, 0, Str, static_cast(Length > 0 ? Length : -1), + reinterpret_cast(const_cast(Data.c_str())), static_cast(Size), nullptr, nullptr); + Data[Size] = 0; + } + else + { + Data.clear(); + } +} + +void RawByteString::Init(const char * Str, intptr_t Length) +{ + Data.resize(Length); + if (Length > 0) + { + memmove(const_cast(Data.c_str()), Str, Length); + } +} + +void RawByteString::Init(const uint8_t * Str, intptr_t Length) +{ + Data.resize(Length); + if (Length > 0) + { + memmove(const_cast(Data.c_str()), Str, Length); + } +} + +RawByteString::operator UnicodeString() const +{ + return UnicodeString(reinterpret_cast(Data.c_str()), Data.size()); +} + +intptr_t RawByteString::Pos(wchar_t Ch) const +{ + return Data.find(static_cast(Ch)) + 1; +} + +intptr_t RawByteString::Pos(const char Ch) const +{ + return Data.find(static_cast(Ch)) + 1; +} + +intptr_t RawByteString::Pos(const char * Str) const +{ + return Data.find(reinterpret_cast(Str)) + 1; +} + +RawByteString::RawByteString(const wchar_t * Str) +{ + Init(Str, ::StrLength(Str)); +} + +RawByteString::RawByteString(const wchar_t * Str, intptr_t Size) +{ + Init(Str, Size); +} + +RawByteString::RawByteString(const char * Str) +{ + Init(Str, Str ? strlen(Str) : 0); +} + +RawByteString::RawByteString(const char * Str, intptr_t Size) +{ + Init(Str, Size); +} + +RawByteString::RawByteString(const uint8_t * Str) +{ + Init(Str, Str ? strlen(reinterpret_cast(Str)) : 0); +} + +RawByteString::RawByteString(const UnicodeString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +RawByteString::RawByteString(const RawByteString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +RawByteString::RawByteString(const AnsiString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +RawByteString::RawByteString(const UTF8String & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +RawByteString & RawByteString::Insert(const char * Str, intptr_t Pos) +{ + Data.insert(Pos - 1, reinterpret_cast(Str)); + return *this; +} + +RawByteString RawByteString::SubString(intptr_t Pos, intptr_t Len) const +{ + rawstring_t s = Data.substr(Pos - 1, Len); + RawByteString Result(s.c_str(), s.size()); + return Result; +} + +RawByteString & RawByteString::operator=(const UnicodeString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +RawByteString & RawByteString::operator=(const RawByteString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +RawByteString & RawByteString::operator=(const AnsiString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +RawByteString & RawByteString::operator=(const UTF8String & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +RawByteString & RawByteString::operator=(const char * lpszData) +{ + if (lpszData) + { + Init(lpszData, strlen(lpszData ? lpszData : "")); + } + return *this; +} + +RawByteString & RawByteString::operator=(const wchar_t * lpwszData) +{ + Init(lpwszData, wcslen(NullToEmpty(lpwszData))); + return *this; +} + +RawByteString RawByteString::operator +(const RawByteString & rhs) const +{ + rawstring_t Result = Data + rhs.Data; + return RawByteString(reinterpret_cast(Result.c_str()), Result.size()); +} + +RawByteString & RawByteString::operator +=(const RawByteString & rhs) +{ + Data.append(reinterpret_cast(rhs.c_str()), rhs.Length()); + return *this; +} + +RawByteString & RawByteString::operator +=(const char Ch) +{ + uint8_t ch(static_cast(Ch)); + Data.append(1, ch); + return *this; +} + +UTF8String::UTF8String(const UTF8String & rht) +{ + Init(rht.c_str(), rht.Length()); +} + +void UTF8String::Init(const wchar_t * Str, intptr_t Length) +{ +// Data.resize(Length); +// if (Length > 0) +// { +// wmemmove(const_cast(Data.c_str()), Str, Length); +// } +// Data = Data.c_str(); + intptr_t Size = ::WideCharToMultiByte(CP_UTF8, 0, Str, static_cast(Length > 0 ? Length : -1), nullptr, 0, nullptr, nullptr); + Data.resize(Size + 1); + if (Size > 0) + { + ::WideCharToMultiByte(CP_UTF8, 0, Str, -1, const_cast(Data.c_str()), static_cast(Size), nullptr, nullptr); + Data[Size] = 0; + } + Data = Data.c_str(); +} + +void UTF8String::Init(const char * Str, intptr_t Length) +{ +// intptr_t Size = ::MultiByteToWideChar(CP_UTF8, 0, Str, static_cast(Length > 0 ? Length : -1), nullptr, 0); +// Data.resize(Size + 1); +// if (Size > 0) +// { +// ::MultiByteToWideChar(CP_UTF8, 0, Str, -1, const_cast(Data.c_str()), static_cast(Size)); +// Data[Size] = 0; +// } +// Data = Data.c_str(); + Data.resize(Length); + if (Length > 0) + { + memmove(const_cast(Data.c_str()), Str, Length); + } + Data = Data.c_str(); +} + +UTF8String::UTF8String(const UnicodeString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +UTF8String::UTF8String(const wchar_t * Str) +{ + Init(Str, ::StrLength(Str)); +} + +UTF8String::UTF8String(const wchar_t * Str, intptr_t Size) +{ + Init(Str, Size); +} + +UTF8String &UTF8String::Delete(intptr_t Index, intptr_t Count) +{ + Data.erase(Index - 1, Count); + return *this; +} + +intptr_t UTF8String::Pos(char Ch) const +{ + return Data.find(Ch) + 1; +} + +int UTF8String::vprintf(const char * Format, va_list ArgList) +{ + SetLength(32 * 1024); + return vsnprintf((char *)Data.c_str(), Data.size(), Format, ArgList); +} + +UTF8String & UTF8String::Insert(const wchar_t * Str, intptr_t Pos) +{ + UTF8String UTF8(Str); + Data.insert(Pos - 1, UTF8); + return *this; +} + +UTF8String UTF8String::SubString(intptr_t Pos, intptr_t Len) const +{ + return UTF8String(Data.substr(Pos - 1, Len).c_str()); +} + +UTF8String & UTF8String::operator=(const UnicodeString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +UTF8String & UTF8String::operator=(const UTF8String & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +UTF8String & UTF8String::operator=(const RawByteString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +UTF8String & UTF8String::operator=(const char * lpszData) +{ + if (lpszData) + { + Init(lpszData, strlen(lpszData ? lpszData : "")); + } + return *this; +} + +UTF8String & UTF8String::operator=(const wchar_t * lpwszData) +{ + Init(lpwszData, wcslen(NullToEmpty(lpwszData))); + return *this; +} + +UTF8String UTF8String::operator +(const UTF8String & rhs) const +{ + string_t Result = Data + rhs.Data; + return UTF8String(Result.c_str(), Result.size()); +} + +UTF8String & UTF8String::operator +=(const UTF8String & rhs) +{ + Data.append(rhs.Data.c_str(), rhs.Length()); + return *this; +} + +UTF8String & UTF8String::operator +=(const RawByteString & rhs) +{ + UTF8String s(rhs.c_str(), rhs.Length()); + Data.append(s.Data.c_str(), s.Length()); + return *this; +} + +UTF8String & UTF8String::operator +=(const char Ch) +{ + uint8_t ch(static_cast(Ch)); + Data.append(1, ch); + return *this; +} + +bool operator ==(const UTF8String & lhs, const UTF8String & rhs) +{ + return lhs.Data == rhs.Data; +} + +bool operator !=(const UTF8String & lhs, const UTF8String & rhs) +{ + return lhs.Data != rhs.Data; +} + +void UnicodeString::Init(const wchar_t * Str, intptr_t Length) +{ + Data.resize(Length); + if (Length > 0) + { + wmemmove(const_cast(Data.c_str()), Str, Length); + } + Data = Data.c_str(); +} + +void UnicodeString::Init(const char * Str, intptr_t Length) +{ + intptr_t Size = ::MultiByteToWideChar(CP_UTF8, 0, Str, static_cast(Length > 0 ? Length : -1), nullptr, 0); + Data.resize(Size + 1); + if (Size > 0) + { + ::MultiByteToWideChar(CP_UTF8, 0, Str, -1, const_cast(Data.c_str()), static_cast(Size)); + Data[Size] = 0; + } + Data = Data.c_str(); +} + +UnicodeString::UnicodeString(const AnsiString & Str) +{ + Init(Str.c_str(), Str.GetLength()); +} + +UnicodeString & UnicodeString::Lower(intptr_t nStartPos, intptr_t nLength) +{ + Data = ::LowerCase(SubString(nStartPos, nLength)).c_str(); + return *this; +} + +UnicodeString & UnicodeString::Upper(intptr_t nStartPos, intptr_t nLength) +{ + Data = ::UpperCase(SubString(nStartPos, nLength)).c_str(); + return *this; +} + +intptr_t UnicodeString::Compare(const UnicodeString & Str) const +{ + return ::AnsiCompare(*this, Str); +} + +intptr_t UnicodeString::CompareIC(const UnicodeString & Str) const +{ + return ::AnsiCompareIC(*this, Str); +} + +intptr_t UnicodeString::ToInt() const +{ + return ::StrToIntDef(*this, 0); +} + +UnicodeString & UnicodeString::Replace(intptr_t Pos, intptr_t Len, const wchar_t * Str, intptr_t DataLen) +{ + Data.replace(Pos - 1, Len, wstring_t(Str, DataLen)); + return *this; +} + +UnicodeString & UnicodeString::Append(const char * lpszAdd, UINT CodePage) +{ + Data.append(::MB2W(lpszAdd, CodePage).c_str()); + return *this; +} + +UnicodeString & UnicodeString::Insert(intptr_t Pos, const wchar_t * Str, intptr_t StrLen) +{ + Data.insert(Pos - 1, Str, StrLen); + return *this; +} + +intptr_t UnicodeString::Pos(wchar_t Ch) const +{ + return Data.find(Ch) + 1; +} + +intptr_t UnicodeString::Pos(const UnicodeString & Str) const +{ + return Data.find(Str.Data) + 1; +} + +bool UnicodeString::RPos(intptr_t & nPos, wchar_t Ch, intptr_t nStartPos) const +{ + size_t Pos = Data.find_last_of(Ch, Data.size() - nStartPos); + nPos = Pos + 1; + return Pos != std::wstring::npos; +} + +UnicodeString UnicodeString::SubStr(intptr_t Pos, intptr_t Len) const +{ + wstring_t Str(Data.substr(Pos - 1, Len)); + return UnicodeString(Str.c_str(), Str.size()); +} + +UnicodeString UnicodeString::SubString(intptr_t Pos, intptr_t Len) const +{ + return SubStr(Pos, Len); +} + +bool UnicodeString::IsDelimiter(const UnicodeString & Chars, intptr_t Pos) const +{ + return ::IsDelimiter(Chars, *this, Pos); +} + +intptr_t UnicodeString::LastDelimiter(const UnicodeString & Delimiters) const +{ + return ::LastDelimiter(Delimiters, *this); +} + +UnicodeString UnicodeString::Trim() const +{ + return ::Trim(*this); +} + +UnicodeString UnicodeString::TrimLeft() const +{ + return ::TrimLeft(*this); +} + +UnicodeString UnicodeString::TrimRight() const +{ + return ::TrimRight(*this); +} + +void UnicodeString::sprintf(const wchar_t * fmt, ...) +{ + va_list args; + va_start(args, fmt); + Data = ::FormatV(fmt, args).c_str(); + va_end(args); +} + +UnicodeString & UnicodeString::operator=(const UnicodeString & StrCopy) +{ + if (*this != StrCopy) + { + Data = StrCopy.Data; + } + return *this; +} + +UnicodeString & UnicodeString::operator=(const RawByteString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +UnicodeString & UnicodeString::operator=(const AnsiString & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + // Data = StrCopy.Data; + return *this; +} + +UnicodeString & UnicodeString::operator=(const UTF8String & StrCopy) +{ + Init(StrCopy.c_str(), StrCopy.Length()); + return *this; +} + +UnicodeString & UnicodeString::operator=(const wchar_t * Str) +{ + Init(Str, wcslen(NullToEmpty(Str))); + return *this; +} + +UnicodeString & UnicodeString::operator=(const wchar_t Ch) +{ + Init(&Ch, 1); + return *this; +} + +UnicodeString & UnicodeString::operator=(const char * lpszData) +{ + if (lpszData) + { + Init(lpszData, strlen(lpszData ? lpszData : "")); + } + return *this; +} + +UnicodeString UnicodeString::operator +(const UnicodeString & rhs) const +{ + wstring_t Result = Data + rhs.Data; + return UnicodeString(Result.c_str(), Result.size()); +} + +UnicodeString & UnicodeString::operator +=(const UnicodeString & rhs) +{ + Data.append(rhs.Data.c_str(), rhs.Length()); + return *this; +} + +UnicodeString & UnicodeString::operator +=(const wchar_t * rhs) +{ + Data.append(rhs); + return *this; +} + +UnicodeString & UnicodeString::operator +=(const RawByteString & rhs) +{ + UnicodeString s(rhs.c_str(), rhs.Length()); + Data.append(s.Data.c_str(), s.Length()); + return *this; +} + +UnicodeString & UnicodeString::operator +=(const char Ch) +{ + Data.append(1, Ch); + return *this; +} + +UnicodeString & UnicodeString::operator +=(const wchar_t Ch) +{ + Data += Ch; + return *this; +} + +wchar_t UnicodeString::operator [](intptr_t Idx) const +{ + ThrowIfOutOfRange(Idx); // Should Range-checking be optional to avoid overhead ?? + return Data[Idx-1]; +} + +wchar_t &UnicodeString::operator [](intptr_t Idx) +{ + ThrowIfOutOfRange(Idx); // Should Range-checking be optional to avoid overhead ?? + return Data[Idx-1]; +} + +void UnicodeString::ThrowIfOutOfRange(intptr_t Idx) const +{ + if (Idx < 1 || Idx > Length()) // NOTE: UnicodeString is 1-based !! + throw std::runtime_error("Index is out of range"); // ERangeError(Sysconst_SRangeError); +} + +UnicodeString operator +(const wchar_t lhs, const UnicodeString & rhs) +{ + return UnicodeString(&lhs, 1) + rhs; +} + +UnicodeString operator +(const UnicodeString & lhs, const wchar_t rhs) +{ + return lhs + UnicodeString(rhs); +} + +UnicodeString operator +(const wchar_t * lhs, const UnicodeString & rhs) +{ + return UnicodeString(lhs) + rhs; +} + +UnicodeString operator +(const UnicodeString & lhs, const wchar_t * rhs) +{ + return lhs + UnicodeString(rhs); +} + +UnicodeString operator +(const UnicodeString & lhs, const char * rhs) +{ + return lhs + UnicodeString(rhs); +} + +bool operator ==(const UnicodeString & lhs, const wchar_t * rhs) +{ + return wcscmp(lhs.Data.c_str(), NullToEmpty(rhs)) == 0; +} + +bool operator ==(const wchar_t * lhs, const UnicodeString & rhs) +{ + return wcscmp(NullToEmpty(lhs), rhs.Data.c_str()) == 0; +} + +bool operator !=(const UnicodeString & lhs, const wchar_t * rhs) +{ + return wcscmp(lhs.Data.c_str(), NullToEmpty(rhs)) != 0; +} + +bool operator !=(const wchar_t * lhs, const UnicodeString & rhs) +{ + return wcscmp(NullToEmpty(lhs), rhs.Data.c_str()) != 0; +} diff --git a/netbox/src/base/UnicodeString.hpp b/netbox/src/base/UnicodeString.hpp new file mode 100644 index 000000000..51aa6bbbd --- /dev/null +++ b/netbox/src/base/UnicodeString.hpp @@ -0,0 +1,376 @@ +#pragma once + +#include + +#include +#include "local.hpp" + +class RawByteString; +class UnicodeString; +class AnsiString; + +class UTF8String +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + UTF8String() {} + UTF8String(const UTF8String & rht); + explicit UTF8String(const UnicodeString & Str); + UTF8String(const wchar_t * Str); + explicit UTF8String(const wchar_t * Str, intptr_t Size); + explicit UTF8String(const char * Str, intptr_t Size) { Init(Str, Size); } + explicit UTF8String(const char * Str) { Init(Str, Str ? strlen(Str) : 0); } + + ~UTF8String() {} + + operator const char * () const { return c_str(); } + const char * c_str() const { return Data.c_str(); } + intptr_t Length() const { return Data.size(); } + intptr_t GetLength() const { return Length(); } + bool IsEmpty() const { return Length() == 0; } + void SetLength(intptr_t nLength) { Data.resize(nLength); } + UTF8String & Delete(intptr_t Index, intptr_t Count); + UTF8String & Insert(const wchar_t * Str, intptr_t Pos); + UTF8String SubString(intptr_t Pos, intptr_t Len = -1) const; + + intptr_t Pos(char Ch) const; + + int vprintf(const char * Format, va_list ArgList); + + void Unique() {} + +public: + UTF8String & operator=(const UnicodeString & StrCopy); + UTF8String & operator=(const UTF8String & StrCopy); + UTF8String & operator=(const RawByteString & StrCopy); + UTF8String & operator=(const char * lpszData); + UTF8String & operator=(const wchar_t * lpwszData); + UTF8String & operator=(wchar_t chData); + + UTF8String operator +(const UTF8String & rhs) const; + UTF8String operator +(const RawByteString & rhs) const; + UTF8String & operator +=(const UTF8String & rhs); + UTF8String & operator +=(const RawByteString & rhs); + UTF8String & operator +=(const char rhs); + UTF8String & operator +=(const char * rhs); + + friend bool operator ==(const UTF8String & lhs, const UTF8String & rhs); + friend bool operator !=(const UTF8String & lhs, const UTF8String & rhs); + +private: + void Init(const wchar_t * Str, intptr_t Length); + void Init(const char * Str, intptr_t Length); + + typedef std::basic_string, custom_nballocator_t > string_t; + string_t Data; +}; + +class UnicodeString +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + UnicodeString() {} + UnicodeString(const wchar_t * Str) { Init(Str, ::StrLength(Str)); } + UnicodeString(const wchar_t * Str, intptr_t Size) { Init(Str, Size); } + UnicodeString(const wchar_t Src) { Init(&Src, 1); } + UnicodeString(const char * Str, intptr_t Size) { Init(Str, Size); } + UnicodeString(const char * Str) { Init(Str, Str ? strlen(Str) : 0); } + UnicodeString(intptr_t Size, wchar_t Ch) : Data(Size, Ch) {} + + UnicodeString(const UnicodeString & Str) { Init(Str.c_str(), Str.GetLength()); } + explicit UnicodeString(const UTF8String & Str) { Init(Str.c_str(), Str.GetLength()); } + explicit UnicodeString(const AnsiString & Str); + + ~UnicodeString() {} + + const wchar_t * c_str() const { return Data.c_str(); } + const wchar_t * data() const { return Data.c_str(); } + intptr_t Length() const { return Data.size(); } + intptr_t GetLength() const { return Length(); } + intptr_t GetBytesCount() const { return (Length() + 1) * sizeof(wchar_t); } + bool IsEmpty() const { return Length() == 0; } + void SetLength(intptr_t nLength) { Data.resize(nLength); } + inline UnicodeString & Delete(intptr_t Index, intptr_t Count) { Data.erase(Index - 1, Count); return *this; } + UnicodeString & Clear() { Data.clear(); return *this; } + + UnicodeString & Lower(intptr_t nStartPos = 1, intptr_t nLength = -1); + UnicodeString & Upper(intptr_t nStartPos = 1, intptr_t nLength = -1); + + UnicodeString & LowerCase() { return Lower(); } + UnicodeString & UpperCase() { return Upper(); } + + intptr_t Compare(const UnicodeString & Str) const; + intptr_t CompareIC(const UnicodeString & Str) const; + intptr_t ToInt() const; + intptr_t FindFirstOf(const wchar_t Ch) const { return Data.find_first_of(Ch, 0); } + intptr_t FindFirstOf(const wchar_t * Str, size_t Offset = 0) const { return Data.find_first_of(Str, Offset); } + intptr_t FindFirstNotOf(const wchar_t * Str) const { return Data.find_first_not_of(Str); } + + UnicodeString & Replace(intptr_t Pos, intptr_t Len, const wchar_t * Str, intptr_t DataLen); + UnicodeString & Replace(intptr_t Pos, intptr_t Len, const UnicodeString & Str) { return Replace(Pos, Len, Str.c_str(), Str.GetLength()); } + UnicodeString & Replace(intptr_t Pos, intptr_t Len, const wchar_t * Str) { return Replace(Pos, Len, Str, ::StrLength(NullToEmpty(Str))); } + UnicodeString & Replace(intptr_t Pos, intptr_t Len, wchar_t Ch) { return Replace(Pos, Len, &Ch, 1); } + UnicodeString & Replace(intptr_t Pos, wchar_t Ch) { return Replace(Pos, 1, &Ch, 1); } + + UnicodeString & Append(const wchar_t * Str, intptr_t StrLen) { return Replace(GetLength(), 0, Str, StrLen); } + UnicodeString & Append(const UnicodeString & Str) { return Append(Str.c_str(), Str.GetLength()); } + UnicodeString & Append(const wchar_t * Str) { return Append(Str, ::StrLength(NullToEmpty(Str))); } + UnicodeString & Append(const wchar_t Ch) { return Append(&Ch, 1); } + UnicodeString & Append(const char * lpszAdd, UINT CodePage=CP_OEMCP); + + UnicodeString & Insert(intptr_t Pos, const wchar_t * Str, intptr_t StrLen); + UnicodeString & Insert(intptr_t Pos, const UnicodeString & Str) { return Insert(Pos, Str.c_str(), Str.Length()); } + UnicodeString & Insert(const wchar_t * Str, intptr_t Pos) { return Insert(Pos, Str, wcslen(NullToEmpty(Str))); } + UnicodeString & Insert(const wchar_t Ch, intptr_t Pos) { return Insert(Pos, &Ch, 1); } + UnicodeString & Insert(const UnicodeString & Str, intptr_t Pos) { return Insert(Pos, Str); } + + intptr_t Pos(wchar_t Ch) const; + intptr_t Pos(const UnicodeString & Str) const; + + intptr_t RPos(wchar_t Ch) const { return Data.find_last_of(Ch) + 1; } + bool RPos(intptr_t & nPos, wchar_t Ch, intptr_t nStartPos = 0) const; + + UnicodeString SubStr(intptr_t Pos, intptr_t Len = -1) const; + UnicodeString SubString(intptr_t Pos, intptr_t Len = -1) const; + + bool IsDelimiter(const UnicodeString & Chars, intptr_t Pos) const; + intptr_t LastDelimiter(const UnicodeString & Delimiters) const; + + UnicodeString Trim() const; + UnicodeString TrimLeft() const; + UnicodeString TrimRight() const; + + void Unique() {} + + void sprintf(const wchar_t * fmt, ...); + +public: + UnicodeString & operator=(const UnicodeString & StrCopy); + UnicodeString & operator=(const RawByteString & StrCopy); + UnicodeString & operator=(const AnsiString & StrCopy); + UnicodeString & operator=(const UTF8String & StrCopy); + UnicodeString & operator=(const wchar_t * lpwszData); + UnicodeString & operator=(const char * lpszData); + UnicodeString & operator=(const wchar_t Ch); + + UnicodeString operator +(const UnicodeString & rhs) const; + UnicodeString operator +(const RawByteString & rhs) const; + UnicodeString operator +(const AnsiString & rhs) const; + UnicodeString operator +(const UTF8String & rhs) const; + + friend UnicodeString operator +(const wchar_t lhs, const UnicodeString & rhs); + friend UnicodeString operator +(const UnicodeString & lhs, wchar_t rhs); + friend UnicodeString operator +(const wchar_t * lhs, const UnicodeString & rhs); + friend UnicodeString operator +(const UnicodeString & lhs, const wchar_t * rhs); + friend UnicodeString operator +(const UnicodeString & lhs, const char * rhs); + + UnicodeString & operator +=(const UnicodeString & rhs); + UnicodeString & operator +=(const wchar_t * rhs); + UnicodeString & operator +=(const UTF8String & rhs); + UnicodeString & operator +=(const RawByteString & rhs); + UnicodeString & operator +=(const char rhs); + UnicodeString & operator +=(const char * rhs); + UnicodeString & operator +=(const wchar_t rhs); + + bool operator ==(const UnicodeString & Str) const { return Data == Str.Data; } + bool operator !=(const UnicodeString & Str) const { return Data != Str.Data; } + bool operator < (const UnicodeString & Str) const { return Data < Str.Data; } + + friend bool operator ==(const UnicodeString & lhs, const wchar_t * rhs); + friend bool operator ==(const wchar_t * lhs, const UnicodeString & rhs); + friend bool operator !=(const UnicodeString & lhs, const wchar_t * rhs); + friend bool operator !=(const wchar_t * lhs, const UnicodeString & rhs); + + wchar_t operator [](intptr_t Idx) const; + wchar_t & operator [](intptr_t Idx); + +private: + void Init(const wchar_t * Str, intptr_t Length); + void Init(const char * Str, intptr_t Length); + void ThrowIfOutOfRange(intptr_t Idx) const; + + typedef std::basic_string, custom_nballocator_t > wstring_t; + wstring_t Data; +}; + +class RawByteString; + +class AnsiString +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + AnsiString() {} + AnsiString(const AnsiString & rht); + AnsiString(intptr_t Size, char Ch) : Data(Size, Ch) {} + explicit AnsiString(const wchar_t * Str); + explicit AnsiString(const wchar_t * Str, intptr_t Size); + AnsiString(const char * Str); + explicit AnsiString(const char * Str, intptr_t Size); + explicit AnsiString(const uint8_t * Str); + explicit AnsiString(const uint8_t * Str, intptr_t Size); + explicit AnsiString(const UnicodeString & Str); + explicit AnsiString(const UTF8String & Str); + explicit AnsiString(const RawByteString & Str); + inline ~AnsiString() {} + + const char * c_str() const { return Data.c_str(); } + intptr_t Length() const { return Data.size(); } + intptr_t GetLength() const { return Length(); } + bool IsEmpty() const { return Length() == 0; } + void SetLength(intptr_t nLength) { Data.resize(nLength); } + inline AnsiString & Delete(intptr_t Index, intptr_t Count) { Data.erase(Index - 1, Count); return *this; } + AnsiString & Clear() { Data.clear(); return *this; } + AnsiString & Insert(const char * Str, intptr_t Pos); + AnsiString SubString(intptr_t Pos, intptr_t Len = -1) const; + + intptr_t Pos(const AnsiString & Str) const; + intptr_t Pos(wchar_t Ch) const; + intptr_t Pos(const wchar_t * Str) const; + + char operator [](intptr_t Idx) const; + char & operator [](intptr_t Idx); + + AnsiString & Append(const char * Str, intptr_t StrLen) { Data.append(Str, StrLen); return *this; } + AnsiString & Append(const AnsiString & Str) { return Append(Str.c_str(), Str.GetLength()); } + AnsiString & Append(const char * Str) { return Append(Str, strlen(Str ? Str : "")); } + AnsiString & Append(const char Ch) { return Append(&Ch, 1); } + + void Unique() {} + +public: + AnsiString & operator=(const UnicodeString & strCopy); + AnsiString & operator=(const RawByteString & strCopy); + AnsiString & operator=(const AnsiString & strCopy); + AnsiString & operator=(const UTF8String & strCopy); + AnsiString & operator=(const char * lpszData); + AnsiString & operator=(const wchar_t * lpwszData); + AnsiString & operator=(wchar_t chData); + + AnsiString operator +(const UnicodeString & rhs) const; + AnsiString operator +(const AnsiString & rhs) const; + + AnsiString & operator +=(const AnsiString & rhs); + AnsiString & operator +=(const char Ch); + AnsiString & operator +=(const char * rhs); + + // bool operator ==(const AnsiString & Str) const { return Data == Str.Data; } + // bool operator !=(const AnsiString & Str) const { return Data != Str.Data; } + + inline friend bool operator ==(const AnsiString & lhs, const AnsiString & rhs) + { return strcmp(lhs.Data.c_str(), rhs.Data.c_str()) == 0; } + inline friend bool operator !=(const AnsiString & lhs, const AnsiString & rhs) + { return strcmp(lhs.Data.c_str(), rhs.Data.c_str()) != 0; } + + inline friend bool operator ==(const AnsiString & lhs, const char * rhs) + { return strcmp(lhs.Data.c_str(), rhs ? rhs : "") == 0; } + inline friend bool operator ==(const char * lhs, const AnsiString & rhs) + { return strcmp(lhs ? lhs : "", rhs.Data.c_str()) == 0; } + inline friend bool operator !=(const AnsiString & lhs, const char * rhs) + { return strcmp(lhs.Data.c_str(), rhs ? rhs : "") != 0; } + inline friend bool operator !=(const char * lhs, const AnsiString & rhs) + { return strcmp(lhs ? lhs : "", rhs.Data.c_str()) != 0; } + +private: + void Init(const wchar_t * Str, intptr_t Length); + void Init(const char * Str, intptr_t Length); + void Init(const uint8_t * Str, intptr_t Length); + void ThrowIfOutOfRange(intptr_t Idx) const; + + typedef std::basic_string, custom_nballocator_t > string_t; + string_t Data; +}; + +class RawByteString +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + RawByteString() {} + explicit RawByteString(const wchar_t * Str); + explicit RawByteString(const wchar_t * Str, intptr_t Size); + RawByteString(const char * Str); + explicit RawByteString(const char * Str, intptr_t Size); + explicit RawByteString(const uint8_t * Str); + explicit RawByteString(const uint8_t * Str, intptr_t Size) { Init(Str, Size); } + RawByteString(const UnicodeString & Str); + RawByteString(const RawByteString & Str); + RawByteString(const AnsiString & Str); + RawByteString(const UTF8String & Str); + ~RawByteString() {} + + operator const char * () const { return reinterpret_cast(Data.c_str()); } + operator UnicodeString() const; + const char * c_str() const { return reinterpret_cast(Data.c_str()); } + intptr_t Length() const { return Data.size(); } + intptr_t GetLength() const { return Length(); } + bool IsEmpty() const { return Length() == 0; } + void SetLength(intptr_t nLength) { Data.resize(nLength); } + RawByteString & Clear() { SetLength(0); return *this; } + inline RawByteString & Delete(intptr_t Index, intptr_t Count) { Data.erase(Index - 1, Count); return *this; } + RawByteString & Insert(const char * Str, intptr_t Pos); + RawByteString SubString(intptr_t Pos, intptr_t Len = -1) const; + + intptr_t Pos(wchar_t Ch) const; + intptr_t Pos(const wchar_t * Str) const; + intptr_t Pos(const char Ch) const; + intptr_t Pos(const char * Ch) const; + + void Unique() {} + +public: + RawByteString & operator=(const UnicodeString & strCopy); + RawByteString & operator=(const RawByteString & strCopy); + RawByteString & operator=(const AnsiString & strCopy); + RawByteString & operator=(const UTF8String & strCopy); + RawByteString & operator=(const char * lpszData); + RawByteString & operator=(const wchar_t * lpwszData); + RawByteString & operator=(wchar_t chData); + + RawByteString operator +(const RawByteString & rhs) const; + + RawByteString & operator +=(const RawByteString & rhs); + RawByteString & operator +=(const char Ch); + + bool operator ==(const char * rhs) const + { return strcmp(reinterpret_cast(Data.c_str()), rhs) == 0; } + inline friend bool operator ==(RawByteString & lhs, RawByteString & rhs) + { return lhs.Data == rhs.Data; } + inline friend bool operator !=(RawByteString & lhs, RawByteString & rhs) + { return lhs.Data != rhs.Data; } + +private: + void Init(const wchar_t * Str, intptr_t Length); + void Init(const char * Str, intptr_t Length); + void Init(const uint8_t * Str, intptr_t Length); + + typedef std::basic_string, custom_nballocator_t > rawstring_t; + rawstring_t Data; +}; + +namespace rde { + +template +inline bool operator==(const S & lhs, const S & rhs) +{ + return lhs.Compare(rhs) == 0; +} + +template +inline bool operator!=(const S & lhs, const S & rhs) +{ + return !(lhs == rhs); +} + +template +inline bool operator<(const S & lhs, const S & rhs) +{ + return lhs.Compare(rhs) < 0; +} + +template +inline bool operator>(const S & lhs, const S & rhs) +{ + return lhs.Compare(rhs) > 0; +} + +} // namespace rde + diff --git a/netbox/src/base/WideStrUtils.cpp b/netbox/src/base/WideStrUtils.cpp new file mode 100644 index 000000000..eb4273e19 --- /dev/null +++ b/netbox/src/base/WideStrUtils.cpp @@ -0,0 +1,82 @@ +#include "WideStrUtils.hpp" + +TEncodeType DetectUTF8Encoding(const RawByteString & S) +{ + const uint8_t *buf = reinterpret_cast(S.c_str()); + intptr_t len = S.Length(); + const uint8_t * endbuf = buf + len; + uint8_t byte2mask = 0x00; + int trailing = 0; // trailing (continuation) bytes to follow + + while (buf != endbuf) + { + uint8_t c = *buf++; + if (trailing) + { + if ((c & 0xC0) == 0x80) // Does trailing byte follow UTF-8 format? + { + if (byte2mask) // Need to check 2nd byte for proper range? + { + if (c & byte2mask) // Are appropriate bits set? + byte2mask = 0x00; + else + return etANSI; + } + trailing--; + } + else + return etANSI; + } + else + { + if ((c & 0x80) == 0x00) + continue; // valid 1 byte UTF-8 + else if ((c & 0xE0) == 0xC0) // valid 2 byte UTF-8 + { + if (c & 0x1E) // Is UTF-8 byte in + // proper range? + trailing = 1; + else + return etANSI; + } + else if ((c & 0xF0) == 0xE0) // valid 3 byte UTF-8 + { + if (!(c & 0x0F)) // Is UTF-8 byte in + // proper range? + byte2mask = 0x20; // If not set mask + // to check next byte + trailing = 2; + } + else if ((c & 0xF8) == 0xF0) // valid 4 byte UTF-8 + { + if (!(c & 0x07)) // Is UTF-8 byte in + { + // proper range? + + byte2mask = 0x30; // If not set mask + } + // to check next byte + trailing = 3; + } + else if ((c & 0xFC) == 0xF8) // valid 5 byte UTF-8 + { + if (!(c & 0x03)) // Is UTF-8 byte in + // proper range? + byte2mask = 0x38; // If not set mask + // to check next byte + trailing = 4; + } + else if ((c & 0xFE) == 0xFC) // valid 6 byte UTF-8 + { + if (!(c & 0x01)) // Is UTF-8 byte in + // proper range? + byte2mask = 0x3C; // If not set mask + // to check next byte + trailing = 5; + } + else + return etANSI; + } + } + return trailing == 0 ? etUTF8 : etANSI; +} diff --git a/netbox/src/base/WideStrUtils.hpp b/netbox/src/base/WideStrUtils.hpp new file mode 100644 index 000000000..653fde190 --- /dev/null +++ b/netbox/src/base/WideStrUtils.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +enum TEncodeType +{ + etUSASCII, + etUTF8, + etANSI, +}; + +TEncodeType DetectUTF8Encoding(const RawByteString & S); diff --git a/netbox/src/base/Windows.hpp b/netbox/src/base/Windows.hpp new file mode 100644 index 000000000..93b6f4c1b --- /dev/null +++ b/netbox/src/base/Windows.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/contnrs.hpp b/netbox/src/base/contnrs.hpp new file mode 100644 index 000000000..538d1eefe --- /dev/null +++ b/netbox/src/base/contnrs.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/disable_warnings_in_std_begin.hpp b/netbox/src/base/disable_warnings_in_std_begin.hpp new file mode 100644 index 000000000..30321bc08 --- /dev/null +++ b/netbox/src/base/disable_warnings_in_std_begin.hpp @@ -0,0 +1,33 @@ +/* +Copyright 2015 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if defined _MSC_VER +#pragma warning(push) +#define DISABLE_WARNINGS_IN_STD_HEADERS +#include "disabled_warnings.hpp" +#undef DISABLE_WARNINGS_IN_STD_HEADERS +#endif diff --git a/netbox/src/base/disable_warnings_in_std_end.hpp b/netbox/src/base/disable_warnings_in_std_end.hpp new file mode 100644 index 000000000..2497c9e5a --- /dev/null +++ b/netbox/src/base/disable_warnings_in_std_end.hpp @@ -0,0 +1,31 @@ +/* +Copyright 2015 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef _MSC_VER +#pragma warning(suppress: 5031) // no page #pragma warning(pop): likely mismatch, popping warning state pushed in different file +#pragma warning(pop) +#endif diff --git a/netbox/src/base/disabled_warnings.hpp b/netbox/src/base/disabled_warnings.hpp new file mode 100644 index 000000000..8749cd8a9 --- /dev/null +++ b/netbox/src/base/disabled_warnings.hpp @@ -0,0 +1,82 @@ +/* +Copyright 2015 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef _MSC_VER +// some warnings are not exist in older VC versions, no sense to care about them +#pragma warning(disable: 4616) // https://msdn.microsoft.com/en-us/library/t7ab6xtd.aspx #pragma warning : warning number 'number' not a valid compiler warning +#pragma warning(disable: 4619) // https://msdn.microsoft.com/en-us/library/tacee08d.aspx #pragma warning : there is no warning number 'number' + +#ifdef DISABLE_WARNINGS_IN_STD_HEADERS +// these warnings are triggered in the standard headers ONLY +#pragma warning(disable: 4091) // https://msdn.microsoft.com/en-us/library/eehkcz60.aspx 'typedef ': ignored on left of 'type' when no variable is declared +#pragma warning(disable: 4265) // https://msdn.microsoft.com/en-us/library/wzxffy8c.aspx 'class' : class has virtual functions, but destructor is not virtual +#pragma warning(disable: 4668) // https://msdn.microsoft.com/en-us/library/4dt9kyhy.aspx 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +#pragma warning(disable: 4917) // https://msdn.microsoft.com/en-us/library/3w98z1xh.aspx 'declarator' : a GUID can only be associated with a class, interface or namespace +#pragma warning(disable: 4996) // https://msdn.microsoft.com/en-us/library/ttcz0bys.aspx The compiler encountered a deprecated declaration +#else +// these in the rest of the code as well +#pragma warning(disable: 4054) // https://msdn.microsoft.com/en-us/library/07d15ax5.aspx 'conversion' : from function pointer 'type1' to data pointer 'type2' +#pragma warning(disable: 4061) // https://msdn.microsoft.com/en-us/library/96f5t7fy.aspx enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label +#pragma warning(disable: 4100) // https://msdn.microsoft.com/en-us/library/26kb9fy0.aspx 'identifier' : unreferenced formal parameter +#pragma warning(disable: 4127) // https://msdn.microsoft.com/en-us/library/6t66728h.aspx conditional expression is constant +#pragma warning(disable: 4191) // https://msdn.microsoft.com/en-us/library/w0eaaaf7.aspx 'operator/operation' : unsafe conversion from 'type of expression' to 'type required' +#pragma warning(disable: 4201) // https://msdn.microsoft.com/en-us/library/c89bw853.aspx nonstandard extension used : nameless struct/union +#pragma warning(disable: 4204) // https://msdn.microsoft.com/en-us/library/6b73z23c.aspx nonstandard extension used : non-constant aggregate initializer +#pragma warning(disable: 4242) // https://msdn.microsoft.com/en-us/library/3hca13eh.aspx 'identifier' : conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable: 4244) // https://msdn.microsoft.com/en-us/library/th7a07tz.aspx 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable: 4245) // https://msdn.microsoft.com/en-us/library/e9s7thk1.aspx 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch +#pragma warning(disable: 4250) // https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx 'class1' : inherits 'class2::member' via dominance +#pragma warning(disable: 4255) // https://msdn.microsoft.com/en-us/library/0k9z2ey4.aspx 'function' : no function prototype given: converting '()' to '(void)' +#pragma warning(disable: 4345) // https://msdn.microsoft.com/en-us/library/wewb47ee.aspx behavior change: an object of POD type constructed with an initializer of the form () will be default-initialized +#pragma warning(disable: 4350) // https://msdn.microsoft.com/en-us/library/0eestyah.aspx behavior change: 'member1' called instead of 'member2' +#pragma warning(disable: 4351) // https://msdn.microsoft.com/en-us/library/1ywe7hcy.aspx new behavior: elements of array 'array' will be default initialized +#pragma warning(disable: 4355) // https://msdn.microsoft.com/en-us/library/3c594ae3.aspx 'this' : used in base member initializer list +#pragma warning(disable: 4365) // https://msdn.microsoft.com/en-us/library/ms173683.aspx 'action' : conversion from 'type_1' to 'type_2', signed/unsigned mismatch +#pragma warning(disable: 4371) // no page layout of class may have changed from a previous version of the compiler due to better packing of member 'member' +#pragma warning(disable: 4396) // https://msdn.microsoft.com/en-us/library/bb384968.aspx "name" : the inline specifier cannot be used when a friend declaration refers to a specialization of a function template +#pragma warning(disable: 4435) // https://msdn.microsoft.com/en-us/library/jj155806.aspx 'class1' : Object layout under /vd2 will change due to virtual base 'class2' +#pragma warning(disable: 4480) // https://msdn.microsoft.com/en-us/library/ms173702.aspx nonstandard extension used: specifying underlying type for enum 'enum' +#pragma warning(disable: 4481) // https://msdn.microsoft.com/en-us/library/ms173703.aspx nonstandard extension used: override specifier 'keyword' +#pragma warning(disable: 4482) // https://msdn.microsoft.com/en-us/library/ms173704.aspx nonstandard extension used: enum 'enum' used in qualified name +#pragma warning(disable: 4512) // https://msdn.microsoft.com/en-us/library/hsyx7kbz.aspx 'class' : assignment operator could not be generated +#pragma warning(disable: 4514) // https://msdn.microsoft.com/en-us/library/cw9x3tcf.aspx 'function' : unreferenced inline function has been removed +#pragma warning(disable: 4625) // https://msdn.microsoft.com/en-us/library/306zwa5e.aspx 'derived class' : copy constructor could not be generated because a base class copy constructor is inaccessible +#pragma warning(disable: 4626) // https://msdn.microsoft.com/en-us/library/6ay4xcyd.aspx 'derived class' : assignment operator could not be generated because a base class assignment operator is inaccessible +#pragma warning(disable: 4640) // https://msdn.microsoft.com/en-us/library/4f5c8560.aspx 'instance' : construction of local static object is not thread-safe +#pragma warning(disable: 4710) // https://msdn.microsoft.com/en-us/library/yd3056cz.aspx 'function' : function not inlined +#pragma warning(disable: 4711) // https://msdn.microsoft.com/en-us/library/k402bt7y.aspx function 'function' selected for inline expansion +#pragma warning(disable: 4770) // no page partially validated enum 'name' used as index +#pragma warning(disable: 4738) // https://msdn.microsoft.com/en-us/library/c24hdbz6.aspx storing 32-bit float result in memory, possible loss of performance +#pragma warning(disable: 4820) // https://msdn.microsoft.com/en-us/library/t7khkyth.aspx 'bytes' bytes padding added after construct 'member_name' +#pragma warning(disable: 4836) // https://msdn.microsoft.com/en-us/library/ms173717.aspx nonstandard extension used : 'type' : local types or unnamed types cannot be used as template arguments +#pragma warning(disable: 4986) // https://msdn.microsoft.com/en-us/library/jj620898.aspx 'function': exception specification does not match previous declaration +#pragma warning(disable: 5025) // no page 'class': move assignment operator was implicitly defined as deleted +#pragma warning(disable: 5026) // no page 'class': move constructor was implicitly defined as deleted because a base class move constructor is inaccessible or deleted +#pragma warning(disable: 5027) // no page 'class': move assignment operator was implicitly defined as deleted because a base class move assignment operator is inaccessible or deleted +#endif + +#endif diff --git a/netbox/src/base/headers.hpp b/netbox/src/base/headers.hpp new file mode 100644 index 000000000..12f4a9b3f --- /dev/null +++ b/netbox/src/base/headers.hpp @@ -0,0 +1,178 @@ +#pragma once + +/* +headers.hpp + +Стандартные заголовки +*/ +/* +Copyright © 1996 Eugene Roshal +Copyright © 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef INCL_WINSOCK_API_TYPEDEFS +#define INCL_WINSOCK_API_TYPEDEFS 1 +#endif +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ +#endif +#ifndef __linux__ +#include +#include +#endif + +#define WIN32_LEAN_AND_MEAN +#define VC_EXTRALEAN +#define SECURITY_WIN32 +#include +#include + +#include "disable_warnings_in_std_begin.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +#undef _W32API_OLD + +#ifdef _MSC_VER +# include +# if _WIN32_WINNT < 0x0501 +# error Windows SDK v7.0 (or higher) required +# endif +#endif //_MSC_VER + +#include "disable_warnings_in_std_end.hpp" + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif //_WIN32_WINNT + +#ifndef _WIN32_IE +#define _WIN32_IE 0x0501 +#endif //_WIN32_IE + +// winnls.h +#ifndef NORM_STOP_ON_NULL +#define NORM_STOP_ON_NULL 0x10000000 +#endif + +#ifndef True +#define True true +#endif +#ifndef False +#define False false +#endif +#ifndef Integer +typedef intptr_t Integer; +#endif +#ifndef Int64 +typedef int64_t Int64; +#endif +#ifndef Boolean +typedef bool Boolean; +#endif +#ifndef Word +typedef WORD Word; +#endif + +#ifndef HIDESBASE +#define HIDESBASE +#endif + +#define NullToEmpty(s) (s ? s : L"") + +template +inline const T Min(const T a, const T b) { return a < b ? a : b; } + +template +inline const T Max(const T a, const T b) { return a > b ? a : b; } + +template +inline const T Round(const T a, const T b) { return a / b + (a % b * 2 > b ? 1 : 0); } + +template +inline void * ToPtr(const T a) { return reinterpret_cast(a); } + +template +inline double ToDouble(const T a) { return static_cast(a); } + +template +inline Word ToWord(const T a) { return static_cast(a); } + +template +inline void ClearStruct(T & s) { ::memset(&s, 0, sizeof(s)); } + +template +inline void ClearStruct(T * s) { T dont_instantiate_this_template_with_pointers = s; } + +template +inline void ClearArray(T (&a)[N]) { ::memset(a, 0, sizeof(a[0]) * N); } + +#ifdef __GNUC__ +#ifndef nullptr +#define nullptr NULL +#endif +#endif + +#if defined(_MSC_VER) && _MSC_VER<1600 +#define nullptr NULL +#endif + +template +bool CheckNullOrStructSize(const T * s) { return !s || (s->StructSize >= sizeof(T)); } +template +bool CheckStructSize(const T * s) { return s && (s->StructSize >= sizeof(T)); } + +#ifdef _DEBUG +#define SELF_TEST(code) \ + namespace { \ + struct SelfTest { \ + SelfTest() { \ + code; \ + } \ + } _SelfTest; \ + } +#else +#define SELF_TEST(code) +#endif + +#define NB_DISABLE_COPY(Class) \ +private: \ + Class(const Class &); \ + Class &operator=(const Class &); + +#define NB_STATIC_ASSERT(Condition, Message) \ + static_assert(bool(Condition), Message) + +#define NB_MAX_PATH 32 * 1024 + +#include "UnicodeString.hpp" +#include "local.hpp" diff --git a/netbox/src/base/local.cpp b/netbox/src/base/local.cpp new file mode 100644 index 000000000..ac4ca0fcd --- /dev/null +++ b/netbox/src/base/local.cpp @@ -0,0 +1,173 @@ +/* +local.cpp + + , +*/ +/* +Copyright 1996 Eugene Roshal +Copyright 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#pragma hdrstop + +#include "local.hpp" + +const wchar_t DOS_EOL_fmt[] = L"\r\n"; +const wchar_t UNIX_EOL_fmt[] = L"\n"; +const wchar_t MAC_EOL_fmt[] = L"\r"; +const wchar_t WIN_EOL_fmt[] = L"\r\r\n"; + +int Win32Platform = 0; +int Win32MajorVersion = 0; +int Win32MinorVersion = 0; +int Win32BuildNumber = 0; +// int Win32CSDVersion = 0; + +const wchar_t * __cdecl FarStrStrI(const wchar_t *str1, const wchar_t *str2) +{ + const wchar_t *cp = str1; + const wchar_t *s1, *s2; + + if (!*str2) + return str1; + + while (*cp) + { + s1 = cp; + s2 = str2; + + while (*s1 && *s2 && !(Lower(*s1)-Lower(*s2))) + { + s1++; + s2++; + } + + if (!*s2) + return cp; + + cp++; + } + + return nullptr; +} + +const wchar_t * __cdecl FarStrStr(const wchar_t *str1, const wchar_t *str2) +{ + const wchar_t *cp = str1; + const wchar_t *s1, *s2; + + if (!*str2) + return str1; + + while (*cp) + { + s1 = cp; + s2 = str2; + + while (*s1 && *s2 && !(*s1 - *s2)) + { + s1++; + s2++; + } + + if (!*s2) + return cp; + + cp++; + } + + return nullptr; +} + +const wchar_t * __cdecl RevStrStrI(const wchar_t *str1, const wchar_t *str2) +{ + intptr_t len1 = StrLength(str1); + intptr_t len2 = StrLength(str2); + + if (len2 > len1) + return nullptr; + + if (!*str2) + return &str1[len1]; + + const wchar_t *cp = &str1[len1-len2]; + const wchar_t *s1, *s2; + + while (cp >= str1) + { + s1 = cp; + s2 = str2; + + while (*s1 && *s2 && !(Lower(*s1)-Lower(*s2))) + { + s1++; + s2++; + } + + if (!*s2) + return cp; + + cp--; + } + + return nullptr; +} + +const wchar_t * __cdecl RevStrStr(const wchar_t *str1, const wchar_t *str2) +{ + intptr_t len1 = StrLength(str1); + intptr_t len2 = StrLength(str2); + + if (len2 > len1) + return nullptr; + + if (!*str2) + return &str1[len1]; + + const wchar_t *cp = &str1[len1-len2]; + const wchar_t *s1, *s2; + + while (cp >= str1) + { + s1 = cp; + s2 = str2; + + while (*s1 && *s2 && !(*s1 - *s2)) + { + s1++; + s2++; + } + + if (!*s2) + return cp; + + cp--; + } + + return nullptr; +} + diff --git a/netbox/src/base/local.hpp b/netbox/src/base/local.hpp new file mode 100644 index 000000000..8d628db12 --- /dev/null +++ b/netbox/src/base/local.hpp @@ -0,0 +1,68 @@ +#pragma once + +/* +local.hpp + +Сравнение без учета регистра, преобразование регистра +*/ +/* +Copyright © 1996 Eugene Roshal +Copyright © 2000 Far Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// #include +// #include +// #define SECURITY_WIN32 +// #include +// #include +#ifndef __linux__ +#include +#endif + +extern const wchar_t DOS_EOL_fmt[]; +extern const wchar_t UNIX_EOL_fmt[]; +extern const wchar_t MAC_EOL_fmt[]; +extern const wchar_t WIN_EOL_fmt[]; + +extern int Win32Platform; +extern int Win32MajorVersion; +extern int Win32MinorVersion; +extern int Win32BuildNumber; +// extern int Win32CSDVersion; + +inline intptr_t __cdecl StrLength(const wchar_t * str) { return wcslen(str ? str : L""); } + +inline wchar_t __cdecl Upper(wchar_t Ch) { CharUpperBuff(&Ch, 1); return Ch; } + +inline wchar_t __cdecl Lower(wchar_t Ch) { CharLowerBuff(&Ch, 1); return Ch; } + +inline int __cdecl StrCmpNNI(const wchar_t * s1, int n1, const wchar_t * s2, int n2) { return CompareString(0, NORM_IGNORECASE|NORM_STOP_ON_NULL|SORT_STRINGSORT, s1, n1, s2, n2) - 2; } +inline int __cdecl StrLIComp(const wchar_t * s1, const wchar_t * s2, int n) { return StrCmpNNI(s1, n, s2, n); } + +inline int __cdecl FarStrCmpI(const wchar_t * s1, const wchar_t * s2) { return CompareString(0, NORM_IGNORECASE|SORT_STRINGSORT, s1,-1, s2, -1) - 2; } + +inline int __cdecl StrCmpNN(const wchar_t * s1, int n1, const wchar_t * s2, int n2) { return CompareString(0, NORM_STOP_ON_NULL|SORT_STRINGSORT, s1, n1, s2, n2) - 2; } + diff --git a/netbox/src/base/nbglobals.h b/netbox/src/base/nbglobals.h new file mode 100644 index 000000000..4ffa5468d --- /dev/null +++ b/netbox/src/base/nbglobals.h @@ -0,0 +1,220 @@ +#pragma once + +#include +#include +#include + +#ifdef USE_DLMALLOC +#include "../../libs/dlmalloc/malloc-2.8.6.h" + +#define nb_malloc(size) dlcalloc(1, size) +#define nb_calloc(count, size) dlcalloc(count, size) +#define nb_realloc(ptr, size) dlrealloc(ptr, size) + +#if defined(__cplusplus) +#define nb_free(ptr) dlfree(reinterpret_cast(ptr)) +#else +#define nb_free(ptr) dlfree((void *)(ptr)) +#endif // if defined(__cplusplus) + +#else + +#define nb_malloc(size) malloc(size) +#define nb_calloc(count, size) calloc(count, size) +#define nb_realloc(ptr, size) realloc(ptr, size) + +#if defined(__cplusplus) +#define nb_free(ptr) free(reinterpret_cast(ptr)) +#else +#define nb_free(ptr) free((void *)(ptr)) +#endif // if defined(__cplusplus) + +#endif // ifdef USE_DLMALLOC + +#if defined(_MSC_VER) +#if (_MSC_VER < 1900) + +#ifndef noexcept +#define noexcept throw() +#endif + +#endif +#endif + +#ifndef STRICT +#define STRICT 1 +#endif + +#if defined(__cplusplus) + +inline void * operator_new(size_t size) +{ + void * p = nb_malloc(size); + /*if (!p) + { + static std::bad_alloc badalloc; + throw badalloc; + }*/ + return p; +} + +inline void operator_delete(void * p) +{ + nb_free(p); +} + +#endif // if defined(__cplusplus) + +#ifdef USE_DLMALLOC +/// custom memory allocation +#define DEF_CUSTOM_MEM_ALLOCATION_IMPL \ + public: \ + void * operator new(size_t sz) \ + { \ + return operator_new(sz); \ + } \ + void operator delete(void * p, size_t) \ + { \ + operator_delete(p); \ + } \ + void * operator new[](size_t sz) \ + { \ + return operator_new(sz); \ + } \ + void operator delete[](void * p, size_t) \ + { \ + operator_delete(p); \ + } \ + void * operator new(size_t, void * p) \ + { \ + return p; \ + } \ + void operator delete(void *, void *) \ + { \ + } \ + void * operator new[](size_t, void * p) \ + { \ + return p; \ + } \ + void operator delete[](void *, void *) \ + { \ + } + +#ifdef _DEBUG +#define CUSTOM_MEM_ALLOCATION_IMPL DEF_CUSTOM_MEM_ALLOCATION_IMPL \ + void * operator new(size_t sz, const char * /*lpszFileName*/, int /*nLine*/) \ + { \ + return operator_new(sz); \ + } \ + void * operator new[](size_t sz, const char * /*lpszFileName*/, int /*nLine*/) \ + { \ + return operator_new(sz); \ + } \ + void operator delete(void * p, const char * /*lpszFileName*/, int /*nLine*/) \ + { \ + operator_delete(p); \ + } \ + void operator delete[](void * p, const char * /*lpszFileName*/, int /*nLine*/) \ + { \ + operator_delete(p); \ + } +#else +#define CUSTOM_MEM_ALLOCATION_IMPL DEF_CUSTOM_MEM_ALLOCATION_IMPL +#endif // ifdef _DEBUG + +#else +#define CUSTOM_MEM_ALLOCATION_IMPL +#endif // ifdef USE_DLMALLOC + +#if defined(__cplusplus) + +namespace nballoc +{ + inline void destruct(char *) {} + inline void destruct(wchar_t *) {} + template + inline void destruct(T * t) { t->~T(); } +} // namespace nballoc + +template struct custom_nballocator_t; + +template <> struct custom_nballocator_t +{ +public: + typedef void * pointer; + typedef const void * const_pointer; + // reference to void members are impossible. + typedef void value_type; + template + struct rebind { typedef custom_nballocator_t other; }; +}; + +template +struct custom_nballocator_t +{ + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T * pointer; + typedef const T * const_pointer; + typedef T & reference; + typedef const T & const_reference; + typedef T value_type; + + template struct rebind { typedef custom_nballocator_t other; }; + inline custom_nballocator_t() noexcept {} + inline custom_nballocator_t(const custom_nballocator_t &) noexcept {} + + template custom_nballocator_t(const custom_nballocator_t &) noexcept {} + + ~custom_nballocator_t() noexcept {} + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } + + pointer allocate(size_type s, void const * = 0) + { + if (0 == s) + return nullptr; + pointer temp = reinterpret_cast(nb_malloc(s * sizeof(T))); +#if !defined(__MINGW32__) + if (temp == nullptr) + throw std::bad_alloc(); +#endif + return temp; + } + + void deallocate(pointer p, size_type) + { + nb_free(p); + } + + size_type max_size() const noexcept + { + // return std::numeric_limits::max() / sizeof(T); + return size_t(-1) / sizeof(T); + } + + void construct(pointer p, const T & val) + { + new(reinterpret_cast(p)) T(val); + } + + void destroy(pointer p) + { + nballoc::destruct(p); + } +}; + +template +inline bool operator==(const custom_nballocator_t &, const custom_nballocator_t &) +{ + return true; +} + +template +inline bool operator!=(const custom_nballocator_t &, const custom_nballocator_t &) +{ + return false; +} + +#endif // if defined(__cplusplus) diff --git a/netbox/src/base/registry.hpp b/netbox/src/base/registry.hpp new file mode 100644 index 000000000..c208b3e2b --- /dev/null +++ b/netbox/src/base/registry.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/netbox/src/base/rtti.cpp b/netbox/src/base/rtti.cpp new file mode 100644 index 000000000..2dfeeeaef --- /dev/null +++ b/netbox/src/base/rtti.cpp @@ -0,0 +1,142 @@ +#include +#include "rtti.hpp" + +TClassInfo * TClassInfo::sm_first = nullptr; +THashTable * TClassInfo::sm_classTable = nullptr; + +TClassInfo::TClassInfo(int classId, + const TClassInfo * baseInfo1, + const TClassInfo * baseInfo2) : + m_classId(classId), + m_baseInfo1(baseInfo1), + m_baseInfo2(baseInfo2), + m_next(sm_first) +{ + sm_first = this; + Register(); +} + +TClassInfo::~TClassInfo() +{ + // remove this object from the linked list of all class infos: if we don't + // do it, loading/unloading a DLL containing static TClassInfo objects is + // not going to work + if (this == sm_first) + { + sm_first = m_next; + } + else + { + TClassInfo * info = sm_first; + while (info) + { + if (info->m_next == this) + { + info->m_next = m_next; + break; + } + + info = info->m_next; + } + } + Unregister(); +} + +bool TClassInfo::IsKindOf(const TClassInfo * info) const +{ + return info != nullptr && + (info == this || + (m_baseInfo1 && m_baseInfo1->IsKindOf(info)) || + (m_baseInfo2 && m_baseInfo2->IsKindOf(info))); +} + +const TClassInfo * TClassInfo::FindClass(int classId) +{ + if (sm_classTable) + { + return TClassInfo::sm_classTable->Get(classId); + } + else + { + for (const TClassInfo * info = sm_first; info; info = info->m_next) + { + if (classId == info->GetClassId()) + return info; + } + + return nullptr; + } +} + +void TClassInfo::Register() +{ + // reentrance guard + static int entry = 0; + + THashTable * classTable; + + if (!sm_classTable) + { + // keep the hash local initially, reentrance is possible + classTable = new THashTable(); + } + else + { + // guard against reentrance once the global has been created + DebugAssert(++entry == 1 || "TClassInfo::Register() reentrance"); + classTable = sm_classTable; + } + + DebugAssert(classTable->Get(m_classId) == nullptr); + + classTable->Put(m_classId, this); + + // if we're using a local hash we need to try to make it global + if (sm_classTable != classTable) + { + if (!sm_classTable) + { + // make the hash global + sm_classTable = classTable; + } + else + { + // the global hash has already been created by a reentrant call, + // so delete the local hash and try again + delete classTable; + Register(); + } + } + +#if _DEBUG + entry = 0; +#endif +} + +void TClassInfo::Unregister() +{ + if (sm_classTable) + { + sm_classTable->Delete(m_classId); + if (sm_classTable->GetCount() == 0) + { + delete sm_classTable; + } + } +} + +const TObject * NbStaticDownCastConst(TObjectClassId ClassId, const TObject * Object) +{ + if (Object != nullptr && Object->IsKindOf(ClassId)) + return Object; + else + return nullptr; +} + +TObject * NbStaticDownCast(TObjectClassId ClassId, TObject * Object) +{ + if (Object != nullptr && Object->IsKindOf(ClassId)) + return Object; + else + return nullptr; +} diff --git a/netbox/src/base/rtti.hpp b/netbox/src/base/rtti.hpp new file mode 100644 index 000000000..e7362f742 --- /dev/null +++ b/netbox/src/base/rtti.hpp @@ -0,0 +1,270 @@ +#pragma once + +#include +#include +#include "nbglobals.h" + +class TObject; +class THashTable; + +enum TObjectClassId +{ + OBJECT_CLASS_TObject = 1, + OBJECT_CLASS_Exception, + OBJECT_CLASS_ExtException, + OBJECT_CLASS_EAbort, + OBJECT_CLASS_EAccessViolation, + OBJECT_CLASS_ESsh, + OBJECT_CLASS_ETerminal, + OBJECT_CLASS_ECommand, + OBJECT_CLASS_EScp, + OBJECT_CLASS_ESkipFile, + OBJECT_CLASS_EFileSkipped, + OBJECT_CLASS_EOSExtException, + OBJECT_CLASS_EFatal, + OBJECT_CLASS_ESshFatal, + OBJECT_CLASS_ESshTerminate, + OBJECT_CLASS_ECallbackGuardAbort, + OBJECT_CLASS_EFileNotFoundError, + OBJECT_CLASS_EOSError, + OBJECT_CLASS_TPersistent, + OBJECT_CLASS_TStrings, + OBJECT_CLASS_TNamedObject, + OBJECT_CLASS_TSessionData, + OBJECT_CLASS_TDateTime, + OBJECT_CLASS_TSHFileInfo, + OBJECT_CLASS_TStream, + OBJECT_CLASS_TRegistry, + OBJECT_CLASS_TShortCut, + OBJECT_CLASS_TDelphiSet, + OBJECT_CLASS_TFormatSettings, + OBJECT_CLASS_TCriticalSection, + OBJECT_CLASS_TBookmarks, + OBJECT_CLASS_TDateTimeParams, + OBJECT_CLASS_TGuard, + OBJECT_CLASS_TUnguard, + OBJECT_CLASS_TValueRestorer, + OBJECT_CLASS_TConfiguration, + OBJECT_CLASS_TShortCuts, + OBJECT_CLASS_TCopyParamType, + OBJECT_CLASS_TFileBuffer, + OBJECT_CLASS_TFileMasks, + OBJECT_CLASS_TParams, + OBJECT_CLASS_TFile, + OBJECT_CLASS_TRemoteFile, + OBJECT_CLASS_TList, + OBJECT_CLASS_TObjectList, + OBJECT_CLASS_TStringList, + OBJECT_CLASS_TBookmarkList, + OBJECT_CLASS_TBookmark, + OBJECT_CLASS_TCustomCommand, + OBJECT_CLASS_TCustomCommandData, + OBJECT_CLASS_TFileOperationProgressType, + OBJECT_CLASS_TSuspendFileOperationProgress, + OBJECT_CLASS_TSinkFileParams, + OBJECT_CLASS_TFileTransferData, + OBJECT_CLASS_TClipboardHandler, + OBJECT_CLASS_TOverwriteFileParams, + OBJECT_CLASS_TOpenRemoteFileParams, + OBJECT_CLASS_TCustomFileSystem, + OBJECT_CLASS_TWebDAVFileSystem, + OBJECT_CLASS_TMessageQueue, + OBJECT_CLASS_TFTPFileListHelper, + OBJECT_CLASS_THierarchicalStorage, + OBJECT_CLASS_TQueryButtonAlias, + OBJECT_CLASS_TQueryParams, + OBJECT_CLASS_TNamedObjectList, + OBJECT_CLASS_TOptions, + OBJECT_CLASS_TUserAction, + OBJECT_CLASS_TSimpleThread, + OBJECT_CLASS_TSignalThread, + OBJECT_CLASS_TQueueItem, + OBJECT_CLASS_TInfo, + OBJECT_CLASS_TQueueItemProxy, + OBJECT_CLASS_TTerminalQueueStatus, + OBJECT_CLASS_TRemoteToken, + OBJECT_CLASS_TRemoteTokenList, + OBJECT_CLASS_TRemoteFileList, + OBJECT_CLASS_TRights, + OBJECT_CLASS_TValidProperties, + OBJECT_CLASS_TRemoteProperties, + OBJECT_CLASS_TCommandSet, + OBJECT_CLASS_TPoolForDataEvent, + OBJECT_CLASS_TSecureShell, + OBJECT_CLASS_TIEProxyConfig, + OBJECT_CLASS_TSessionActionRecord, + OBJECT_CLASS_TSessionInfo, + OBJECT_CLASS_TFileSystemInfo, + OBJECT_CLASS_TSessionAction, + OBJECT_CLASS_TActionLog, + OBJECT_CLASS_TSFTPSupport, + OBJECT_CLASS_TSFTPPacket, + OBJECT_CLASS_TSFTPQueuePacket, + OBJECT_CLASS_TSFTPQueue, + OBJECT_CLASS_TSFTPBusy, + OBJECT_CLASS_TLoopDetector, + OBJECT_CLASS_TMoveFileParams, + OBJECT_CLASS_TFilesFindParams, + OBJECT_CLASS_TCallbackGuard, + OBJECT_CLASS_TOutputProxy, + OBJECT_CLASS_TSynchronizeFileData, + OBJECT_CLASS_TSynchronizeData, + OBJECT_CLASS_TTerminal, + OBJECT_CLASS_TTerminalList, + OBJECT_CLASS_TTerminalItem, + OBJECT_CLASS_TCustomCommandParams, + OBJECT_CLASS_TCalculateSizeStats, + OBJECT_CLASS_TCalculateSizeParams, + OBJECT_CLASS_TMakeLocalFileListParams, + OBJECT_CLASS_TSynchronizeOptions, + OBJECT_CLASS_TSynchronizeChecklist, + OBJECT_CLASS_TItem, + OBJECT_CLASS_TChecklistItem, + OBJECT_CLASS_TFileInfo, + OBJECT_CLASS_TSpaceAvailable, + OBJECT_CLASS_TWebDAVFileListHelper, + OBJECT_CLASS_TCFileZillaApi, + OBJECT_CLASS_TFTPServerCapabilities, + OBJECT_CLASS_TCFileZillaTools, + OBJECT_CLASS_T_directory, + OBJECT_CLASS_T_direntry, + OBJECT_CLASS_TCopyParamRuleData, + OBJECT_CLASS_TCopyParamRule, + OBJECT_CLASS_TCopyParamList, + OBJECT_CLASS_TProgramParamsOwner, + OBJECT_CLASS_TSynchronizeParamType, + OBJECT_CLASS_TSynchronizeController, + OBJECT_CLASS_TFarDialog, + OBJECT_CLASS_TFarDialogContainer, + OBJECT_CLASS_TFarDialogItem, + OBJECT_CLASS_TConsoleTitleParam, + OBJECT_CLASS_TFarMessageParams, + OBJECT_CLASS_TCustomFarPlugin, + OBJECT_CLASS_TCustomFarFileSystem, + OBJECT_CLASS_TFarButton, + OBJECT_CLASS_TTabButton, + OBJECT_CLASS_TFarList, + OBJECT_CLASS_TWinSCPPlugin, + OBJECT_CLASS_TWinSCPFileSystem, + OBJECT_CLASS_TFarPanelModes, + OBJECT_CLASS_TFarKeyBarTitles, + OBJECT_CLASS_TCustomFarPanelItem, + OBJECT_CLASS_TFarPanelInfo, + OBJECT_CLASS_TFarEditorInfo, + OBJECT_CLASS_TFarEnvGuard, + OBJECT_CLASS_TFarListBox, + OBJECT_CLASS_TFarEdit, + OBJECT_CLASS_TLabelList, + OBJECT_CLASS_TGUIConfiguration, + OBJECT_CLASS_TFarConfiguration, + OBJECT_CLASS_TGUICopyParamType, + OBJECT_CLASS_TCNBFile, + OBJECT_CLASS_TMultipleEdit, + OBJECT_CLASS_TEditHistory, + OBJECT_CLASS_TFarMessageData, + OBJECT_CLASS_TMessageParams, + OBJECT_CLASS_TFileZillaIntern, + OBJECT_CLASS_CApiLog, + OBJECT_CLASS_TFarPanelItem, + OBJECT_CLASS_TFarText, + OBJECT_CLASS_TFarCheckBox, + +}; + +class TClassInfo +{ +CUSTOM_MEM_ALLOCATION_IMPL +public: + TClassInfo(int classId, + const TClassInfo * baseInfo1, + const TClassInfo * baseInfo2); + ~TClassInfo(); + + int GetClassId() const { return m_classId; } + int GetBaseClassId1() const + { return m_baseInfo1 ? m_baseInfo1->GetClassId() : 0; } + int GetBaseClassId2() const + { return m_baseInfo2 ? m_baseInfo2->GetClassId() : 0; } + const TClassInfo * GetBaseClassInfo1() const { return m_baseInfo1; } + const TClassInfo * GetBaseClassInfo2() const { return m_baseInfo2; } + + static const TClassInfo * GetFirst() { return sm_first; } + const TClassInfo * GetNext() const { return m_next; } + static const TClassInfo * FindClass(int classId); + + // Climb upwards through inheritance hierarchy. + bool IsKindOf(const TClassInfo * info) const; + +private: + int m_classId; + + const TClassInfo * m_baseInfo1; + const TClassInfo * m_baseInfo2; + + // class info object live in a linked list: + // pointers to its head and the next element in it + static TClassInfo * sm_first; + TClassInfo * m_next; + + static THashTable * sm_classTable; + +protected: + // registers the class + void Register(); + void Unregister(); + +private: + TClassInfo(const TClassInfo &); + TClassInfo & operator=(const TClassInfo &); +}; + +#define NB_DECLARE_RUNTIME_CLASS(name) \ +public: \ + static TClassInfo FClassInfo; \ + virtual TClassInfo * GetClassInfo() const; + +#define NB_DECLARE_CLASS(name) \ + NB_DECLARE_RUNTIME_CLASS(name) \ + +#define NB_GET_CLASS_INFO(name) \ + &name::FClassInfo + +#define NB_IMPLEMENT_CLASS(name, baseclassinfo1, baseclassinfo2) \ + TClassInfo name::FClassInfo(OBJECT_CLASS_##name, \ + baseclassinfo1, \ + baseclassinfo2); \ + TClassInfo * name::GetClassInfo() const \ + { return &name::FClassInfo; } + +const TObject * NbStaticDownCastConst(TObjectClassId ClassId, const TObject * Object); +TObject * NbStaticDownCast(TObjectClassId ClassId, TObject * Object); + +#define NB_STATIC_DOWNCAST_CONST(class_name, object) (static_cast(NbStaticDownCastConst(OBJECT_CLASS_##class_name, static_cast(object)))) +#define NB_STATIC_DOWNCAST(class_name, object) (static_cast(NbStaticDownCast(OBJECT_CLASS_##class_name, static_cast(object)))) + +class THashTable : public std::map +{ +CUSTOM_MEM_ALLOCATION_IMPL +typedef std::map ancestor; +public: + ancestor::mapped_type Get(const ancestor::key_type & key) const + { + ancestor::const_iterator it = ancestor::find(key); + if (it != ancestor::end()) + return it->second; + else + return nullptr; + } + void Put(const ancestor::key_type & key, ancestor::mapped_type value) + { + ancestor::insert(ancestor::value_type(key, value)); + } + void Delete(const ancestor::key_type & key) + { + ancestor::iterator it = ancestor::find(key); + if (it != ancestor::end()) + ancestor::erase(it); + } + size_t GetCount() const { return ancestor::size(); } +}; + diff --git a/netbox/src/base/tchar.h b/netbox/src/base/tchar.h new file mode 100644 index 000000000..b27bed0ee --- /dev/null +++ b/netbox/src/base/tchar.h @@ -0,0 +1,2 @@ +#pragma once +#include \ No newline at end of file diff --git a/netbox/src/base/vcl.h b/netbox/src/base/vcl.h new file mode 100644 index 000000000..407d02b5d --- /dev/null +++ b/netbox/src/base/vcl.h @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/netbox/src/base/wincon.h b/netbox/src/base/wincon.h new file mode 100644 index 000000000..b27bed0ee --- /dev/null +++ b/netbox/src/base/wincon.h @@ -0,0 +1,2 @@ +#pragma once +#include \ No newline at end of file diff --git a/netbox/src/core/Bookmarks.cpp b/netbox/src/core/Bookmarks.cpp new file mode 100644 index 000000000..138eab0e9 --- /dev/null +++ b/netbox/src/core/Bookmarks.cpp @@ -0,0 +1,576 @@ +#include +#pragma hdrstop +#include +#include "NamedObjs.h" +#include "Bookmarks.h" +#include "Configuration.h" +#include "HierarchicalStorage.h" +#include "TextsCore.h" + +TBookmarks::TBookmarks() : TObject() +{ + FSharedKey = UnicodeString(CONST_HIDDEN_PREFIX) + "shared"; + FBookmarkLists = CreateSortedStringList(false, dupError); +} + +TBookmarks::~TBookmarks() +{ + Clear(); + SAFE_DESTROY(FBookmarkLists); +} + +void TBookmarks::Clear() +{ + for (intptr_t Index = 0; Index < FBookmarkLists->GetCount(); ++Index) + { + TObject * Object = FBookmarkLists->GetObj(Index); + SAFE_DESTROY(Object); + } + FBookmarkLists->Clear(); +} + +UnicodeString TBookmarks::Keys[] = { "Local", "Remote", "ShortCuts", "Options" }; + +void TBookmarks::Load(THierarchicalStorage * Storage) +{ + for (intptr_t Idx = 0; Idx <= 3; ++Idx) + { + if (Storage->OpenSubKey(Keys[Idx], false)) + { + std::unique_ptr BookmarkKeys(new TStringList()); + try__finally + { + Storage->GetSubKeyNames(BookmarkKeys.get()); + for (intptr_t Index2 = 0; Index2 < BookmarkKeys->GetCount(); ++Index2) + { + UnicodeString Key = BookmarkKeys->GetString(Index2); + if (Storage->OpenSubKey(Key, false)) + { + TBookmarkList * BookmarkList = GetBookmarks(Key); + if (BookmarkList == nullptr) + { + BookmarkList = new TBookmarkList(); + FBookmarkLists->AddObject(Key, BookmarkList); + } + if (Idx < 3) + { + LoadLevel(Storage, L"", Idx, BookmarkList); + } + else + { + BookmarkList->LoadOptions(Storage); + } + Storage->CloseSubKey(); + } + } + } + __finally + { +// delete BookmarkKeys; + }; + Storage->CloseSubKey(); + } + } + + ModifyAll(false); +} + +void TBookmarks::LoadLevel(THierarchicalStorage * Storage, const UnicodeString & Key, + intptr_t AIndex, TBookmarkList * BookmarkList) +{ + std::unique_ptr Names(new TStringList()); + try__finally + { + Storage->GetValueNames(Names.get()); + UnicodeString Name; + UnicodeString Directory; + TShortCut ShortCut(0); + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + Name = Names->GetString(Index); + bool IsDirectory = (AIndex == 0) || (AIndex == 1); + if (IsDirectory) + { + Directory = Storage->ReadString(Name, L""); + } + else + { + Directory.Clear(); // use only in case of malformed config + ShortCut = static_cast(Storage->ReadInteger(Name, 0)); + } + if (IsNumber(Name)) + { + DebugAssert(IsDirectory); // unless malformed + Name = Directory; + } + if (!Name.IsEmpty()) + { + TBookmark * Bookmark = BookmarkList->FindByName(Key, Name); + bool New = (Bookmark == nullptr); + if (New) + { + Bookmark = new TBookmark(); + Bookmark->SetNode(Key); + Bookmark->SetName(Name); + } + switch (AIndex) + { + case 0: + Bookmark->SetLocal(Directory); + break; + + case 1: + Bookmark->SetRemote(Directory); + break; + + case 2: + Bookmark->SetShortCut(ShortCut); + break; + } + if (New) + { + BookmarkList->Add(Bookmark); + } + } + } + + Storage->GetSubKeyNames(Names.get()); + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + Name = Names->GetString(Index); + if (Storage->OpenSubKey(Name, false)) + { + LoadLevel(Storage, Key + (Key.IsEmpty() ? L"" : L"/") + Name, AIndex, BookmarkList); + Storage->CloseSubKey(); + } + } + } + __finally + { +// delete Names; + }; +} + +void TBookmarks::Save(THierarchicalStorage * Storage, bool All) +{ + for (intptr_t Idx = 0; Idx <= 3; Idx++) + { + if (Storage->OpenSubKey(Keys[Idx], true)) + { + for (intptr_t Index = 0; Index < FBookmarkLists->GetCount(); ++Index) + { + TBookmarkList * BookmarkList = NB_STATIC_DOWNCAST(TBookmarkList, FBookmarkLists->GetObj(Index)); + if (All || BookmarkList->GetModified()) + { + UnicodeString Key = FBookmarkLists->GetString(Index); + Storage->RecursiveDeleteSubKey(Key); + if (Storage->OpenSubKey(Key, true)) + { + if (Idx < 3) + { + for (intptr_t IndexB = 0; IndexB < BookmarkList->GetCount(); IndexB++) + { + TBookmark * Bookmark = BookmarkList->GetBookmarks(IndexB); + // avoid creating empty subfolder if there's no shortcut + if ((Idx == 0) || (Idx == 1) || + ((Idx == 2) && (Bookmark->GetShortCut() != 0))) + { + bool HasNode = !Bookmark->GetNode().IsEmpty(); + if (!HasNode || Storage->OpenSubKey(Bookmark->GetNode(), true)) + { + switch (Idx) + { + case 0: + Storage->WriteString(Bookmark->GetName(), Bookmark->GetLocal()); + break; + + case 1: + Storage->WriteString(Bookmark->GetName(), Bookmark->GetRemote()); + break; + + case 2: + DebugAssert(Bookmark->GetShortCut() != 0); + Storage->WriteInteger(Bookmark->GetName(), Bookmark->GetShortCut()); + break; + } + + if (HasNode) + { + Storage->CloseSubKey(); + } + } + } + } + } + else + { + BookmarkList->SaveOptions(Storage); + } + Storage->CloseSubKey(); + } + } + } + Storage->CloseSubKey(); + } + } + + if (!All) + { + ModifyAll(false); + } +} + +void TBookmarks::ModifyAll(bool Modify) +{ + for (intptr_t Index = 0; Index < FBookmarkLists->GetCount(); ++Index) + { + TBookmarkList * BookmarkList = NB_STATIC_DOWNCAST(TBookmarkList, FBookmarkLists->GetObj(Index)); + DebugAssert(BookmarkList); + BookmarkList->SetModified(Modify); + } +} + +TBookmarkList * TBookmarks::GetBookmarks(const UnicodeString & AIndex) +{ + intptr_t Index = FBookmarkLists->IndexOf(AIndex.c_str()); + if (Index >= 0) + { + return NB_STATIC_DOWNCAST(TBookmarkList, FBookmarkLists->GetObj(Index)); + } + else + { + return nullptr; + } +} + +void TBookmarks::SetBookmarks(const UnicodeString & AIndex, TBookmarkList * Value) +{ + intptr_t Index = FBookmarkLists->IndexOf(AIndex.c_str()); + if (Index >= 0) + { + TBookmarkList * BookmarkList; + BookmarkList = NB_STATIC_DOWNCAST(TBookmarkList, FBookmarkLists->GetObj(Index)); + BookmarkList->Assign(Value); + } + else + { + TBookmarkList * BookmarkList = new TBookmarkList(); + BookmarkList->Assign(Value); + FBookmarkLists->AddObject(AIndex, BookmarkList); + } +} + +TBookmarkList * TBookmarks::GetSharedBookmarks() +{ + return GetBookmarks(FSharedKey); +} + +void TBookmarks::SetSharedBookmarks(TBookmarkList * Value) +{ + SetBookmarks(FSharedKey, Value); +} + +TBookmarkList::TBookmarkList() : + TPersistent(), + FBookmarks(new TStringList()), + FOpenedNodes(CreateSortedStringList()), + FModified(false) +{ + FBookmarks->SetCaseSensitive(false); +} + +TBookmarkList::~TBookmarkList() +{ + Clear(); + SAFE_DESTROY(FBookmarks); + SAFE_DESTROY(FOpenedNodes); +} + +void TBookmarkList::Clear() +{ + for (intptr_t Index = 0; Index < FBookmarks->GetCount(); ++Index) + { + TObject * Object = FBookmarks->GetObj(Index); + SAFE_DESTROY(Object); + } + FBookmarks->Clear(); + FOpenedNodes->Clear(); +} + +void TBookmarkList::Assign(const TPersistent * Source) +{ + const TBookmarkList * SourceList = NB_STATIC_DOWNCAST_CONST(TBookmarkList, Source); + if (SourceList) + { + Clear(); + for (intptr_t Index = 0; Index < SourceList->FBookmarks->GetCount(); ++Index) + { + TBookmark * Bookmark = new TBookmark(); + Bookmark->Assign(NB_STATIC_DOWNCAST(TBookmark, SourceList->FBookmarks->GetObj(Index))); + Add(Bookmark); + } + FOpenedNodes->Assign(SourceList->FOpenedNodes); + SetModified(SourceList->GetModified()); + } + else + { + TPersistent::Assign(Source); + } +} + +void TBookmarkList::LoadOptions(THierarchicalStorage * Storage) +{ + FOpenedNodes->SetCommaText(Storage->ReadString("OpenedNodes", L"")); +} + +void TBookmarkList::SaveOptions(THierarchicalStorage * Storage) +{ + Storage->WriteString("OpenedNodes", FOpenedNodes->GetCommaText()); +} + +void TBookmarkList::Add(TBookmark * Bookmark) +{ + Insert(GetCount(), Bookmark); +} + +void TBookmarkList::InsertBefore(TBookmark * BeforeBookmark, TBookmark * Bookmark) +{ + DebugAssert(BeforeBookmark); + intptr_t Index = FBookmarks->IndexOf(BeforeBookmark->GetKey().c_str()); + DebugAssert(Index >= 0); + Insert(Index, Bookmark); +} + +void TBookmarkList::MoveTo(TBookmark * ToBookmark, + TBookmark * Bookmark, bool Before) +{ + DebugAssert(ToBookmark != nullptr); + intptr_t NewIndex = FBookmarks->IndexOf(ToBookmark->GetKey().c_str()); + DebugAssert(Bookmark != nullptr); + intptr_t OldIndex = FBookmarks->IndexOf(Bookmark->GetKey().c_str()); + if (Before && (NewIndex > OldIndex)) + { + // otherwise item is moved after the item in the target index + NewIndex--; + } + else if (!Before && (NewIndex < OldIndex)) + { + NewIndex++; + } + FModified = true; + FBookmarks->Move(OldIndex, NewIndex); +} + +void TBookmarkList::Insert(intptr_t Index, TBookmark * Bookmark) +{ + DebugAssert(Bookmark); + DebugAssert(!Bookmark->FOwner); + DebugAssert(!Bookmark->GetName().IsEmpty()); + + FModified = true; + Bookmark->FOwner = this; + if (FBookmarks->IndexOf(Bookmark->GetKey().c_str()) >= 0) + { + throw Exception(FMTLOAD(DUPLICATE_BOOKMARK, Bookmark->GetName().c_str())); + } + FBookmarks->InsertObject(Index, Bookmark->GetKey(), Bookmark); +} + +void TBookmarkList::Delete(TBookmark *& Bookmark) +{ + DebugAssert(Bookmark); + DebugAssert(Bookmark->FOwner == this); + intptr_t Index = IndexOf(Bookmark); + DebugAssert(Index >= 0); + FModified = true; + Bookmark->FOwner = nullptr; + FBookmarks->Delete(Index); + SAFE_DESTROY(Bookmark); +} + +intptr_t TBookmarkList::IndexOf(TBookmark * Bookmark) +{ + return FBookmarks->IndexOf(Bookmark->GetKey().c_str()); +} + +void TBookmarkList::KeyChanged(intptr_t Index) +{ + DebugAssert(Index < GetCount()); + TBookmark * Bookmark = NB_STATIC_DOWNCAST(TBookmark, FBookmarks->GetObj(Index)); + DebugAssert(FBookmarks->GetString(Index) != Bookmark->GetKey()); + if (FBookmarks->IndexOf(Bookmark->GetKey().c_str()) >= 0) + { + throw Exception(FMTLOAD(DUPLICATE_BOOKMARK, Bookmark->GetName().c_str())); + } + FBookmarks->SetString(Index, Bookmark->GetKey()); +} + +TBookmark * TBookmarkList::FindByName(const UnicodeString & Node, const UnicodeString & Name) +{ + intptr_t Index = FBookmarks->IndexOf(TBookmark::BookmarkKey(Node, Name).c_str()); + TBookmark * Bookmark = ((Index >= 0) ? NB_STATIC_DOWNCAST(TBookmark, FBookmarks->GetObj(Index)) : nullptr); + DebugAssert(!Bookmark || (Bookmark->GetNode() == Node && Bookmark->GetName() == Name)); + return Bookmark; +} + +TBookmark * TBookmarkList::FindByShortCut(const TShortCut & ShortCut) +{ + for (intptr_t Index = 0; Index < FBookmarks->GetCount(); ++Index) + { + if (GetBookmarks(Index)->GetShortCut() == ShortCut) + { + return GetBookmarks(Index); + } + } + return nullptr; +} + +intptr_t TBookmarkList::GetCount() const +{ + return FBookmarks->GetCount(); +} + +TBookmark * TBookmarkList::GetBookmarks(intptr_t Index) +{ + TBookmark * Bookmark = NB_STATIC_DOWNCAST(TBookmark, FBookmarks->GetObj(Index)); + DebugAssert(Bookmark); + return Bookmark; +} + +bool TBookmarkList::GetNodeOpened(const UnicodeString & Index) +{ + return (FOpenedNodes->IndexOf(Index.c_str()) >= 0); +} + +void TBookmarkList::SetNodeOpened(const UnicodeString & AIndex, bool Value) +{ + intptr_t Index = FOpenedNodes->IndexOf(AIndex.c_str()); + if ((Index >= 0) != Value) + { + if (Value) + { + FOpenedNodes->Add(AIndex); + } + else + { + FOpenedNodes->Delete(Index); + } + FModified = true; + } +} + +void TBookmarkList::ShortCuts(TShortCuts & ShortCuts) +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TBookmark * Bookmark = GetBookmarks(Index); + if (Bookmark->GetShortCut() != 0) + { + ShortCuts.Add(Bookmark->GetShortCut()); + } + } +} + + +TBookmark::TBookmark() : + FOwner(nullptr) +{ +} + +void TBookmark::Assign(const TPersistent * Source) +{ + const TBookmark * SourceBookmark = NB_STATIC_DOWNCAST_CONST(TBookmark, Source); + if (SourceBookmark) + { + SetName(SourceBookmark->GetName()); + SetLocal(SourceBookmark->GetLocal()); + SetRemote(SourceBookmark->GetRemote()); + SetNode(SourceBookmark->GetNode()); + SetShortCut(SourceBookmark->GetShortCut()); + } + else + { + TPersistent::Assign(Source); + } +} + +void TBookmark::SetName(const UnicodeString & Value) +{ + if (GetName() != Value) + { + intptr_t OldIndex = FOwner ? FOwner->IndexOf(this) : -1; + UnicodeString OldName = FName; + FName = Value; + try + { + Modify(OldIndex); + } + catch (...) + { + FName = OldName; + throw; + } + } +} + +void TBookmark::SetLocal(const UnicodeString & Value) +{ + if (GetLocal() != Value) + { + FLocal = Value; + Modify(-1); + } +} + +void TBookmark::SetRemote(const UnicodeString & Value) +{ + if (GetRemote() != Value) + { + FRemote = Value; + Modify(-1); + } +} + +void TBookmark::SetNode(const UnicodeString & Value) +{ + if (GetNode() != Value) + { + intptr_t OldIndex = FOwner ? FOwner->IndexOf(this) : -1; + FNode = Value; + Modify(OldIndex); + } +} + +void TBookmark::SetShortCut(const TShortCut & Value) +{ + if (GetShortCut() != Value) + { + FShortCut = Value; + Modify(-1); + } +} + +void TBookmark::Modify(intptr_t OldIndex) +{ + if (FOwner) + { + FOwner->SetModified(true); + if (OldIndex >= 0) + { + FOwner->KeyChanged(OldIndex); + } + } +} + +UnicodeString TBookmark::BookmarkKey(const UnicodeString & Node, const UnicodeString & Name) +{ + return FORMAT(L"%s\1%s", Node.c_str(), Name.c_str()); +} + +UnicodeString TBookmark::GetKey() const +{ + return BookmarkKey(GetNode(), GetName()); +} + +NB_IMPLEMENT_CLASS(TBookmark, NB_GET_CLASS_INFO(TPersistent), nullptr) +NB_IMPLEMENT_CLASS(TBookmarkList, NB_GET_CLASS_INFO(TPersistent), nullptr) diff --git a/netbox/src/core/Bookmarks.h b/netbox/src/core/Bookmarks.h new file mode 100644 index 000000000..e99b71c7a --- /dev/null +++ b/netbox/src/core/Bookmarks.h @@ -0,0 +1,134 @@ +#pragma once + +#include + +class THierarchicalStorage; +class TBookmarkList; +class TShortCuts; + +class TBookmarks : public TObject +{ +NB_DISABLE_COPY(TBookmarks) +public: + TBookmarks(); + virtual ~TBookmarks(); + + void Load(THierarchicalStorage * Storage); + void Save(THierarchicalStorage * Storage, bool All); + void ModifyAll(bool Modify); + void Clear(); + +/* __property TBookmarkList * Bookmarks[UnicodeString Index] = { read = GetBookmarks, write = SetBookmarks }; + __property TBookmarkList * SharedBookmarks = { read = GetSharedBookmarks, write = SetSharedBookmarks };*/ + +private: + TStringList * FBookmarkLists; + UnicodeString FSharedKey; + static UnicodeString Keys[]; + +public: + TBookmarkList * GetBookmarks(const UnicodeString & Index); + void SetBookmarks(const UnicodeString & Index, TBookmarkList * Value); + TBookmarkList * GetSharedBookmarks(); + void SetSharedBookmarks(TBookmarkList * Value); + +private: + void LoadLevel(THierarchicalStorage * Storage, const UnicodeString & Key, + intptr_t AIndex, TBookmarkList * BookmarkList); +}; + +class TBookmark; +class TBookmarkList : public TPersistent +{ +friend class TBookmarks; +friend class TBookmark; +NB_DISABLE_COPY(TBookmarkList) +NB_DECLARE_CLASS(TBookmarkList) +public: + TBookmarkList(); + virtual ~TBookmarkList(); + + void Clear(); + void Add(TBookmark * Bookmark); + void Insert(intptr_t Index, TBookmark * Bookmark); + void InsertBefore(TBookmark * BeforeBookmark, TBookmark * Bookmark); + void MoveTo(TBookmark * ToBookmark, TBookmark * Bookmark, bool Before); + void Delete(TBookmark *& Bookmark); + TBookmark * FindByName(const UnicodeString & Node, const UnicodeString & Name); + TBookmark * FindByShortCut(const TShortCut & ShortCut); + virtual void Assign(const TPersistent * Source); + void LoadOptions(THierarchicalStorage * Storage); + void SaveOptions(THierarchicalStorage * Storage); + void ShortCuts(TShortCuts & ShortCuts); + +/* __property int Count = { read = GetCount }; + __property TBookmark * Bookmarks[int Index] = { read = GetBookmarks }; + __property bool NodeOpened[UnicodeString Index] = { read = GetNodeOpened, write = SetNodeOpened };*/ + +protected: + intptr_t IndexOf(TBookmark * Bookmark); + void KeyChanged(intptr_t Index); + +// __property bool Modified = { read = FModified, write = FModified }; + bool GetModified() const { return FModified; } + void SetModified(bool Value) { FModified = Value; } + +private: + TStringList * FBookmarks; + TStringList * FOpenedNodes; + bool FModified; + +public: + + intptr_t GetCount() const; + TBookmark * GetBookmarks(intptr_t Index); + bool GetNodeOpened(const UnicodeString & Index); + void SetNodeOpened(const UnicodeString & Index, bool Value); +}; + +class TBookmark : public TPersistent +{ +friend class TBookmarkList; +NB_DISABLE_COPY(TBookmark) +NB_DECLARE_CLASS(TBookmark) +public: + TBookmark(); + + virtual void Assign(const TPersistent * Source); + +/* __property UnicodeString Name = { read = FName, write = SetName }; + __property UnicodeString Local = { read = FLocal, write = SetLocal }; + __property UnicodeString Remote = { read = FRemote, write = SetRemote }; + __property UnicodeString Node = { read = FNode, write = SetNode }; + __property TShortCut ShortCut = { read = FShortCut, write = SetShortCut };*/ + +protected: + TBookmarkList * FOwner; + + static UnicodeString BookmarkKey(const UnicodeString & Node, const UnicodeString & Name); + // __property UnicodeString Key = { read = GetKey }; + +private: + UnicodeString FName; + UnicodeString FLocal; + UnicodeString FRemote; + UnicodeString FNode; + TShortCut FShortCut; + +public: + void SetName(const UnicodeString & Value); + UnicodeString GetName() const { return FName; } + void SetLocal(const UnicodeString & Value); + UnicodeString GetLocal() const { return FLocal; } + void SetRemote(const UnicodeString & Value); + UnicodeString GetRemote() const { return FRemote; } + void SetNode(const UnicodeString & Value); + UnicodeString GetNode() const { return FNode; } + void SetShortCut(const TShortCut & Value); + TShortCut GetShortCut() const { return FShortCut; } + UnicodeString GetKey() const; + +private: + void Modify(intptr_t OldIndex); +}; + diff --git a/netbox/src/core/Configuration.cpp b/netbox/src/core/Configuration.cpp new file mode 100644 index 000000000..754dcc78a --- /dev/null +++ b/netbox/src/core/Configuration.cpp @@ -0,0 +1,1724 @@ +#include +#pragma hdrstop + +#ifndef __linux__ +#include +#endif +#include + +#include +#include +#include "Configuration.h" +#include "PuttyIntf.h" +#include "TextsCore.h" +#include "Interface.h" +#include "CoreMain.h" +#include "WinSCPSecurity.h" +#include + +#define BUILD_OFFICIAL + +const wchar_t * AutoSwitchNames = L"On;Off;Auto"; +const wchar_t * NotAutoSwitchNames = L"Off;On;Auto"; + +// See http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml +const UnicodeString Sha1ChecksumAlg(L"sha-1"); +const UnicodeString Sha224ChecksumAlg(L"sha-224"); +const UnicodeString Sha256ChecksumAlg(L"sha-256"); +const UnicodeString Sha384ChecksumAlg(L"sha-384"); +const UnicodeString Sha512ChecksumAlg(L"sha-512"); +const UnicodeString Md5ChecksumAlg(L"md5"); +// Not defined by IANA +const UnicodeString Crc32ChecksumAlg(L"crc32"); + +const UnicodeString SshFingerprintType(L"ssh"); +const UnicodeString TlsFingerprintType(L"tls"); + +TConfiguration::TConfiguration() : + FDontSave(false), + FChanged(false), + FUpdating(0), + FApplicationInfo(nullptr), + FLogging(false), + FPermanentLogging(false), + FLogWindowLines(0), + FLogFileAppend(false), + FLogSensitive(false), + FPermanentLogSensitive(false), + FLogProtocol(0), + FPermanentLogProtocol(0), + FActualLogProtocol(0), + FLogActions(false), + FPermanentLogActions(false), + FConfirmOverwriting(false), + FConfirmResume(false), + FAutoReadDirectoryAfterOp(false), + FSessionReopenAuto(0), + FSessionReopenBackground(0), + FSessionReopenTimeout(0), + FSessionReopenAutoStall(0), + FProgramIniPathWrittable(0), + FTunnelLocalPortNumberLow(0), + FTunnelLocalPortNumberHigh(0), + FCacheDirectoryChangesMaxSize(0), + FShowFtpWelcomeMessage(false), + FTryFtpWhenSshFails(false), + FScripting(false), + FDisablePasswordStoring(false), + FForceBanners(false), + FDisableAcceptingHostKeys(false), + FDefaultCollectUsage(false), + FSessionReopenAutoMaximumNumberOfRetries(0) +{ + FUpdating = 0; + FStorage = stRegistry; + FDontSave = false; + FApplicationInfo = nullptr; + // FUsage = new TUsage(this); + // FDefaultCollectUsage = false; + FScripting = false; + + UnicodeString RandomSeedPath; + if (!base::GetEnvironmentVariable("APPDATA").IsEmpty()) + { + RandomSeedPath = "%APPDATA%"; + } +#ifndef __linux__ + else + { + RandomSeedPath = ::GetShellFolderPath(CSIDL_LOCAL_APPDATA); + if (RandomSeedPath.IsEmpty()) + { + RandomSeedPath = ::GetShellFolderPath(CSIDL_APPDATA); + } + } +#endif + FDefaultRandomSeedFile = ::IncludeTrailingBackslash(RandomSeedPath) + "winscp.rnd"; +} + +void TConfiguration::Default() +{ + TGuard Guard(FCriticalSection); + + FDisablePasswordStoring = false; + FForceBanners = false; + FDisableAcceptingHostKeys = false; + + std::unique_ptr AdminStorage(new TRegistryStorage(GetRegistryStorageKey(), HKEY_LOCAL_MACHINE)); + try__finally + { + if (AdminStorage->OpenRootKey(false)) + { + LoadAdmin(AdminStorage.get()); + AdminStorage->CloseSubKey(); + } + } + __finally + { +// delete AdminStorage; + }; + + SetRandomSeedFile(FDefaultRandomSeedFile); + SetPuttyRegistryStorageKey(OriginalPuttyRegistryStorageKey); + FConfirmOverwriting = true; + FConfirmResume = true; + FAutoReadDirectoryAfterOp = true; + FSessionReopenAuto = 5000; + FSessionReopenBackground = 2000; + FSessionReopenTimeout = 0; + FSessionReopenAutoStall = 60000; + FTunnelLocalPortNumberLow = 50000; + FTunnelLocalPortNumberHigh = 50099; + FCacheDirectoryChangesMaxSize = 100; + FShowFtpWelcomeMessage = false; + FExternalIpAddress.Clear(); + FTryFtpWhenSshFails = true; + SetCollectUsage(FDefaultCollectUsage); + FSessionReopenAutoMaximumNumberOfRetries = CONST_DEFAULT_NUMBER_OF_RETRIES; + FDefaultCollectUsage = false; + + FLogging = false; + FPermanentLogging = false; + FLogFileName = GetDefaultLogFileName(); + FPermanentLogFileName = FLogFileName; + FLogFileAppend = true; + FLogSensitive = false; + FPermanentLogSensitive = FLogSensitive; + FLogWindowLines = 100; + FLogProtocol = 0; + FPermanentLogProtocol = FLogProtocol; + UpdateActualLogProtocol(); + FLogActions = false; + FPermanentLogActions = false; + FActionsLogFileName = "%TEMP%\\&S.xml"; + FPermanentActionsLogFileName = FActionsLogFileName; + FProgramIniPathWrittable = -1; + + Changed(); +} + +TConfiguration::~TConfiguration() +{ + DebugAssert(!FUpdating); + if (FApplicationInfo) + { + FreeFileInfo(FApplicationInfo); + } + // delete FCriticalSection; + // delete FUsage; +} + +void TConfiguration::UpdateStaticUsage() +{ + // Usage->Set(L"ConfigurationIniFile", (Storage == stIniFile)); + // Usage->Set("Unofficial", IsUnofficial); + + // this is called from here, because we are guarded from calling into + // master password handler here, see TWinConfiguration::UpdateStaticUsage + StoredSessions->UpdateStaticUsage(); +} + +THierarchicalStorage * TConfiguration::CreateConfigStorage() +{ + bool SessionList = false; + return CreateStorage(SessionList); +} + +THierarchicalStorage * TConfiguration::CreateStorage(bool & SessionList) +{ + THierarchicalStorage * Result = nullptr; + if (GetStorage() == stRegistry) + { + Result = new TRegistryStorage(GetRegistryStorageKey()); + } + else + { + ThrowNotImplemented(3005); + DebugAssert(false); + } + + if ((FOptionsStorage.get() != nullptr) && (FOptionsStorage->GetCount() > 0)) + { + if (!SessionList) + { +// Result = new TOptionsStorage(FOptionsStorage.get(), ConfigurationSubKey, Result); + } + else + { + // cannot reuse session list storage for configuration as for it we need + // the option-override storage above + } + } + else + { + // All the above stores can be reused for configuration, + // if no options-overrides are set + SessionList = false; + } + + return Result; +} + +UnicodeString TConfiguration::PropertyToKey(const UnicodeString & Property) +{ + // no longer useful + intptr_t P = Property.LastDelimiter(L".>"); + return Property.SubString(P + 1, Property.Length() - P); +} + +#define LASTELEM(ELEM) \ + ELEM.SubString(ELEM.LastDelimiter(L".>") + 1, ELEM.Length() - ELEM.LastDelimiter(L".>")) +#define BLOCK(KEY, CANCREATE, BLOCK) \ + if (Storage->OpenSubKey(KEY, CANCREATE, true)) \ + { SCOPE_EXIT { Storage->CloseSubKey(); }; { BLOCK } } +#define KEY(TYPE, VAR) KEYEX(TYPE, VAR, VAR) +#undef REGCONFIG +#define REGCONFIG(CANCREATE) \ + BLOCK(L"Interface", CANCREATE, \ + KEY(String, RandomSeedFile); \ + KEY(String, PuttyRegistryStorageKey); \ + KEY(Bool, ConfirmOverwriting); \ + KEY(Bool, ConfirmResume); \ + KEY(Bool, AutoReadDirectoryAfterOp); \ + KEY(Integer, SessionReopenAuto); \ + KEY(Integer, SessionReopenBackground); \ + KEY(Integer, SessionReopenTimeout); \ + KEY(Integer, SessionReopenAutoStall); \ + KEY(Integer, TunnelLocalPortNumberLow); \ + KEY(Integer, TunnelLocalPortNumberHigh); \ + KEY(Integer, CacheDirectoryChangesMaxSize); \ + KEY(Bool, ShowFtpWelcomeMessage); \ + KEY(String, ExternalIpAddress); \ + KEY(Bool, TryFtpWhenSshFails); \ + KEY(Bool, CollectUsage); \ + KEY(Integer, SessionReopenAutoMaximumNumberOfRetries); \ + ); \ + BLOCK(L"Logging", CANCREATE, \ + KEYEX(Bool, PermanentLogging, Logging); \ + KEYEX(String,PermanentLogFileName, LogFileName); \ + KEY(Bool, LogFileAppend); \ + KEYEX(Bool, PermanentLogSensitive, LogSensitive); \ + KEY(Integer, LogWindowLines); \ + KEYEX(Integer,PermanentLogProtocol, LogProtocol); \ + KEYEX(Bool, PermanentLogActions, LogActions); \ + KEYEX(String,PermanentActionsLogFileName, ActionsLogFileName); \ + ); + +void TConfiguration::SaveData(THierarchicalStorage * Storage, bool /*All*/) +{ +#define KEYEX(TYPE, NAME, VAR) Storage->Write ## TYPE(LASTELEM(UnicodeString(MB_TEXT(#NAME))), Get ## VAR()) + REGCONFIG(true); +#undef KEYEX + + if (Storage->OpenSubKey("Usage", true)) + { + // FUsage->Save(Storage); + Storage->CloseSubKey(); + } +} + +void TConfiguration::Save() +{ + // only modified, implicit + DoSave(false, false); +} + +void TConfiguration::SaveExplicit() +{ + // only modified, explicit + DoSave(false, true); +} + +void TConfiguration::DoSave(bool All, bool Explicit) +{ + if (FDontSave) + { + return; + } + + std::unique_ptr Storage(CreateConfigStorage()); + try__finally + { + Storage->SetAccessMode(smReadWrite); + Storage->SetExplicit(Explicit); + if (Storage->OpenSubKey(GetConfigurationSubKey(), true)) + { + // if saving to TOptionsStorage, make sure we save everything so that + // all configuration is properly transferred to the master storage + bool ConfigAll = All || Storage->GetTemporary(); + SaveData(Storage.get(), ConfigAll); + } + } + __finally + { +// delete AStorage; + }; + + Saved(); + + if (All) + { + StoredSessions->Save(true, Explicit); + } + + // clean up as last, so that if it fails (read only INI), the saving can proceed + if (GetStorage() == stRegistry) + { + CleanupIniFile(); + } +} + +void TConfiguration::Export(const UnicodeString & /*AFileName*/) +{ + ThrowNotImplemented(3004); + /* + // not to "append" the export to an existing file + if (FileExists(FileName)) + { + DeleteFileChecked(FileName); + } + + THierarchicalStorage * Storage = NULL; + THierarchicalStorage * ExportStorage = NULL; + try + { + ExportStorage = new TIniFileStorage(FileName); + ExportStorage->AccessMode = smReadWrite; + ExportStorage->Explicit = true; + + Storage = CreateConfigStorage(); + Storage->AccessMode = smRead; + + CopyData(Storage, ExportStorage); + + if (ExportStorage->OpenSubKey(ConfigurationSubKey, true)) + { + SaveData(ExportStorage, true); + } + } + __finally + { + delete ExportStorage; + delete Storage; + } + + StoredSessions->Export(FileName); + */ +} + +void TConfiguration::Import(const UnicodeString & /*AFileName*/) +{ + ThrowNotImplemented(3005); + +/*THierarchicalStorage * Storage = NULL; + THierarchicalStorage * ImportStorage = NULL; + try + { + ImportStorage = new TIniFileStorage(FileName); + ImportStorage->AccessMode = smRead; + + Storage = CreateConfigStorage(); + Storage->AccessMode = smReadWrite; + Storage->Explicit = true; + + CopyData(ImportStorage, Storage); + + Default(); + LoadFrom(ImportStorage); + + if (ImportStorage->OpenSubKey(Configuration->StoredSessionsSubKey, false)) + { + StoredSessions->Clear(); + StoredSessions->DefaultSettings->Default(); + StoredSessions->Load(ImportStorage); + } + } + __finally + { + delete ImportStorage; + delete Storage; + } + + // save all and explicit + DoSave(true, true);*/ +} + +void TConfiguration::LoadData(THierarchicalStorage * Storage) +{ +#define KEYEX(TYPE, NAME, VAR) Set ## VAR(Storage->Read ## TYPE(LASTELEM(UnicodeString(MB_TEXT(#NAME))), Get ## VAR())) + REGCONFIG(false); +#undef KEYEX + + if (Storage->OpenSubKey("Usage", false)) + { + // FUsage->Load(Storage); + Storage->CloseSubKey(); + } + + if (FPermanentLogActions && FPermanentActionsLogFileName.IsEmpty() && + FPermanentLogging && !FPermanentLogFileName.IsEmpty()) + { + FPermanentActionsLogFileName = FPermanentLogFileName; + FPermanentLogging = false; + FPermanentLogFileName.Clear(); + } +} + +void TConfiguration::LoadAdmin(THierarchicalStorage * Storage) +{ + FDisablePasswordStoring = Storage->ReadBool("DisablePasswordStoring", FDisablePasswordStoring); + FForceBanners = Storage->ReadBool("ForceBanners", FForceBanners); + FDisableAcceptingHostKeys = Storage->ReadBool("DisableAcceptingHostKeys", FDisableAcceptingHostKeys); + FDefaultCollectUsage = Storage->ReadBool("DefaultCollectUsage", FDefaultCollectUsage); +} + +void TConfiguration::LoadFrom(THierarchicalStorage * Storage) +{ + if (Storage->OpenSubKey(GetConfigurationSubKey(), false)) + { + LoadData(Storage); + Storage->CloseSubKey(); + } +} + +void TConfiguration::Load(THierarchicalStorage * Storage) +{ + TGuard Guard(FCriticalSection); + TStorageAccessMode StorageAccessMode = Storage->GetAccessMode(); + try__finally + { + SCOPE_EXIT + { + Storage->SetAccessMode(StorageAccessMode); + }; + Storage->SetAccessMode(smRead); + LoadFrom(Storage); + } + __finally + { +// Storage->AccessMode = StorageAccessMode; + }; +} + +void TConfiguration::CopyData(THierarchicalStorage * Source, + THierarchicalStorage * Target) +{ + std::unique_ptr Names(new TStringList()); + try__finally + { + if (Source->OpenSubKey(GetConfigurationSubKey(), false)) + { + if (Target->OpenSubKey(GetConfigurationSubKey(), true)) + { + if (Source->OpenSubKey("CDCache", false)) + { + if (Target->OpenSubKey("CDCache", true)) + { + Names->Clear(); + Source->GetValueNames(Names.get()); + + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + Target->WriteBinaryData(Names->GetString(Index), + Source->ReadBinaryData(Names->GetString(Index))); + } + + Target->CloseSubKey(); + } + Source->CloseSubKey(); + } + + if (Source->OpenSubKey("Banners", false)) + { + if (Target->OpenSubKey("Banners", true)) + { + Names->Clear(); + Source->GetValueNames(Names.get()); + + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + Target->WriteString(Names->GetString(Index), + Source->ReadString(Names->GetString(Index), L"")); + } + + Target->CloseSubKey(); + } + Source->CloseSubKey(); + } + + Target->CloseSubKey(); + } + Source->CloseSubKey(); + } + + if (Source->OpenSubKey(GetSshHostKeysSubKey(), false)) + { + if (Target->OpenSubKey(GetSshHostKeysSubKey(), true)) + { + Names->Clear(); + Source->GetValueNames(Names.get()); + + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + Target->WriteStringRaw(Names->GetString(Index), + Source->ReadStringRaw(Names->GetString(Index), L"")); + } + + Target->CloseSubKey(); + } + Source->CloseSubKey(); + } + } + __finally + { +// delete Names; + }; +} + +void TConfiguration::LoadDirectoryChangesCache(const UnicodeString & SessionKey, + TRemoteDirectoryChangesCache * DirectoryChangesCache) +{ + std::unique_ptr Storage(CreateConfigStorage()); + try__finally + { + Storage->SetAccessMode(smRead); + if (Storage->OpenSubKey(GetConfigurationSubKey(), false) && + Storage->OpenSubKey("CDCache", false) && + Storage->ValueExists(SessionKey)) + { + DirectoryChangesCache->Deserialize(Storage->ReadBinaryData(SessionKey)); + } + } + __finally + { +// delete Storage; + }; +} + +void TConfiguration::SaveDirectoryChangesCache(const UnicodeString & SessionKey, + TRemoteDirectoryChangesCache * DirectoryChangesCache) +{ + std::unique_ptr Storage(CreateConfigStorage()); + try__finally + { + Storage->SetAccessMode(smReadWrite); + if (Storage->OpenSubKey(GetConfigurationSubKey(), true) && + Storage->OpenSubKey("CDCache", true)) + { + UnicodeString Data; + DirectoryChangesCache->Serialize(Data); + Storage->WriteBinaryData(SessionKey, Data); + } + } + __finally + { +// delete Storage; + }; +} + +UnicodeString TConfiguration::BannerHash(const UnicodeString & Banner) const +{ + RawByteString Result; + Result.SetLength(16); + md5checksum( + reinterpret_cast(Banner.c_str()), static_cast(Banner.Length() * sizeof(wchar_t)), + reinterpret_cast(const_cast(Result.c_str()))); + return BytesToHex(Result); +} + +bool TConfiguration::ShowBanner(const UnicodeString & SessionKey, + const UnicodeString & Banner) +{ + bool Result = false; + std::unique_ptr Storage(CreateConfigStorage()); + try__finally + { + Storage->SetAccessMode(smRead); + Result = + !Storage->OpenSubKey(GetConfigurationSubKey(), false) || + !Storage->OpenSubKey("Banners", false) || + !Storage->ValueExists(SessionKey) || + (Storage->ReadString(SessionKey, L"") != BannerHash(Banner)); + return Result; + } + __finally + { +// delete Storage; + }; + + return Result; +} + +void TConfiguration::NeverShowBanner(const UnicodeString & SessionKey, + const UnicodeString & Banner) +{ + std::unique_ptr Storage(CreateConfigStorage()); + try__finally + { + Storage->SetAccessMode(smReadWrite); + + if (Storage->OpenSubKey(GetConfigurationSubKey(), true) && + Storage->OpenSubKey("Banners", true)) + { + Storage->WriteString(SessionKey, BannerHash(Banner)); + } + } + __finally + { +// delete Storage; + }; +} +//--------------------------------------------------------------------------- +UnicodeString TConfiguration::FormatFingerprintKey(const UnicodeString & SiteKey, const UnicodeString & FingerprintType) const +{ + return FORMAT(L"%s:%s", SiteKey.c_str(), FingerprintType.c_str()); +} + +void TConfiguration::RememberLastFingerprint(const UnicodeString & SiteKey, const UnicodeString & FingerprintType, const UnicodeString & Fingerprint) +{ + std::unique_ptr Storage(CreateConfigStorage()); + Storage->SetAccessMode(smReadWrite); + + if (Storage->OpenSubKey(GetConfigurationSubKey(), true) && + Storage->OpenSubKey("LastFingerprints", true)) + { + UnicodeString FingerprintKey = FormatFingerprintKey(SiteKey, FingerprintType); + Storage->WriteString(FingerprintKey, Fingerprint); + } +} + +UnicodeString TConfiguration::GetLastFingerprint(const UnicodeString & SiteKey, const UnicodeString & FingerprintType) +{ + UnicodeString Result; + + std::unique_ptr Storage(CreateConfigStorage()); + Storage->SetAccessMode(smRead); + + if (Storage->OpenSubKey(GetConfigurationSubKey(), false) && + Storage->OpenSubKey("LastFingerprints", false)) + { + UnicodeString FingerprintKey = FormatFingerprintKey(SiteKey, FingerprintType); + Result = Storage->ReadString(FingerprintKey, L""); + } + return Result; +} + +void TConfiguration::Changed() +{ + if (FUpdating == 0) + { + if (GetOnChange()) + { + GetOnChange()(this); + } + } + else + { + FChanged = true; + } +} + +void TConfiguration::BeginUpdate() +{ + if (FUpdating == 0) + { + FChanged = false; + } + FUpdating++; + // Greater value would probably indicate some nesting problem in code + DebugAssert(FUpdating < 6); +} + +void TConfiguration::EndUpdate() +{ + DebugAssert(FUpdating > 0); + FUpdating--; + if ((FUpdating == 0) && FChanged) + { + FChanged = false; + Changed(); + } +} + +void TConfiguration::CleanupConfiguration() +{ + try + { + CleanupRegistry(GetConfigurationSubKey()); + if (GetStorage() == stRegistry) + { + FDontSave = true; + } + } + catch (Exception & E) + { + throw ExtException(&E, LoadStr(CLEANUP_CONFIG_ERROR)); + } +} + +void TConfiguration::CleanupRegistry(const UnicodeString & CleanupSubKey) +{ + std::unique_ptr Registry(new TRegistryStorage(GetRegistryStorageKey())); + try__finally + { + Registry->RecursiveDeleteSubKey(CleanupSubKey); + } + __finally + { +// delete Registry; + }; +} + +void TConfiguration::CleanupHostKeys() +{ + try + { + CleanupRegistry(GetSshHostKeysSubKey()); + } + catch (Exception & E) + { + throw ExtException(&E, LoadStr(CLEANUP_HOSTKEYS_ERROR)); + } +} + +void TConfiguration::CleanupRandomSeedFile() +{ + try + { + DontSaveRandomSeed(); + if (::FileExists(ApiPath(GetRandomSeedFileName()))) + { + DeleteFileChecked(GetRandomSeedFileName()); + } + } + catch (Exception & E) + { + throw ExtException(&E, LoadStr(CLEANUP_SEEDFILE_ERROR)); + } +} + +void TConfiguration::CleanupIniFile() +{ +#if 0 + try + { + if (::FileExists(ApiPath(GetIniFileStorageNameForReading()))) + { + DeleteFileChecked(GetIniFileStorageNameForReading()); + } + if (GetStorage() == stIniFile) + { + FDontSave = true; + } + } + catch (Exception & E) + { + throw ExtException(&E, LoadStr(CLEANUP_INIFILE_ERROR)); + } +#endif +} + +void TConfiguration::DontSave() +{ + FDontSave = true; +} + +RawByteString TConfiguration::EncryptPassword(const UnicodeString & Password, const UnicodeString & Key) +{ + if (Password.IsEmpty()) + { + return RawByteString(); + } + else + { + return ::EncryptPassword(Password, Key); + } +} + +UnicodeString TConfiguration::DecryptPassword(const RawByteString & Password, const UnicodeString & Key) +{ + if (Password.IsEmpty()) + { + return UnicodeString(); + } + else + { + return ::DecryptPassword(Password, Key); + } +} + +RawByteString TConfiguration::StronglyRecryptPassword(const RawByteString & Password, const UnicodeString & /*Key*/) +{ + return Password; +} + +UnicodeString TConfiguration::GetOSVersionStr() const +{ + UnicodeString Result; +#ifndef __linux__ + OSVERSIONINFO OSVersionInfo; + OSVersionInfo.dwOSVersionInfoSize = sizeof(OSVersionInfo); + if (::GetVersionEx(&OSVersionInfo) != 0) + { + Result = FORMAT(L"%d.%d.%d", int(OSVersionInfo.dwMajorVersion), + int(OSVersionInfo.dwMinorVersion), int(OSVersionInfo.dwBuildNumber)); + UnicodeString CSDVersion = OSVersionInfo.szCSDVersion; + if (!CSDVersion.IsEmpty()) + { + Result += L" " + CSDVersion; + } + UnicodeString ProductName = WindowsProductName(); + if (!ProductName.IsEmpty()) + { + Result += L" - " + ProductName; + } + } +#endif + return Result; +} + +TVSFixedFileInfo * TConfiguration::GetFixedApplicationInfo() const +{ + return GetFixedFileInfo(GetApplicationInfo()); +} + +intptr_t TConfiguration::GetCompoundVersion() const +{ +#ifndef __linux__ + TVSFixedFileInfo * FileInfo = GetFixedApplicationInfo(); + if (FileInfo) + { + return CalculateCompoundVersion( + HIWORD(FileInfo->dwFileVersionMS), LOWORD(FileInfo->dwFileVersionMS), + HIWORD(FileInfo->dwFileVersionLS), LOWORD(FileInfo->dwFileVersionLS)); + } + else +#endif + return 0; +} + +UnicodeString TConfiguration::ModuleFileName() const +{ + ThrowNotImplemented(204); + return L""; +} + +void * TConfiguration::GetFileApplicationInfo(const UnicodeString & AFileName) const +{ + void * Result; + if (AFileName.IsEmpty()) + { + if (!FApplicationInfo) + { + FApplicationInfo = CreateFileInfo(ModuleFileName()); + } + Result = FApplicationInfo; + } + else + { + Result = CreateFileInfo(AFileName); + } + return Result; +} + +void * TConfiguration::GetApplicationInfo() const +{ + return GetFileApplicationInfo(L""); +} + +UnicodeString TConfiguration::GetFileProductName(const UnicodeString & AFileName) const +{ + return GetFileFileInfoString(L"ProductName", AFileName); +} + +UnicodeString TConfiguration::GetFileCompanyName(const UnicodeString & AFileName) const +{ + // particularly in IDE build, company name is empty + return GetFileFileInfoString(L"CompanyName", AFileName, true); +} + +UnicodeString TConfiguration::GetProductName() const +{ + return GetFileProductName(L""); +} + +UnicodeString TConfiguration::GetCompanyName() const +{ + return GetFileCompanyName(L""); +} + +UnicodeString TConfiguration::GetFileProductVersion(const UnicodeString & AFileName) const +{ + return TrimVersion(GetFileFileInfoString(L"ProductVersion", AFileName)); +} + +UnicodeString TConfiguration::GetFileDescription(const UnicodeString & AFileName) +{ + return GetFileFileInfoString(L"FileDescription", AFileName); +} + +UnicodeString TConfiguration::GetFileProductVersion() const +{ + return GetFileProductVersion(L""); +} + +UnicodeString TConfiguration::GetReleaseType() const +{ + return GetFileInfoString(L"ReleaseType"); +} + +bool TConfiguration::GetIsUnofficial() const +{ + #ifdef BUILD_OFFICIAL + return false; + #else + return true; + #endif +} + +UnicodeString TConfiguration::GetProductVersionStr() const +{ + UnicodeString Result; +#ifndef __linux__ + TGuard Guard(FCriticalSection); + try + { + TVSFixedFileInfo * Info = GetFixedApplicationInfo(); + /*return FMTLOAD(VERSION, + HIWORD(Info->dwFileVersionMS), + LOWORD(Info->dwFileVersionMS), + HIWORD(Info->dwFileVersionLS), + LOWORD(Info->dwFileVersionLS));*/ + UnicodeString BuildStr; + if (!GetIsUnofficial()) + { + BuildStr = LoadStr(VERSION_BUILD); + } + else + { + #ifdef _DEBUG + BuildStr = LoadStr(VERSION_DEBUG_BUILD); + #else + BuildStr = LoadStr(VERSION_DEV_BUILD); + #endif + } + + int Build = LOWORD(Info->dwFileVersionLS); + if (Build > 0) + { + BuildStr += L" " + ::IntToStr(Build); + } + +#if 0 + #ifndef BUILD_OFFICIAL + UnicodeString BuildDate = __DATE__; + UnicodeString MonthStr = CutToChar(BuildDate, L' ', true); + int Month = ParseShortEngMonthName(MonthStr); + int Day = StrToInt64(CutToChar(BuildDate, L' ', true)); + int Year = StrToInt64(Trim(BuildDate)); + UnicodeString DateStr = FORMAT("%d-%2.2d-%2.2d", Year, Month, Day); + AddToList(BuildStr, DateStr, L" "); + #endif +#endif + + UnicodeString FullVersion = GetProductVersion(); + + UnicodeString AReleaseType = GetReleaseType(); + if (DebugAlwaysTrue(!AReleaseType.IsEmpty()) && + !SameText(AReleaseType, L"stable") && + !SameText(AReleaseType, L"development")) + { + FullVersion += L" " + AReleaseType; + } + + Result = FMTLOAD(VERSION2, GetProductVersion().c_str(), Build); + +#if 0 + #ifndef BUILD_OFFICIAL + Result += L" " + LoadStr(VERSION_DONT_DISTRIBUTE); + #endif +#endif + } + catch (Exception & E) + { + throw ExtException(&E, "Can't get application version"); + } +#endif + return Result; +} + +UnicodeString TConfiguration::GetFileVersion(const UnicodeString & FileName) +{ + UnicodeString Result; + void * FileInfo = CreateFileInfo(FileName); + try__finally + { + SCOPE_EXIT + { + FreeFileInfo(FileInfo); + }; + Result = GetFileVersion(GetFixedFileInfo(FileInfo)); + } + __finally + { + FreeFileInfo(FileInfo); + }; + return Result; +} + +UnicodeString TConfiguration::GetFileVersion(TVSFixedFileInfo * Info) +{ +#ifndef __linux__ + TGuard Guard(FCriticalSection); + try + { + UnicodeString Result = + FormatVersion( + HIWORD(Info->dwFileVersionMS), + LOWORD(Info->dwFileVersionMS), + HIWORD(Info->dwFileVersionLS)); + return Result; + } + catch (Exception &E) + { + throw ExtException(&E, L"Can't get file version"); + } +#else + return UnicodeString(L"1.0"); +#endif +} + +UnicodeString TConfiguration::GetProductVersion() const +{ + TGuard Guard(FCriticalSection); + UnicodeString Result; +#ifndef __linux__ + try + { + TVSFixedFileInfo * Info = GetFixedApplicationInfo(); + if (Info) + { + Result = FormatVersion( + HIWORD(Info->dwFileVersionMS), + LOWORD(Info->dwFileVersionMS), + HIWORD(Info->dwFileVersionLS)); + } + } + catch (Exception & E) + { + throw ExtException(&E, "Can't get application version"); + } +#endif + return Result; +} + +UnicodeString TConfiguration::GetVersion() +{ + return GetFileVersion(GetFixedApplicationInfo()); +} + +UnicodeString TConfiguration::GetFileFileInfoString(const UnicodeString & AKey, + const UnicodeString & AFileName, bool AllowEmpty) const +{ + TGuard Guard(FCriticalSection); + + UnicodeString Result; + void * Info = GetFileApplicationInfo(AFileName); + try__finally + { + SCOPE_EXIT + { + if (!AFileName.IsEmpty() && Info) + { + FreeFileInfo(Info); + } + }; + if ((Info != nullptr) && (GetTranslationCount(Info) > 0)) + { + TTranslation Translation = GetTranslation(Info, 0); + try + { + Result = ::GetFileInfoString(Info, Translation, AKey, AllowEmpty); + } + catch (const std::exception & e) + { + (void)e; + DEBUG_PRINTF("Error: %s", ::MB2W(e.what()).c_str()); + Result.Clear(); + } + } + else + { + DebugAssert(!AFileName.IsEmpty()); + } + } + __finally + { + if (!AFileName.IsEmpty()) + { + FreeFileInfo(Info); + } + }; + return Result; +} + +UnicodeString TConfiguration::GetFileInfoString(const UnicodeString & Key) const +{ + return GetFileFileInfoString(Key, L""); +} + +UnicodeString TConfiguration::GetRegistryStorageKey() const +{ + return GetRegistryKey(); +} + +void TConfiguration::SetNulStorage() +{ + FStorage = stNul; +} + +void TConfiguration::SetDefaultStorage() +{ + FStorage = stDetect; +} + +/* +void TConfiguration::SetIniFileStorageName(const UnicodeString & Value) +{ + FIniFileStorageName = Value; + FStorage = stIniFile; +} + +UnicodeString TConfiguration::GetIniFileStorageNameForReading() +{ + return GetIniFileStorageName(true); +} + +UnicodeString TConfiguration::GetIniFileStorageNameForReadingWriting() +{ + return GetIniFileStorageName(false); +} + +UnicodeString TConfiguration::GetIniFileStorageName(bool ReadingOnly) +{ + if (FIniFileStorageName.IsEmpty()) + { + UnicodeString ProgramPath = ParamStr(0); + + UnicodeString ProgramIniPath = ChangeFileExt(ProgramPath, L".ini"); + + UnicodeString IniPath; + if (::FileExists(ApiPath(ProgramIniPath))) + { + IniPath = ProgramIniPath; + } + else + { + UnicodeString AppDataIniPath = + IncludeTrailingBackslash(GetShellFolderPath(CSIDL_APPDATA)) + + ::ExtractFileName(ProgramIniPath); + if (::FileExists(ApiPath(AppDataIniPath))) + { + IniPath = AppDataIniPath; + } + else + { + // avoid expensive test if we are interested in existing files only + if (!ReadingOnly && (FProgramIniPathWrittable < 0)) + { + UnicodeString ProgramDir = ExtractFilePath(ProgramPath); + FProgramIniPathWrittable = IsDirectoryWriteable(ProgramDir) ? 1 : 0; + } + + // does not really matter what we return when < 0 + IniPath = (FProgramIniPathWrittable == 0) ? AppDataIniPath : ProgramIniPath; + } + } + + // BACKWARD COMPATIBILITY with 4.x + if (FVirtualIniFileStorageName.IsEmpty() && + TPath::IsDriveRooted(IniPath)) + { + UnicodeString LocalAppDataPath = GetShellFolderPath(CSIDL_LOCAL_APPDATA); + // virtual store for non-system drives have a different virtual store, + // do not bother about them + if (TPath::IsDriveRooted(LocalAppDataPath) && + SameText(ExtractFileDrive(IniPath), ExtractFileDrive(LocalAppDataPath))) + { + FVirtualIniFileStorageName = + IncludeTrailingBackslash(LocalAppDataPath) + + L"VirtualStore\\" + + IniPath.SubString(4, IniPath.Length() - 3); + } + } + + if (!FVirtualIniFileStorageName.IsEmpty() && + ::FileExists(ApiPath(FVirtualIniFileStorageName))) + { + return FVirtualIniFileStorageName; + } + else + { + return IniPath; + } + } + else + { + return FIniFileStorageName; + } +} +*/ + +void TConfiguration::SetOptionsStorage(TStrings * Value) +{ + if (FOptionsStorage.get() == nullptr) + { + FOptionsStorage.reset(new TStringList()); + } + FOptionsStorage->AddStrings(Value); +} + +TStrings * TConfiguration::GetOptionsStorage() +{ + return FOptionsStorage.get(); +} + +UnicodeString TConfiguration::GetPuttySessionsKey() const +{ + return GetPuttyRegistryStorageKey() + "\\Sessions"; +} + +UnicodeString TConfiguration::GetStoredSessionsSubKey() const +{ + return "Sessions"; +} + +UnicodeString TConfiguration::GetSshHostKeysSubKey() const +{ + return "SshHostKeys"; +} + +UnicodeString TConfiguration::GetConfigurationSubKey() const +{ + return "Configuration"; +} + +UnicodeString TConfiguration::GetRootKeyStr() const +{ + return RootKeyToStr(HKEY_CURRENT_USER); +} + +void TConfiguration::SetStorage(TStorage Value) +{ + if (FStorage != Value) + { + TStorage StorageBak = FStorage; + try + { + std::unique_ptr SourceStorage(CreateConfigStorage()); + std::unique_ptr TargetStorage(CreateConfigStorage()); + try__finally + { + SourceStorage->SetAccessMode(smRead); + + FStorage = Value; + + TargetStorage->SetAccessMode(smReadWrite); + TargetStorage->SetExplicit(true); + + // copy before save as it removes the ini file, + // when switching from ini to registry + CopyData(SourceStorage.get(), TargetStorage.get()); + } + __finally + { +// delete SourceStorage; +// delete TargetStorage; + }; + // save all and explicit, + // this also removes an INI file, when switching to registry storage + DoSave(true, true); + } + catch (...) + { + // If this fails, do not pretend that storage was switched. + // For instance: + // - When writing to an INI file fails (unlikely, as we fallback to user profile) + // - When removing INI file fails, when switching to registry + // (possible, when the INI file is in Program Files folder) + FStorage = StorageBak; + throw; + } + } +} + +void TConfiguration::Saved() +{ + // nothing +} + +TStorage TConfiguration::GetStorage() const +{ + if (FStorage == stDetect) + { + /*if (::FileExists(ApiPath(IniFileStorageNameForReading))) + { + FStorage = stIniFile; + } + else*/ + { + FStorage = stRegistry; + } + } + return FStorage; +} + +TStoredSessionList * TConfiguration::SelectFilezillaSessionsForImport( + TStoredSessionList * Sessions, UnicodeString & Error) +{ + std::unique_ptr ImportSessionList(new TStoredSessionList(true)); + ImportSessionList->SetDefaultSettings(Sessions->GetDefaultSettings()); + +#ifndef __linux__ + UnicodeString AppDataPath = GetShellFolderPath(CSIDL_APPDATA); +#else + UnicodeString AppDataPath = L"./"; +#endif + UnicodeString FilezillaSiteManagerFile = + IncludeTrailingBackslash(AppDataPath) + L"FileZilla\\sitemanager.xml"; + + if (FileExists(ApiPath(FilezillaSiteManagerFile))) + { + ImportSessionList->ImportFromFilezilla(FilezillaSiteManagerFile); + + if (ImportSessionList->GetCount() > 0) + { + ImportSessionList->SelectSessionsToImport(Sessions, true); + } + else + { + Error = FMTLOAD(FILEZILLA_NO_SITES, FilezillaSiteManagerFile.c_str()); + } + } + else + { + Error = FMTLOAD(FILEZILLA_SITE_MANAGER_NOT_FOUND, FilezillaSiteManagerFile.c_str()); + } + + return ImportSessionList.release(); +} + +bool TConfiguration::AnyFilezillaSessionForImport(TStoredSessionList * Sessions) +{ + try + { + UnicodeString Error; + std::unique_ptr Sesssions(SelectFilezillaSessionsForImport(Sessions, Error)); + return (Sesssions->GetCount() > 0); + } + catch (...) + { + return false; + } +} + +void TConfiguration::SetRandomSeedFile(const UnicodeString & Value) +{ + if (GetRandomSeedFile() != Value) + { + UnicodeString PrevRandomSeedFileName = GetRandomSeedFileName(); + + FRandomSeedFile = Value; + + // never allow empty seed file to avoid Putty trying to reinitialize the path + if (GetRandomSeedFileName().IsEmpty()) + { + FRandomSeedFile = FDefaultRandomSeedFile; + } + + if (!PrevRandomSeedFileName.IsEmpty() && + (PrevRandomSeedFileName != GetRandomSeedFileName()) && + ::FileExists(ApiPath(PrevRandomSeedFileName))) + { + // ignore any error + ::RemoveFile(ApiPath(PrevRandomSeedFileName)); + } + } +} + +UnicodeString TConfiguration::GetRandomSeedFileName() const +{ + // StripPathQuotes should not be needed as we do not feed quotes anymore + return StripPathQuotes(::ExpandEnvironmentVariables(FRandomSeedFile)).Trim(); +} + +void TConfiguration::SetExternalIpAddress(const UnicodeString & Value) +{ + SET_CONFIG_PROPERTY(ExternalIpAddress); +} + +void TConfiguration::SetTryFtpWhenSshFails(bool Value) +{ + SET_CONFIG_PROPERTY(TryFtpWhenSshFails); +} + +void TConfiguration::SetPuttyRegistryStorageKey(const UnicodeString & Value) +{ + SET_CONFIG_PROPERTY(PuttyRegistryStorageKey); +} + +TEOLType TConfiguration::GetLocalEOLType() const +{ + return eolCRLF; +} + +bool TConfiguration::GetCollectUsage() const +{ + return false; // FUsage->Collect; +} + +void TConfiguration::SetCollectUsage(bool /*Value*/) +{ + // FUsage->Collect = Value; +} + +void TConfiguration::TemporaryLogging(const UnicodeString & ALogFileName) +{ + if (SameText(ExtractFileExt(ALogFileName), L".xml")) + { + TemporaryActionsLogging(ALogFileName); + } + else + { + FLogging = true; + FLogFileName = ALogFileName; + UpdateActualLogProtocol(); + } +} + +void TConfiguration::TemporaryActionsLogging(const UnicodeString & ALogFileName) +{ + FLogActions = true; + FActionsLogFileName = ALogFileName; +} + +void TConfiguration::TemporaryLogProtocol(intptr_t ALogProtocol) +{ + FLogProtocol = ALogProtocol; + UpdateActualLogProtocol(); +} + +void TConfiguration::TemporaryLogSensitive(bool ALogSensitive) +{ + FLogSensitive = ALogSensitive; +} + +void TConfiguration::SetLogging(bool Value) +{ + if (GetLogging() != Value) + { + FPermanentLogging = Value; + FLogging = Value; + UpdateActualLogProtocol(); + Changed(); + } +} + +void TConfiguration::SetLogFileName(const UnicodeString & Value) +{ + if (GetLogFileName() != Value) + { + FPermanentLogFileName = Value; + FLogFileName = Value; + Changed(); + } +} +//--------------------------------------------------------------------- +void TConfiguration::SetActionsLogFileName(const UnicodeString & Value) +{ + if (GetActionsLogFileName() != Value) + { + FPermanentActionsLogFileName = Value; + FActionsLogFileName = Value; + Changed(); + } +} +//--------------------------------------------------------------------- +void TConfiguration::SetLogToFile(bool Value) +{ + if (Value != GetLogToFile()) + { + SetLogFileName(Value ? GetDefaultLogFileName() : UnicodeString(L"")); + Changed(); + } +} + +bool TConfiguration::GetLogToFile() const +{ + return !GetLogFileName().IsEmpty(); +} + +void TConfiguration::UpdateActualLogProtocol() +{ + FActualLogProtocol = FLogging ? FLogProtocol : 0; +} + +void TConfiguration::SetLogProtocol(intptr_t Value) +{ + if (GetLogProtocol() != Value) + { + FPermanentLogProtocol = Value; + FLogProtocol = Value; + Changed(); + UpdateActualLogProtocol(); + } +} + +void TConfiguration::SetLogActions(bool Value) +{ + if (GetLogActions() != Value) + { + FPermanentLogActions = Value; + FLogActions = Value; + Changed(); + } +} + +void TConfiguration::SetLogFileAppend(bool Value) +{ + SET_CONFIG_PROPERTY(LogFileAppend); +} + +void TConfiguration::SetLogSensitive(bool Value) +{ + if (GetLogSensitive() != Value) + { + FPermanentLogSensitive = Value; + FLogSensitive = Value; + Changed(); + } +} + +void TConfiguration::SetLogWindowLines(intptr_t Value) +{ + SET_CONFIG_PROPERTY(LogWindowLines); +} + +void TConfiguration::SetLogWindowComplete(bool Value) +{ + if (Value != GetLogWindowComplete()) + { + SetLogWindowLines(Value ? 0 : 50); + Changed(); + } +} + +bool TConfiguration::GetLogWindowComplete() const +{ + return static_cast(GetLogWindowLines() == 0); +} + +UnicodeString TConfiguration::GetDefaultLogFileName() const +{ + return "%TEMP%\\&S.log"; +} + +void TConfiguration::SetConfirmOverwriting(bool Value) +{ + TGuard Guard(FCriticalSection); + SET_CONFIG_PROPERTY(ConfirmOverwriting); +} + +bool TConfiguration::GetConfirmOverwriting() const +{ + TGuard Guard(FCriticalSection); + return FConfirmOverwriting; +} + +void TConfiguration::SetConfirmResume(bool Value) +{ + TGuard Guard(FCriticalSection); + SET_CONFIG_PROPERTY(ConfirmResume); +} + +bool TConfiguration::GetConfirmResume() const +{ + TGuard Guard(FCriticalSection); + return FConfirmResume; +} + +void TConfiguration::SetAutoReadDirectoryAfterOp(bool Value) +{ + TGuard Guard(FCriticalSection); + SET_CONFIG_PROPERTY(AutoReadDirectoryAfterOp); +} + +bool TConfiguration::GetAutoReadDirectoryAfterOp() const +{ + TGuard Guard(FCriticalSection); + return FAutoReadDirectoryAfterOp; +} + +UnicodeString TConfiguration::GetTimeFormat() const +{ + return "h:nn:ss"; +} + +UnicodeString TConfiguration::GetPartialExt() const +{ + return PARTIAL_EXT; +} + +UnicodeString TConfiguration::GetDefaultKeyFile() const +{ + return L""; +} + +bool TConfiguration::GetRememberPassword() const +{ + return false; +} + +void TConfiguration::SetSessionReopenAuto(intptr_t Value) +{ + SET_CONFIG_PROPERTY(SessionReopenAuto); +} + +void TConfiguration::SetSessionReopenAutoMaximumNumberOfRetries(intptr_t Value) +{ + SET_CONFIG_PROPERTY(SessionReopenAutoMaximumNumberOfRetries); +} + +void TConfiguration::SetSessionReopenBackground(intptr_t Value) +{ + SET_CONFIG_PROPERTY(SessionReopenBackground); +} + +void TConfiguration::SetSessionReopenTimeout(intptr_t Value) +{ + SET_CONFIG_PROPERTY(SessionReopenTimeout); +} + +void TConfiguration::SetSessionReopenAutoStall(intptr_t Value) +{ + SET_CONFIG_PROPERTY(SessionReopenAutoStall); +} + +void TConfiguration::SetTunnelLocalPortNumberLow(intptr_t Value) +{ + SET_CONFIG_PROPERTY(TunnelLocalPortNumberLow); +} + +void TConfiguration::SetTunnelLocalPortNumberHigh(intptr_t Value) +{ + SET_CONFIG_PROPERTY(TunnelLocalPortNumberHigh); +} + +void TConfiguration::SetCacheDirectoryChangesMaxSize(intptr_t Value) +{ + SET_CONFIG_PROPERTY(CacheDirectoryChangesMaxSize); +} + +void TConfiguration::SetShowFtpWelcomeMessage(bool Value) +{ + SET_CONFIG_PROPERTY(ShowFtpWelcomeMessage); +} + +UnicodeString TConfiguration::GetPermanentLogFileName() const +{ + return FPermanentLogFileName; +} + +void TConfiguration::SetPermanentLogFileName(const UnicodeString & Value) +{ + FPermanentLogFileName = Value; +} + +UnicodeString TConfiguration::GetPermanentActionsLogFileName() const +{ + return FPermanentActionsLogFileName; +} + +void TConfiguration::SetPermanentActionsLogFileName(const UnicodeString & Value) +{ + FPermanentActionsLogFileName = Value; +} + +bool TConfiguration::GetPersistent() const +{ + return (GetStorage() != stNul); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +void TShortCuts::Add(const TShortCut & ShortCut) +{ + FShortCuts.push_back(ShortCut); +} + +bool TShortCuts::Has(const TShortCut & ShortCut) const +{ + return std::find(FShortCuts.begin(), FShortCuts.end(), ShortCut) != FShortCuts.end(); +} + +NB_IMPLEMENT_CLASS(TConfiguration, NB_GET_CLASS_INFO(TObject), nullptr) diff --git a/netbox/src/core/Configuration.h b/netbox/src/core/Configuration.h new file mode 100644 index 000000000..da7773cc0 --- /dev/null +++ b/netbox/src/core/Configuration.h @@ -0,0 +1,374 @@ +#pragma once + +#include + +#include "RemoteFiles.h" +#include "HierarchicalStorage.h" + +#define SET_CONFIG_PROPERTY_EX(PROPERTY, APPLY) \ + if (Get ## PROPERTY() != Value) { F ## PROPERTY = Value; Changed(); APPLY; } +#define SET_CONFIG_PROPERTY(PROPERTY) \ + SET_CONFIG_PROPERTY_EX(PROPERTY, ) + +#define CONST_DEFAULT_NUMBER_OF_RETRIES 2 + +extern const wchar_t * AutoSwitchNames; +extern const wchar_t * NotAutoSwitchNames; + +enum TAutoSwitch +{ + asOn, + asOff, + asAuto, +}; + +enum TFtpEncryptionSwitch_219 +{ + fesPlainFTP, + fesExplicitSSL, + fesImplicit, + fesExplicitTLS +}; + +class TStoredSessionList; + +class TConfiguration : public TObject +{ +NB_DECLARE_CLASS(TConfiguration) +NB_DISABLE_COPY(TConfiguration) +private: + bool FDontSave; + bool FChanged; + intptr_t FUpdating; + TNotifyEvent FOnChange; + + mutable void * FApplicationInfo; + // TUsage * FUsage; + bool FLogging; + bool FPermanentLogging; + UnicodeString FLogFileName; + UnicodeString FPermanentLogFileName; + intptr_t FLogWindowLines; + bool FLogFileAppend; + bool FLogSensitive; + bool FPermanentLogSensitive; + intptr_t FLogProtocol; + intptr_t FPermanentLogProtocol; + intptr_t FActualLogProtocol; + bool FLogActions; + bool FPermanentLogActions; + UnicodeString FActionsLogFileName; + UnicodeString FPermanentActionsLogFileName; + bool FConfirmOverwriting; + bool FConfirmResume; + bool FAutoReadDirectoryAfterOp; + intptr_t FSessionReopenAuto; + intptr_t FSessionReopenBackground; + intptr_t FSessionReopenTimeout; + intptr_t FSessionReopenAutoStall; + UnicodeString FIniFileStorageName; + UnicodeString FVirtualIniFileStorageName; + std::unique_ptr FOptionsStorage; + intptr_t FProgramIniPathWrittable; + intptr_t FTunnelLocalPortNumberLow; + intptr_t FTunnelLocalPortNumberHigh; + intptr_t FCacheDirectoryChangesMaxSize; + intptr_t FSessionReopenAutoMaximumNumberOfRetries; + bool FShowFtpWelcomeMessage; + UnicodeString FDefaultRandomSeedFile; + UnicodeString FRandomSeedFile; + UnicodeString FPuttyRegistryStorageKey; + UnicodeString FExternalIpAddress; + bool FTryFtpWhenSshFails; + bool FScripting; + + bool FDisablePasswordStoring; + bool FForceBanners; + bool FDisableAcceptingHostKeys; + bool FDefaultCollectUsage; + +public: + + UnicodeString GetOSVersionStr() const; + TVSFixedFileInfo * GetFixedApplicationInfo() const; + void * GetApplicationInfo() const; + virtual UnicodeString GetProductVersionStr() const; + virtual UnicodeString GetProductVersion() const; + UnicodeString GetVersion(); + UnicodeString GetFileProductVersion() const; + UnicodeString GetProductName() const; + UnicodeString GetCompanyName() const; + UnicodeString GetFileVersion(TVSFixedFileInfo * Info); + UnicodeString GetStoredSessionsSubKey() const; + UnicodeString GetPuttySessionsKey() const; + void SetRandomSeedFile(const UnicodeString & Value); + UnicodeString GetRandomSeedFileName() const; + void SetPuttyRegistryStorageKey(const UnicodeString & Value); + UnicodeString GetSshHostKeysSubKey() const; + UnicodeString GetRootKeyStr() const; + UnicodeString GetConfigurationSubKey() const; + TEOLType GetLocalEOLType() const; + void SetLogging(bool Value); + void SetLogFileName(const UnicodeString & Value); + bool GetLogToFile() const; + void SetLogWindowLines(intptr_t Value); + void SetLogWindowComplete(bool Value); + bool GetLogWindowComplete() const; + void SetLogFileAppend(bool Value); + bool GetLogSensitive() const { return FLogSensitive; } + void SetLogSensitive(bool Value); + void SetLogProtocol(intptr_t Value); + void SetLogActions(bool Value); + void SetActionsLogFileName(const UnicodeString & Value); + UnicodeString GetDefaultLogFileName() const; + UnicodeString GetTimeFormat() const; + void SetStorage(TStorage Value); + UnicodeString GetRegistryStorageKey() const; +// UnicodeString GetIniFileStorageNameForReadingWriting() const; +// UnicodeString GetIniFileStorageNameForReading(); +// UnicodeString GetIniFileStorageName(bool ReadingOnly); + void SetIniFileStorageName(const UnicodeString & Value); + void SetOptionsStorage(TStrings * Value); + TStrings * GetOptionsStorage(); + UnicodeString GetPartialExt() const; + UnicodeString GetFileInfoString(const UnicodeString & Key) const; + void SetSessionReopenAuto(intptr_t Value); + void SetSessionReopenBackground(intptr_t Value); + void SetSessionReopenTimeout(intptr_t Value); + void SetSessionReopenAutoStall(intptr_t Value); + void SetTunnelLocalPortNumberLow(intptr_t Value); + void SetTunnelLocalPortNumberHigh(intptr_t Value); + void SetCacheDirectoryChangesMaxSize(intptr_t Value); + void SetShowFtpWelcomeMessage(bool Value); + intptr_t GetCompoundVersion() const; + void UpdateActualLogProtocol(); + void SetExternalIpAddress(const UnicodeString & Value); + void SetTryFtpWhenSshFails(bool Value); + bool GetCollectUsage() const; + void SetCollectUsage(bool Value); + bool GetIsUnofficial() const; + bool GetPersistent() const; + bool GetScripting() const { return FScripting; } + void SetScripting(bool Value) { FScripting = Value; } + +protected: + mutable TStorage FStorage; + TCriticalSection FCriticalSection; + +public: + virtual TStorage GetStorage() const; + virtual void Changed(); + virtual void SaveData(THierarchicalStorage * Storage, bool All); + virtual void LoadData(THierarchicalStorage * Storage); + virtual void LoadFrom(THierarchicalStorage * Storage); + virtual void CopyData(THierarchicalStorage * Source, THierarchicalStorage * Target); + virtual void LoadAdmin(THierarchicalStorage * Storage); + virtual UnicodeString GetDefaultKeyFile() const; + virtual void Saved(); + void CleanupRegistry(const UnicodeString & CleanupSubKey); + UnicodeString BannerHash(const UnicodeString & Banner) const; + static UnicodeString PropertyToKey(const UnicodeString & Property); + virtual void DoSave(bool All, bool Explicit); + UnicodeString FormatFingerprintKey(const UnicodeString & SiteKey, const UnicodeString & FingerprintType) const; + + virtual bool GetConfirmOverwriting() const; + virtual void SetConfirmOverwriting(bool Value); + bool GetConfirmResume() const; + void SetConfirmResume(bool Value); + bool GetAutoReadDirectoryAfterOp() const; + void SetAutoReadDirectoryAfterOp(bool Value); + virtual bool GetRememberPassword() const; + UnicodeString GetReleaseType() const; + + virtual UnicodeString ModuleFileName() const; + + UnicodeString GetFileFileInfoString(const UnicodeString & AKey, + const UnicodeString & AFileName, bool AllowEmpty = false) const; + void * GetFileApplicationInfo(const UnicodeString & AFileName) const; + UnicodeString GetFileProductVersion(const UnicodeString & AFileName) const; + UnicodeString GetFileProductName(const UnicodeString & AFileName) const; + UnicodeString GetFileCompanyName(const UnicodeString & AFileName) const; + + /*__property bool PermanentLogging = { read=FPermanentLogging, write=SetLogging }; + __property UnicodeString PermanentLogFileName = { read=FPermanentLogFileName, write=SetLogFileName }; + __property bool PermanentLogActions = { read=FPermanentLogActions, write=SetLogActions }; + __property UnicodeString PermanentActionsLogFileName = { read=FPermanentActionsLogFileName, write=SetActionsLogFileName }; + __property int PermanentLogProtocol = { read=FPermanentLogProtocol, write=SetLogProtocol }; + __property bool PermanentLogSensitive = { read=FPermanentLogSensitive, write=SetLogSensitive };*/ + + bool GetPermanentLogging() const { return FPermanentLogging; } + void SetPermanentLogging(bool Value) { FPermanentLogging = Value; } + UnicodeString GetPermanentLogFileName() const; + void SetPermanentLogFileName(const UnicodeString & Value); + bool GetPermanentLogActions() const { return FPermanentLogActions; } + void SetPermanentLogActions(bool Value) { FPermanentLogActions = Value; } + UnicodeString GetPermanentActionsLogFileName() const; + void SetPermanentActionsLogFileName(const UnicodeString & Value); + intptr_t GetPermanentLogProtocol() const { return FPermanentLogProtocol; } + bool GetPermanentLogSensitive() const { return FPermanentLogSensitive; } + +public: + TConfiguration(); + virtual ~TConfiguration(); + virtual void Default(); + virtual void UpdateStaticUsage(); + void Load(THierarchicalStorage * Storage); + void Save(); + void SaveExplicit(); + void SetNulStorage(); + void SetDefaultStorage(); + void Export(const UnicodeString & AFileName); + void Import(const UnicodeString & AFileName); + void CleanupConfiguration(); + void CleanupIniFile(); + void CleanupHostKeys(); + void CleanupRandomSeedFile(); + void BeginUpdate(); + void EndUpdate(); + void DontSave(); + void LoadDirectoryChangesCache(const UnicodeString & SessionKey, + TRemoteDirectoryChangesCache * DirectoryChangesCache); + void SaveDirectoryChangesCache(const UnicodeString & SessionKey, + TRemoteDirectoryChangesCache * DirectoryChangesCache); + bool ShowBanner(const UnicodeString & SessionKey, const UnicodeString & Banner); + void NeverShowBanner(const UnicodeString & SessionKey, const UnicodeString & Banner); + void RememberLastFingerprint(const UnicodeString & SiteKey, const UnicodeString & FingerprintType, const UnicodeString & Fingerprint); + UnicodeString GetLastFingerprint(const UnicodeString & SiteKey, const UnicodeString & FingerprintType); + virtual THierarchicalStorage * CreateConfigStorage(); + virtual THierarchicalStorage * CreateStorage(bool & SessionList); + void TemporaryLogging(const UnicodeString & ALogFileName); + void TemporaryActionsLogging(const UnicodeString & ALogFileName); + void TemporaryLogProtocol(intptr_t ALogProtocol); + void TemporaryLogSensitive(bool ALogSensitive); + virtual RawByteString EncryptPassword(const UnicodeString & Password, const UnicodeString & Key); + virtual UnicodeString DecryptPassword(const RawByteString & Password, const UnicodeString & Key); + virtual RawByteString StronglyRecryptPassword(const RawByteString & Password, const UnicodeString & Key); + UnicodeString GetFileDescription(const UnicodeString & AFileName); + UnicodeString GetFileVersion(const UnicodeString & AFileName); + + TStoredSessionList * SelectFilezillaSessionsForImport( + TStoredSessionList * Sessions, UnicodeString & Error); + bool AnyFilezillaSessionForImport(TStoredSessionList * Sessions); + + /*__property TVSFixedFileInfo *FixedApplicationInfo = { read=GetFixedApplicationInfo }; + __property void * ApplicationInfo = { read=GetApplicationInfo }; + __property TUsage * Usage = { read = FUsage }; + __property bool CollectUsage = { read = GetCollectUsage, write = SetCollectUsage }; + __property UnicodeString StoredSessionsSubKey = {read=GetStoredSessionsSubKey}; + __property UnicodeString PuttyRegistryStorageKey = { read=FPuttyRegistryStorageKey, write=SetPuttyRegistryStorageKey }; + __property UnicodeString PuttySessionsKey = { read=GetPuttySessionsKey }; + __property UnicodeString RandomSeedFile = { read=FRandomSeedFile, write=SetRandomSeedFile }; + __property UnicodeString RandomSeedFileName = { read=GetRandomSeedFileName }; + __property UnicodeString SshHostKeysSubKey = { read=GetSshHostKeysSubKey }; + __property UnicodeString RootKeyStr = { read=GetRootKeyStr }; + __property UnicodeString ConfigurationSubKey = { read=GetConfigurationSubKey }; + __property TEOLType LocalEOLType = { read = GetLocalEOLType }; + __property UnicodeString VersionStr = { read=GetVersionStr }; + __property UnicodeString Version = { read=GetVersion }; + __property int CompoundVersion = { read=GetCompoundVersion }; + __property UnicodeString ProductVersion = { read=GetProductVersion }; + __property UnicodeString ProductName = { read=GetProductName }; + __property UnicodeString CompanyName = { read=GetCompanyName }; + __property UnicodeString OSVersionStr = { read = GetOSVersionStr }; + __property bool IsUnofficial = { read = GetIsUnofficial }; + __property bool Logging = { read=FLogging, write=SetLogging }; + __property UnicodeString LogFileName = { read=FLogFileName, write=SetLogFileName }; + __property bool LogToFile = { read=GetLogToFile }; + __property bool LogFileAppend = { read=FLogFileAppend, write=SetLogFileAppend }; + __property bool LogSensitive = { read=FLogSensitive, write=SetLogSensitive }; + __property int LogProtocol = { read=FLogProtocol, write=SetLogProtocol }; + __property int ActualLogProtocol = { read=FActualLogProtocol }; + __property bool LogActions = { read=FLogActions, write=SetLogActions }; + __property UnicodeString ActionsLogFileName = { read=FActionsLogFileName, write=SetActionsLogFileName }; + __property int LogWindowLines = { read=FLogWindowLines, write=SetLogWindowLines }; + __property bool LogWindowComplete = { read=GetLogWindowComplete, write=SetLogWindowComplete }; + __property UnicodeString DefaultLogFileName = { read=GetDefaultLogFileName }; + __property TNotifyEvent OnChange = { read = FOnChange, write = FOnChange }; + __property bool ConfirmOverwriting = { read = GetConfirmOverwriting, write = SetConfirmOverwriting}; + __property bool ConfirmResume = { read = GetConfirmResume, write = SetConfirmResume}; + __property bool AutoReadDirectoryAfterOp = { read = GetAutoReadDirectoryAfterOp, write = SetAutoReadDirectoryAfterOp}; + __property bool RememberPassword = { read = GetRememberPassword }; + __property UnicodeString PartialExt = {read=GetPartialExt}; + __property int SessionReopenAuto = { read = FSessionReopenAuto, write = SetSessionReopenAuto }; + __property int SessionReopenBackground = { read = FSessionReopenBackground, write = SetSessionReopenBackground }; + __property int SessionReopenTimeout = { read = FSessionReopenTimeout, write = SetSessionReopenTimeout }; + __property int SessionReopenAutoStall = { read = FSessionReopenAutoStall, write = SetSessionReopenAutoStall }; + __property int TunnelLocalPortNumberLow = { read = FTunnelLocalPortNumberLow, write = SetTunnelLocalPortNumberLow }; + __property int TunnelLocalPortNumberHigh = { read = FTunnelLocalPortNumberHigh, write = SetTunnelLocalPortNumberHigh }; + __property int CacheDirectoryChangesMaxSize = { read = FCacheDirectoryChangesMaxSize, write = SetCacheDirectoryChangesMaxSize }; + __property bool ShowFtpWelcomeMessage = { read = FShowFtpWelcomeMessage, write = SetShowFtpWelcomeMessage }; + __property UnicodeString ExternalIpAddress = { read = FExternalIpAddress, write = SetExternalIpAddress }; + __property bool TryFtpWhenSshFails = { read = FTryFtpWhenSshFails, write = SetTryFtpWhenSshFails }; + + __property UnicodeString TimeFormat = { read = GetTimeFormat }; + __property TStorage Storage = { read=GetStorage, write=SetStorage }; + __property UnicodeString RegistryStorageKey = { read=GetRegistryStorageKey }; + __property UnicodeString IniFileStorageName = { read=GetIniFileStorageNameForReadingWriting, write=SetIniFileStorageName }; + __property UnicodeString IniFileStorageNameForReading = { read=GetIniFileStorageNameForReading }; + __property TStrings * OptionsStorage = { read = GetOptionsStorage, write = SetOptionsStorage }; + __property bool Persistent = { read = GetPersistent }; + __property bool Scripting = { read = FScripting, write = FScripting }; + + __property UnicodeString DefaultKeyFile = { read = GetDefaultKeyFile }; + + __property bool DisablePasswordStoring = { read = FDisablePasswordStoring }; + __property bool ForceBanners = { read = FForceBanners }; + __property bool DisableAcceptingHostKeys = { read = FDisableAcceptingHostKeys }; +*/ + // TUsage * GetUsage() { return FUsage; } + UnicodeString GetPuttyRegistryStorageKey() const { return FPuttyRegistryStorageKey; } + UnicodeString GetRandomSeedFile() const { return FRandomSeedFile; } + bool GetLogging() const { return FLogging; } + UnicodeString GetLogFileName() const { return FLogFileName; } + bool GetLogFileAppend() const { return FLogFileAppend; } + intptr_t GetLogProtocol() const { return FLogProtocol; } + intptr_t GetActualLogProtocol() const { return FActualLogProtocol; } + bool GetLogActions() const { return FLogActions; } + UnicodeString GetActionsLogFileName() const { return FActionsLogFileName; } + intptr_t GetLogWindowLines() const { return FLogWindowLines; } + TNotifyEvent & GetOnChange() { return FOnChange; } + void SetOnChange(TNotifyEvent Value) { FOnChange = Value; } + intptr_t GetSessionReopenAuto() const { return FSessionReopenAuto; } + intptr_t GetSessionReopenBackground() const { return FSessionReopenBackground; } + intptr_t GetSessionReopenTimeout() const { return FSessionReopenTimeout; } + intptr_t GetSessionReopenAutoStall() const { return FSessionReopenAutoStall; } + intptr_t GetTunnelLocalPortNumberLow() const { return FTunnelLocalPortNumberLow; } + intptr_t GetTunnelLocalPortNumberHigh() const { return FTunnelLocalPortNumberHigh; } + intptr_t GetCacheDirectoryChangesMaxSize() const { return FCacheDirectoryChangesMaxSize; } + bool GetShowFtpWelcomeMessage() const { return FShowFtpWelcomeMessage; } + UnicodeString GetExternalIpAddress() const { return FExternalIpAddress; } + bool GetTryFtpWhenSshFails() const { return FTryFtpWhenSshFails; } + bool GetDisablePasswordStoring() const { return FDisablePasswordStoring; } + bool GetForceBanners() const { return FForceBanners; } + bool GetDisableAcceptingHostKeys() const { return FDisableAcceptingHostKeys; } + void SetLogToFile(bool Value); + intptr_t GetSessionReopenAutoMaximumNumberOfRetries() const { return FSessionReopenAutoMaximumNumberOfRetries; } + void SetSessionReopenAutoMaximumNumberOfRetries(intptr_t Value); + +}; + +class TShortCuts : public TObject +{ +public: + void Add(const TShortCut & ShortCut); + bool Has(const TShortCut & ShortCut) const; + +private: + std::vector FShortCuts; +}; + +extern const UnicodeString OriginalPuttyRegistryStorageKey; +extern const UnicodeString KittyRegistryStorageKey; +extern const UnicodeString OriginalPuttyExecutable; +extern const UnicodeString KittyExecutable; + +extern const UnicodeString Sha1ChecksumAlg; +extern const UnicodeString Sha224ChecksumAlg; +extern const UnicodeString Sha256ChecksumAlg; +extern const UnicodeString Sha384ChecksumAlg; +extern const UnicodeString Sha512ChecksumAlg; +extern const UnicodeString Md5ChecksumAlg; +extern const UnicodeString Crc32ChecksumAlg; + +extern const UnicodeString SshFingerprintType; +extern const UnicodeString TlsFingerprintType; diff --git a/netbox/src/core/CopyParam.cpp b/netbox/src/core/CopyParam.cpp new file mode 100644 index 000000000..7acc5576d --- /dev/null +++ b/netbox/src/core/CopyParam.cpp @@ -0,0 +1,969 @@ +#include +#pragma hdrstop + +#include +#include +#include "CopyParam.h" +#include "HierarchicalStorage.h" +#include "TextsCore.h" +#include "Interface.h" + +const wchar_t * TransferModeNames[] = { L"binary", L"ascii", L"automatic" }; +const int TransferModeNamesCount = _countof(TransferModeNames); +const wchar_t * ToggleNames[] = { L"off", L"on" }; + +TCopyParamType::TCopyParamType() +{ + Default(); +} + +TCopyParamType::TCopyParamType(const TCopyParamType & Source) +{ + Assign(&Source); +} + +TCopyParamType::~TCopyParamType() +{ +} + +void TCopyParamType::Default() +{ + // when changing defaults, make sure GetInfoStr() can handle it + SetFileNameCase(ncNoChange); + SetPreserveReadOnly(false); + SetPreserveTime(true); + SetPreserveTimeDirs(false); + FRights.SetNumber(TRights::rfDefault); + SetPreserveRights(false); // Was True until #106 + SetIgnorePermErrors(false); + FAsciiFileMask.SetMasks(UnicodeString(L"*.*html; *.htm; *.txt; *.php; *.php3; *.cgi; *.c; *.cpp; *.h; *.pas; " + L"*.bas; *.tex; *.pl; *.js; .htaccess; *.xtml; *.css; *.cfg; *.ini; *.sh; *.xml")); + SetTransferMode(tmBinary); + SetAddXToDirectories(true); + SetResumeSupport(rsSmart); + SetResumeThreshold(100 * 1024); // (100 KB) + SetInvalidCharsReplacement(TokenReplacement); + SetLocalInvalidChars(LOCAL_INVALID_CHARS); + SetCalculateSize(true); + SetFileMask(L"*.*"); + GetIncludeFileMask().SetMasks(L""); + SetTransferSkipList(nullptr); + SetTransferResumeFile(L""); + SetClearArchive(false); + SetRemoveCtrlZ(false); + SetRemoveBOM(false); + SetCPSLimit(0); + SetNewerOnly(false); +} + +UnicodeString TCopyParamType::GetInfoStr( + const UnicodeString & Separator, intptr_t Attrs) const +{ + UnicodeString Result; + bool SomeAttrIncluded; + UnicodeString ScriptArgs; + bool NoScriptArgs; + UnicodeString AssemblyCode; + bool NoCodeProperties; + DoGetInfoStr( + Separator, Attrs, Result, SomeAttrIncluded, + UnicodeString(), ScriptArgs, NoScriptArgs, /*TAssemblyLanguage(0), AssemblyCode, */NoCodeProperties); + return Result; +} + +bool TCopyParamType::AnyUsableCopyParam(intptr_t Attrs) const +{ + UnicodeString Result; + bool SomeAttrIncluded; + UnicodeString ScriptArgs; + bool NoScriptArgs; + UnicodeString AssemblyCode; + bool NoCodeProperties; + DoGetInfoStr( + L";", Attrs, Result, SomeAttrIncluded, + UnicodeString(), ScriptArgs, NoScriptArgs, /*TAssemblyLanguage(0), AssemblyCode, */NoCodeProperties); + return SomeAttrIncluded; +} + +UnicodeString TCopyParamType::GenerateTransferCommandArgs(int Attrs, const UnicodeString & Link, bool & NoScriptArgs) const +{ + UnicodeString Result; + bool SomeAttrIncluded; + UnicodeString ScriptArgs; + UnicodeString AssemblyCode; + bool NoCodeProperties; + DoGetInfoStr( + L";", Attrs, Result, SomeAttrIncluded, + Link, ScriptArgs, NoScriptArgs, /*TAssemblyLanguage(0), AssemblyCode, */NoCodeProperties); + return ScriptArgs; +} + +void TCopyParamType::DoGetInfoStr( + const UnicodeString & Separator, intptr_t Options, + UnicodeString & Result, bool & SomeAttrIncluded, + const UnicodeString & Link, UnicodeString & ScriptArgs, bool & NoScriptArgs, /*TAssemblyLanguage Language, UnicodeString & AssemblyCode,*/ + bool & NoCodeProperties) const +{ + TCopyParamType Defaults; + + bool SomeAttrExcluded = false; + NoScriptArgs = false; + NoCodeProperties = false; + SomeAttrIncluded = false; + #define ADD(STR, EXCEPT) \ + if (FLAGCLEAR(Options, EXCEPT)) \ + { \ + AddToList(Result, (STR), Separator); \ + SomeAttrIncluded = true; \ + } \ + else \ + { \ + SomeAttrExcluded = true; \ + } + + bool AsciiFileMaskDiffers = (GetTransferMode() == tmAutomatic) && !(GetAsciiFileMask() == Defaults.GetAsciiFileMask()); + bool TransferModeDiffers = ((GetTransferMode() != Defaults.GetTransferMode()) || AsciiFileMaskDiffers); + + if (FLAGCLEAR(Options, cpaIncludeMaskOnly | cpaNoTransferMode)) + { + // Adding Transfer type unconditionally + bool FormatMask; + int Ident; + switch (GetTransferMode()) + { + case tmBinary: + FormatMask = false; + Ident = 2; + break; + case tmAscii: + FormatMask = false; + Ident = 3; + break; + case tmAutomatic: + default: + FormatMask = !(GetAsciiFileMask() == Defaults.GetAsciiFileMask()); + Ident = FormatMask ? 4 : 5; + break; + } + UnicodeString S = FORMAT(LoadStrPart(COPY_INFO_TRANSFER_TYPE2, 1).c_str(), + LoadStrPart(COPY_INFO_TRANSFER_TYPE2, Ident).c_str()); + if (FormatMask) + { + S = FORMAT(S.c_str(), GetAsciiFileMask().GetMasks().c_str()); + } + AddToList(Result, S, Separator); + + if (TransferModeDiffers) + { + ADD("", cpaIncludeMaskOnly | cpaNoTransferMode); + + /*ScriptArgs += RtfSwitchValue(TRANSFER_SWITCH, Link, TransferModeNames[TransferMode]); + const wchar_t * TransferModeMembers[] = { L"Binary", L"Ascii", L"Automatic" }; + AssemblyCode += AssemblyProperty( + Language, TransferOptionsClassName, L"TransferMode", L"TransferMode", TransferModeMembers[TransferMode], false); + if (AsciiFileMaskDiffers) + { + NoScriptArgs = true; + NoCodeProperties = true; + }*/ + } + } + else + { + if (TransferModeDiffers) + { + SomeAttrExcluded = true; + NoScriptArgs = true; + NoCodeProperties = true; + } + } + + if (GetFileNameCase() != Defaults.GetFileNameCase()) + { + ADD(FORMAT(LoadStrPart(COPY_INFO_FILENAME, 1).c_str(), + LoadStrPart(COPY_INFO_FILENAME, GetFileNameCase() + 2).c_str()), + cpaIncludeMaskOnly); + + NoScriptArgs = true; + NoCodeProperties = true; + } + + if ((GetInvalidCharsReplacement() == NoReplacement) != + (Defaults.GetInvalidCharsReplacement() == NoReplacement)) + { + DebugAssert(GetInvalidCharsReplacement() == NoReplacement); + if (GetInvalidCharsReplacement() == NoReplacement) + { + ADD(LoadStr(COPY_INFO_DONT_REPLACE_INV_CHARS).c_str(), cpaIncludeMaskOnly); + } + + NoScriptArgs = true; + NoCodeProperties = true; + } + + if ((GetPreserveRights() != Defaults.GetPreserveRights()) || + (GetPreserveRights() && + ((GetRights() != Defaults.GetRights()) || (GetAddXToDirectories() != Defaults.GetAddXToDirectories())))) + { + const int Except = cpaIncludeMaskOnly | cpaNoRights; + if (DebugAlwaysTrue(GetPreserveRights())) + { + UnicodeString RightsStr = GetRights().GetText(); + if (GetAddXToDirectories()) + { + RightsStr += L", " + LoadStr(COPY_INFO_ADD_X_TO_DIRS); + } + ADD(FORMAT(LoadStr(COPY_INFO_PERMISSIONS).c_str(), RightsStr.c_str()), + Except); + if (FLAGCLEAR(Options, Except)) + { +// ScriptArgs += RtfSwitchValue(PERMISSIONS_SWITCH, Link, Rights.Octal); + +// const UnicodeString FilePermissionsClassName = L"FilePermissions"; +// const bool Inline = true; +// UnicodeString FilePermissions = +// AssemblyNewClassInstanceStart(Language, FilePermissionsClassName, Inline) + +// AssemblyProperty(Language, FilePermissionsClassName, L"Octal", Rights.Octal, Inline) + +// AssemblyNewClassInstanceEnd(Language, Inline); + +// AssemblyCode += AssemblyPropertyRaw(Language, TransferOptionsClassName, L"FilePermissions", FilePermissions, false); + } + } + + if ((GetAddXToDirectories() != Defaults.GetAddXToDirectories()) && FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + + bool APreserveTimeDirs = GetPreserveTime() && GetPreserveTimeDirs(); + if ((GetPreserveTime() != Defaults.GetPreserveTime()) || (APreserveTimeDirs != Defaults.GetPreserveTimeDirs())) + { + bool AddPreserveTime = false; + UnicodeString Str = LoadStr(GetPreserveTime() ? COPY_INFO_TIMESTAMP : COPY_INFO_DONT_PRESERVE_TIME); + + const int ExceptDirs = cpaNoPreserveTimeDirs; + if (APreserveTimeDirs != Defaults.GetPreserveTimeDirs()) + { + if (DebugAlwaysTrue(GetPreserveTimeDirs())) + { + if (FLAGCLEAR(Options, ExceptDirs)) + { + Str = FMTLOAD(COPY_INFO_PRESERVE_TIME_DIRS, (Str)); + AddPreserveTime = true; + } + } + ADD("", ExceptDirs); + } + + const int Except = cpaIncludeMaskOnly | cpaNoPreserveTime; + if (GetPreserveTime() != Defaults.GetPreserveTime()) + { + if (FLAGCLEAR(Options, Except)) + { + AddPreserveTime = true; + } + ADD(L"", Except); + } + + if (AddPreserveTime) + { + AddToList(Result, Str, Separator); + } + + if (FLAGCLEAR(Options, Except)) + { + if (GetPreserveTime()) + { + if (GetPreserveTimeDirs() && FLAGCLEAR(Options, ExceptDirs)) + { + //ScriptArgs += RtfSwitchValue(PRESERVETIME_SWITCH, Link, PRESERVETIMEDIRS_SWITCH_VALUE); + NoCodeProperties = true; + } + else + { + DebugFail(); // should never get here + //ScriptArgs += RtfSwitch(PRESERVETIME_SWITCH, Link); + } + } + else + { +// ScriptArgs += RtfSwitch(NOPRESERVETIME_SWITCH, Link); +// AssemblyCode += AssemblyProperty(Language, TransferOptionsClassName, L"PreserveTimestamp", false, false); + } + } + } + + if ((GetPreserveRights() || GetPreserveTime()) && + (GetIgnorePermErrors() != Defaults.GetIgnorePermErrors())) + { + if (DebugAlwaysTrue(GetIgnorePermErrors())) + { + const int Except = cpaIncludeMaskOnly | cpaNoIgnorePermErrors; + ADD(LoadStr(COPY_INFO_IGNORE_PERM_ERRORS), Except); + if (FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + } + + if (GetPreserveReadOnly() != Defaults.GetPreserveReadOnly()) + { + if (DebugAlwaysTrue(GetPreserveReadOnly())) + { + const int Except = cpaIncludeMaskOnly | cpaNoPreserveReadOnly; + ADD(LoadStr(COPY_INFO_PRESERVE_READONLY), Except); + if (FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + } + + if (GetCalculateSize() != Defaults.GetCalculateSize()) + { + if (DebugAlwaysTrue(!GetCalculateSize())) + { + ADD(LoadStr(COPY_INFO_DONT_CALCULATE_SIZE), cpaIncludeMaskOnly); + // Always false in scripting, in assembly controlled by use of FileTransferProgress + } + } + + if (GetClearArchive() != Defaults.GetClearArchive()) + { + if (DebugAlwaysTrue(GetClearArchive())) + { + const int Except = cpaIncludeMaskOnly | cpaNoClearArchive; + ADD(LoadStr(COPY_INFO_CLEAR_ARCHIVE), Except); + if (FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + } + + if ((GetTransferMode() == tmAscii) || (GetTransferMode() == tmAutomatic)) + { + if (GetRemoveBOM() != Defaults.GetRemoveBOM()) + { + if (DebugAlwaysTrue(GetRemoveBOM())) + { + const int Except = cpaIncludeMaskOnly | cpaNoRemoveBOM | cpaNoTransferMode; + ADD(LoadStr(COPY_INFO_REMOVE_BOM), Except); + if (FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + } + + if (GetRemoveCtrlZ() != Defaults.GetRemoveCtrlZ()) + { + if (DebugAlwaysTrue(GetRemoveCtrlZ())) + { + const int Except = cpaIncludeMaskOnly | cpaNoRemoveCtrlZ | cpaNoTransferMode; + ADD(LoadStr(COPY_INFO_REMOVE_CTRLZ),Except); + if (FLAGCLEAR(Options, Except)) + { + NoScriptArgs = true; + NoCodeProperties = true; + } + } + } + } + + if (!(GetIncludeFileMask() == Defaults.GetIncludeFileMask())) + { + ADD(FORMAT(LoadStr(COPY_INFO_FILE_MASK).c_str(), GetIncludeFileMask().GetMasks().c_str()), + cpaNoIncludeMask); + +// ScriptArgs += RtfSwitch(FILEMASK_SWITCH, Link, IncludeFileMask.Masks); +// AssemblyCode += AssemblyProperty(Language, TransferOptionsClassName, L"FileMask", IncludeFileMask.Masks, false); + } + + DebugAssert(FTransferSkipList.get() == nullptr); + DebugAssert(FTransferResumeFile.IsEmpty()); + + if (GetCPSLimit() > 0) + { + intptr_t LimitKB = intptr_t(GetCPSLimit() / 1024); + ADD(FMTLOAD(COPY_INFO_CPS_LIMIT2, (LimitKB)), cpaIncludeMaskOnly); + +// ScriptArgs += RtfSwitch(SPEED_SWITCH, Link, LimitKB); +// AssemblyCode += AssemblyProperty(Language, TransferOptionsClassName, L"Speed", LimitKB, false); + } + + if (GetNewerOnly() != Defaults.GetNewerOnly()) + { + if (DebugAlwaysTrue(GetNewerOnly())) + { + const int Except = cpaIncludeMaskOnly | cpaNoNewerOnly; + ADD(StripHotkey(LoadStr(COPY_PARAM_NEWER_ONLY)), Except); + if (FLAGCLEAR(Options, Except)) + { +// ScriptArgs += RtfSwitch(NEWERONLY_SWICH, Link); + NoCodeProperties = true; + } + } + } + + bool ResumeThresholdDiffers = ((GetResumeSupport() == rsSmart) && (GetResumeThreshold() != Defaults.GetResumeThreshold())); + if (((GetResumeSupport() != Defaults.GetResumeSupport()) || ResumeThresholdDiffers) && + (GetTransferMode() != tmAscii) && FLAGCLEAR(Options, cpaNoResumeSupport)) + { + UnicodeString Value; + UnicodeString CodeState; + intptr_t ResumeThresholdKB = (GetResumeThreshold() / 1024); + switch (GetResumeSupport()) + { + case rsOff: + Value = ToggleNames[ToggleOff]; + CodeState = L"Off"; + break; + + case rsOn: + Value = ToggleNames[ToggleOn]; + CodeState = L"On"; + break; + + case rsSmart: + Value = IntToStr(ResumeThresholdKB); + break; + } +// ScriptArgs += RtfSwitchValue(RESUMESUPPORT_SWITCH, Link, Value); + + const UnicodeString ResumeSupportClassName = L"TransferResumeSupport"; +// const bool Inline = true; +// UnicodeString ResumeSupportCode = +// AssemblyNewClassInstanceStart(Language, ResumeSupportClassName, Inline); + if (GetResumeSupport() == rsSmart) + { +// ResumeSupportCode += AssemblyProperty(Language, ResumeSupportClassName, L"Threshold", ResumeThresholdKB, Inline); + } + else + { +// ResumeSupportCode += AssemblyProperty(Language, ResumeSupportClassName, L"State", L"TransferResumeSupportState", CodeState, Inline); + } +// ResumeSupportCode += AssemblyNewClassInstanceEnd(Language, Inline); + +// AssemblyCode += AssemblyPropertyRaw(Language, TransferOptionsClassName, L"ResumeSupport", ResumeSupportCode, false); + } + + if (SomeAttrExcluded) + { + Result += (Result.IsEmpty() ? UnicodeString() : Separator) + + FORMAT(LoadStrPart(COPY_INFO_NOT_USABLE, 1).c_str(), + LoadStrPart(COPY_INFO_NOT_USABLE, (SomeAttrIncluded ? 2 : 3)).c_str()); + } + else if (Result.IsEmpty()) + { + Result = LoadStr(COPY_INFO_DEFAULT); + } + #undef ADD +} + +void TCopyParamType::Assign(const TCopyParamType * Source) +{ + DebugAssert(Source != nullptr); + #define COPY(Prop) Set ## Prop(Source->Get ## Prop()) + COPY(FileNameCase); + COPY(PreserveReadOnly); + COPY(PreserveTime); + COPY(PreserveTimeDirs); + COPY(Rights); + COPY(AsciiFileMask); + COPY(TransferMode); + COPY(AddXToDirectories); + COPY(PreserveRights); + COPY(IgnorePermErrors); + COPY(ResumeSupport); + COPY(ResumeThreshold); + COPY(InvalidCharsReplacement); + COPY(LocalInvalidChars); + COPY(CalculateSize); + COPY(FileMask); + COPY(IncludeFileMask); + COPY(TransferSkipList); + COPY(TransferResumeFile); + COPY(ClearArchive); + COPY(RemoveCtrlZ); + COPY(RemoveBOM); + COPY(CPSLimit); + COPY(NewerOnly); + #undef COPY +} + +TCopyParamType & TCopyParamType::operator =(const TCopyParamType & rhp) +{ + Assign(&rhp); + return *this; +} + +void TCopyParamType::SetLocalInvalidChars(const UnicodeString & Value) +{ + if (Value != GetLocalInvalidChars()) + { + FLocalInvalidChars = Value; + FTokenizibleChars = FLocalInvalidChars; // + TokenPrefix; + } +} + +bool TCopyParamType::GetReplaceInvalidChars() const +{ + return (GetInvalidCharsReplacement() != NoReplacement); +} + +void TCopyParamType::SetReplaceInvalidChars(bool Value) +{ + if (GetReplaceInvalidChars() != Value) + { + SetInvalidCharsReplacement(Value ? TokenReplacement : NoReplacement); + } +} + +UnicodeString TCopyParamType::ValidLocalFileName(const UnicodeString & AFileName) const +{ + return ::ValidLocalFileName(AFileName, GetInvalidCharsReplacement(), FTokenizibleChars, LOCAL_INVALID_CHARS); +} + +UnicodeString TCopyParamType::RestoreChars(const UnicodeString & AFileName) const +{ + UnicodeString FileName = AFileName; + if (GetInvalidCharsReplacement() == TokenReplacement) + { + wchar_t * InvalidChar = const_cast(FileName.c_str()); + while ((InvalidChar = wcschr(InvalidChar, TokenPrefix)) != nullptr) + { + intptr_t Index = InvalidChar - FileName.c_str() + 1; + if (FileName.Length() >= Index + 2) + { + UnicodeString Hex = FileName.SubString(Index + 1, 2); + wchar_t Char = static_cast(HexToByte(Hex)); + if ((Char != L'\0') && + ((FTokenizibleChars.Pos(Char) > 0) || + (((Char == L' ') || (Char == L'.')) && (Index == FileName.Length() - 2)))) + { + FileName[Index] = Char; + FileName.Delete(Index + 1, 2); + InvalidChar = const_cast(FileName.c_str() + Index); + } + else if ((Hex == L"00") && + ((Index == FileName.Length() - 2) || (FileName[Index + 3] == L'.')) && + IsReservedName(FileName.SubString(1, Index - 1) + FileName.SubString(Index + 3, FileName.Length() - Index - 3 + 1))) + { + FileName.Delete(Index, 3); + InvalidChar = const_cast(FileName.c_str() + Index - 1); + } + else + { + InvalidChar++; + } + } + else + { + InvalidChar++; + } + } + } + return FileName; +} + +UnicodeString TCopyParamType::ValidLocalPath(const UnicodeString & APath) const +{ + UnicodeString Result; + UnicodeString Path = APath; + while (!Path.IsEmpty()) + { + if (!Result.IsEmpty()) + { + Result += L"\\"; + } + Result += ValidLocalFileName(CutToChar(Path, L'\\', false)); + } + return Result; +} + +UnicodeString TCopyParamType::ChangeFileName(const UnicodeString & AFileName, + TOperationSide Side, bool FirstLevel) const +{ + UnicodeString FileName = AFileName; + if (FirstLevel) + { + FileName = MaskFileName(FileName, GetFileMask()); + } + switch (GetFileNameCase()) + { + case ncUpperCase: + FileName = FileName.UpperCase(); + break; + case ncLowerCase: + FileName = FileName.LowerCase(); + break; + case ncFirstUpperCase: + FileName = FileName.SubString(1, 1).UpperCase() + + FileName.SubString(2, FileName.Length() - 1).LowerCase(); + break; + case ncLowerCaseShort: + if ((FileName.Length() <= 12) && (FileName.Pos(L".") <= 9) && + (FileName == FileName.UpperCase())) + { + FileName = FileName.LowerCase(); + } + break; + case ncNoChange: + default: + /*nothing*/ + break; + } + if (Side == osRemote) + { + FileName = ValidLocalFileName(FileName); + } + else + { + FileName = RestoreChars(FileName); + } + return FileName; +} + +bool TCopyParamType::UseAsciiTransfer(const UnicodeString & AFileName, + TOperationSide Side, const TFileMasks::TParams & Params) const +{ + switch (GetTransferMode()) + { + case tmBinary: + return false; + case tmAscii: + return true; + case tmAutomatic: + return GetAsciiFileMask().Matches(AFileName, (Side == osLocal), + false, &Params); + default: + DebugFail(); + return false; + } +} + +TRights TCopyParamType::RemoteFileRights(uintptr_t Attrs) const +{ + TRights R = GetRights(); + if ((Attrs & faDirectory) && GetAddXToDirectories()) + R.AddExecute(); + return R; +} + +UnicodeString TCopyParamType::GetLogStr() const +{ + wchar_t CaseC[] = L"NULFS"; + wchar_t ModeC[] = L"BAM"; + wchar_t ResumeC[] = L"YSN"; + // OpenArray (ARRAYOFCONST) supports only up to 19 arguments, so we had to split it + return + FORMAT( + L" PrTime: %s%s; PrRO: %s; Rght: %s; PrR: %s (%s); FnCs: %c; RIC: %c; " + L"Resume: %c (%d); CalcS: %s; Mask: %s\n", + BooleanToEngStr(GetPreserveTime()).c_str(), + UnicodeString(GetPreserveTime() && GetPreserveTimeDirs() ? L"+Dirs" : L"").c_str(), + BooleanToEngStr(GetPreserveReadOnly()).c_str(), + GetRights().GetText().c_str(), + BooleanToEngStr(GetPreserveRights()).c_str(), + BooleanToEngStr(GetIgnorePermErrors()).c_str(), + CaseC[GetFileNameCase()], + CharToHex(GetInvalidCharsReplacement()).c_str(), + ResumeC[GetResumeSupport()], + (int)GetResumeThreshold(), + BooleanToEngStr(GetCalculateSize()).c_str(), + GetFileMask().c_str()) + + FORMAT( + L" TM: %c; ClAr: %s; RemEOF: %s; RemBOM: %s; CPS: %u; NewerOnly: %s; InclM: %s; ResumeL: %d\n" + L" AscM: %s\n", + ModeC[GetTransferMode()], + BooleanToEngStr(GetClearArchive()).c_str(), + BooleanToEngStr(GetRemoveCtrlZ()).c_str(), + BooleanToEngStr(GetRemoveBOM()).c_str(), + int(GetCPSLimit()), + BooleanToEngStr(GetNewerOnly()).c_str(), + GetIncludeFileMask().GetMasks().c_str(), + ((FTransferSkipList.get() != nullptr) ? FTransferSkipList->GetCount() : 0) + (!FTransferResumeFile.IsEmpty() ? 1 : 0), + GetAsciiFileMask().GetMasks().c_str()); +} + +DWORD TCopyParamType::LocalFileAttrs(const TRights & Rights) const +{ + DWORD Result = 0; + if (GetPreserveReadOnly() && !Rights.GetRight(TRights::rrUserWrite)) + { + Result |= faReadOnly; + } + return Result; +} + +bool TCopyParamType::AllowResume(int64_t Size) const +{ + switch (GetResumeSupport()) + { + case rsOn: + return true; + case rsOff: + return false; + case rsSmart: + return (Size >= GetResumeThreshold()); + default: + DebugFail(); + return false; + } +} + +bool TCopyParamType::AllowAnyTransfer() const +{ + return + GetIncludeFileMask().GetMasks().IsEmpty() && + ((FTransferSkipList.get() == nullptr) || (FTransferSkipList->GetCount() == 0)) && + FTransferResumeFile.IsEmpty(); +} + +bool TCopyParamType::AllowTransfer(const UnicodeString & AFileName, + TOperationSide Side, bool Directory, const TFileMasks::TParams & Params) const +{ + bool Result = true; + if (!GetIncludeFileMask().GetMasks().IsEmpty()) + { + Result = GetIncludeFileMask().Matches(AFileName, (Side == osLocal), + Directory, &Params); + } + return Result; +} + +bool TCopyParamType::SkipTransfer( + const UnicodeString & AFileName, bool Directory) const +{ + bool Result = false; + // we deliberately do not filter directories, as path is added to resume list + // when a transfer of file or directory is started, + // so for directories we need to recurse and check every single file + if (!Directory && FTransferSkipList.get() != nullptr) + { + Result = (FTransferSkipList->IndexOf(AFileName) >= 0); + } + return Result; +} + +bool TCopyParamType::ResumeTransfer(const UnicodeString & AFileName) const +{ + // Returning true has the same effect as cpResume + return + (AFileName == FTransferResumeFile) && + DebugAlwaysTrue(!FTransferResumeFile.IsEmpty()); +} + +TStrings * TCopyParamType::GetTransferSkipList() const +{ + return FTransferSkipList.get(); +} + +void TCopyParamType::SetTransferSkipList(TStrings * Value) +{ + if ((Value == nullptr) || (Value->GetCount() == 0)) + { + FTransferSkipList.reset(nullptr); + } + else + { + FTransferSkipList.reset(new TStringList()); + FTransferSkipList->AddStrings(Value); + FTransferSkipList->SetSorted(true); + } +} + +void TCopyParamType::Load(THierarchicalStorage * Storage) +{ + SetAddXToDirectories(Storage->ReadBool("AddXToDirectories", GetAddXToDirectories())); + GetAsciiFileMask().SetMasks(Storage->ReadString("Masks", GetAsciiFileMask().GetMasks())); + SetFileNameCase(static_cast(Storage->ReadInteger("FileNameCase", GetFileNameCase()))); + SetPreserveReadOnly(Storage->ReadBool("PreserveReadOnly", GetPreserveReadOnly())); + SetPreserveTime(Storage->ReadBool("PreserveTime", GetPreserveTime())); + SetPreserveTimeDirs(Storage->ReadBool("PreserveTimeDirs", GetPreserveTimeDirs())); + SetPreserveRights(Storage->ReadBool("PreserveRights", GetPreserveRights())); + SetIgnorePermErrors(Storage->ReadBool("IgnorePermErrors", GetIgnorePermErrors())); + FRights.SetText(Storage->ReadString("Text", GetRights().GetText())); + SetTransferMode(static_cast(Storage->ReadInteger("TransferMode", GetTransferMode()))); + SetResumeSupport(static_cast(Storage->ReadInteger("ResumeSupport", GetResumeSupport()))); + SetResumeThreshold(Storage->ReadInt64("ResumeThreshold", GetResumeThreshold())); + SetInvalidCharsReplacement(static_cast(Storage->ReadInteger("ReplaceInvalidChars", + static_cast(GetInvalidCharsReplacement())))); + SetLocalInvalidChars(Storage->ReadString("LocalInvalidChars", GetLocalInvalidChars())); + SetCalculateSize(Storage->ReadBool("CalculateSize", GetCalculateSize())); + if (Storage->ValueExists("IncludeFileMask")) + { + GetIncludeFileMask().SetMasks(Storage->ReadString("IncludeFileMask", GetIncludeFileMask().GetMasks())); + } + else if (Storage->ValueExists("ExcludeFileMask")) + { + UnicodeString ExcludeFileMask = Storage->ReadString("ExcludeFileMask", UnicodeString()); + if (!ExcludeFileMask.IsEmpty()) + { + bool NegativeExclude = Storage->ReadBool("NegativeExclude", false); + if (NegativeExclude) + { + GetIncludeFileMask().SetMasks(ExcludeFileMask); + } + // convert at least simple cases to new format + else if (ExcludeFileMask.Pos(INCLUDE_EXCLUDE_FILE_MASKS_DELIMITER) == 0) + { + GetIncludeFileMask().SetMasks(UnicodeString(INCLUDE_EXCLUDE_FILE_MASKS_DELIMITER) + ExcludeFileMask); + } + } + } + SetTransferSkipList(nullptr); + SetTransferResumeFile(L""); + SetClearArchive(Storage->ReadBool("ClearArchive", GetClearArchive())); + SetRemoveCtrlZ(Storage->ReadBool("RemoveCtrlZ", GetRemoveCtrlZ())); + SetRemoveBOM(Storage->ReadBool("RemoveBOM", GetRemoveBOM())); + SetCPSLimit(Storage->ReadInteger("CPSLimit", GetCPSLimit())); + SetNewerOnly(Storage->ReadBool("NewerOnly", GetNewerOnly())); +} + +void TCopyParamType::Save(THierarchicalStorage * Storage) const +{ + Storage->WriteBool("AddXToDirectories", GetAddXToDirectories()); + Storage->WriteString("Masks", GetAsciiFileMask().GetMasks()); + Storage->WriteInteger("FileNameCase", GetFileNameCase()); + Storage->WriteBool("PreserveReadOnly", GetPreserveReadOnly()); + Storage->WriteBool("PreserveTime", GetPreserveTime()); + Storage->WriteBool("PreserveTimeDirs", GetPreserveTimeDirs()); + Storage->WriteBool("PreserveRights", GetPreserveRights()); + Storage->WriteBool("IgnorePermErrors", GetIgnorePermErrors()); + Storage->WriteString("Text", GetRights().GetText()); + Storage->WriteInteger("TransferMode", GetTransferMode()); + Storage->WriteInteger("ResumeSupport", GetResumeSupport()); + + Storage->WriteInt64("ResumeThreshold", GetResumeThreshold()); + Storage->WriteInteger("ReplaceInvalidChars", static_cast(GetInvalidCharsReplacement())); + Storage->WriteString("LocalInvalidChars", GetLocalInvalidChars()); + Storage->WriteBool("CalculateSize", GetCalculateSize()); + Storage->WriteString("IncludeFileMask", GetIncludeFileMask().GetMasks()); + Storage->DeleteValue("ExcludeFileMask"); // obsolete + Storage->DeleteValue("NegativeExclude"); // obsolete + DebugAssert(FTransferSkipList.get() == nullptr); + DebugAssert(FTransferResumeFile.IsEmpty()); + Storage->WriteBool("ClearArchive", GetClearArchive()); + Storage->WriteBool("RemoveCtrlZ", GetRemoveCtrlZ()); + Storage->WriteBool("RemoveBOM", GetRemoveBOM()); + Storage->WriteInteger("CPSLimit", GetCPSLimit()); + Storage->WriteBool("NewerOnly", GetNewerOnly()); +} + +#define C(Property) (Get ## Property() == rhp.Get ## Property()) +bool TCopyParamType::operator==(const TCopyParamType & rhp) const +{ + DebugAssert(FTransferSkipList.get() == nullptr); + DebugAssert(FTransferResumeFile.IsEmpty()); + DebugAssert(rhp.FTransferSkipList.get() == nullptr); + DebugAssert(rhp.FTransferResumeFile.IsEmpty()); + return + C(AddXToDirectories) && + C(AsciiFileMask) && + C(FileNameCase) && + C(PreserveReadOnly) && + C(PreserveTime) && + C(PreserveTimeDirs) && + C(PreserveRights) && + C(IgnorePermErrors) && + C(Rights) && + C(TransferMode) && + C(ResumeSupport) && + C(ResumeThreshold) && + C(InvalidCharsReplacement) && + C(LocalInvalidChars) && + C(CalculateSize) && + C(IncludeFileMask) && + C(ClearArchive) && + C(RemoveCtrlZ) && + C(RemoveBOM) && + C(CPSLimit) && + C(NewerOnly) && + true; +} +#undef C + +static bool TryGetSpeedLimit(const UnicodeString & Text, uintptr_t & Speed) +{ + bool Result; + if (AnsiSameText(Text, LoadStr(SPEED_UNLIMITED))) + { + Speed = 0; + Result = true; + } + else + { + int64_t SSpeed; + Result = TryStrToInt(Text, SSpeed) && (SSpeed >= 0); + if (Result) + { + Speed = SSpeed * 1024; + } + } + return Result; +} + +uintptr_t GetSpeedLimit(const UnicodeString & Text) +{ + uintptr_t Speed = 0; + if (!TryGetSpeedLimit(Text, Speed)) + { + throw Exception(FMTLOAD(SPEED_INVALID, Text.c_str())); + } + return Speed; +} + +UnicodeString SetSpeedLimit(uintptr_t Limit) +{ + UnicodeString Text; + if (Limit == 0) + { + Text = LoadStr(SPEED_UNLIMITED); + } + else + { + Text = ::IntToStr(Limit / 1024); + } + return Text; +} + +void CopySpeedLimits(TStrings * Source, TStrings * Dest) +{ + std::unique_ptr Temp(new TStringList()); + + bool Unlimited = false; + for (intptr_t Index = 0; Index < Source->GetCount(); ++Index) + { + UnicodeString Text = Source->GetString(Index); + uintptr_t Speed; + bool Valid = TryGetSpeedLimit(Text, Speed); + if ((!Valid || (Speed == 0)) && !Unlimited) + { + Temp->Add(LoadStr(SPEED_UNLIMITED)); + Unlimited = true; + } + else if (Valid && (Speed > 0)) + { + Temp->Add(Text); + } + } + + if (!Unlimited) + { + Temp->Insert(0, LoadStr(SPEED_UNLIMITED)); + } + + Dest->Assign(Temp.get()); +} + +NB_IMPLEMENT_CLASS(TCopyParamType, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/CopyParam.h b/netbox/src/core/CopyParam.h new file mode 100644 index 000000000..e4a566a52 --- /dev/null +++ b/netbox/src/core/CopyParam.h @@ -0,0 +1,219 @@ +#pragma once + +#include "FileMasks.h" +#include "RemoteFiles.h" + +// When adding new options, mind TCopyParamType::GetLogStr() +enum TOperationSide +{ + osLocal, + osRemote, + osCurrent, +}; + +enum TFileNameCase +{ + ncNoChange, + ncUpperCase, + ncLowerCase, + ncFirstUpperCase, + ncLowerCaseShort, +}; + +// TScript::OptionProc depend on the order +enum TTransferMode +{ + tmBinary, + tmAscii, + tmAutomatic, +}; + +enum TResumeSupport +{ + rsOn, + rsSmart, + rsOff, +}; + +class THierarchicalStorage; +const int cpaIncludeMaskOnly = 0x01; +const int cpaNoTransferMode = 0x02; +const int cpaNoIncludeMask = 0x04; +const int cpaNoClearArchive = 0x08; +const int cpaNoPreserveTime = 0x10; +const int cpaNoRights = 0x20; +const int cpaNoPreserveReadOnly = 0x40; +const int cpaNoIgnorePermErrors = 0x80; +const int cpaNoNewerOnly = 0x100; +const int cpaNoRemoveCtrlZ = 0x200; +const int cpaNoRemoveBOM = 0x400; +const int cpaNoPreserveTimeDirs = 0x800; +const int cpaNoResumeSupport = 0x1000; +//--------------------------------------------------------------------------- +struct TUsableCopyParamAttrs +{ + int General; + int Upload; + int Download; +}; + +class TCopyParamType : public TObject +{ +NB_DECLARE_CLASS(TCopyParamType) +private: + TFileMasks FAsciiFileMask; + TFileNameCase FFileNameCase; + bool FPreserveReadOnly; + bool FPreserveTime; + bool FPreserveTimeDirs; + TRights FRights; + TTransferMode FTransferMode; + bool FAddXToDirectories; + bool FPreserveRights; + bool FIgnorePermErrors; + TResumeSupport FResumeSupport; + int64_t FResumeThreshold; + wchar_t FInvalidCharsReplacement; + UnicodeString FLocalInvalidChars; + UnicodeString FTokenizibleChars; + bool FCalculateSize; + UnicodeString FFileMask; + TFileMasks FIncludeFileMask; + std::unique_ptr FTransferSkipList; + UnicodeString FTransferResumeFile; + bool FClearArchive; + bool FRemoveCtrlZ; + bool FRemoveBOM; + uintptr_t FCPSLimit; + bool FNewerOnly; + +public: + static const wchar_t TokenPrefix = L'%'; + static const wchar_t NoReplacement = wchar_t(0); + static const wchar_t TokenReplacement = wchar_t(1); + +public: + void SetLocalInvalidChars(const UnicodeString & Value); + bool GetReplaceInvalidChars() const; + void SetReplaceInvalidChars(bool Value); + UnicodeString RestoreChars(const UnicodeString & AFileName) const; + void DoGetInfoStr(const UnicodeString & Separator, intptr_t Attrs, + UnicodeString & Result, bool & SomeAttrIncluded, + const UnicodeString & Link, UnicodeString & ScriptArgs, bool & NoScriptArgs, + /*TAssemblyLanguage Language, UnicodeString & AssemblyCode, */bool & NoCodeProperties) const; + TStrings * GetTransferSkipList() const; + void SetTransferSkipList(TStrings * Value); + UnicodeString GetTransferResumeFile() const { return FTransferResumeFile; } + void SetTransferResumeFile(const UnicodeString & Value) { FTransferResumeFile = Value; } + +public: + TCopyParamType(); + TCopyParamType(const TCopyParamType & Source); + virtual ~TCopyParamType(); + TCopyParamType & operator =(const TCopyParamType & rhs); + virtual void Assign(const TCopyParamType * Source); + virtual void Default(); + UnicodeString ChangeFileName(const UnicodeString & AFileName, + TOperationSide Side, bool FirstLevel) const; + DWORD LocalFileAttrs(const TRights & Rights) const; + TRights RemoteFileRights(uintptr_t Attrs) const; + bool UseAsciiTransfer(const UnicodeString & AFileName, TOperationSide Side, + const TFileMasks::TParams & Params) const; + bool AllowResume(int64_t Size) const; + bool ResumeTransfer(const UnicodeString & AFileName) const; + UnicodeString ValidLocalFileName(const UnicodeString & AFileName) const; + UnicodeString ValidLocalPath(const UnicodeString & APath) const; + bool AllowAnyTransfer() const; + bool AllowTransfer(const UnicodeString & AFileName, TOperationSide Side, + bool Directory, const TFileMasks::TParams & Params) const; + bool SkipTransfer(const UnicodeString & AFileName, bool Directory) const; + + void Load(THierarchicalStorage * Storage); + void Save(THierarchicalStorage * Storage) const; + UnicodeString GetInfoStr(const UnicodeString & Separator, intptr_t Options) const; + bool AnyUsableCopyParam(intptr_t Attrs) const; + UnicodeString GenerateTransferCommandArgs( + int Attrs, const UnicodeString & Link, bool & NoScriptArgs) const; + //UnicodeString GenerateAssemblyCode(TAssemblyLanguage Language, int Attrs, bool & NoCodeProperties) const; + + bool operator==(const TCopyParamType & rhp) const; + + /*__property TFileMasks AsciiFileMask = { read = FAsciiFileMask, write = FAsciiFileMask }; + __property TFileNameCase FileNameCase = { read = FFileNameCase, write = FFileNameCase }; + __property bool PreserveReadOnly = { read = FPreserveReadOnly, write = FPreserveReadOnly }; + __property bool PreserveTime = { read = FPreserveTime, write = FPreserveTime }; + __property bool PreserveTimeDirs = { read = FPreserveTimeDirs, write = FPreserveTimeDirs }; + __property TRights Rights = { read = FRights, write = FRights }; + __property TTransferMode TransferMode = { read = FTransferMode, write = FTransferMode }; + __property UnicodeString LogStr = { read=GetLogStr }; + __property bool AddXToDirectories = { read=FAddXToDirectories, write=FAddXToDirectories }; + __property bool PreserveRights = { read = FPreserveRights, write = FPreserveRights }; + __property bool IgnorePermErrors = { read = FIgnorePermErrors, write = FIgnorePermErrors }; + __property TResumeSupport ResumeSupport = { read = FResumeSupport, write = FResumeSupport }; + __property __int64 ResumeThreshold = { read = FResumeThreshold, write = FResumeThreshold }; + __property wchar_t InvalidCharsReplacement = { read = FInvalidCharsReplacement, write = FInvalidCharsReplacement }; + __property bool ReplaceInvalidChars = { read = GetReplaceInvalidChars, write = SetReplaceInvalidChars }; + __property UnicodeString LocalInvalidChars = { read = FLocalInvalidChars, write = SetLocalInvalidChars }; + __property bool CalculateSize = { read = FCalculateSize, write = FCalculateSize }; + __property UnicodeString FileMask = { read = FFileMask, write = FFileMask }; + __property TFileMasks IncludeFileMask = { read = FIncludeFileMask, write = FIncludeFileMask }; + __property TStrings * TransferSkipList = { read = GetTransferSkipList, write = SetTransferSkipList }; + __property UnicodeString TransferResumeFile = { read = FTransferResumeFile, write = FTransferResumeFile }; + __property bool ClearArchive = { read = FClearArchive, write = FClearArchive }; + __property bool RemoveCtrlZ = { read = FRemoveCtrlZ, write = FRemoveCtrlZ }; + __property bool RemoveBOM = { read = FRemoveBOM, write = FRemoveBOM }; + __property unsigned long CPSLimit = { read = FCPSLimit, write = FCPSLimit }; + __property bool NewerOnly = { read = FNewerOnly, write = FNewerOnly };*/ + const TFileMasks & GetAsciiFileMask() const { return FAsciiFileMask; } + TFileMasks & GetAsciiFileMask() { return FAsciiFileMask; } + void SetAsciiFileMask(const TFileMasks & Value) { FAsciiFileMask = Value; } + const TFileNameCase & GetFileNameCase() const { return FFileNameCase; } + void SetFileNameCase(TFileNameCase Value) { FFileNameCase = Value; } + bool GetPreserveReadOnly() const { return FPreserveReadOnly; } + void SetPreserveReadOnly(bool Value) { FPreserveReadOnly = Value; } + bool GetPreserveTime() const { return FPreserveTime; } + void SetPreserveTime(bool Value) { FPreserveTime = Value; } + bool GetPreserveTimeDirs() const { return FPreserveTimeDirs; } + void SetPreserveTimeDirs(bool Value) { FPreserveTimeDirs = Value; } + const TRights & GetRights() const { return FRights; } + TRights & GetRights() { return FRights; } + void SetRights(const TRights & Value) { FRights.Assign(&Value); } + TTransferMode GetTransferMode() const { return FTransferMode; } + void SetTransferMode(TTransferMode Value) { FTransferMode = Value; } + UnicodeString GetLogStr() const; + bool GetAddXToDirectories() const { return FAddXToDirectories; } + void SetAddXToDirectories(bool Value) { FAddXToDirectories = Value; } + bool GetPreserveRights() const { return FPreserveRights; } + void SetPreserveRights(bool Value) { FPreserveRights = Value; } + bool GetIgnorePermErrors() const { return FIgnorePermErrors; } + void SetIgnorePermErrors(bool Value) { FIgnorePermErrors = Value; } + TResumeSupport GetResumeSupport() const { return FResumeSupport; } + void SetResumeSupport(TResumeSupport Value) { FResumeSupport = Value; } + int64_t GetResumeThreshold() const { return FResumeThreshold; } + void SetResumeThreshold(int64_t Value) { FResumeThreshold = Value; } + wchar_t GetInvalidCharsReplacement() const { return FInvalidCharsReplacement; } + void SetInvalidCharsReplacement(wchar_t Value) { FInvalidCharsReplacement = Value; } + UnicodeString GetLocalInvalidChars() const { return FLocalInvalidChars; } + bool GetCalculateSize() const { return FCalculateSize; } + void SetCalculateSize(bool Value) { FCalculateSize = Value; } + UnicodeString GetFileMask() const { return FFileMask; } + void SetFileMask(const UnicodeString & Value) { FFileMask = Value; } + const TFileMasks & GetIncludeFileMask() const { return FIncludeFileMask; } + TFileMasks & GetIncludeFileMask() { return FIncludeFileMask; } + void SetIncludeFileMask(const TFileMasks & Value) { FIncludeFileMask = Value; } + bool GetClearArchive() const { return FClearArchive; } + void SetClearArchive(bool Value) { FClearArchive = Value; } + bool GetRemoveCtrlZ() const { return FRemoveCtrlZ; } + void SetRemoveCtrlZ(bool Value) { FRemoveCtrlZ = Value; } + bool GetRemoveBOM() const { return FRemoveBOM; } + void SetRemoveBOM(bool Value) { FRemoveBOM = Value; } + uintptr_t GetCPSLimit() const { return FCPSLimit; } + void SetCPSLimit(uintptr_t Value) { FCPSLimit = Value; } + bool GetNewerOnly() const { return FNewerOnly; } + void SetNewerOnly(bool Value) { FNewerOnly = Value; } + +}; + +uintptr_t GetSpeedLimit(const UnicodeString & Text); +UnicodeString SetSpeedLimit(uintptr_t Limit); +void CopySpeedLimits(TStrings * Source, TStrings * Dest); diff --git a/netbox/src/core/CoreMain.cpp b/netbox/src/core/CoreMain.cpp new file mode 100644 index 000000000..996d846f2 --- /dev/null +++ b/netbox/src/core/CoreMain.cpp @@ -0,0 +1,259 @@ +#include +#pragma hdrstop + +#include + +#include "CoreMain.h" +#include "Interface.h" +#include "Configuration.h" +#include "PuttyIntf.h" +#include "Cryptography.h" +#include +#ifndef NO_FILEZILLA +#ifndef __linux__ +#include "FileZillaIntf.h" +#else +#include "libfilezilla/libfilezilla.hpp" +#endif +#endif +#include "WebDAVFileSystem.h" + +TStoredSessionList * StoredSessions = nullptr; + +TQueryButtonAlias::TQueryButtonAlias() : + Button(0), + OnClick(nullptr), + GroupWith(-1), + Default(false), + GrouppedShiftState(ssShift), + ElevationRequired(false) +{ +} + +TQueryParams::TQueryParams(uintptr_t AParams, const UnicodeString & AHelpKeyword) : + Aliases(nullptr), + AliasesCount(0), + Params(AParams), + Timer(0), + TimerEvent(nullptr), + TimerMessage(L""), + TimerAnswers(0), + TimerQueryType(static_cast(-1)), + Timeout(0), + TimeoutAnswer(0), + NoBatchAnswers(0), + HelpKeyword(AHelpKeyword) +{ +} + +TQueryParams::TQueryParams(const TQueryParams & Source) +{ + Assign(Source); +} + +void TQueryParams::Assign(const TQueryParams & Source) +{ + Params = Source.Params; + Aliases = Source.Aliases; + AliasesCount = Source.AliasesCount; + Timer = Source.Timer; + TimerEvent = Source.TimerEvent; + TimerMessage = Source.TimerMessage; + TimerAnswers = Source.TimerAnswers; + TimerQueryType = Source.TimerQueryType; + Timeout = Source.Timeout; + TimeoutAnswer = Source.TimeoutAnswer; + NoBatchAnswers = Source.NoBatchAnswers; + HelpKeyword = Source.HelpKeyword; +} + +TQueryParams & TQueryParams::operator=(const TQueryParams & other) +{ + Assign(other); + return *this; +} + +bool IsAuthenticationPrompt(TPromptKind Kind) +{ + return + (Kind == pkUserName) || (Kind == pkPassphrase) || (Kind == pkTIS) || + (Kind == pkCryptoCard) || (Kind == pkKeybInteractive) || + (Kind == pkPassword) || (Kind == pkNewPassword); +} + +bool IsPasswordOrPassphrasePrompt(TPromptKind Kind, TStrings * Prompts) +{ + return + (Prompts->GetCount() == 1) && FLAGCLEAR(reinterpret_cast(Prompts->GetObj(0)), pupEcho) && + ((Kind == pkPassword) || (Kind == pkPassphrase) || (Kind == pkKeybInteractive) || + (Kind == pkTIS) || (Kind == pkCryptoCard)); +} + +bool IsPasswordPrompt(TPromptKind Kind, TStrings * Prompts) +{ + return + IsPasswordOrPassphrasePrompt(Kind, Prompts) && + (Kind != pkPassphrase); +} + +TConfiguration * GetConfiguration() +{ + static TConfiguration * Configuration = nullptr; + if (Configuration == nullptr) + { + Configuration = CreateConfiguration(); + } + return Configuration; +} + +void DeleteConfiguration() +{ + static bool ConfigurationDeleted = false; + if (!ConfigurationDeleted) + { + TConfiguration * Conf = GetConfiguration(); + SAFE_DESTROY(Conf); + ConfigurationDeleted = true; + } +} + +void CoreLoad() +{ + bool SessionList = false; + std::unique_ptr SessionsStorage(GetConfiguration()->CreateStorage(SessionList)); + THierarchicalStorage * ConfigStorage = nullptr; + std::unique_ptr ConfigStorageAuto; + if (!SessionList) + { + // can reuse this for configuration + ConfigStorage = SessionsStorage.get(); + } + else + { + ConfigStorageAuto.reset(GetConfiguration()->CreateConfigStorage()); + ConfigStorage = ConfigStorageAuto.get(); + } + + DebugAssert(GetConfiguration() != nullptr); + + try + { + GetConfiguration()->Load(ConfigStorage); + } + catch (Exception & E) + { + ShowExtendedException(&E); + } + + // should be noop, unless exception occurred above + ConfigStorage->CloseAll(); + + StoredSessions = new TStoredSessionList(); + + try + { + if (SessionsStorage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), false)) + { + StoredSessions->Load(SessionsStorage.get()); + } + } + catch (Exception & E) + { + ShowExtendedException(&E); + } +} + +void CoreInitialize() +{ + WinInitialize(); + Randomize(); + CryptographyInitialize(); + + // we do not expect configuration re-creation + DebugAssert(GetConfiguration() != nullptr); + // configuration needs to be created and loaded before putty is initialized, + // so that random seed path is known +// Configuration = CreateConfiguration(); + + PuttyInitialize(); + #ifndef NO_FILEZILLA + TFileZillaIntf::Initialize(); + #endif + NeonInitialize(); + + CoreLoad(); +} + +void CoreFinalize() +{ + try + { + GetConfiguration()->Save(); + } + catch (Exception & E) + { + ShowExtendedException(&E); + } + + NeonFinalize(); + #ifndef NO_FILEZILLA + TFileZillaIntf::Finalize(); + #endif + PuttyFinalize(); + + SAFE_DESTROY(StoredSessions); + DeleteConfiguration(); + + CryptographyFinalize(); + WinFinalize(); +} + +void CoreSetResourceModule(void * ResourceHandle) +{ + #ifndef NO_FILEZILLA + TFileZillaIntf::SetResourceModule(ResourceHandle); + #else + DebugUsedParam(ResourceHandle); + #endif +} + +void CoreMaintenanceTask() +{ + DontSaveRandomSeed(); +} + +TOperationVisualizer::TOperationVisualizer(bool UseBusyCursor) : + FUseBusyCursor(UseBusyCursor), + FToken(nullptr) +{ + if (FUseBusyCursor) + { + FToken = BusyStart(); + } +} + +TOperationVisualizer::~TOperationVisualizer() +{ + if (FUseBusyCursor) + { + BusyEnd(FToken); + } +} + +TInstantOperationVisualizer::TInstantOperationVisualizer() : + TOperationVisualizer(true), + FStart(Now()) +{ +} + +TInstantOperationVisualizer::~TInstantOperationVisualizer() +{ + TDateTime Time = Now(); + int64_t Duration = MilliSecondsBetween(Time, FStart); + const int64_t MinDuration = 250; + if (Duration < MinDuration) + { + ::Sleep(static_cast(MinDuration - Duration)); + } +} + diff --git a/netbox/src/core/CoreMain.h b/netbox/src/core/CoreMain.h new file mode 100644 index 000000000..ed7db83f7 --- /dev/null +++ b/netbox/src/core/CoreMain.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class TConfiguration; +class TStoredSessionList; +extern TStoredSessionList * StoredSessions; + +void CoreInitialize(); +void CoreFinalize(); +void CoreSetResourceModule(void * ResourceHandle); +void CoreMaintenanceTask(); +TConfiguration * GetConfiguration(); + +UnicodeString NeonVersion(); +UnicodeString ExpatVersion(); + diff --git a/netbox/src/core/Cryptography.cpp b/netbox/src/core/Cryptography.cpp new file mode 100644 index 000000000..daedcbd57 --- /dev/null +++ b/netbox/src/core/Cryptography.cpp @@ -0,0 +1,640 @@ +#include +#pragma hdrstop + +#include + +#include "PuttyIntf.h" +#include "Cryptography.h" + +/* + --------------------------------------------------------------------------- + Copyright (c) 2002, Dr Brian Gladman , Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + ------------------------------------------------------------------------- + + This file implements password based file encryption and authentication + using AES in CTR mode, HMAC-SHA1 authentication and RFC2898 password + based key derivation. + + This is an implementation of HMAC, the FIPS standard keyed hash function + +*/ + +#include + +#define sha1_ctx SHA_State +#define sha1_begin(ctx) putty_SHA_Init(ctx) +#define sha1_hash(buf, len, ctx) putty_SHA_Bytes(ctx, buf, len) +#define sha1_end(dig, ctx) putty_SHA_Final(ctx, dig) + +#define IN_BLOCK_LENGTH 64 +#define OUT_BLOCK_LENGTH 20 +#define HMAC_IN_DATA 0xffffffff + +typedef struct +{ uint8_t key[IN_BLOCK_LENGTH]; + sha1_ctx ctx[1]; + uint32_t klen; +} hmac_ctx; + +/* initialise the HMAC context to zero */ +static void hmac_sha1_begin(hmac_ctx cx[1]) +{ + ::memset(cx, 0, sizeof(hmac_ctx)); +} + +/* input the HMAC key (can be called multiple times) */ +static void hmac_sha1_key(const uint8_t key[], uint32_t key_len, hmac_ctx cx[1]) +{ + if(cx->klen + key_len > IN_BLOCK_LENGTH) /* if the key has to be hashed */ + { + if(cx->klen <= IN_BLOCK_LENGTH) /* if the hash has not yet been */ + { /* started, initialise it and */ + sha1_begin(cx->ctx); /* hash stored key characters */ + sha1_hash(cx->key, cx->klen, cx->ctx); + } + + sha1_hash(const_cast(key), key_len, cx->ctx); /* hash long key data into hash */ + } + else /* otherwise store key data */ + memcpy(cx->key + cx->klen, key, key_len); + + cx->klen += key_len; /* update the key length count */ +} + +/* input the HMAC data (can be called multiple times) - */ +/* note that this call terminates the key input phase */ +static void hmac_sha1_data(const uint8_t data[], uint32_t data_len, hmac_ctx cx[1]) +{ + if (cx->klen != HMAC_IN_DATA) /* if not yet in data phase */ + { + if (cx->klen > IN_BLOCK_LENGTH) /* if key is being hashed */ + { /* complete the hash and */ + sha1_end(cx->key, cx->ctx); /* store the result as the */ + cx->klen = OUT_BLOCK_LENGTH; /* key and set new length */ + } + + /* pad the key if necessary */ + if (cx->klen < IN_BLOCK_LENGTH) + { + ::memset(cx->key + cx->klen, 0, IN_BLOCK_LENGTH - cx->klen); + } + + /* xor ipad into key value */ + for (uint32 i = 0; i < (IN_BLOCK_LENGTH >> 2); ++i) + ((uint32_t*)cx->key)[i] ^= 0x36363636; + + /* and start hash operation */ + sha1_begin(cx->ctx); + sha1_hash(cx->key, IN_BLOCK_LENGTH, cx->ctx); + + /* mark as now in data mode */ + cx->klen = HMAC_IN_DATA; + } + + /* hash the data (if any) */ + if (data_len) + sha1_hash(const_cast(data), data_len, cx->ctx); +} + +/* compute and output the MAC value */ +static void hmac_sha1_end(uint8_t mac[], uint32_t mac_len, hmac_ctx cx[1]) +{ + uint8_t dig[OUT_BLOCK_LENGTH]; + uint32_t i; + + /* if no data has been entered perform a null data phase */ + if(cx->klen != HMAC_IN_DATA) + hmac_sha1_data((const uint8_t *)0, 0, cx); + + sha1_end(dig, cx->ctx); /* complete the inner hash */ + + /* set outer key value using opad and removing ipad */ + for(i = 0; i < (IN_BLOCK_LENGTH >> 2); ++i) + ((uint32_t*)cx->key)[i] ^= 0x36363636 ^ 0x5c5c5c5c; + + /* perform the outer hash operation */ + sha1_begin(cx->ctx); + sha1_hash(cx->key, IN_BLOCK_LENGTH, cx->ctx); + sha1_hash(dig, OUT_BLOCK_LENGTH, cx->ctx); + sha1_end(dig, cx->ctx); + + /* output the hash value */ + for(i = 0; i < mac_len; ++i) + mac[i] = dig[i]; +} + +#define BLOCK_SIZE 16 + +static void aes_set_encrypt_key(const uint8_t in_key[], uint32_t klen, void * cx) +{ + call_aes_setup(cx, BLOCK_SIZE, const_cast(in_key), klen); +} + +void aes_encrypt_block(const uint8_t in_blk[], uint8_t out_blk[], void * cx) +{ + intptr_t Index; + memmove(out_blk, in_blk, BLOCK_SIZE); + for (Index = 0; Index < 4; ++Index) + { + uint8_t t; + t = out_blk[Index * 4 + 0]; + out_blk[Index * 4 + 0] = out_blk[Index * 4 + 3]; + out_blk[Index * 4 + 3] = t; + t = out_blk[Index * 4 + 1]; + out_blk[Index * 4 + 1] = out_blk[Index * 4 + 2]; + out_blk[Index * 4 + 2] = t; + } + call_aes_encrypt(cx, reinterpret_cast(out_blk)); + for (Index = 0; Index < 4; ++Index) + { + uint8_t t; + t = out_blk[Index * 4 + 0]; + out_blk[Index * 4 + 0] = out_blk[Index * 4 + 3]; + out_blk[Index * 4 + 3] = t; + t = out_blk[Index * 4 + 1]; + out_blk[Index * 4 + 1] = out_blk[Index * 4 + 2]; + out_blk[Index * 4 + 2] = t; + } +} + +typedef struct +{ uint8_t nonce[BLOCK_SIZE]; /* the CTR nonce */ + uint8_t encr_bfr[BLOCK_SIZE]; /* encrypt buffer */ + void * encr_ctx; /* encryption context */ + hmac_ctx auth_ctx; /* authentication context */ + uint32_t encr_pos; /* block position (enc) */ + uint32_t pwd_len; /* password length */ + uint32_t mode; /* File encryption mode */ +} fcrypt_ctx; + +#define MAX_KEY_LENGTH 32 +#define KEYING_ITERATIONS 1000 +#define PWD_VER_LENGTH 2 + +/* + Field lengths (in bytes) versus File Encryption Mode (0 < mode < 4) + + Mode Key Salt MAC Overhead + 1 16 8 10 18 + 2 24 12 10 22 + 3 32 16 10 26 + + The following macros assume that the mode value is correct. +*/ + +#define KEY_LENGTH(mode) (8 * (mode & 3) + 8) +#define SALT_LENGTH(mode) (4 * (mode & 3) + 4) +#define MAC_LENGTH(mode) (10) + +/* subroutine for data encryption/decryption */ +/* this could be speeded up a lot by aligning */ +/* buffers and using 32 bit operations */ + +static void derive_key(const uint8_t pwd[], /* the PASSWORD */ + uint32_t pwd_len, /* and its length */ + const uint8_t salt[], /* the SALT and its */ + uint32_t salt_len, /* length */ + uint32_t iter, /* the number of iterations */ + uint8_t key[], /* space for the output key */ + uint32_t key_len)/* and its required length */ +{ + uint32_t i, j, k, n_blk; + uint8_t uu[OUT_BLOCK_LENGTH], ux[OUT_BLOCK_LENGTH]; + hmac_ctx c1[1] = {0}, c2[1] = {0}, c3[1] = {0}; + + /* set HMAC context (c1) for password */ + hmac_sha1_begin(c1); + hmac_sha1_key(pwd, pwd_len, c1); + + /* set HMAC context (c2) for password and salt */ + memmove(c2, c1, sizeof(hmac_ctx)); + hmac_sha1_data(salt, salt_len, c2); + + /* find the number of SHA blocks in the key */ + n_blk = 1 + (key_len - 1) / OUT_BLOCK_LENGTH; + + for(i = 0; i < n_blk; ++i) /* for each block in key */ + { + /* ux[] holds the running xor value */ + ::memset(ux, 0, OUT_BLOCK_LENGTH); + + /* set HMAC context (c3) for password and salt */ + memmove(c3, c2, sizeof(hmac_ctx)); + + /* enter additional data for 1st block into uu */ + uu[0] = (uint8_t)((i + 1) >> 24); + uu[1] = (uint8_t)((i + 1) >> 16); + uu[2] = (uint8_t)((i + 1) >> 8); + uu[3] = (uint8_t)(i + 1); + + /* this is the key mixing iteration */ + for(j = 0, k = 4; j < iter; ++j) + { + /* add previous round data to HMAC */ + hmac_sha1_data(uu, k, c3); + + /* obtain HMAC for uu[] */ + hmac_sha1_end(uu, OUT_BLOCK_LENGTH, c3); + + /* xor into the running xor block */ + for(k = 0; k < OUT_BLOCK_LENGTH; ++k) + ux[k] ^= uu[k]; + + /* set HMAC context (c3) for password */ + memmove(c3, c1, sizeof(hmac_ctx)); + } + + /* compile key blocks into the key output */ + j = 0; k = i * OUT_BLOCK_LENGTH; + while(j < OUT_BLOCK_LENGTH && k < key_len) + key[k++] = ux[j++]; + } +} + +static void encr_data(uint8_t data[], uint32_t d_len, fcrypt_ctx cx[1]) +{ + uint32_t i = 0, pos = cx->encr_pos; + + while(i < d_len) + { + if(pos == BLOCK_SIZE) + { uint32_t j = 0; + /* increment encryption nonce */ + while(j < 8 && !++cx->nonce[j]) + ++j; + /* encrypt the nonce to form next xor buffer */ + aes_encrypt_block(cx->nonce, cx->encr_bfr, cx->encr_ctx); + pos = 0; + } + + data[i++] ^= cx->encr_bfr[pos++]; + } + + cx->encr_pos = pos; +} + +static void fcrypt_init( + int mode, /* the mode to be used (input) */ + const uint8_t pwd[], /* the user specified password (input) */ + uint32_t pwd_len, /* the length of the password (input) */ + const uint8_t salt[], /* the salt (input) */ + uint8_t pwd_ver[PWD_VER_LENGTH], /* 2 byte password verifier (output) */ + fcrypt_ctx cx[1]) /* the file encryption context (output) */ +{ + uint8_t kbuf[2 * MAX_KEY_LENGTH + PWD_VER_LENGTH]; + + cx->mode = mode; + cx->pwd_len = pwd_len; + + /* derive the encryption and authentication keys and the password verifier */ + derive_key(pwd, pwd_len, salt, SALT_LENGTH(mode), KEYING_ITERATIONS, + kbuf, 2 * KEY_LENGTH(mode) + PWD_VER_LENGTH); + + /* initialise the encryption nonce and buffer pos */ + cx->encr_pos = BLOCK_SIZE; + /* if we need a random component in the encryption */ + /* nonce, this is where it would have to be set */ + ::memset(cx->nonce, 0, BLOCK_SIZE * sizeof(uint8_t)); + + /* initialise for encryption using key 1 */ + cx->encr_ctx = call_aes_make_context(); + aes_set_encrypt_key(kbuf, KEY_LENGTH(mode), cx->encr_ctx); + + /* initialise for authentication using key 2 */ + hmac_sha1_begin(&cx->auth_ctx); + hmac_sha1_key(kbuf + KEY_LENGTH(mode), KEY_LENGTH(mode), &cx->auth_ctx); + + if (pwd_ver != nullptr) + { + memmove(pwd_ver, kbuf + 2 * KEY_LENGTH(mode), PWD_VER_LENGTH); + } +} + +/* perform 'in place' encryption and authentication */ + +static void fcrypt_encrypt(uint8_t data[], uint32_t data_len, fcrypt_ctx cx[1]) +{ + encr_data(data, data_len, cx); + hmac_sha1_data(data, data_len, &cx->auth_ctx); +} + +/* perform 'in place' authentication and decryption */ + +static void fcrypt_decrypt(uint8_t data[], uint32_t data_len, fcrypt_ctx cx[1]) +{ + hmac_sha1_data(data, data_len, &cx->auth_ctx); + encr_data(data, data_len, cx); +} + +/* close encryption/decryption and return the MAC value */ + +static int fcrypt_end(uint8_t mac[], fcrypt_ctx cx[1]) +{ + hmac_sha1_end(mac, MAC_LENGTH(cx->mode), &cx->auth_ctx); + call_aes_free_context(cx->encr_ctx); + return MAC_LENGTH(cx->mode); /* return MAC length in bytes */ +} + +#define PASSWORD_MANAGER_AES_MODE 3 + +static void FillBufferWithRandomData(char * Buf, intptr_t Len) +{ + while (Len > 0) + { + *Buf = static_cast((rand() >> 7) & 0xFF); + Buf++; + Len--; + } +} + +static RawByteString AES256Salt() +{ + RawByteString Result; + Result.SetLength(SALT_LENGTH(PASSWORD_MANAGER_AES_MODE)); + FillBufferWithRandomData(const_cast(Result.c_str()), Result.Length()); + return Result; +} + +void AES256EncyptWithMAC(const RawByteString & Input, const UnicodeString & Password, + RawByteString & Salt, RawByteString & Output, RawByteString & Mac) +{ + fcrypt_ctx aes; + if (Salt.IsEmpty()) + { + Salt = AES256Salt(); + } + DebugAssert(Salt.Length() == SALT_LENGTH(PASSWORD_MANAGER_AES_MODE)); + UTF8String UtfPassword = UTF8String(Password); + fcrypt_init(PASSWORD_MANAGER_AES_MODE, + reinterpret_cast(UtfPassword.c_str()), static_cast(UtfPassword.Length()), + reinterpret_cast(Salt.c_str()), nullptr, &aes); + Output = Input; + Output.Unique(); + fcrypt_encrypt(reinterpret_cast(const_cast(Output.c_str())), static_cast(Output.Length()), &aes); + Mac.SetLength(MAC_LENGTH(PASSWORD_MANAGER_AES_MODE)); + fcrypt_end(reinterpret_cast(const_cast(Mac.c_str())), &aes); +} + +void AES256EncyptWithMAC(const RawByteString & Input, const UnicodeString & Password, + RawByteString & Output) +{ + RawByteString Salt; + RawByteString Encrypted; + RawByteString Mac; + AES256EncyptWithMAC(Input, Password, Salt, Encrypted, Mac); + Output = Salt + Encrypted + Mac; +} + +bool AES256DecryptWithMAC(const RawByteString & Input, const UnicodeString & Password, + const RawByteString & Salt, RawByteString & Output, const RawByteString & Mac) +{ + fcrypt_ctx aes; + DebugAssert(Salt.Length() == SALT_LENGTH(PASSWORD_MANAGER_AES_MODE)); + UTF8String UtfPassword = UTF8String(Password); + fcrypt_init(PASSWORD_MANAGER_AES_MODE, + reinterpret_cast(UtfPassword.c_str()), static_cast(UtfPassword.Length()), + reinterpret_cast(Salt.c_str()), nullptr, &aes); + Output = Input; + fcrypt_decrypt(reinterpret_cast(const_cast(Output.c_str())), static_cast(Output.Length()), &aes); + RawByteString Mac2; + Mac2.SetLength(MAC_LENGTH(PASSWORD_MANAGER_AES_MODE)); + DebugAssert(Mac.Length() == Mac2.Length()); + fcrypt_end(reinterpret_cast(const_cast(Mac2.c_str())), &aes); + return (Mac2 == Mac); +} + +bool AES256DecryptWithMAC(const RawByteString & Input, const UnicodeString & Password, + RawByteString & Output) +{ + bool Result = + Input.Length() > SALT_LENGTH(PASSWORD_MANAGER_AES_MODE) + MAC_LENGTH(PASSWORD_MANAGER_AES_MODE); + if (Result) + { + RawByteString Salt = Input.SubString(1, SALT_LENGTH(PASSWORD_MANAGER_AES_MODE)); + RawByteString Encrypted = + Input.SubString(SALT_LENGTH(PASSWORD_MANAGER_AES_MODE) + 1, + Input.Length() - SALT_LENGTH(PASSWORD_MANAGER_AES_MODE) - MAC_LENGTH(PASSWORD_MANAGER_AES_MODE)); + RawByteString Mac = + Input.SubString(Input.Length() - MAC_LENGTH(PASSWORD_MANAGER_AES_MODE) + 1, + MAC_LENGTH(PASSWORD_MANAGER_AES_MODE)); + Result = AES256DecryptWithMAC(Encrypted, Password, Salt, Output, Mac); + } + return Result; +} + +void AES256CreateVerifier(const UnicodeString & Input, RawByteString & Verifier) +{ + RawByteString Salt = AES256Salt(); + RawByteString Dummy = AES256Salt(); + + RawByteString Encrypted; + RawByteString Mac; + AES256EncyptWithMAC(Dummy, Input, Salt, Encrypted, Mac); + + Verifier = Salt + Dummy + Mac; +} + +bool AES256Verify(const UnicodeString & Input, const RawByteString & Verifier) +{ + int SaltLength = SALT_LENGTH(PASSWORD_MANAGER_AES_MODE); + RawByteString Salt = Verifier.SubString(1, SaltLength); + RawByteString Dummy = Verifier.SubString(SaltLength + 1, SaltLength); + RawByteString Mac = Verifier.SubString(SaltLength + SaltLength + 1, MAC_LENGTH(PASSWORD_MANAGER_AES_MODE)); + + RawByteString Encrypted; + RawByteString Mac2; + AES256EncyptWithMAC(Dummy, Input, Salt, Encrypted, Mac2); + + DebugAssert(Mac2.Length() == Mac.Length()); + + return (Mac == Mac2); +} + +static uint8_t SScrambleTable[256] = +{ + 0, 223, 235, 233, 240, 185, 88, 102, 22, 130, 27, 53, 79, 125, 66, 201, + 90, 71, 51, 60, 134, 104, 172, 244, 139, 84, 91, 12, 123, 155, 237, 151, + 192, 6, 87, 32, 211, 38, 149, 75, 164, 145, 52, 200, 224, 226, 156, 50, + 136, 190, 232, 63, 129, 209, 181, 120, 28, 99, 168, 94, 198, 40, 238, 112, + 55, 217, 124, 62, 227, 30, 36, 242, 208, 138, 174, 231, 26, 54, 214, 148, + 37, 157, 19, 137, 187, 111, 228, 39, 110, 17, 197, 229, 118, 246, 153, 80, + 21, 128, 69, 117, 234, 35, 58, 67, 92, 7, 132, 189, 5, 103, 10, 15, + 252, 195, 70, 147, 241, 202, 107, 49, 20, 251, 133, 76, 204, 73, 203, 135, + 184, 78, 194, 183, 1, 121, 109, 11, 143, 144, 171, 161, 48, 205, 245, 46, + 31, 72, 169, 131, 239, 160, 25, 207, 218, 146, 43, 140, 127, 255, 81, 98, + 42, 115, 173, 142, 114, 13, 2, 219, 57, 56, 24, 126, 3, 230, 47, 215, + 9, 44, 159, 33, 249, 18, 93, 95, 29, 113, 220, 89, 97, 182, 248, 64, + 68, 34, 4, 82, 74, 196, 213, 165, 179, 250, 108, 254, 59, 14, 236, 175, + 85, 199, 83, 106, 77, 178, 167, 225, 45, 247, 163, 158, 8, 221, 61, 191, + 119, 16, 253, 105, 186, 23, 170, 100, 216, 65, 162, 122, 150, 176, 154, 193, + 206, 222, 188, 152, 210, 243, 96, 41, 86, 180, 101, 177, 166, 141, 212, 116 +}; + +uint8_t * ScrambleTable; +uint8_t * UnscrambleTable; + +RawByteString ScramblePassword(const UnicodeString & Password) +{ +#define SCRAMBLE_LENGTH_EXTENSION 50 + UTF8String UtfPassword = UTF8String(Password); + intptr_t Len = UtfPassword.Length(); + char * Buf = static_cast(nb_malloc(Len + SCRAMBLE_LENGTH_EXTENSION)); + intptr_t Padding = (((Len + 3) / 17) * 17 + 17) - 3 - Len; + for (intptr_t Index = 0; Index < Padding; ++Index) + { + int P = 0; + while ((P <= 0) || (P > 255) || IsDigit(static_cast(P))) + { + P = (int)((double)rand() / ((double)RAND_MAX / 256.0)); + } + Buf[Index] = (uint8_t)P; + } + Buf[Padding] = (char)('0' + (Len % 10)); + Buf[Padding + 1] = (char)('0' + ((Len / 10) % 10)); + Buf[Padding + 2] = (char)('0' + ((Len / 100) % 10)); + strncpy(Buf + Padding + 3, const_cast((UtfPassword.c_str())), UtfPassword.Length()); + char * S = Buf; + int Last = 31; + while (*S != '\0') + { + Last = (Last + (uint8_t)*S) % 255 + 1; + *S = ScrambleTable[Last]; + S++; + } + RawByteString Result = Buf; + ::memset(Buf, 0, Len + SCRAMBLE_LENGTH_EXTENSION); + nb_free(Buf); + return Result; +} + +bool UnscramblePassword(const RawByteString & Scrambled, UnicodeString & Password) +{ + RawByteString LocalScrambled = Scrambled; + char * S = const_cast(LocalScrambled.c_str()); + int Last = 31; + while (*S != '\0') + { + int X = (int)UnscrambleTable[(uint8_t)*S] - 1 - (Last % 255); + if (X <= 0) + { + X += 255; + } + *S = (char)X; + Last = (Last + X) % 255 + 1; + S++; + } + + S = const_cast(LocalScrambled.c_str()); + while ((*S != '\0') && ((*S < '0') || (*S > '9'))) + { + S++; + } + bool Result = false; + if (strlen(S) >= 3) + { + int Len = (S[0] - '0') + 10 * (S[1] - '0') + 100 * (S[2] - '0'); + int Total = (((Len + 3) / 17) * 17 + 17); + if ((Len >= 0) && (Total == LocalScrambled.Length()) && (Total - (S - LocalScrambled.c_str()) - 3 == Len)) + { + LocalScrambled.Delete(1, LocalScrambled.Length() - Len); + Result = true; + } + } + if (Result) + { + Password = UTF8ToString(LocalScrambled); + } + else + { + Password.Clear(); + } + return Result; +} + +void CryptographyInitialize() +{ + ScrambleTable = SScrambleTable; + UnscrambleTable = static_cast(nb_malloc(256)); + for (intptr_t Index = 0; Index < 256; ++Index) + { + UnscrambleTable[SScrambleTable[Index]] = (uint8_t)Index; + } +#ifdef __linux__ +#define _getpid getpid +#endif + srand((uint32_t)time(nullptr) ^ (uint32_t)_getpid()); +} + +void CryptographyFinalize() +{ + nb_free(UnscrambleTable); + UnscrambleTable = nullptr; + ScrambleTable = nullptr; +} + +int PasswordMaxLength() +{ + return 128; +} + +int IsValidPassword(const UnicodeString & Password) +{ + if (Password.IsEmpty() || (Password.Length() > PasswordMaxLength())) + { + return -1; + } + else + { + int A = 0; + int B = 0; + int C = 0; + int D = 0; + for (intptr_t Index = 1; Index <= Password.Length(); ++Index) + { + if (IsLowerCaseLetter(Password[Index])) + { + A = 1; + } + else if (IsUpperCaseLetter(Password[Index])) + { + B = 1; + } + else if (IsDigit(Password[Index])) + { + C = 1; + } + else + { + D = 1; + } + } + return (Password.Length() >= 6) && ((A + B + C + D) >= 2); + } +} + diff --git a/netbox/src/core/Cryptography.h b/netbox/src/core/Cryptography.h new file mode 100644 index 000000000..dc5452e14 --- /dev/null +++ b/netbox/src/core/Cryptography.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +void CryptographyInitialize(); +void CryptographyFinalize(); +RawByteString ScramblePassword(const UnicodeString & Password); +bool UnscramblePassword(const RawByteString & Scrambled, UnicodeString & Password); +void AES256EncyptWithMAC(const RawByteString & Input, const UnicodeString & Password, + RawByteString & Output); +bool AES256DecryptWithMAC(const RawByteString & Input, const UnicodeString & Password, + RawByteString & Output); +void AES256CreateVerifier(const UnicodeString & Input, RawByteString & Verifier); +bool AES256Verify(const UnicodeString & Input, const RawByteString & Verifier); +int IsValidPassword(const UnicodeString & Password); +int PasswordMaxLength(); + diff --git a/netbox/src/core/FileInfo.cpp b/netbox/src/core/FileInfo.cpp new file mode 100644 index 000000000..c11fbdcd1 --- /dev/null +++ b/netbox/src/core/FileInfo.cpp @@ -0,0 +1,299 @@ +#include +#pragma hdrstop + +#include +#include +#include +#include + +#include "FileInfo.h" + +#define DWORD_ALIGN( base, ptr ) \ + ( (LPBYTE)(base) + ((((LPBYTE)(ptr) - (LPBYTE)(base)) + 3) & ~3) ) +struct VS_VERSION_INFO_STRUCT32 +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[1]; +}; + +static uintptr_t VERSION_GetFileVersionInfo_PE(const wchar_t * FileName, uintptr_t DataSize, void * Data) +{ + uintptr_t Len = 0; +#ifndef __linux__ + bool NeedFree = false; + HMODULE Module = ::GetModuleHandle(FileName); + if (Module == nullptr) + { + Module = ::LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE); + NeedFree = true; + } + if (Module == nullptr) + { + Len = 0; + } + else + { + try__finally + { + SCOPE_EXIT + { + if (NeedFree) + { + ::FreeLibrary(Module); + } + }; + HRSRC Rsrc = ::FindResource(Module, MAKEINTRESOURCE(VS_VERSION_INFO), + MAKEINTRESOURCE(VS_FILE_INFO)); + if (Rsrc == nullptr) + { + } + else + { + Len = ::SizeofResource(Module, static_cast(Rsrc)); + HANDLE Mem = ::LoadResource(Module, static_cast(Rsrc)); + if (Mem == nullptr) + { + } + else + { + try__finally + { + SCOPE_EXIT + { + ::FreeResource(Mem); + }; + VS_VERSION_INFO_STRUCT32 * VersionInfo = static_cast(LockResource(Mem)); + const VS_FIXEDFILEINFO * FixedInfo = + (VS_FIXEDFILEINFO *)DWORD_ALIGN(VersionInfo, VersionInfo->szKey + wcslen(VersionInfo->szKey) + 1); + + if (FixedInfo->dwSignature != VS_FFI_SIGNATURE) + { + Len = 0; + } + else + { + if (Data != nullptr) + { + if (DataSize < Len) + { + Len = DataSize; + } + if (Len > 0) + { + memmove(Data, VersionInfo, Len); + } + } + } + } + __finally + { + FreeResource(Mem); + }; + } + } + } + __finally + { + if (NeedFree) + { + FreeLibrary(Module); + } + }; + } +#endif + return Len; +} + +static uintptr_t GetFileVersionInfoSizeFix(const wchar_t * FileName, DWORD * AHandle) +{ + uintptr_t Len = 0; +#ifndef __linux__ + if (IsWin7()) + { + *AHandle = 0; + Len = VERSION_GetFileVersionInfo_PE(FileName, 0, nullptr); + + if (Len != 0) + { + Len = (Len * 2) + 4; + } + } + else + { + Len = ::GetFileVersionInfoSize(const_cast(FileName), AHandle); + } +#endif + return Len; +} + +bool GetFileVersionInfoFix(const wchar_t * FileName, uint32_t Handle, + uintptr_t DataSize, void * Data) +{ + bool Result = false; +#ifndef __linux__ + if (IsWin7()) + { + VS_VERSION_INFO_STRUCT32 * VersionInfo = static_cast(Data); + + uintptr_t Len = VERSION_GetFileVersionInfo_PE(FileName, DataSize, Data); + + Result = (Len != 0); + if (Result) + { + static const char Signature[] = "FE2X"; + uintptr_t BufSize = static_cast(VersionInfo->wLength + strlen(Signature)); + + if (DataSize >= BufSize) + { + uintptr_t ConvBuf = DataSize - VersionInfo->wLength; + memmove((static_cast(Data)) + VersionInfo->wLength, Signature, ConvBuf > 4 ? 4 : ConvBuf ); + } + } + } + else + { + Result = ::GetFileVersionInfo(FileName, Handle, static_cast(DataSize), Data) != 0; + } +#endif + return Result; +} + +// Return pointer to file version info block +void * CreateFileInfo(const UnicodeString & AFileName) +{ + DWORD Handle; + uintptr_t Size; + void * Result = nullptr; + + // Get file version info block size + Size = GetFileVersionInfoSizeFix(AFileName.c_str(), &Handle); + // If size is valid + if (Size > 0) + { + Result = nb_malloc(Size); + // Get file version info block + if (!GetFileVersionInfoFix(AFileName.c_str(), Handle, Size, Result)) + { + nb_free(Result); + Result = nullptr; + } + } + else + { + } + return Result; +} + +// Free file version info block memory +void FreeFileInfo(void * FileInfo) +{ + if (FileInfo) + nb_free(FileInfo); +} + +typedef TTranslation TTranslations[65536]; +typedef TTranslation *PTranslations; + +// Return pointer to fixed file version info +TVSFixedFileInfo *GetFixedFileInfo(void * FileInfo) +{ + UINT Len; + TVSFixedFileInfo *Result = nullptr; +#ifndef __linux__ + if (FileInfo && !::VerQueryValue(FileInfo, L"\\", reinterpret_cast(&Result), &Len)) + { + throw Exception(L"Fixed file info not available"); + } +#endif + return Result; +} + +// Return number of available file version info translations +uint32_t GetTranslationCount(void * FileInfo) +{ + PTranslations P; + UINT Len = 0; +#ifndef __linux__ + if (!::VerQueryValue(FileInfo, L"\\VarFileInfo\\Translation", reinterpret_cast(&P), &Len)) + throw Exception(L"File info translations not available"); +#endif + return Len / 4; +} + +// Return i-th translation in the file version info translation list +TTranslation GetTranslation(void * FileInfo, intptr_t I) +{ + PTranslations P = nullptr; + UINT Len = 0; +#ifndef __linux__ + if (!::VerQueryValue(FileInfo, L"\\VarFileInfo\\Translation", reinterpret_cast(&P), &Len)) + throw Exception(L"File info translations not available"); + if (I * sizeof(TTranslation) >= Len) + throw Exception(L"Specified translation not available"); +#endif + return P[I]; +} + +// Return the name of the specified language +UnicodeString GetLanguage(Word Language) +{ +#ifndef __linux__ + uintptr_t Len = 0; + wchar_t P[256]; + Len = ::VerLanguageName(Language, P, _countof(P)); + if (Len > _countof(P)) + throw Exception(L"Language not available"); + return UnicodeString(P, Len); +#else + return UnicodeString("RU"); +#endif +} + +// Return the value of the specified file version info string using the +// specified translation +UnicodeString GetFileInfoString(void * FileInfo, + TTranslation Translation, const UnicodeString & StringName, bool AllowEmpty) +{ + UnicodeString Result; +#ifndef __linux__ + wchar_t * P; + UINT Len; + + if (!::VerQueryValue(FileInfo, (UnicodeString(L"\\StringFileInfo\\") + + ::IntToHex(Translation.Language, 4) + + ::IntToHex(Translation.CharSet, 4) + + L"\\" + StringName).c_str(), reinterpret_cast(&P), &Len)) + { + if (!AllowEmpty) + { + throw Exception(L"Specified file info string not available"); + } + } + else + { + Result = UnicodeString(P, Len); + PackStr(Result); + } +#endif + return Result; +} + +intptr_t CalculateCompoundVersion(intptr_t MajorVer, + intptr_t MinorVer, intptr_t Release, intptr_t Build) +{ + intptr_t CompoundVer = Build + 10000 * (Release + 100 * (MinorVer + + 100 * MajorVer)); + return CompoundVer; +} + +intptr_t StrToCompoundVersion(const UnicodeString & AStr) +{ + UnicodeString S(AStr); + int64_t MajorVer = StrToInt64(CutToChar(S, L'.', false)); + int64_t MinorVer = StrToInt64(CutToChar(S, L'.', false)); + int64_t Release = S.IsEmpty() ? 0 : StrToInt64(CutToChar(S, L'.', false)); + int64_t Build = S.IsEmpty() ? 0 : StrToInt64(CutToChar(S, L'.', false)); + return CalculateCompoundVersion(MajorVer, MinorVer, Release, Build); +} diff --git a/netbox/src/core/FileInfo.h b/netbox/src/core/FileInfo.h new file mode 100644 index 000000000..e9bc496a3 --- /dev/null +++ b/netbox/src/core/FileInfo.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +struct TTranslation +{ + Word Language, CharSet; +}; + +// Return pointer to file version info block +void * CreateFileInfo(const UnicodeString & AFileName); + +// Free file version info block memory +void FreeFileInfo(void * FileInfo); + +// Return pointer to fixed file version info +TVSFixedFileInfo *GetFixedFileInfo(void * FileInfo); + +// Return number of available file version info translations +uint32_t GetTranslationCount(void * FileInfo); + +// Return i-th translation in the file version info translation list +TTranslation GetTranslation(void * FileInfo, intptr_t I); + +// Return the name of the specified language +UnicodeString GetLanguage(Word Language); + +// Return the value of the specified file version info string using the +// specified translation +UnicodeString GetFileInfoString(void * FileInfo, + TTranslation Translation, const UnicodeString & StringName, bool AllowEmpty); + +intptr_t CalculateCompoundVersion(intptr_t MajorVer, + intptr_t MinorVer, intptr_t Release, intptr_t Build); + +intptr_t StrToCompoundVersion(const UnicodeString & AStr); diff --git a/netbox/src/core/FileMasks.cpp b/netbox/src/core/FileMasks.cpp new file mode 100644 index 000000000..a45297830 --- /dev/null +++ b/netbox/src/core/FileMasks.cpp @@ -0,0 +1,1309 @@ +#include +#pragma hdrstop + +#include +#include + +#include "FileMasks.h" + +#include "TextsCore.h" +#include "RemoteFiles.h" +#include "PuttyTools.h" +#include "Terminal.h" + +#define FILE_MASKS_DELIMITERS L";," +#define ALL_FILE_MASKS_DELIMITERS L";,|" +#define DIRECTORY_MASK_DELIMITERS L"/\\" +#define FILE_MASKS_DELIMITER_STR L"; " + +EFileMasksException::EFileMasksException( + const UnicodeString & AMessage, intptr_t AErrorStart, intptr_t AErrorLen) : + Exception(AMessage), ErrorStart(AErrorStart), ErrorLen(AErrorLen) +{ +} + +static UnicodeString MaskFilePart(const UnicodeString & Part, const UnicodeString & Mask, bool & Masked) +{ + UnicodeString Result; + intptr_t RestStart = 1; + bool Delim = false; + for (intptr_t Index = 1; Index <= Mask.Length(); ++Index) + { + switch (Mask[Index]) + { + case L'\\': + if (!Delim) + { + Delim = true; + Masked = true; + } + break; + + case L'*': + if (!Delim) + { + Result += Part.SubString(RestStart, Part.Length() - RestStart + 1); + RestStart = Part.Length() + 1; + Masked = true; + } + break; + + case L'?': + if (!Delim) + { + if (RestStart <= Part.Length()) + { + Result += Part[RestStart]; + RestStart++; + } + Masked = true; + } + break; + + default: + Result += Mask[Index]; + RestStart++; + Delim = false; + break; + } + } + return Result; +} + +UnicodeString MaskFileName(const UnicodeString & AFileName, const UnicodeString & Mask) +{ + UnicodeString FileName = AFileName; + if (IsEffectiveFileNameMask(Mask)) + { + bool Masked = false; + intptr_t P = Mask.LastDelimiter(L"."); + if (P > 0) + { + intptr_t P2 = FileName.LastDelimiter(L"."); + // only dot at beginning of file name is not considered as + // name/ext separator + UnicodeString FileExt = P2 > 1 ? + FileName.SubString(P2 + 1, FileName.Length() - P2) : UnicodeString(); + FileExt = MaskFilePart(FileExt, Mask.SubString(P + 1, Mask.Length() - P), Masked); + if (P2 > 1) + { + FileName.SetLength(P2 - 1); + } + FileName = MaskFilePart(FileName, Mask.SubString(1, P - 1), Masked); + if (!FileExt.IsEmpty()) + { + FileName += L"." + FileExt; + } + } + else + { + FileName = MaskFilePart(FileName, Mask, Masked); + } + } + return FileName; +} + +bool IsFileNameMask(const UnicodeString & AMask) +{ + bool Result = AMask.IsEmpty(); // empty mask is the same as * + if (!Result) + { + MaskFilePart(UnicodeString(), AMask, Result); + } + return Result; +} + +bool IsEffectiveFileNameMask(const UnicodeString & AMask) +{ + return !AMask.IsEmpty() && (AMask != L"*") && (AMask != L"*.*"); +} + +UnicodeString DelimitFileNameMask(const UnicodeString & AMask) +{ + UnicodeString Mask = AMask; + for (intptr_t Index = 1; Index <= Mask.Length(); ++Index) + { + if (wcschr(L"\\*?", Mask[Index]) != nullptr) + { + Mask.Insert(L"\\", Index); + ++Index; + } + } + return Mask; +} + + +TFileMasks::TParams::TParams() : + Size(0) +{ +} + +UnicodeString TFileMasks::TParams::ToString() const +{ + return UnicodeString(L"[") + ::Int64ToStr(Size) + L"/" + ::DateTimeToString(Modification) + L"]"; +} + +bool TFileMasks::IsMask(const UnicodeString & Mask) +{ + return (Mask.LastDelimiter(L"?*[/") > 0); +} + +bool TFileMasks::IsAnyMask(const UnicodeString & Mask) +{ + return Mask.IsEmpty() || (Mask == L"*.*") || (Mask == L"*"); +} + +UnicodeString TFileMasks::NormalizeMask(const UnicodeString & Mask, const UnicodeString & AnyMask) +{ + if (IsAnyMask(Mask)) + { + return AnyMask; + } + else + { + return Mask; + } +} + +UnicodeString TFileMasks::ComposeMaskStr( + TStrings * MasksStr, bool Directory) +{ + UnicodeString Result; + UnicodeString ResultNoDirMask; + for (intptr_t Index = 0; Index < MasksStr->GetCount(); ++Index) + { + UnicodeString Str = MasksStr->GetString(Index).Trim(); + if (!Str.IsEmpty()) + { + for (intptr_t P = 1; P <= Str.Length(); P++) + { + if (Str.IsDelimiter(ALL_FILE_MASKS_DELIMITERS, P)) + { + Str.Insert(Str[P], P); + P++; + } + } + + UnicodeString StrNoDirMask; + if (Directory) + { + StrNoDirMask = Str; + Str = MakeDirectoryMask(Str); + } + else + { + while (Str.IsDelimiter(DIRECTORY_MASK_DELIMITERS, Str.Length())) + { + Str.SetLength(Str.Length() - 1); + } + StrNoDirMask = Str; + } + + AddToList(Result, Str, FILE_MASKS_DELIMITER_STR); + AddToList(ResultNoDirMask, StrNoDirMask, FILE_MASKS_DELIMITER_STR); + } + } + + // For directories, the above will add slash by the end of masks, + // breaking size and time masks and thus circumventing their validation. + // This performs as hoc validation to cover the scenario. + // For files this makes no difference, but no harm either + TFileMasks Temp(Directory ? 1 : 0); + Temp = ResultNoDirMask; + + return Result; +} + +UnicodeString TFileMasks::ComposeMaskStr( + TStrings * IncludeFileMasksStr, TStrings * ExcludeFileMasksStr, + TStrings * IncludeDirectoryMasksStr, TStrings * ExcludeDirectoryMasksStr) +{ + UnicodeString IncludeMasks = ComposeMaskStr(IncludeFileMasksStr, false); + AddToList(IncludeMasks, ComposeMaskStr(IncludeDirectoryMasksStr, true), FILE_MASKS_DELIMITER_STR); + UnicodeString ExcludeMasks = ComposeMaskStr(ExcludeFileMasksStr, false); + AddToList(ExcludeMasks, ComposeMaskStr(ExcludeDirectoryMasksStr, true), FILE_MASKS_DELIMITER_STR); + + UnicodeString Result = IncludeMasks; + if (!ExcludeMasks.IsEmpty()) + { + if (!Result.IsEmpty()) + { + Result += L' '; + } + Result += UnicodeString(INCLUDE_EXCLUDE_FILE_MASKS_DELIMITER) + L' ' + ExcludeMasks; + } + return Result; +} + +TFileMasks::TFileMasks() +{ + Init(); +} + +TFileMasks::TFileMasks(intptr_t ForceDirectoryMasks) : + FForceDirectoryMasks(ForceDirectoryMasks) +{ + Init(); +} + +TFileMasks::TFileMasks(const TFileMasks & Source) +{ + Init(); + FForceDirectoryMasks = Source.FForceDirectoryMasks; + SetStr(Source.GetMasks(), false); +} + +TFileMasks::TFileMasks(const UnicodeString & AMasks) +{ + Init(); + SetStr(AMasks, false); +} + +TFileMasks::~TFileMasks() +{ + Clear(); +} + +void TFileMasks::Init() +{ + FForceDirectoryMasks = -1; + for (intptr_t Index = 0; Index < 4; ++Index) + { + FMasksStr[Index] = nullptr; + } + + DoInit(false); +} + +void TFileMasks::DoInit(bool Delete) +{ + for (intptr_t Index = 0; Index < 4; ++Index) + { + if (Delete) + { + SAFE_DESTROY(FMasksStr[Index]); + } + FMasksStr[Index] = nullptr; + } +} + +void TFileMasks::Clear() +{ + DoInit(true); + + for (intptr_t Index = 0; Index < 4; ++Index) + { + Clear(FMasks[Index]); + } +} + +void TFileMasks::Clear(TMasks & Masks) +{ + TMasks::iterator it = Masks.begin(); + while (it != Masks.end()) + { + ReleaseMaskMask(it->FileNameMask); + ReleaseMaskMask(it->DirectoryMask); + ++it; + } + Masks.clear(); +} + +bool TFileMasks::MatchesMasks(const UnicodeString & AFileName, bool Directory, + const UnicodeString & APath, const TParams * Params, const TMasks & Masks, bool Recurse) +{ + bool Result = false; + + TMasks::const_iterator it = Masks.begin(); + while (!Result && (it != Masks.end())) + { + const TMask & Mask = *it; + Result = + MatchesMaskMask(Mask.DirectoryMask, APath) && + MatchesMaskMask(Mask.FileNameMask, AFileName); + + if (Result) + { + bool HasSize = (Params != nullptr); + + switch (Mask.HighSizeMask) + { + case TMask::None: + Result = true; + break; + + case TMask::Open: + Result = HasSize && (Params->Size < Mask.HighSize); + break; + + case TMask::Close: + Result = HasSize && (Params->Size <= Mask.HighSize); + break; + } + + if (Result) + { + switch (Mask.LowSizeMask) + { + case TMask::None: + Result = true; + break; + + case TMask::Open: + Result = HasSize && (Params->Size > Mask.LowSize); + break; + + case TMask::Close: + Result = HasSize && (Params->Size >= Mask.LowSize); //-V595 + break; + } + } + + bool HasModification = (Params != nullptr); + + if (Result) + { + switch (Mask.HighModificationMask) + { + case TMask::None: + Result = true; + break; + + case TMask::Open: + Result = HasModification && (Params->Modification < Mask.HighModification); + break; + + case TMask::Close: + Result = HasModification && (Params->Modification <= Mask.HighModification); + break; + } + } + + if (Result) + { + switch (Mask.LowModificationMask) + { + case TMask::None: + Result = true; + break; + + case TMask::Open: + Result = HasModification && (Params->Modification > Mask.LowModification); + break; + + case TMask::Close: + Result = HasModification && (Params->Modification >= Mask.LowModification); + break; + } + } + } + + ++it; + } + + if (!Result && Directory && !core::IsUnixRootPath(APath) && Recurse) + { + UnicodeString ParentFileName = base::UnixExtractFileName(APath); + UnicodeString ParentPath = core::SimpleUnixExcludeTrailingBackslash(core::UnixExtractFilePath(APath)); + // Pass Params down or not? + // Currently it includes Size/Time only, what is not used for directories. + // So it depends on future use. Possibly we should make a copy + // and pass on only relevant fields. + Result = MatchesMasks(ParentFileName, true, ParentPath, Params, Masks, Recurse); + } + + return Result; +} + +bool TFileMasks::Matches(const UnicodeString & AFileName, bool Directory, + const UnicodeString & APath, const TParams * Params) const +{ + bool ImplicitMatch; + return Matches(AFileName, Directory, APath, Params, true, ImplicitMatch); +} + +bool TFileMasks::Matches(const UnicodeString & AFileName, bool Directory, + const UnicodeString & APath, const TParams * Params, + bool RecurseInclude, bool & ImplicitMatch) const +{ + bool ImplicitIncludeMatch = FMasks[MASK_INDEX(Directory, true)].empty(); + bool ExplicitIncludeMatch = MatchesMasks(AFileName, Directory, APath, Params, FMasks[MASK_INDEX(Directory, true)], RecurseInclude); + bool Result = + (ImplicitIncludeMatch || ExplicitIncludeMatch) && + !MatchesMasks(AFileName, Directory, APath, Params, FMasks[MASK_INDEX(Directory, false)], false); + ImplicitMatch = + Result && ImplicitIncludeMatch && !ExplicitIncludeMatch && + FMasks[MASK_INDEX(Directory, false)].empty(); + return Result; +} + +bool TFileMasks::Matches(const UnicodeString & AFileName, bool Local, + bool Directory, const TParams * Params) const +{ + bool ImplicitMatch; + return Matches(AFileName, Local, Directory, Params, true, ImplicitMatch); +} + +bool TFileMasks::Matches(const UnicodeString & AFileName, bool Local, + bool Directory, const TParams * Params, bool RecurseInclude, bool & ImplicitMatch) const +{ + bool Result; + if (Local) + { + UnicodeString Path = ::ExtractFilePath(AFileName); + if (!Path.IsEmpty()) + { + Path = core::ToUnixPath(::ExcludeTrailingBackslash(Path)); + } + Result = Matches(base::ExtractFileName(AFileName, false), Directory, Path, Params, + RecurseInclude, ImplicitMatch); + } + else + { + Result = Matches(base::UnixExtractFileName(AFileName), Directory, + core::SimpleUnixExcludeTrailingBackslash(core::UnixExtractFilePath(AFileName)), Params, + RecurseInclude, ImplicitMatch); + } + return Result; +} + +bool TFileMasks::operator ==(const TFileMasks & rhm) const +{ + return (GetMasks() == rhm.GetMasks()); +} + +TFileMasks & TFileMasks::operator =(const UnicodeString & rhs) +{ + SetMasks(rhs); + return *this; +} + +TFileMasks & TFileMasks::operator =(const TFileMasks & rhm) +{ + FForceDirectoryMasks = rhm.FForceDirectoryMasks; + SetMasks(rhm.GetMasks()); + return *this; +} + +bool TFileMasks::operator ==(const UnicodeString & rhs) const +{ + return (GetMasks() == rhs); +} + +void TFileMasks::ThrowError(intptr_t Start, intptr_t End) const +{ + throw EFileMasksException( + FMTLOAD(MASK_ERROR, GetMasks().SubString(Start, End - Start + 1).c_str()), + Start, End - Start + 1); +} + +void TFileMasks::CreateMaskMask(const UnicodeString & Mask, intptr_t Start, intptr_t End, + bool Ex, TMaskMask & MaskMask) const +{ + try + { + DebugAssert(MaskMask.Mask == nullptr); + if (Ex && IsAnyMask(Mask)) + { + MaskMask.Kind = TMaskMask::Any; + MaskMask.Mask = nullptr; + } + else + { + MaskMask.Kind = (Ex && (Mask == L"*.")) ? TMaskMask::NoExt : TMaskMask::Regular; + MaskMask.Mask = new Masks::TMask(Mask); + } + } + catch (...) + { + ThrowError(Start, End); + } +} + +UnicodeString TFileMasks::MakeDirectoryMask(const UnicodeString & AStr) +{ + DebugAssert(!AStr.IsEmpty()); + UnicodeString Str = AStr; + if (Str.IsEmpty() || !Str.IsDelimiter(DIRECTORY_MASK_DELIMITERS, Str.Length())) + { + intptr_t D = Str.LastDelimiter(DIRECTORY_MASK_DELIMITERS); + // if there's any [back]slash anywhere in str, + // add the same [back]slash at the end, otherwise add slash + wchar_t Delimiter = (D > 0) ? Str[D] : L'/'; + Str += Delimiter; + } + return Str; +} + +void TFileMasks::CreateMask( + const UnicodeString & MaskStr, intptr_t MaskStart, intptr_t /*MaskEnd*/, bool Include) +{ + bool Directory = false; // shut up + TMask Mask; + + Mask.MaskStr = MaskStr; + Mask.UserStr = MaskStr; + Mask.FileNameMask.Kind = TMaskMask::Any; + Mask.FileNameMask.Mask = nullptr; + Mask.DirectoryMask.Kind = TMaskMask::Any; + Mask.DirectoryMask.Mask = nullptr; + Mask.HighSizeMask = TMask::None; + Mask.LowSizeMask = TMask::None; + Mask.HighModificationMask = TMask::None; + Mask.LowModificationMask = TMask::None; + + wchar_t NextPartDelimiter = L'\0'; + intptr_t NextPartFrom = 1; + while (NextPartFrom <= MaskStr.Length()) + { + wchar_t PartDelimiter = NextPartDelimiter; + intptr_t PartFrom = NextPartFrom; + UnicodeString PartStr = CopyToChars(MaskStr, NextPartFrom, L"<>", false, &NextPartDelimiter, true); + + intptr_t PartStart = MaskStart + PartFrom - 1; + intptr_t PartEnd = MaskStart + NextPartFrom - 1 - 2; + + TrimEx(PartStr, PartStart, PartEnd); + + if (PartDelimiter != L'\0') + { + bool Low = (PartDelimiter == L'>'); + + TMask::TMaskBoundary Boundary; + if ((PartStr.Length() >= 1) && (PartStr[1] == L'=')) + { + Boundary = TMask::Close; + PartStr.Delete(1, 1); + } + else + { + Boundary = TMask::Open; + } + + TFormatSettings FormatSettings = TFormatSettings::Create(GetDefaultLCID()); + FormatSettings.DateSeparator = L'-'; + FormatSettings.TimeSeparator = L':'; + FormatSettings.ShortDateFormat = "yyyy/mm/dd"; + FormatSettings.ShortTimeFormat = "hh:nn:ss"; + + TDateTime Modification; + if (TryStrToDateTime(PartStr, Modification, FormatSettings) || + TryRelativeStrToDateTime(PartStr, Modification)) + { + TMask::TMaskBoundary & ModificationMask = + (Low ? Mask.LowModificationMask : Mask.HighModificationMask); + + if ((ModificationMask != TMask::None) || Directory) + { + // include delimiter into size part + ThrowError(PartStart - 1, PartEnd); + } + + ModificationMask = Boundary; + (Low ? Mask.LowModification : Mask.HighModification) = Modification; + } + else + { + TMask::TMaskBoundary & SizeMask = (Low ? Mask.LowSizeMask : Mask.HighSizeMask); + int64_t & Size = (Low ? Mask.LowSize : Mask.HighSize); + + if ((SizeMask != TMask::None) || Directory) + { + // include delimiter into size part + ThrowError(PartStart - 1, PartEnd); + } + + SizeMask = Boundary; + Size = ParseSize(PartStr); + } + } + else if (!PartStr.IsEmpty()) + { + intptr_t D = PartStr.LastDelimiter(DIRECTORY_MASK_DELIMITERS); + + Directory = (D > 0) && (D == PartStr.Length()); + + if (Directory) + { + do + { + PartStr.SetLength(PartStr.Length() - 1); + Mask.UserStr.Delete(PartStart - MaskStart + D, 1); + D--; + } + while (PartStr.IsDelimiter(DIRECTORY_MASK_DELIMITERS, PartStr.Length())); + + D = PartStr.LastDelimiter(DIRECTORY_MASK_DELIMITERS); + + if (FForceDirectoryMasks == 0) + { + Directory = false; + Mask.MaskStr = Mask.UserStr; + } + } + else if (FForceDirectoryMasks > 0) + { + Directory = true; + Mask.MaskStr.Insert(L'/', PartStart - MaskStart + PartStr.Length()); + } + + if (D > 0) + { + // make sure sole "/" (root dir) is preserved as is + CreateMaskMask( + core::SimpleUnixExcludeTrailingBackslash(core::ToUnixPath(PartStr.SubString(1, D))), + PartStart, PartStart + D - 1, false, + Mask.DirectoryMask); + CreateMaskMask( + PartStr.SubString(D + 1, PartStr.Length() - D), + PartStart + D, PartEnd, true, + Mask.FileNameMask); + } + else + { + CreateMaskMask(PartStr, PartStart, PartEnd, true, Mask.FileNameMask); + } + } + } + + FMasks[MASK_INDEX(Directory, Include)].push_back(Mask); +} + +TStrings * TFileMasks::GetMasksStr(intptr_t Index) const +{ + if (FMasksStr[Index] == nullptr) + { + FMasksStr[Index] = new TStringList(); + TMasks::const_iterator it = FMasks[Index].begin(); + while (it != FMasks[Index].end()) + { + FMasksStr[Index]->Add((*it).UserStr); + ++it; + } + } + + return FMasksStr[Index]; +} + +void TFileMasks::ReleaseMaskMask(TMaskMask & MaskMask) +{ + SAFE_DESTROY(MaskMask.Mask); +} + +void TFileMasks::TrimEx(UnicodeString & Str, intptr_t & Start, intptr_t & End) +{ + UnicodeString Buf = ::TrimLeft(Str); + Start += Str.Length() - Buf.Length(); + Str = ::TrimRight(Buf); + End -= Buf.Length() - Str.Length(); +} + +bool TFileMasks::MatchesMaskMask(const TMaskMask & MaskMask, const UnicodeString & Str) +{ + bool Result; + if (MaskMask.Kind == TMaskMask::Any) + { + Result = true; + } + else if ((MaskMask.Kind == TMaskMask::NoExt) && (Str.Pos(L".") == 0)) + { + Result = true; + } + else + { + Result = MaskMask.Mask->Matches(Str); + } + return Result; +} + +void TFileMasks::SetMasks(const UnicodeString & Value) +{ + if (FStr != Value) + { + SetStr(Value, false); + } +} + +void TFileMasks::SetMask(const UnicodeString & Mask) +{ + SetStr(Mask, true); +} + +void TFileMasks::SetStr(const UnicodeString & Str, bool SingleMask) +{ + UnicodeString Backup = FStr; + try + { + FStr = Str; + Clear(); + + intptr_t NextMaskFrom = 1; + bool Include = true; + while (NextMaskFrom <= Str.Length()) + { + intptr_t MaskStart = NextMaskFrom; + wchar_t NextMaskDelimiter; + UnicodeString MaskStr; + if (SingleMask) + { + MaskStr = Str; + NextMaskFrom = Str.Length() + 1; + NextMaskDelimiter = L'\0'; + } + else + { + MaskStr = CopyToChars(Str, NextMaskFrom, ALL_FILE_MASKS_DELIMITERS, false, &NextMaskDelimiter, true); + } + intptr_t MaskEnd = NextMaskFrom - 2; + + TrimEx(MaskStr, MaskStart, MaskEnd); + + if (!MaskStr.IsEmpty()) + { + CreateMask(MaskStr, MaskStart, MaskEnd, Include); + } + + if (NextMaskDelimiter == INCLUDE_EXCLUDE_FILE_MASKS_DELIMITER) + { + if (Include) + { + Include = false; + } + else + { + ThrowError(NextMaskFrom - 1, Str.Length()); + } + } + } + } + catch (...) + { + // this does not work correctly if previous mask was set using SetMask. + // this should not fail (the mask was validated before), + // otherwise we end in an infinite loop + SetStr(Backup, false); + throw; + } +} + + +#define TEXT_TOKEN L'\255' + +const wchar_t TCustomCommand::NoQuote = L'\0'; +#define CONST_QUOTES L"\"'" + +TCustomCommand::TCustomCommand() +{ +} + +void TCustomCommand::GetToken( + const UnicodeString & Command, intptr_t Index, intptr_t & Len, wchar_t & PatternCmd) const +{ + DebugAssert(Index <= Command.Length()); + const wchar_t * Ptr = Command.c_str() + Index - 1; + + if (Ptr[0] == L'!') + { + PatternCmd = Ptr[1]; + if (PatternCmd == L'\0') + { + Len = 1; + } + else if (PatternCmd == L'!') + { + Len = 2; + } + else + { + Len = PatternLen(Command, Index); + } + + if (Len <= 0) + { + throw Exception(FMTLOAD(CUSTOM_COMMAND_UNKNOWN, PatternCmd, Index)); + } + else + { + if ((Command.Length() - Index + 1) < Len) + { + throw Exception(FMTLOAD(CUSTOM_COMMAND_UNTERMINATED, PatternCmd, Index)); + } + } + } + else + { + PatternCmd = TEXT_TOKEN; + const wchar_t * NextPattern = wcschr(Ptr, L'!'); + if (NextPattern == nullptr) + { + Len = Command.Length() - Index + 1; + } + else + { + Len = NextPattern - Ptr; + } + } +} + +void TCustomCommand::PatternHint(intptr_t /*Index*/, const UnicodeString & /*Pattern*/) +{ + // noop +} + +UnicodeString TCustomCommand::Complete(const UnicodeString & Command, + bool LastPass) +{ + intptr_t Index = 1; + intptr_t PatternIndex = 0; + while (Index <= Command.Length()) + { + intptr_t Len; + wchar_t PatternCmd; + GetToken(Command, Index, Len, PatternCmd); + + if (PatternCmd == TEXT_TOKEN) + { + } + else if (PatternCmd == L'!') + { + } + else + { + UnicodeString Pattern = Command.SubString(Index, Len); + PatternHint(PatternIndex, Pattern); + PatternIndex++; + } + + Index += Len; + } + + UnicodeString Result; + Index = 1; + PatternIndex = 0; + while (Index <= Command.Length()) + { + intptr_t Len; + wchar_t PatternCmd; + GetToken(Command, Index, Len, PatternCmd); + + if (PatternCmd == TEXT_TOKEN) + { + Result += Command.SubString(Index, Len); + } + else if (PatternCmd == L'!') + { + if (LastPass) + { + Result += L'!'; + } + else + { + Result += Command.SubString(Index, Len); + } + } + else + { + wchar_t Quote = NoQuote; + UnicodeString Quotes(CONST_QUOTES); + if ((Index > 1) && (Index + Len - 1 < Command.Length()) && + Command.IsDelimiter(Quotes, Index - 1) && + Command.IsDelimiter(Quotes, Index + Len) && + (Command[Index - 1] == Command[Index + Len])) + { + Quote = Command[Index - 1]; + } + UnicodeString Pattern = Command.SubString(Index, Len); + UnicodeString Replacement; + bool Delimit = true; + if (PatternReplacement(PatternIndex, Pattern, Replacement, Delimit)) + { + if (!LastPass) + { + Replacement = ReplaceStr(Replacement, L"!", L"!!"); + } + if (Delimit) + { + DelimitReplacement(Replacement, Quote); + } + Result += Replacement; + } + else + { + Result += Pattern; + } + + PatternIndex++; + } + + Index += Len; + } + + return Result; +} + +void TCustomCommand::DelimitReplacement(UnicodeString & Replacement, wchar_t Quote) +{ + Replacement = ShellDelimitStr(Replacement, Quote); +} + +void TCustomCommand::Validate(const UnicodeString & Command) +{ + CustomValidate(Command, nullptr); +} + +void TCustomCommand::CustomValidate(const UnicodeString & Command, + void * Arg) +{ + intptr_t Index = 1; + + while (Index <= Command.Length()) + { + intptr_t Len; + wchar_t PatternCmd; + GetToken(Command, Index, Len, PatternCmd); + ValidatePattern(Command, Index, Len, PatternCmd, Arg); + + Index += Len; + } +} + +bool TCustomCommand::FindPattern(const UnicodeString & Command, + wchar_t PatternCmd) const +{ + bool Result = false; + intptr_t Index = 1; + + while (!Result && (Index <= Command.Length())) + { + intptr_t Len; + wchar_t APatternCmd; + GetToken(Command, Index, Len, APatternCmd); + if (((PatternCmd != L'!') && (PatternCmd == APatternCmd)) || + ((PatternCmd == L'!') && (Len == 1) && (APatternCmd != TEXT_TOKEN))) + { + Result = true; + } + + Index += Len; + } + + return Result; +} + +void TCustomCommand::ValidatePattern(const UnicodeString & /*Command*/, + intptr_t /*Index*/, intptr_t /*Len*/, wchar_t /*PatternCmd*/, void * /*Arg*/) +{ +} + + +TInteractiveCustomCommand::TInteractiveCustomCommand( + TCustomCommand * ChildCustomCommand) +{ + FChildCustomCommand = ChildCustomCommand; +} + +void TInteractiveCustomCommand::Prompt( + intptr_t /*Index*/, const UnicodeString & /*Prompt*/, UnicodeString & Value) const +{ + Value.Clear(); +} + +void TInteractiveCustomCommand::Execute( + const UnicodeString & /*Command*/, UnicodeString & Value) const +{ + Value.Clear(); +} + +intptr_t TInteractiveCustomCommand::PatternLen(const UnicodeString & Command, intptr_t Index) const +{ + intptr_t Len = 0; + wchar_t PatternCmd = (Index < Command.Length()) ? Command[Index + 1] : L'\0'; + switch (PatternCmd) + { + case L'?': + { + const wchar_t * Ptr = Command.c_str() + Index - 1; + const wchar_t * PatternEnd = wcschr(Ptr + 1, L'!'); + if (PatternEnd == nullptr) + { + throw Exception(FMTLOAD(CUSTOM_COMMAND_UNTERMINATED, Command[Index + 1], Index)); + } + Len = PatternEnd - Ptr + 1; + } + break; + + case L'`': + { + const wchar_t * Ptr = Command.c_str() + Index - 1; + const wchar_t * PatternEnd = wcschr(Ptr + 2, L'`'); + if (PatternEnd == nullptr) + { + throw Exception(FMTLOAD(CUSTOM_COMMAND_UNTERMINATED, Command[Index + 1], Index)); + } + Len = PatternEnd - Ptr + 1; + } + break; + + default: + Len = FChildCustomCommand->PatternLen(Command, Index); + break; + } + return Len; +} + +bool TInteractiveCustomCommand::IsPromptPattern(const UnicodeString & Pattern) const +{ + return (Pattern.Length() >= 3) && (Pattern[2] == L'?'); +} + +void TInteractiveCustomCommand::ParsePromptPattern( + const UnicodeString & Pattern, UnicodeString & Prompt, UnicodeString & Default, bool & Delimit) const +{ + intptr_t Pos = Pattern.SubString(3, Pattern.Length() - 2).Pos(L"?"); + if (Pos > 0) + { + Default = Pattern.SubString(3 + Pos, Pattern.Length() - 3 - Pos); + if ((Pos > 1) && (Pattern[3 + Pos - 2] == L'\\')) + { + Delimit = false; + Pos--; + } + Prompt = Pattern.SubString(3, Pos - 1); + } + else + { + Prompt = Pattern.SubString(3, Pattern.Length() - 3); + } +} + +bool TInteractiveCustomCommand::PatternReplacement(intptr_t Index, const UnicodeString & Pattern, + UnicodeString & Replacement, bool & Delimit) const +{ + bool Result; + if (IsPromptPattern(Pattern)) + { + UnicodeString PromptStr; + // The PromptStr and Replacement are actually never used + // as the only implementation (TWinInteractiveCustomCommand) uses + // prompts and defaults from PatternHint. + ParsePromptPattern(Pattern, PromptStr, Replacement, Delimit); + + Prompt(Index, PromptStr, Replacement); + + Result = true; + } + else if ((Pattern.Length() >= 3) && (Pattern[2] == L'`')) + { + UnicodeString Command = Pattern.SubString(3, Pattern.Length() - 3); + Command = FChildCustomCommand->Complete(Command, true); + Execute(Command, Replacement); + Delimit = false; + Result = true; + } + else + { + Result = false; + } + + return Result; +} + + +TCustomCommandData::TCustomCommandData() +{ +} + +TCustomCommandData::TCustomCommandData(const TCustomCommandData & Data) +{ + this->operator=(Data); +} + +//--------------------------------------------------------------------------- +TCustomCommandData::TCustomCommandData(TTerminal * Terminal) +{ + Init(Terminal->GetSessionData(), Terminal->TerminalGetUserName(), Terminal->GetPassword(), + Terminal->GetSessionInfo().HostKeyFingerprint); +} + +TCustomCommandData::TCustomCommandData( + TSessionData * SessionData, const UnicodeString & AUserName, const UnicodeString & APassword) +{ + Init(SessionData, AUserName, APassword, UnicodeString()); +} + +void TCustomCommandData::Init( + TSessionData * ASessionData, const UnicodeString & AUserName, + const UnicodeString & APassword, const UnicodeString & AHostKey) +{ + FSessionData.reset(new TSessionData(L"")); + FSessionData->Assign(ASessionData); + FSessionData->SetUserName(AUserName); + FSessionData->SetPassword(APassword); + FSessionData->SetHostKey(AHostKey); +} + +TCustomCommandData & TCustomCommandData::operator=(const TCustomCommandData & Data) +{ + if (&Data != this) + { + DebugAssert(Data.GetSessionData() != nullptr); + FSessionData.reset(new TSessionData(L"")); + FSessionData->Assign(Data.GetSessionData()); + } + return *this; +} + +TSessionData * TCustomCommandData::GetSessionData() const +{ + return FSessionData.get(); +} + +TFileCustomCommand::TFileCustomCommand() +{ +} + + +TFileCustomCommand::TFileCustomCommand(const TCustomCommandData & Data, + const UnicodeString & APath) : + FData(Data), + FPath(APath) +{ +} + +TFileCustomCommand::TFileCustomCommand(const TCustomCommandData & Data, + const UnicodeString & APath, const UnicodeString & AFileName, + const UnicodeString & FileList) : + FData(Data), + FPath(APath), + FFileName(AFileName), + FFileList(FileList) +{ +} + +intptr_t TFileCustomCommand::PatternLen(const UnicodeString & Command, intptr_t Index) const +{ + intptr_t Len; + wchar_t PatternCmd = (Index < Command.Length()) ? Command[Index + 1] : L'\0'; + switch (PatternCmd) + { + case L'S': + case L'@': + case L'U': + case L'P': + case L'#': + case L'/': + case L'&': + case L'N': + Len = 2; + break; + + default: + Len = 1; + break; + } + return Len; +} + +bool TFileCustomCommand::PatternReplacement( + intptr_t /*Index*/, const UnicodeString & Pattern, UnicodeString & Replacement, bool & Delimit) const +{ + // keep consistent with TSessionLog::OpenLogFile + + TSessionData * SessionData = FData.GetSessionData(); + + if (AnsiSameText(Pattern, L"!s")) + { + Replacement = SessionData->GenerateSessionUrl(sufComplete); + } + else if (Pattern == L"!@") + { + Replacement = SessionData->GetHostNameExpanded(); + } + else if (::AnsiSameText(Pattern, L"!u")) + { + Replacement = SessionData->SessionGetUserName(); + } + else if (::AnsiSameText(Pattern, L"!p")) + { + Replacement = SessionData->GetPassword(); + } + else if (::AnsiSameText(Pattern, L"!#")) + { + Replacement = IntToStr(SessionData->GetPortNumber()); + } + else if (Pattern == L"!/") + { + Replacement = core::UnixIncludeTrailingBackslash(FPath); + } + else if (Pattern == L"!&") + { + Replacement = FFileList; + // already delimited + Delimit = false; + } + else if (AnsiSameText(Pattern, L"!n")) + { + Replacement = SessionData->GetSessionName(); + } + else + { + DebugAssert(Pattern.Length() == 1); + Replacement = FFileName; + } + + return true; +} + +void TFileCustomCommand::Validate(const UnicodeString & Command) +{ + intptr_t Found[2] = { 0, 0 }; + CustomValidate(Command, &Found); + if ((Found[0] > 0) && (Found[1] > 0)) + { + throw Exception(FMTLOAD(CUSTOM_COMMAND_FILELIST_ERROR, + Found[1], Found[0])); + } +} + +void TFileCustomCommand::ValidatePattern(const UnicodeString & Command, + intptr_t Index, intptr_t /*Len*/, wchar_t PatternCmd, void * Arg) +{ + intptr_t * Found = static_cast(Arg); + + DebugAssert(Index > 0); + + if (PatternCmd == L'&') + { + Found[0] = static_cast(Index); + } + else if ((PatternCmd != TEXT_TOKEN) && (PatternLen(Command, Index) == 1)) + { + Found[1] = Index; + } +} + +bool TFileCustomCommand::IsFileListCommand(const UnicodeString & Command) const +{ + return FindPattern(Command, L'&'); +} + +bool TFileCustomCommand::IsRemoteFileCommand(const UnicodeString & Command) const +{ + return FindPattern(Command, L'!') || FindPattern(Command, L'&'); +} + +bool TFileCustomCommand::IsFileCommand(const UnicodeString & Command) const +{ + return IsRemoteFileCommand(Command); +} + +bool TFileCustomCommand::IsSiteCommand(const UnicodeString & Command) const +{ + return FindPattern(Command, L'@'); +} + +bool TFileCustomCommand::IsPasswordCommand(const UnicodeString & Command) const +{ + return FindPattern(Command, L'p'); +} + diff --git a/netbox/src/core/FileMasks.h b/netbox/src/core/FileMasks.h new file mode 100644 index 000000000..b7eda05d4 --- /dev/null +++ b/netbox/src/core/FileMasks.h @@ -0,0 +1,275 @@ + +#pragma once + +#include +#include +#include +#include "SessionData.h" + +class EFileMasksException : public Exception +{ +public: + explicit EFileMasksException(const UnicodeString & AMessage, intptr_t ErrorStart, intptr_t ErrorLen); + intptr_t ErrorStart; + intptr_t ErrorLen; +}; + +#define INCLUDE_EXCLUDE_FILE_MASKS_DELIMITER L'|' +#define MASK_INDEX(DIRECTORY, INCLUDE) ((DIRECTORY ? 2 : 0) + (INCLUDE ? 0 : 1)) + +class TFileMasks : public TObject +{ +public: + struct TParams : public TObject + { + TParams(); + int64_t Size; + TDateTime Modification; + + UnicodeString ToString() const; + }; + + static bool IsMask(const UnicodeString & Mask); + static UnicodeString NormalizeMask(const UnicodeString & Mask, const UnicodeString & AnyMask = L""); + static UnicodeString ComposeMaskStr( + TStrings * IncludeFileMasksStr, TStrings * ExcludeFileMasksStr, + TStrings * IncludeDirectoryMasksStr, TStrings * ExcludeDirectoryMasksStr); + static UnicodeString ComposeMaskStr(TStrings * MasksStr, bool Directory); + + TFileMasks(); + explicit TFileMasks(intptr_t ForceDirectoryMasks); + TFileMasks(const TFileMasks & Source); + explicit TFileMasks(const UnicodeString & AMasks); + virtual ~TFileMasks(); + TFileMasks & operator =(const TFileMasks & rhm); + TFileMasks & operator =(const UnicodeString & rhs); + bool operator ==(const TFileMasks & rhm) const; + bool operator ==(const UnicodeString & rhs) const; + + void SetMask(const UnicodeString & Mask); + + bool Matches(const UnicodeString & AFileName, bool Directory = false, + const UnicodeString & APath = L"", const TParams * Params = nullptr) const; + bool Matches(const UnicodeString & AFileName, bool Directory, + const UnicodeString & APath, const TParams * Params, + bool RecurseInclude, bool & ImplicitMatch) const; + bool Matches(const UnicodeString & AFileName, bool Local, bool Directory, + const TParams * Params = nullptr) const; + bool Matches(const UnicodeString & AFileName, bool Local, bool Directory, + const TParams * Params, bool RecurseInclude, bool & ImplicitMatch) const; + + /*__property UnicodeString Masks = { read = FStr, write = SetMasks }; + + __property TStrings * IncludeFileMasksStr = { read = GetMasksStr, index = MASK_INDEX(false, true) }; + __property TStrings * ExcludeFileMasksStr = { read = GetMasksStr, index = MASK_INDEX(false, false) }; + __property TStrings * IncludeDirectoryMasksStr = { read = GetMasksStr, index = MASK_INDEX(true, true) }; + __property TStrings * ExcludeDirectoryMasksStr = { read = GetMasksStr, index = MASK_INDEX(true, false) };*/ + + UnicodeString GetMasks() const { return FStr; } + void SetMasks(const UnicodeString & Value); + + TStrings * GetIncludeFileMasksStr() const { return GetMasksStr(MASK_INDEX(false, true)); } + TStrings * GetExcludeFileMasksStr() const { return GetMasksStr(MASK_INDEX(false, false)); } + TStrings * GetIncludeDirectoryMasksStr() const { return GetMasksStr(MASK_INDEX(true, true)); } + TStrings * GetExcludeDirectoryMasksStr() const { return GetMasksStr(MASK_INDEX(true, false)); } + +private: + intptr_t FForceDirectoryMasks; + UnicodeString FStr; + + struct TMaskMask : public TObject + { + TMaskMask() : + Kind(Any), + Mask(nullptr) + {} + enum + { + Any, + NoExt, + Regular, + } Kind; + Masks::TMask * Mask; + }; + + struct TMask : public TObject + { + TMask() : + HighSizeMask(None), + HighSize(0), + LowSizeMask(None), + LowSize(0), + HighModificationMask(None), + LowModificationMask(None) + {} + TMaskMask FileNameMask; + TMaskMask DirectoryMask; + + enum TMaskBoundary + { + None, + Open, + Close, + }; + + TMaskBoundary HighSizeMask; + int64_t HighSize; + TMaskBoundary LowSizeMask; + int64_t LowSize; + + TMaskBoundary HighModificationMask; + TDateTime HighModification; + TMaskBoundary LowModificationMask; + TDateTime LowModification; + + UnicodeString MaskStr; + UnicodeString UserStr; + }; + + typedef std::vector TMasks; + TMasks FMasks[4]; + mutable TStrings * FMasksStr[4]; + +private: + void SetStr(const UnicodeString & Value, bool SingleMask); + void CreateMaskMask(const UnicodeString & Mask, intptr_t Start, intptr_t End, + bool Ex, TMaskMask & MaskMask) const; + void CreateMask(const UnicodeString & MaskStr, intptr_t MaskStart, + intptr_t MaskEnd, bool Include); + TStrings * GetMasksStr(intptr_t Index) const; + static UnicodeString MakeDirectoryMask(const UnicodeString & AStr); + static inline void ReleaseMaskMask(TMaskMask & MaskMask); + inline void Init(); + void DoInit(bool Delete); + void Clear(); + static void Clear(TMasks & Masks); + static void TrimEx(UnicodeString & Str, intptr_t & Start, intptr_t & End); + static bool MatchesMasks(const UnicodeString & AFileName, bool Directory, + const UnicodeString & APath, const TParams * Params, const TMasks & Masks, bool Recurse); + static inline bool MatchesMaskMask(const TMaskMask & MaskMask, const UnicodeString & Str); + static inline bool IsAnyMask(const UnicodeString & Mask); + void ThrowError(intptr_t Start, intptr_t End) const; +}; + +UnicodeString MaskFileName(const UnicodeString & AFileName, const UnicodeString & Mask); +bool IsFileNameMask(const UnicodeString & AMask); +bool IsEffectiveFileNameMask(const UnicodeString & AMask); +UnicodeString DelimitFileNameMask(const UnicodeString & AMask); + +//typedef void __fastcall (__closure * TCustomCommandPatternEvent) +// (int Index, const UnicodeString Pattern, void * Arg, UnicodeString & Replacement, +// bool & LastPass); +DEFINE_CALLBACK_TYPE5(TCustomCommandPatternEvent, void, + int /*Index*/, const UnicodeString & /*Pattern*/, void * /*Arg*/, UnicodeString & /*Replacement*/, + bool & /*LastPass*/); + +class TCustomCommand : public TObject +{ +friend class TInteractiveCustomCommand; + +public: + TCustomCommand(); + virtual ~TCustomCommand() {} + + UnicodeString Complete(const UnicodeString & Command, bool LastPass); + virtual void Validate(const UnicodeString & Command); + +protected: + static const wchar_t NoQuote; + void GetToken(const UnicodeString & Command, + intptr_t Index, intptr_t & Len, wchar_t & PatternCmd) const; + void CustomValidate(const UnicodeString & Command, void * Arg); + bool FindPattern(const UnicodeString & Command, wchar_t PatternCmd) const; + + virtual void ValidatePattern(const UnicodeString & Command, + intptr_t Index, intptr_t Len, wchar_t PatternCmd, void * Arg); + + virtual intptr_t PatternLen(const UnicodeString & Command, intptr_t Index) const = 0; + virtual void PatternHint(intptr_t Index, const UnicodeString & Pattern); + virtual bool PatternReplacement(intptr_t Index, const UnicodeString & Pattern, + UnicodeString & Replacement, bool & Delimit) const = 0; + virtual void DelimitReplacement(UnicodeString & Replacement, wchar_t Quote); +}; + +class TInteractiveCustomCommand : public TCustomCommand +{ +NB_DISABLE_COPY(TInteractiveCustomCommand) +public: + explicit TInteractiveCustomCommand(TCustomCommand * ChildCustomCommand); + +protected: + virtual void Prompt(intptr_t Index, const UnicodeString & Prompt, + UnicodeString & Value) const; + virtual void Execute(const UnicodeString & Command, + UnicodeString & Value) const; + virtual intptr_t PatternLen(const UnicodeString & Command, intptr_t Index) const; + virtual bool PatternReplacement(intptr_t Index, const UnicodeString & Pattern, + UnicodeString & Replacement, bool & Delimit) const; + void ParsePromptPattern( + const UnicodeString & Pattern, UnicodeString & Prompt, UnicodeString & Default, bool & Delimit) const; + bool IsPromptPattern(const UnicodeString & Pattern) const; + +private: + TCustomCommand * FChildCustomCommand; +}; + +class TTerminal; + +struct TCustomCommandData : public TObject +{ +//NB_DISABLE_COPY(TCustomCommandData) +public: + TCustomCommandData(); + explicit TCustomCommandData(const TCustomCommandData & Data); + explicit TCustomCommandData(TTerminal * Terminal); + explicit TCustomCommandData( + TSessionData * SessionData, const UnicodeString & AUserName, + const UnicodeString & APassword); + + // __property TSessionData * SessionData = { read = GetSesssionData }; + + TCustomCommandData & operator=(const TCustomCommandData & Data); + +private: + std::unique_ptr FSessionData; + void Init( + TSessionData * SessionData, const UnicodeString & AUserName, + const UnicodeString & APassword, const UnicodeString & AHostKey); + +public: + TSessionData * GetSessionData() const; +}; + +class TFileCustomCommand : public TCustomCommand +{ +public: + TFileCustomCommand(); + explicit TFileCustomCommand(const TCustomCommandData & Data, const UnicodeString & APath); + explicit TFileCustomCommand(const TCustomCommandData & Data, const UnicodeString & APath, + const UnicodeString & AFileName, const UnicodeString & FileList); + virtual ~TFileCustomCommand() {} + + virtual void Validate(const UnicodeString & Command); + virtual void ValidatePattern(const UnicodeString & Command, + intptr_t Index, intptr_t Len, wchar_t PatternCmd, void * Arg); + + bool IsFileListCommand(const UnicodeString & Command) const; + virtual bool IsFileCommand(const UnicodeString & Command) const; + bool IsRemoteFileCommand(const UnicodeString & Command) const; + bool IsSiteCommand(const UnicodeString & Command) const; + bool IsPasswordCommand(const UnicodeString & Command) const; + +protected: + virtual intptr_t PatternLen(const UnicodeString & Command, intptr_t Index) const; + virtual bool PatternReplacement(intptr_t Index, const UnicodeString & Pattern, + UnicodeString & Replacement, bool & Delimit) const; + +private: + TCustomCommandData FData; + UnicodeString FPath; + UnicodeString FFileName; + UnicodeString FFileList; +}; + +typedef TFileCustomCommand TRemoteCustomCommand; + diff --git a/netbox/src/core/FileOperationProgress.cpp b/netbox/src/core/FileOperationProgress.cpp new file mode 100644 index 000000000..e00db1730 --- /dev/null +++ b/netbox/src/core/FileOperationProgress.cpp @@ -0,0 +1,557 @@ +#include +#pragma hdrstop + +#include + +#include "FileOperationProgress.h" + +#define TRANSFER_BUF_SIZE 128 * 1024 + +TFileOperationProgressType::TFileOperationProgressType() : + FOnProgress(nullptr), + FOnFinished(nullptr), + FReset(false) +{ + Clear(); +} + +TFileOperationProgressType::TFileOperationProgressType( + TFileOperationProgressEvent AOnProgress, TFileOperationFinishedEvent AOnFinished) : + FOnProgress(AOnProgress), + FOnFinished(AOnFinished), + FReset(false) +{ + Clear(); +} + +TFileOperationProgressType::~TFileOperationProgressType() +{ + DebugAssert(!InProgress || FReset); + DebugAssert(!Suspended || FReset); +} + +void TFileOperationProgressType::AssignButKeepSuspendState(const TFileOperationProgressType & Other) +{ + TValueRestorer SuspendTimeRestorer(FSuspendTime); + TValueRestorer SuspendedRestorer(Suspended); + + *this = Other; +} + +void TFileOperationProgressType::Clear() +{ + FSuspendTime = 0, + FFileStartTime = 0.0; + FFilesFinished = 0; + FReset = false; + FLastSecond = 0; + FRemainingCPS = 0; + FTicks.clear(); + FTotalTransferredThen.clear(); + FCounterSet = false; + Operation = foNone; + Side = osLocal; + FileName.Clear(); + Directory.Clear(); + AsciiTransfer = false; + TransferingFile = false; + Temp = false; + LocalSize = 0; + LocallyUsed = 0; + // to bypass check in ClearTransfer() + TransferSize = 0; + TransferedSize = 0; + SkippedSize = 0; + InProgress = false; + FileInProgress = false; + Cancel = csContinue; + Count = 0; + StartTime = Now(); + TotalTransfered = 0; + TotalSkipped = 0; + TotalSize = 0; + BatchOverwrite = boNo; + SkipToAll = false; + CPSLimit = 0; + TotalSizeSet = false; + Suspended = false; + + ClearTransfer(); +} + +void TFileOperationProgressType::ClearTransfer() +{ + if ((TransferSize > 0) && (TransferedSize < TransferSize)) + { + int64_t RemainingSize = (TransferSize - TransferedSize); + TotalSkipped += RemainingSize; + } + LocalSize = 0; + TransferSize = 0; + LocallyUsed = 0; + SkippedSize = 0; + TransferedSize = 0; + TransferingFile = false; + FLastSecond = 0; +} + +void TFileOperationProgressType::Start(TFileOperation AOperation, + TOperationSide ASide, intptr_t ACount) +{ + Start(AOperation, ASide, ACount, false, L"", 0); +} + +void TFileOperationProgressType::Start(TFileOperation AOperation, + TOperationSide ASide, intptr_t ACount, bool ATemp, + const UnicodeString & ADirectory, uintptr_t ACPSLimit) +{ + Clear(); + Operation = AOperation; + Side = ASide; + Count = ACount; + InProgress = true; + Cancel = csContinue; + Directory = ADirectory; + Temp = ATemp; + CPSLimit = ACPSLimit; + try + { + DoProgress(); + } + catch (...) + { + // connection can be lost during progress callbacks + ClearTransfer(); + InProgress = false; + throw; + } +} + +void TFileOperationProgressType::Reset() +{ + FReset = true; +} + +void TFileOperationProgressType::Stop() +{ + // added to include remaining bytes to TotalSkipped, in case + // the progress happens to update before closing + ClearTransfer(); + InProgress = false; + DoProgress(); +} + +void TFileOperationProgressType::Suspend() +{ + DebugAssert(!Suspended); + Suspended = true; + FSuspendTime = ::GetTickCount(); + DoProgress(); +} + +void TFileOperationProgressType::Resume() +{ + DebugAssert(Suspended); + Suspended = false; + + // shift timestamps for CPS calculation in advance + // by the time the progress was suspended + uint32_t Stopped = static_cast(::GetTickCount() - FSuspendTime); + size_t Index = 0; + while (Index < FTicks.size()) + { + FTicks[Index] += Stopped; + ++Index; + } + + DoProgress(); +} + +intptr_t TFileOperationProgressType::OperationProgress() const +{ + //DebugAssert(Count); + intptr_t Result = Count ? (FFilesFinished * 100) / Count : 0; + return Result; +} + +intptr_t TFileOperationProgressType::TransferProgress() const +{ + intptr_t Result; + if (TransferSize) + { + Result = static_cast((TransferedSize * 100) / TransferSize); + } + else + { + Result = 0; + } + return Result; +} + +intptr_t TFileOperationProgressType::TotalTransferProgress() const +{ + DebugAssert(TotalSizeSet); + intptr_t Result = TotalSize > 0 ? static_cast(((TotalTransfered + TotalSkipped) * 100) / TotalSize) : 0; + return Result < 100 ? Result : 100; +} + +intptr_t TFileOperationProgressType::OverallProgress() const +{ + if (TotalSizeSet) + { + DebugAssert((Operation == foCopy) || (Operation == foMove)); + return TotalTransferProgress(); + } + else + { + return OperationProgress(); + } +} + +void TFileOperationProgressType::Progress() +{ + DoProgress(); +} + +void TFileOperationProgressType::DoProgress() +{ +#ifndef __linux__ + SetThreadExecutionState(ES_SYSTEM_REQUIRED); +#endif + FOnProgress(*this); +} + +void TFileOperationProgressType::Finish(const UnicodeString & AFileName, + bool Success, TOnceDoneOperation & OnceDoneOperation) +{ + DebugAssert(InProgress); + + FOnFinished(Operation, Side, Temp, AFileName, + /* TODO : There wasn't 'Success' condition, was it by mistake or by purpose? */ + Success && (Cancel == csContinue), OnceDoneOperation); + FFilesFinished++; + DoProgress(); +} + +void TFileOperationProgressType::SetFile(const UnicodeString & AFileName, bool AFileInProgress) +{ + FileName = AFileName; + FullFileName = FileName; + if (Side == osRemote) + { + // historically set were passing filename-only for remote site operations, + // now we need to collect a full paths, so we pass in full path, + // but still want to have filename-only in FileName + FileName = base::UnixExtractFileName(FileName); + } + FileInProgress = AFileInProgress; + ClearTransfer(); + FFileStartTime = Now(); + DoProgress(); +} + +void TFileOperationProgressType::SetFileInProgress() +{ + DebugAssert(!FileInProgress); + FileInProgress = true; + DoProgress(); +} + +void TFileOperationProgressType::SetLocalSize(int64_t ASize) +{ + LocalSize = ASize; + DoProgress(); +} + +void TFileOperationProgressType::AddLocallyUsed(int64_t ASize) +{ + LocallyUsed += ASize; + if (LocallyUsed > LocalSize) + { + LocalSize = LocallyUsed; + } + DoProgress(); +} + +bool TFileOperationProgressType::IsLocallyDone() const +{ + DebugAssert(LocallyUsed <= LocalSize); + return (LocallyUsed == LocalSize); +} + +void TFileOperationProgressType::SetSpeedCounters() +{ + if ((CPSLimit > 0) && !FCounterSet) + { + FCounterSet = true; + // Configuration->Usage->Inc(L"SpeedLimitUses"); + } +} + +void TFileOperationProgressType::ThrottleToCPSLimit( + uintptr_t Size) +{ + uintptr_t Remaining = Size; + while (Remaining > 0) + { + Remaining -= AdjustToCPSLimit(Remaining); + } +} + +uintptr_t TFileOperationProgressType::AdjustToCPSLimit( + uintptr_t Size) +{ + SetSpeedCounters(); + + if (CPSLimit > 0) + { + // we must not return 0, hence, if we reach zero, + // we wait until the next second + do + { + uintptr_t Second = (::GetTickCount() / MSecsPerSec); + + if (Second != FLastSecond) + { + FRemainingCPS = CPSLimit; + FLastSecond = Second; + } + + if (FRemainingCPS == 0) + { +#ifndef __linux__ + SleepEx(100, true); +#else + usleep(100000); +#endif + DoProgress(); + } + } + while ((CPSLimit > 0) && (FRemainingCPS == 0)); + + // CPSLimit may have been dropped in DoProgress + if (CPSLimit > 0) + { + if (FRemainingCPS < Size) + { + Size = FRemainingCPS; + } + + FRemainingCPS -= Size; + } + } + return Size; +} + +uintptr_t TFileOperationProgressType::LocalBlockSize() +{ + uintptr_t Result = TRANSFER_BUF_SIZE; + if (LocallyUsed + (int64_t)Result > LocalSize) + { + Result = static_cast(LocalSize - LocallyUsed); + } + Result = AdjustToCPSLimit(Result); + return Result; +} + +void TFileOperationProgressType::SetTotalSize(int64_t ASize) +{ + TotalSize = ASize; + TotalSizeSet = true; + DoProgress(); +} + +void TFileOperationProgressType::SetTransferSize(int64_t ASize) +{ + TransferSize = ASize; + DoProgress(); +} + +void TFileOperationProgressType::ChangeTransferSize(int64_t ASize) +{ + // reflect change on file size (due to text transfer mode conversion particularly) + // on total transfer size + if (TotalSizeSet) + { + TotalSize += (ASize - TransferSize); + } + TransferSize = ASize; + DoProgress(); +} + +void TFileOperationProgressType::RollbackTransfer() +{ + TransferedSize -= SkippedSize; + DebugAssert(TransferedSize <= TotalTransfered); + TotalTransfered -= TransferedSize; + DebugAssert(SkippedSize <= TotalSkipped); + FTicks.clear(); + FTotalTransferredThen.clear(); + TotalSkipped -= SkippedSize; + SkippedSize = 0; + TransferedSize = 0; + TransferSize = 0; + LocallyUsed = 0; +} + +void TFileOperationProgressType::AddTransfered(int64_t ASize, + bool AddToTotals) +{ + TransferedSize += ASize; + if (TransferedSize > TransferSize) + { + // this can happen with SFTP when downloading file that + // grows while being downloaded + if (TotalSizeSet) + { + // we should probably guard this with AddToTotals + TotalSize += (TransferedSize - TransferSize); + } + TransferSize = TransferedSize; + } + if (AddToTotals) + { + TotalTransfered += ASize; + uint32_t Ticks = static_cast(::GetTickCount()); + if (FTicks.empty() || + (FTicks.back() > Ticks) || // ticks wrap after 49.7 days + ((Ticks - FTicks.back()) >= static_cast(MSecsPerSec))) + { + FTicks.push_back(Ticks); + FTotalTransferredThen.push_back(TotalTransfered); + } + + if (FTicks.size() > 10) + { + FTicks.erase(FTicks.begin()); + FTotalTransferredThen.erase(FTotalTransferredThen.begin()); + } + } + DoProgress(); +} + +void TFileOperationProgressType::AddResumed(int64_t ASize) +{ + TotalSkipped += ASize; + SkippedSize += ASize; + AddTransfered(ASize, false); + AddLocallyUsed(ASize); +} + +void TFileOperationProgressType::AddSkippedFileSize(int64_t ASize) +{ + TotalSkipped += ASize; + DoProgress(); +} + +uintptr_t TFileOperationProgressType::TransferBlockSize() +{ + uintptr_t Result = TRANSFER_BUF_SIZE; + if (TransferedSize + static_cast(Result) > TransferSize) + { + Result = static_cast(TransferSize - TransferedSize); + } + Result = AdjustToCPSLimit(Result); + return Result; +} + +uintptr_t TFileOperationProgressType::StaticBlockSize() +{ + return TRANSFER_BUF_SIZE; +} + +bool TFileOperationProgressType::IsTransferDone() const +{ + DebugAssert(TransferedSize <= TransferSize); + return (TransferedSize == TransferSize); +} + +void TFileOperationProgressType::SetAsciiTransfer(bool AAsciiTransfer) +{ + AsciiTransfer = AAsciiTransfer; + DoProgress(); +} + +TDateTime TFileOperationProgressType::TimeElapsed() const +{ + return Now() - StartTime; +} + +uintptr_t TFileOperationProgressType::CPS() const +{ + uintptr_t Result; + if (FTicks.empty()) + { + Result = 0; + } + else + { + uintptr_t Ticks = (Suspended ? FSuspendTime : ::GetTickCount()); + uintptr_t TimeSpan; + if (Ticks < FTicks.front()) + { + // clocks has wrapped, guess 10 seconds difference + TimeSpan = 10000; + } + else + { + TimeSpan = (Ticks - FTicks.front()); + } + + if (TimeSpan == 0) + { + Result = 0; + } + else + { + int64_t Transferred = (TotalTransfered - FTotalTransferredThen.front()); + Result = static_cast(Transferred * MSecsPerSec / TimeSpan); + } + } + return Result; +} + +TDateTime TFileOperationProgressType::TimeExpected() const +{ + uintptr_t CurCps = CPS(); + if (CurCps) + { + return TDateTime(ToDouble((ToDouble(TransferSize - TransferedSize)) / CurCps) / SecsPerDay); + } + else + { + return TDateTime(0.0); + } +} + +TDateTime TFileOperationProgressType::TotalTimeExpected() const +{ + DebugAssert(TotalSizeSet); + uintptr_t CurCps = CPS(); + // sanity check + if ((CurCps > 0) && (TotalSize > TotalSkipped)) + { + return TDateTime(ToDouble(ToDouble(TotalSize - TotalSkipped) / CurCps) / + SecsPerDay); + } + else + { + return TDateTime(0.0); + } +} + +TDateTime TFileOperationProgressType::TotalTimeLeft() const +{ + DebugAssert(TotalSizeSet); + uintptr_t CurCps = CPS(); + // sanity check + if ((CurCps > 0) && (TotalSize > TotalSkipped + TotalTransfered)) + { + return TDateTime(ToDouble(ToDouble(TotalSize - TotalSkipped - TotalTransfered) / CurCps) / + SecsPerDay); + } + else + { + return TDateTime(0.0); + } +} diff --git a/netbox/src/core/FileOperationProgress.h b/netbox/src/core/FileOperationProgress.h new file mode 100644 index 000000000..9a275bd8d --- /dev/null +++ b/netbox/src/core/FileOperationProgress.h @@ -0,0 +1,180 @@ +#pragma once + +#include + +#include "Configuration.h" +#include "CopyParam.h" + +class TFileOperationProgressType; +enum TFileOperation +{ + foNone, foCopy, foMove, foDelete, foSetProperties, + foRename, foCustomCommand, foCalculateSize, foRemoteMove, foRemoteCopy, + foGetProperties, foCalculateChecksum, foLock, foUnlock +}; + +// csCancelTransfer and csRemoteAbort are used with SCP only +enum TCancelStatus +{ + csContinue = 0, + csCancel, + csCancelTransfer, + csRemoteAbort, +}; + +enum TBatchOverwrite +{ + boNo, + boAll, + boNone, + boOlder, + boAlternateResume, + boAppend, + boResume, +}; + +/*typedef void __fastcall (__closure *TFileOperationProgressEvent) + (TFileOperationProgressType & ProgressData);*/ +DEFINE_CALLBACK_TYPE1(TFileOperationProgressEvent, void, + TFileOperationProgressType & /*ProgressData*/); +/*typedef void __fastcall (__closure *TFileOperationFinished) + (TFileOperation Operation, TOperationSide Side, bool Temp, + const UnicodeString & FileName, bool Success, TOnceDoneOperation & OnceDoneOperation);*/ +DEFINE_CALLBACK_TYPE6(TFileOperationFinishedEvent, void, + TFileOperation /*Operation*/, TOperationSide /*Side*/, bool /*Temp*/, + const UnicodeString & /*FileName*/, bool /*Success*/, TOnceDoneOperation & /*OnceDoneOperation*/); +//--------------------------------------------------------------------------- +class TFileOperationProgressType : public TObject +{ +private: + // when it was last time suspended (to calculate suspend time in Resume()) + uintptr_t FSuspendTime; + // when current file was started being transfered + TDateTime FFileStartTime; + intptr_t FFilesFinished; + TFileOperationProgressEvent FOnProgress; + TFileOperationFinishedEvent FOnFinished; + bool FReset; + uintptr_t FLastSecond; + uintptr_t FRemainingCPS; + bool FCounterSet; + std::vector FTicks; + std::vector FTotalTransferredThen; + +protected: + void ClearTransfer(); + inline void DoProgress(); + +public: + // common data + TFileOperation Operation; + // on what side if operation being processed (local/remote), source of copy + TOperationSide Side; + UnicodeString FileName; + UnicodeString FullFileName; + UnicodeString Directory; + bool AsciiTransfer; + // Can be true with SCP protocol only + bool TransferingFile; + bool Temp; + + // file size to read/write + int64_t LocalSize; + int64_t LocallyUsed; + int64_t TransferSize; + int64_t TransferedSize; + int64_t SkippedSize; + bool InProgress; + bool FileInProgress; + TCancelStatus Cancel; + intptr_t Count; + // when operation started + TDateTime StartTime; + // bytes transfered + int64_t TotalTransfered; + int64_t TotalSkipped; + int64_t TotalSize; + + TBatchOverwrite BatchOverwrite; + bool SkipToAll; + uintptr_t CPSLimit; + + bool TotalSizeSet; + + bool Suspended; + + explicit TFileOperationProgressType(); + explicit TFileOperationProgressType( + TFileOperationProgressEvent AOnProgress, TFileOperationFinishedEvent AOnFinished); + virtual ~TFileOperationProgressType(); + void AssignButKeepSuspendState(const TFileOperationProgressType & Other); + void AddLocallyUsed(int64_t ASize); + void AddTransfered(int64_t ASize, bool AddToTotals = true); + void AddResumed(int64_t ASize); + void AddSkippedFileSize(int64_t ASize); + void Clear(); + uintptr_t CPS() const; + void Finish(const UnicodeString & AFileName, bool Success, + TOnceDoneOperation & OnceDoneOperation); + void Progress(); + uintptr_t LocalBlockSize(); + bool IsLocallyDone() const; + bool IsTransferDone() const; + void SetFile(const UnicodeString & AFileName, bool AFileInProgress = true); + void SetFileInProgress(); + intptr_t OperationProgress() const; + uintptr_t TransferBlockSize(); + uintptr_t AdjustToCPSLimit(uintptr_t Size); + void ThrottleToCPSLimit(uintptr_t Size); + static uintptr_t StaticBlockSize(); + void Reset(); + void Resume(); + void SetLocalSize(int64_t ASize); + void SetAsciiTransfer(bool AAsciiTransfer); + void SetTransferSize(int64_t ASize); + void ChangeTransferSize(int64_t ASize); + void RollbackTransfer(); + void SetTotalSize(int64_t ASize); + void Start(TFileOperation AOperation, TOperationSide ASide, intptr_t ACount); + void Start(TFileOperation AOperation, + TOperationSide ASide, intptr_t ACount, bool ATemp, const UnicodeString & ADirectory, + uintptr_t ACPSLimit); + void Stop(); + void Suspend(); + // whole operation + TDateTime TimeElapsed() const; + // only current file + TDateTime TimeExpected() const; + TDateTime TotalTimeExpected() const; + TDateTime TotalTimeLeft() const; + intptr_t TransferProgress() const; + intptr_t OverallProgress() const; + intptr_t TotalTransferProgress() const; + void SetSpeedCounters(); +}; + +class TSuspendFileOperationProgress : public TObject +{ +NB_DISABLE_COPY(TSuspendFileOperationProgress) +public: + explicit TSuspendFileOperationProgress(TFileOperationProgressType * OperationProgress) : + FOperationProgress(OperationProgress) + { + if (FOperationProgress != nullptr) + { + FOperationProgress->Suspend(); + } + } + + virtual ~TSuspendFileOperationProgress() + { + if (FOperationProgress != nullptr) + { + FOperationProgress->Resume(); + } + } + +private: + TFileOperationProgressType * FOperationProgress; +}; + diff --git a/netbox/src/core/FileSystems.cpp b/netbox/src/core/FileSystems.cpp new file mode 100644 index 000000000..621d3b359 --- /dev/null +++ b/netbox/src/core/FileSystems.cpp @@ -0,0 +1,46 @@ +#include +#pragma hdrstop + +#include + +#include "FileSystems.h" +#include "RemoteFiles.h" +#include "CopyParam.h" + +TCustomFileSystem::TCustomFileSystem(TTerminal * ATerminal) : + FTerminal(ATerminal) +{ + DebugAssert(FTerminal); +} + +UnicodeString TCustomFileSystem::CreateTargetDirectory( + IN const UnicodeString & AFileName, + IN const UnicodeString & ADirectory, + IN const TCopyParamType * CopyParam) +{ + UnicodeString Result = ADirectory; + UnicodeString DestFileName = CopyParam->ChangeFileName(base::UnixExtractFileName(AFileName), + osRemote, true); + UnicodeString FileNamePath = ::ExtractFilePath(DestFileName); + if (!FileNamePath.IsEmpty()) + { + Result = ::IncludeTrailingBackslash(ADirectory + FileNamePath); + ::ForceDirectories(ApiPath(Result)); + } + return Result; +} + +TCustomFileSystem::~TCustomFileSystem() +{ +#ifdef USE_DLMALLOC + dlmalloc_trim(0); // 64 * 1024); +#endif +} + +NB_IMPLEMENT_CLASS(TCustomFileSystem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TSinkFileParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TFileTransferData, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TOpenRemoteFileParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TOverwriteFileParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TClipboardHandler, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/FileSystems.h b/netbox/src/core/FileSystems.h new file mode 100644 index 000000000..5c7639664 --- /dev/null +++ b/netbox/src/core/FileSystems.h @@ -0,0 +1,221 @@ +#pragma once + +#include +#include + +#include "SessionInfo.h" + +class TTerminal; +class TRights; +class TRemoteFile; +class TRemoteFileList; +class TCopyParamType; +struct TSpaceAvailable; +class TFileOperationProgressType; +class TRemoteProperties; + +enum TFSCommand +{ + fsNull = 0, fsVarValue, fsLastLine, fsFirstLine, + fsCurrentDirectory, fsChangeDirectory, fsListDirectory, fsListCurrentDirectory, + fsListFile, fsLookupUsersGroups, fsCopyToRemote, fsCopyToLocal, fsDeleteFile, + fsRenameFile, fsCreateDirectory, fsChangeMode, fsChangeGroup, fsChangeOwner, + fsHomeDirectory, fsUnset, fsUnalias, fsCreateLink, fsCopyFile, + fsAnyCommand, fsLang, fsReadSymlink, fsChangeProperties, fsMoveFile, + fsLock, +}; + +const int dfNoRecursive = 0x01; +const int dfAlternative = 0x02; +const int dfForceDelete = 0x04; + +enum TOverwriteMode +{ + omOverwrite, + omAppend, + omResume, + omComplete +}; + +const int tfFirstLevel = 0x01; +const int tfAutoResume = 0x02; +const int tfNewDirectory = 0x04; + +struct TSinkFileParams : public TObject +{ +NB_DECLARE_CLASS(TSinkFileParams) +public: + UnicodeString TargetDir; + const TCopyParamType * CopyParam; + TFileOperationProgressType * OperationProgress; + intptr_t Params; + uintptr_t Flags; + bool Skipped; +}; + +struct TFileTransferData : public TObject +{ +NB_DISABLE_COPY(TFileTransferData) +NB_DECLARE_CLASS(TFileTransferData) +public: + TFileTransferData() : + CopyParam(nullptr), + Modification(0.0), + Params(0), + OverwriteResult(-1), + AutoResume(false) + { + } + + UnicodeString FileName; + const TCopyParamType * CopyParam; + TDateTime Modification; + intptr_t Params; + int OverwriteResult; + bool AutoResume; +}; + +struct TOverwriteFileParams : public TObject +{ +NB_DECLARE_CLASS(TOverwriteFileParams) +public: + TOverwriteFileParams() : + SourceSize(0), + DestSize(0), + SourcePrecision(mfFull), + DestPrecision(mfFull) + {} + + int64_t SourceSize; + int64_t DestSize; + TDateTime SourceTimestamp; + TDateTime DestTimestamp; + TModificationFmt SourcePrecision; + TModificationFmt DestPrecision; +}; + +struct TOpenRemoteFileParams : public TObject +{ +NB_DISABLE_COPY(TOpenRemoteFileParams) +NB_DECLARE_CLASS(TOpenRemoteFileParams) +public: + TOpenRemoteFileParams() : + LocalFileAttrs(0), + OperationProgress(nullptr), + CopyParam(nullptr), + Params(0), + Resume(false), + Resuming(false), + OverwriteMode(omOverwrite), + DestFileSize(0), + FileParams(nullptr), + Confirmed(false) + {} + uintptr_t LocalFileAttrs; + UnicodeString FileName; + UnicodeString RemoteFileName; + TFileOperationProgressType * OperationProgress; + const TCopyParamType * CopyParam; + intptr_t Params; + bool Resume; + bool Resuming; + TOverwriteMode OverwriteMode; + int64_t DestFileSize; // output + RawByteString RemoteFileHandle; // output + TOverwriteFileParams * FileParams; + bool Confirmed; +}; + +/** @brief Interface for custom filesystems + * + */ +class TFileSystemIntf +{ +public: + virtual ~TFileSystemIntf() {} + + virtual void Init(void *) = 0; + virtual void FileTransferProgress(int64_t TransferSize, int64_t Bytes) = 0; +}; + +class TCustomFileSystem : public TObject, public TFileSystemIntf +{ +NB_DISABLE_COPY(TCustomFileSystem) +NB_DECLARE_CLASS(TCustomFileSystem) +public: + virtual ~TCustomFileSystem(); + + virtual void Open() = 0; + virtual void Close() = 0; + virtual bool GetActive() const = 0; + virtual void CollectUsage() = 0; + virtual void Idle() = 0; + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) = 0; + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const = 0; + virtual void AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent) = 0; + virtual void ChangeDirectory(const UnicodeString & Directory) = 0; + virtual void CachedChangeDirectory(const UnicodeString & Directory) = 0; + virtual void AnnounceFileListOperation() = 0; + virtual void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action) = 0; + virtual bool LoadFilesProperties(TStrings * AFileList) = 0; + virtual void CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum) = 0; + virtual void CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) = 0; + virtual void CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) = 0; + virtual void RemoteCreateDirectory(const UnicodeString & ADirName) = 0; + virtual void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic) = 0; + virtual void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, + TRmSessionAction & Action) = 0; + virtual void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent) = 0; + virtual void DoStartup() = 0; + virtual void HomeDirectory() = 0; + virtual bool IsCapable(intptr_t Capability) const = 0; + virtual void LookupUsersGroups() = 0; + virtual void ReadCurrentDirectory() = 0; + virtual void ReadDirectory(TRemoteFileList * FileList) = 0; + virtual void ReadFile(const UnicodeString & AFileName, + TRemoteFile *& File) = 0; + virtual void ReadSymlink(TRemoteFile * SymLinkFile, + TRemoteFile *& File) = 0; + virtual void RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) = 0; + virtual void RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) = 0; + virtual TStrings * GetFixedPaths() = 0; + virtual void SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable) = 0; + virtual const TSessionInfo & GetSessionInfo() const = 0; + virtual const TFileSystemInfo & GetFileSystemInfo(bool Retrieve) = 0; + virtual bool TemporaryTransferFile(const UnicodeString & AFileName) = 0; + virtual bool GetStoredCredentialsTried() const = 0; + virtual UnicodeString FSGetUserName() const = 0; + virtual void GetSupportedChecksumAlgs(TStrings * Algs) = 0; + virtual void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile) = 0; + virtual void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile) = 0; + virtual void UpdateFromMain(TCustomFileSystem * MainFileSystem) = 0; + + virtual UnicodeString GetCurrDirectory() const = 0; + +protected: + TTerminal * FTerminal; + + explicit TCustomFileSystem(TTerminal * ATerminal); + + UnicodeString CreateTargetDirectory( + IN const UnicodeString & AFileName, + IN const UnicodeString & ADirectory, + IN const TCopyParamType * CopyParam); +}; + diff --git a/netbox/src/core/FtpFileSystem.cpp b/netbox/src/core/FtpFileSystem.cpp new file mode 100644 index 000000000..7ea502059 --- /dev/null +++ b/netbox/src/core/FtpFileSystem.cpp @@ -0,0 +1,5083 @@ + +#include +#pragma hdrstop + +#ifndef NO_FILEZILLA + +#include +#ifndef MPEXT +#define MPEXT +#endif +#include "FtpFileSystem.h" +#ifndef __linux__ +#include "FileZillaIntf.h" +#else +#include "libfilezilla/libfilezilla.hpp" +#endif + +#include +#include +#include "Terminal.h" +#include "TextsCore.h" +#include "TextsFileZilla.h" +#include "HelpCore.h" +#include "WinSCPSecurity.h" +#include +#include +#include + +const int DummyCodeClass = 8; +const int DummyTimeoutCode = 801; +const int DummyCancelCode = 802; +const int DummyDisconnectCode = 803; + +class TFileZillaImpl : public TFileZillaIntf +{ +public: + explicit TFileZillaImpl(TFTPFileSystem * FileSystem); + virtual ~TFileZillaImpl() {} + + virtual const wchar_t * Option(intptr_t OptionID) const; + virtual intptr_t OptionVal(intptr_t OptionID) const; + +protected: + virtual bool DoPostMessage(TMessageType Type, WPARAM wParam, LPARAM lParam); + + virtual bool HandleStatus(const wchar_t * Status, int Type); + virtual bool HandleAsynchRequestOverwrite( + wchar_t * FileName1, size_t FileName1Len, const wchar_t * FileName2, + const wchar_t * Path1, const wchar_t * Path2, + int64_t Size1, int64_t Size2, time_t LocalTime, + bool HasLocalTime, const TRemoteFileTime & RemoteTime, void * UserData, + HANDLE & LocalFileHandle, int & RequestResult); + virtual bool HandleAsynchRequestVerifyCertificate( + const TFtpsCertificateData & Data, int & RequestResult); + virtual bool HandleAsynchRequestNeedPass( + struct TNeedPassRequestData & Data, int & RequestResult); + virtual bool HandleListData(const wchar_t * Path, const TListDataEntry * Entries, + uintptr_t Count); + virtual bool HandleTransferStatus(bool Valid, int64_t TransferSize, + int64_t Bytes, bool FileTransfer); + virtual bool HandleReply(intptr_t Command, uintptr_t Reply); + virtual bool HandleCapabilities(TFTPServerCapabilities * ServerCapabilities); + virtual bool CheckError(intptr_t ReturnCode, const wchar_t * Context); + + virtual void PreserveDownloadFileTime(HANDLE AHandle, void * UserData); + virtual bool GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time); + virtual wchar_t * LastSysErrorMessage() const; + virtual std::wstring GetClientString() const; + +private: + TFTPFileSystem * FFileSystem; +}; + +TFileZillaImpl::TFileZillaImpl(TFTPFileSystem * FileSystem) : + TFileZillaIntf(), + FFileSystem(FileSystem) +{ +} + +const wchar_t * TFileZillaImpl::Option(intptr_t OptionID) const +{ + return FFileSystem->GetOption(OptionID); +} + +intptr_t TFileZillaImpl::OptionVal(intptr_t OptionID) const +{ + return FFileSystem->GetOptionVal(OptionID); +} + +bool TFileZillaImpl::DoPostMessage(TMessageType Type, WPARAM wParam, LPARAM lParam) +{ + return FFileSystem->FTPPostMessage(Type, wParam, lParam); +} + +bool TFileZillaImpl::HandleStatus(const wchar_t * Status, int Type) +{ + return FFileSystem->HandleStatus(Status, Type); +} + +bool TFileZillaImpl::HandleAsynchRequestOverwrite( + wchar_t * FileName1, size_t FileName1Len, const wchar_t * FileName2, + const wchar_t * Path1, const wchar_t * Path2, + int64_t Size1, int64_t Size2, time_t LocalTime, + bool HasLocalTime, const TRemoteFileTime & RemoteTime, void * UserData, + HANDLE & LocalFileHandle, + int & RequestResult) +{ + return FFileSystem->HandleAsynchRequestOverwrite( + FileName1, FileName1Len, FileName2, Path1, Path2, Size1, Size2, LocalTime, + HasLocalTime, RemoteTime, UserData, LocalFileHandle, RequestResult); +} + +bool TFileZillaImpl::HandleAsynchRequestVerifyCertificate( + const TFtpsCertificateData & Data, int & RequestResult) +{ + return FFileSystem->HandleAsynchRequestVerifyCertificate(Data, RequestResult); +} + +bool TFileZillaImpl::HandleAsynchRequestNeedPass( + struct TNeedPassRequestData & Data, int & RequestResult) +{ + return FFileSystem->HandleAsynchRequestNeedPass(Data, RequestResult); +} + +bool TFileZillaImpl::HandleListData(const wchar_t * Path, + const TListDataEntry * Entries, uintptr_t Count) +{ + return FFileSystem->HandleListData(Path, Entries, Count); +} + +bool TFileZillaImpl::HandleTransferStatus(bool Valid, int64_t TransferSize, + int64_t Bytes, bool FileTransfer) +{ + return FFileSystem->HandleTransferStatus(Valid, TransferSize, Bytes, FileTransfer); +} + +bool TFileZillaImpl::HandleReply(intptr_t Command, uintptr_t Reply) +{ + return FFileSystem->HandleReply(Command, Reply); +} + +bool TFileZillaImpl::HandleCapabilities(TFTPServerCapabilities * ServerCapabilities) +{ + return FFileSystem->HandleCapabilities(ServerCapabilities); +} + +bool TFileZillaImpl::CheckError(intptr_t ReturnCode, const wchar_t * Context) +{ + return FFileSystem->CheckError(ReturnCode, Context); +} + +void TFileZillaImpl::PreserveDownloadFileTime(HANDLE AHandle, void * UserData) +{ + return FFileSystem->PreserveDownloadFileTime(AHandle, UserData); +} + +bool TFileZillaImpl::GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time) +{ + return FFileSystem->GetFileModificationTimeInUtc(FileName, Time); +} + +wchar_t * TFileZillaImpl::LastSysErrorMessage() const +{ + return _wcsdup(::LastSysErrorMessage().c_str()); +} + +std::wstring TFileZillaImpl::GetClientString() const +{ + return std::wstring(GetSshVersionString().c_str()); +} + +static const wchar_t FtpsCertificateStorageKey[] = L"FtpsCertificates"; +const UnicodeString SiteCommand(L"SITE"); +const UnicodeString SymlinkSiteCommand(L"SYMLINK"); +const UnicodeString CopySiteCommand(L"COPY"); +const UnicodeString HashCommand(L"HASH"); // Cerberos + FileZilla servers +const UnicodeString AvblCommand(L"AVBL"); +const UnicodeString XQuotaCommand(L"XQUOTA"); +const UnicodeString DirectoryHasBytesPrefix(L"226-Directory has"); + +class TFTPFileListHelper : public TObject +{ +NB_DISABLE_COPY(TFTPFileListHelper) +public: + explicit TFTPFileListHelper(TFTPFileSystem * FileSystem, TRemoteFileList * FileList, + bool IgnoreFileList) : + FFileSystem(FileSystem), + FFileList(FFileSystem->FFileList), + FIgnoreFileList(FFileSystem->FIgnoreFileList) + { + FFileSystem->FFileList = FileList; + FFileSystem->FIgnoreFileList = IgnoreFileList; + } + + ~TFTPFileListHelper() + { + FFileSystem->FFileList = FFileList; + FFileSystem->FIgnoreFileList = FIgnoreFileList; + } + +private: + TFTPFileSystem * FFileSystem; + TRemoteFileList * FFileList; + bool FIgnoreFileList; +}; + +TFTPFileSystem::TFTPFileSystem(TTerminal * ATerminal) : + TCustomFileSystem(ATerminal), + FFileZillaIntf(nullptr), + FQueueEvent(::CreateEvent(nullptr, true, false, nullptr)), + FFileSystemInfoValid(false), + FReply(0), + FCommandReply(0), + FLastCommand(CMD_UNKNOWN), + FPasswordFailed(false), + FStoredPasswordTried(false), + FMultineResponse(false), + FLastCode(0), + FLastCodeClass(0), + FLastReadDirectoryProgress(0), + FLastResponse(new TStringList()), + FLastErrorResponse(new TStringList()), + FLastError(new TStringList()), + FFeatures(new TStringList()), + FFileList(nullptr), + FFileListCache(nullptr), + FActive(false), + FOpening(false), + FWaitingForReply(false), + FFileTransferAbort(ftaNone), + FIgnoreFileList(false), + FFileTransferCancelled(false), + FFileTransferResumed(0), + FFileTransferPreserveTime(false), + FFileTransferRemoveBOM(false), + FFileTransferNoList(false), + FFileTransferCPSLimit(0), + FAwaitingProgress(false), + FOnCaptureOutput(nullptr), + FListAll(asOn), + FDoListAll(false), + FReadCurrentDirectory(false), + FServerCapabilities(new TFTPServerCapabilities()), + FDetectTimeDifference(false), + FTimeDifference(0), + FSupportsAnyChecksumFeature(false), + FCertificate(nullptr), + FPrivateKey(nullptr), + FTransferActiveImmediately(false), + FWindowsServer(false), + FBytesAvailable(0), + FBytesAvailableSupported(false) +{ +} + +void TFTPFileSystem::Init(void *) +{ + //FQueue.reserve(1000); + ResetReply(); + + FListAll = FTerminal->GetSessionData()->GetFtpListAll(); + FFileSystemInfo.ProtocolBaseName = L"FTP"; + FFileSystemInfo.ProtocolName = FFileSystemInfo.ProtocolBaseName; + FTimeoutStatus = LoadStr(IDS_ERRORMSG_TIMEOUT); + FDisconnectStatus = LoadStr(IDS_STATUSMSG_DISCONNECTED); + FServerCapabilities = new TFTPServerCapabilities(); + FHashAlgs.reset(new TStringList()); + FSupportedCommands.reset(CreateSortedStringList()); + FSupportedSiteCommands.reset(CreateSortedStringList()); + FCertificate = nullptr; + FPrivateKey = nullptr; + FBytesAvailable = -1; + FBytesAvailableSupported = false; + + FChecksumAlgs.reset(new TStringList()); + FChecksumCommands.reset(new TStringList()); + RegisterChecksumAlgCommand(Sha1ChecksumAlg, L"XSHA1"); // e.g. Cerberos FTP + RegisterChecksumAlgCommand(Sha256ChecksumAlg, L"XSHA256"); // e.g. Cerberos FTP + RegisterChecksumAlgCommand(Sha512ChecksumAlg, L"XSHA512"); // e.g. Cerberos FTP + RegisterChecksumAlgCommand(Md5ChecksumAlg, L"XMD5"); // e.g. Cerberos FTP + RegisterChecksumAlgCommand(Md5ChecksumAlg, L"MD5"); // e.g. Apache FTP + RegisterChecksumAlgCommand(Crc32ChecksumAlg, L"XCRC"); // e.g. Cerberos FTP +} + +TFTPFileSystem::~TFTPFileSystem() +{ + DebugAssert(FFileList == nullptr); + + if (FFileZillaIntf) + { + FFileZillaIntf->Destroying(); + } + + // to release memory associated with the messages + DiscardMessages(); + + SAFE_DESTROY(FFileZillaIntf); + + ::CloseHandle(FQueueEvent); + FQueueEvent = nullptr; + + SAFE_DESTROY(FLastResponse); + SAFE_DESTROY(FLastErrorResponse); + SAFE_DESTROY(FLastError); + SAFE_DESTROY(FFeatures); + SAFE_DESTROY(FServerCapabilities); + SAFE_DESTROY(FLastError); + SAFE_DESTROY(FFeatures); + + ResetCaches(); +} + +void TFTPFileSystem::Open() +{ + // on reconnect, typically there may be pending status messages from previous session + DiscardMessages(); + + ResetCaches(); + FReadCurrentDirectory = true; + FCurrentDirectory.Clear(); + FHomeDirectory.Clear(); + + TSessionData * Data = FTerminal->GetSessionData(); + + FSessionInfo.LoginTime = Now(); + FSessionInfo.ProtocolBaseName = L"FTP"; + FSessionInfo.ProtocolName = FSessionInfo.ProtocolBaseName; + + switch (Data->GetFtps()) + { + case ftpsNone: + // noop; + break; + + case ftpsImplicit: + FSessionInfo.SecurityProtocolName = LoadStr(FTPS_IMPLICIT); + break; + + case ftpsExplicitSsl: + case ftpsExplicitTls: + FSessionInfo.SecurityProtocolName = LoadStr(FTPS_EXPLICIT); + break; + + default: + DebugFail(); + break; + } + + FLastDataSent = Now(); + + FMultineResponse = false; + + // initialize FZAPI on the first connect only + if (FFileZillaIntf == nullptr) + { + std::unique_ptr FileZillaImpl(new TFileZillaImpl(this)); + + try__catch + { + TFileZillaIntf::TLogLevel LogLevel; + switch (FTerminal->GetConfiguration()->GetActualLogProtocol()) + { + default: + case 0: + case 1: + LogLevel = TFileZillaIntf::LOG_PROGRESS; + break; + + case 2: + LogLevel = TFileZillaIntf::LOG_INFO; + break; + } + FileZillaImpl->SetDebugLevel(LogLevel); + FileZillaImpl->Init(); + FFileZillaIntf = FileZillaImpl.release(); + } + /*catch (...) + { + delete FFileZillaIntf; + FFileZillaIntf = NULL; + throw; + }*/ + } + + FWindowsServer = false; + FTransferActiveImmediately = (Data->GetFtpTransferActiveImmediately() == asOn); + + FSessionInfo.LoginTime = Now(); + + UnicodeString HostName = Data->GetHostNameExpanded(); + UnicodeString UserName = Data->GetUserNameExpanded(); + UnicodeString Password = Data->GetPassword(); + UnicodeString Account = Data->GetFtpAccount(); + UnicodeString Path = Data->GetRemoteDirectory(); + int ServerType = 0; + switch (Data->GetFtps()) + { + case ftpsNone: + ServerType = TFileZillaIntf::SERVER_FTP; + break; + + case ftpsImplicit: + ServerType = TFileZillaIntf::SERVER_FTP_SSL_IMPLICIT; + FSessionInfo.SecurityProtocolName = LoadStr(FTPS_IMPLICIT); + break; + + case ftpsExplicitSsl: + ServerType = TFileZillaIntf::SERVER_FTP_SSL_EXPLICIT; + FSessionInfo.SecurityProtocolName = LoadStr(FTPS_EXPLICIT); + break; + + case ftpsExplicitTls: + ServerType = TFileZillaIntf::SERVER_FTP_TLS_EXPLICIT; + FSessionInfo.SecurityProtocolName = LoadStr(FTPS_EXPLICIT); + break; + + default: + DebugFail(); + break; + } + + intptr_t Pasv = (Data->GetFtpPasvMode() ? 1 : 2); + + intptr_t TimeZoneOffset = Data->GetTimeDifferenceAuto() ? 0 : TimeToMinutes(Data->GetTimeDifference()); + + int UTF8 = 0; + uintptr_t CodePage = Data->GetCodePageAsNumber(); + + switch (CodePage) + { + case CP_ACP: + UTF8 = 2; // no UTF8 + break; + + case CP_UTF8: + UTF8 = 1; // always UTF8 + break; + + default: + UTF8 = 0; // auto detect + break; + } + + FPasswordFailed = false; + FStoredPasswordTried = false; + bool PromptedForCredentials = false; + + do + { + FDetectTimeDifference = Data->GetTimeDifferenceAuto(); + FTimeDifference = 0; + ResetFeatures(); + FSystem.Clear(); + FWelcomeMessage.Clear(); + FFileSystemInfoValid = false; + + TODO("the same for account? it ever used?"); + + // ask for username if it was not specified in advance, even on retry, + // but keep previous one as default, + if (Data->GetUserNameExpanded().IsEmpty() && !FTerminal->GetSessionData()->GetFingerprintScan()) + { + FTerminal->LogEvent("Username prompt (no username provided)"); + + if (!FPasswordFailed && !PromptedForCredentials) + { + FTerminal->Information(LoadStr(FTP_CREDENTIAL_PROMPT), false); + PromptedForCredentials = true; + } + + if (!FTerminal->PromptUser(Data, pkUserName, LoadStr(USERNAME_TITLE), L"", + LoadStr(USERNAME_PROMPT2), true, 0, UserName)) + { + FTerminal->FatalError(nullptr, LoadStr(AUTHENTICATION_FAILED)); + } + else + { + FUserName = UserName; + } + } + + // On retry ask for password. + // This is particularly important, when stored password is no longer valid, + // so we do not blindly try keep trying it in a loop (possibly causing account lockout) + if (FPasswordFailed) + { + FTerminal->LogEvent("Password prompt (last login attempt failed)"); + + Password.Clear(); + if (!FTerminal->PromptUser(Data, pkPassword, LoadStr(PASSWORD_TITLE), L"", + LoadStr(PASSWORD_PROMPT), false, 0, Password)) + { + FTerminal->FatalError(nullptr, LoadStr(AUTHENTICATION_FAILED)); + } + } + + if ((Data->GetFtps() != ftpsNone) && (FCertificate == nullptr)) + { + FTerminal->LoadTlsCertificate(FCertificate, FPrivateKey); + } + + FPasswordFailed = false; + TAutoFlag OpeningFlag(FOpening); + + FActive = FFileZillaIntf->Connect( + HostName.c_str(), static_cast(Data->GetPortNumber()), UserName.c_str(), + Password.c_str(), Account.c_str(), Path.c_str(), + ServerType, static_cast(Pasv), static_cast(TimeZoneOffset), UTF8, + static_cast(CodePage), + static_cast(Data->GetFtpForcePasvIp()), + static_cast(Data->GetFtpUseMlsd()), + static_cast(Data->GetFtpDupFF()), + static_cast(Data->GetFtpUndupFF()), + FCertificate, FPrivateKey); + + DebugAssert(FActive); + + try + { + // do not wait for FTP response code as Connect is complex operation + GotReply(WaitForCommandReply(false), REPLY_CONNECT, LoadStr(CONNECTION_FAILED)); + + Shred(Password); + + // we have passed, even if we got 530 on the way (if it is possible at all), + // ignore it + DebugAssert(!FPasswordFailed); + FPasswordFailed = false; + } + catch (...) + { + if (FPasswordFailed) + { + FTerminal->Information(LoadStr(Password.IsEmpty() ? FTP_ACCESS_DENIED_EMPTY_PASSWORD : FTP_ACCESS_DENIED), false); + } + else + { + // see handling of REPLY_CONNECT in GotReply + FTerminal->Closed(); + throw; + } + } + } + while (FPasswordFailed); + + // see also TWebDAVFileSystem::CollectTLSSessionInfo() + FSessionInfo.CSCipher = FFileZillaIntf->GetCipherName().c_str(); + FSessionInfo.SCCipher = FSessionInfo.CSCipher; + UnicodeString TlsVersionStr = FFileZillaIntf->GetTlsVersionStr().c_str(); + AddToList(FSessionInfo.SecurityProtocolName, TlsVersionStr, L", "); +} + +void TFTPFileSystem::Close() +{ + DebugAssert(FActive); + bool Result; + + FFileZillaIntf->CustomCommand(L"QUIT"); + Result = true; + + /*if (FFileZillaIntf->Close(FOpening)) + { + DebugCheck(FLAGSET(WaitForCommandReply(false), TFileZillaIntf::REPLY_DISCONNECTED)); + Result = true; + } + else + { + // See TFileZillaIntf::Close + Result = FOpening; + }*/ + + if (DebugAlwaysTrue(Result)) + { + DebugAssert(FActive); + Discard(); + FTerminal->Closed(); + } +} + +bool TFTPFileSystem::GetActive() const +{ + return FActive; +} + +void TFTPFileSystem::CollectUsage() +{ + /*switch (FTerminal->SessionData->Ftps) + { + case ftpsNone: + // noop + break; + + case ftpsImplicit: + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPSImplicit"); + break; + + case ftpsExplicitSsl: + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPSExplicitSSL"); + break; + + case ftpsExplicitTls: + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPSExplicitTLS"); + break; + + default: + DebugFail(); + break; + } + + if (!FTerminal->SessionData->TlsCertificateFile.IsEmpty()) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPSCertificate"); + } + + if (FFileZillaIntf->UsingMlsd()) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPMLSD"); + } + else + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPLIST"); + } + + if (FFileZillaIntf->UsingUtf8()) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPUTF8"); + } + else + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPNonUTF8"); + } + if (!GetCurrentDirectory().IsEmpty() && (GetCurrentDirectory()[1] != L'/')) + { + if (::IsUnixStyleWindowsPath(GetCurrentDirectory())) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPWindowsPath"); + } + else if ((GetCurrentDirectory().Length() >= 3) && IsLetter(GetCurrentDirectory()[1]) && (GetCurrentDirectory()[2] == L':') && (CurrentDirectory[3] == L'/')) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPRealWindowsPath"); + } + else + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPOtherPath"); + } + } + + UnicodeString TlsVersionStr = FFileZillaIntf->GetTlsVersionStr().c_str(); + if (!TlsVersionStr.IsEmpty()) + { + FTerminal->CollectTlsUsage(TlsVersionStr); + } + + // 220-FileZilla Server version 0.9.43 beta + // 220-written by Tim Kosse (Tim.Kosse@gmx.de) + // 220 Please visit http://sourceforge.net/projects/filezilla/ + // SYST + // 215 UNIX emulated by FileZilla + // (Welcome message is configurable) + if (ContainsText(FSystem, L"FileZilla")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPFileZilla"); + } + // 220 ProFTPD 1.3.4a Server (Debian) [::ffff:192.168.179.137] + // SYST + // 215 UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"ProFTPD")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPProFTPD"); + } + // 220 Microsoft FTP Service + // SYST + // 215 Windows_NT + else if (ContainsText(FWelcomeMessage, L"Microsoft FTP Service") || + ContainsText(FSystem, L"Windows_NT")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPIIS"); + } + // 220 (vsFTPd 3.0.2) + // SYST + // 215 UNIX Type: L8 + // (Welcome message is configurable) + else if (ContainsText(FWelcomeMessage, L"vsFTPd")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPvsFTPd"); + } + // 220 Welcome to Pure-FTPd. + // ... + // SYST + // 215 UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"Pure-FTPd")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPPureFTPd"); + } + // 220 Titan FTP Server 10.47.1892 Ready. + // ... + // SYST + // 215 UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"Titan FTP Server")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPTitan"); + } + // 220-Cerberus FTP Server - Home Edition + // 220-This is the UNLICENSED Home Edition and may be used for home, personal use only + // 220-Welcome to Cerberus FTP Server + // 220 Created by Cerberus, LLC + else if (ContainsText(FWelcomeMessage, L"Cerberus FTP Server")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPCerberus"); + } + // 220 Serv-U FTP Server v15.1 ready... + else if (ContainsText(FWelcomeMessage, L"Serv-U FTP Server")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPServU"); + } + else if (ContainsText(FWelcomeMessage, L"WS_FTP")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPWSFTP"); + } + // 220 Welcome to the most popular FTP hosting service! Save on hardware, software, hosting and admin. Share files/folders with read-write permission. Visit http://www.drivehq.com/ftp/; + // ... + // SYST + // 215 UNIX emulated by DriveHQ FTP Server. + else if (ContainsText(FSystem, L"DriveHQ")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPDriveHQ"); + } + // 220 GlobalSCAPE EFT Server (v. 6.0) * UNREGISTERED COPY * + // ... + // SYST + // 215 UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"GlobalSCAPE")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPGlobalScape"); + } + // 220- + // 220 CompleteFTP v 8.1.3 + // ... + // SYST + // UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"CompleteFTP")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPComplete"); + } + // 220 Core FTP Server Version 1.2, build 567, 64-bit, installed 8 days ago Unregistered + // ... + // SYST + // 215 UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"Core FTP Server")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPCore"); + } + // 220 Service ready for new user. + // .. + // SYST + // 215 UNIX Type: Apache FtpServer + // (e.g. brickftp.com) + else if (ContainsText(FSystem, L"Apache FtpServer")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPApache"); + } + // 220 pos1 FTP server (GNU inetutils 1.3b) ready. + // ... + // SYST + // 215 UNIX Type: L8 Version: Linux 2.6.15.7-ELinOS-314pm3 + // Displaying "(GNU inetutils 1.3b)" in a welcome message can be turned off (-q switch): + // 220 pos1 FTP server ready. + // (the same for "Version: Linux 2.6.15.7-ELinOS-314pm3" in SYST response) + else if (ContainsText(FWelcomeMessage, L"GNU inetutils")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPInetutils"); + } + // 220 Syncplify.me Server! FTP(S) Service Ready + // Message is configurable + else if (ContainsText(FWelcomeMessage, L"Syncplify")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPSyncplify"); + } + // 220-Idea FTP Server v0.80 (xxx.home.pl) [xxx.xxx.xxx.xxx] + // 220 Ready + // ... + // SYST + // UNIX Type: L8 + else if (ContainsText(FWelcomeMessage, L"Idea FTP Server")) + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPIdea"); + } + else + { + FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPOther"); + }*/ +} + +void TFTPFileSystem::Idle() +{ + if (FActive && !FWaitingForReply) + { + PoolForFatalNonCommandReply(); + + // Keep session alive + if ((FTerminal->GetSessionData()->GetFtpPingType() != ptOff) && + ((Now() - FLastDataSent).GetValue() > FTerminal->GetSessionData()->GetFtpPingIntervalDT().GetValue() * 4)) + { + FTerminal->LogEvent("Dummy directory read to keep session alive."); + FLastDataSent = Now(); + + std::unique_ptr Files(new TRemoteDirectory(FTerminal)); + try__finally + { + try + { + Files->SetDirectory(GetCurrDirectory()); + DoReadDirectory(Files.get()); + } + catch (...) + { + // ignore non-fatal errors + // (i.e. current directory may not exist anymore) + if (!FTerminal->GetActive()) + { + throw; + } + } + } + __finally + { +// delete Files; + }; + } + } +} + +void TFTPFileSystem::Discard() +{ + // remove all pending messages, to get complete log + // note that we need to retry discard on reconnect, as there still may be another + // "disconnect/timeout/..." status messages coming + DiscardMessages(); + DebugAssert(FActive); + FActive = false; + + // See neon's ne_ssl_clicert_free + if (FPrivateKey != nullptr) + { + EVP_PKEY_free(FPrivateKey); + FPrivateKey = nullptr; + } + if (FCertificate != nullptr) + { + X509_free(FCertificate); + FCertificate = nullptr; + } +} + +UnicodeString TFTPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool Local) +{ + return static_cast(this)->GetAbsolutePath(APath, Local); +} + +UnicodeString TFTPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool /*Local*/) const +{ + TODO("improve (handle .. etc.)"); + if (core::UnixIsAbsolutePath(APath)) + { + return APath; + } + else + { + return core::AbsolutePath(FCurrentDirectory, APath); + } +} + +UnicodeString TFTPFileSystem::ActualCurrentDirectory() +{ + UnicodeString CurrentPath(1024, 0); + UnicodeString Result; + if (FFileZillaIntf->GetCurrentPath(const_cast(CurrentPath.c_str()), CurrentPath.Length())) + { + Result = core::UnixExcludeTrailingBackslash(CurrentPath); + } + if (Result.IsEmpty()) + { + Result = ROOTDIRECTORY; + } + return Result; +} + +void TFTPFileSystem::EnsureLocation() +{ + // if we do not know what's the current directory, do nothing + if (!FCurrentDirectory.IsEmpty()) + { + // Make sure that the FZAPI current working directory, + // is actually our working directory. + // It may not be because: + // 1) We did cached directory change + // 2) Listing was requested for non-current directory, which + // makes FZAPI change its current directory (and not restoring it back afterwards) + if (!core::UnixSamePath(ActualCurrentDirectory(), FCurrentDirectory)) + { + FTerminal->LogEvent(FORMAT(L"Synchronizing current directory \"%s\".", + FCurrentDirectory.c_str())); + DoChangeDirectory(FCurrentDirectory); + // make sure FZAPI is aware that we changed current working directory + FFileZillaIntf->SetCurrentPath(FCurrentDirectory.c_str()); + } + } +} + +void TFTPFileSystem::AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent) +{ + // end-user has right to expect that client current directory is really + // current directory for the server + EnsureLocation(); + + DebugAssert(FOnCaptureOutput == nullptr); + FOnCaptureOutput = OutputEvent; + try__finally + { + SCOPE_EXIT + { + FOnCaptureOutput = nullptr; + }; + SendCommand(Command); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE); + } + __finally + { + FOnCaptureOutput = nullptr; + }; +} + +void TFTPFileSystem::ResetCaches() +{ + SAFE_DESTROY(FFileListCache); +} + +void TFTPFileSystem::AnnounceFileListOperation() +{ + ResetCaches(); +} + +void TFTPFileSystem::DoChangeDirectory(const UnicodeString & Directory) +{ + UnicodeString Command = FORMAT(L"CWD %s", Directory.c_str()); + SendCommand(Command); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); +} + +void TFTPFileSystem::ChangeDirectory(const UnicodeString & ADirectory) +{ + UnicodeString Directory = ADirectory; + try + { + // For changing directory, we do not make paths absolute, instead we + // delegate this to the server, hence we synchronize current working + // directory with the server and only then we ask for the change with + // relative path. + // But if synchronization fails, typically because current working directory + // no longer exists, we fall back to out own resolution, to give + // user chance to leave the non-existing directory. + EnsureLocation(); + } + catch (...) + { + if (FTerminal->GetActive()) + { + Directory = GetAbsolutePath(Directory, false); + } + else + { + throw; + } + } + + DoChangeDirectory(Directory); + + // make next ReadCurrentDirectory retrieve actual server-side current directory + FReadCurrentDirectory = true; +} + +void TFTPFileSystem::CachedChangeDirectory(const UnicodeString & Directory) +{ + FCurrentDirectory = core::UnixExcludeTrailingBackslash(Directory); + if (FCurrentDirectory.IsEmpty()) + { + FCurrentDirectory = ROOTDIRECTORY; + } + FReadCurrentDirectory = false; +} + +void TFTPFileSystem::ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action) +{ + DebugAssert(Properties); + DebugAssert(!Properties->Valid.Contains(vpGroup)); //-V595 + DebugAssert(!Properties->Valid.Contains(vpOwner)); //-V595 + DebugAssert(!Properties->Valid.Contains(vpLastAccess)); //-V595 + DebugAssert(!Properties->Valid.Contains(vpModification)); //-V595 + + if (Properties && Properties->Valid.Contains(vpRights)) + { + std::unique_ptr OwnedFile; + + try__finally + { + UnicodeString FileName = GetAbsolutePath(AFileName, false); + + if (AFile == nullptr) + { + TRemoteFile * File = nullptr; + ReadFile(FileName, File); + OwnedFile.reset(File); + AFile = File; + } + + if ((AFile != nullptr) && AFile->GetIsDirectory() && FTerminal->CanRecurseToDirectory(AFile) && Properties->Recursive) + { + try + { + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TTerminal::ChangeFileProperties, FTerminal), + static_cast(const_cast(Properties))); + } + catch (...) + { + Action.Cancel(); + throw; + } + } + + TRights Rights; + if (AFile != nullptr) + { + Rights = *AFile->GetRights(); + } + Rights |= Properties->Rights.GetNumberSet(); + Rights &= static_cast(~Properties->Rights.GetNumberUnset()); + if ((AFile != nullptr) && AFile->GetIsDirectory() && Properties->AddXToDirectories) + { + Rights.AddExecute(); + } + + Action.Rights(Rights); + + UnicodeString FileNameOnly = base::UnixExtractFileName(FileName); + UnicodeString FilePath = core::UnixExtractFilePath(FileName); + // FZAPI wants octal number represented as decadic + FFileZillaIntf->Chmod(Rights.GetNumberDecadic(), FileNameOnly.c_str(), FilePath.c_str()); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } + __finally + { +// delete OwnedFile; + }; + } + else + { + Action.Cancel(); + } +} + +bool TFTPFileSystem::LoadFilesProperties(TStrings * /*FileList*/) +{ + DebugFail(); + return false; +} + +UnicodeString TFTPFileSystem::DoCalculateFileChecksum( + bool UsingHashCommand, const UnicodeString & Alg, TRemoteFile * File) +{ + // Overview of server supporting various hash commands is at: + // https://tools.ietf.org/html/draft-ietf-ftpext2-hash-03#appendix-B + + UnicodeString CommandName; + + if (UsingHashCommand) + { + CommandName = HashCommand; + } + else + { + intptr_t Index = FChecksumAlgs->IndexOf(Alg); + if (Index < 0) + { + DebugFail(); + ThrowExtException(); + } + else + { + CommandName = FChecksumCommands->GetString(Index); + } + } + + UnicodeString FileName = File->GetFullFileName(); + // FTP way is not to quote. + // But as Serv-U, GlobalSCAPE and possibly others allow + // additional parameters (SP ER range), they need to quote file name. + // Cerberus and FileZilla Server on the other hand can do without quotes + // (but they can handle them, not sure about other servers) + + // Quoting: + // FileZilla Server simply checks if argument starts and ends with double-quote + // and strips them, no double-quote escaping is possible. + // That's for all commands, not just HASH + // ProFTPD: TODO: Check how "SITE SYMLINK target link" is parsed + + // We can possibly autodetect this from announced command format: + // XCRC filename;start;end + // XMD5 filename;start;end + // XSHA1 filename;start;end + // XSHA256 filename;start;end + // XSHA512 filename;start;end + if (FileName.Pos(L" ") > 0) + { + FileName = FORMAT(L"\"%s\"", FileName.c_str()); + } + + UnicodeString Command = FORMAT(L"%s %s", CommandName.c_str(), FileName.c_str()); + SendCommand(Command); + UnicodeString ResponseText = GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_SINGLE_LINE); + + UnicodeString Hash; + if (UsingHashCommand) + { + // Code should be 213, but let's be tolerant and accept any 2xx + + // ("213" SP) hashname SP start-point "-" end-point SP filehash SP (CRLF) + UnicodeString Buf = ResponseText; + // skip alg + CutToChar(Buf, L' ', true); + // skip range + UnicodeString Range = CutToChar(Buf, L' ', true); + // This should be range (SP-EP), but if it does not conform to the format, + // it's likely because the server uses version of the HASH spec + // before draft-ietf-ftpext2-hash-01 + // (including draft-bryan-ftp-hash-06 implemented by FileZilla server; or Cerberus), + // that did not have the "range" part. + // The FileZilla Server even omits the file name. + // The latest draft as of implementing this is draft-bryan-ftpext-hash-02. + if (Range.Pos(L"-") > 0) + { + Hash = CutToChar(Buf, L' ', true); + } + else + { + Hash = Range; + } + } + else // All hash-specific commands + { + // Accepting any 2xx response. Most servers use 213, + // but for example WS_FTP uses non-sense code 220 (Service ready for new user) + + // MD5 response according to a draft-twine-ftpmd5-00 includes a file name + // (implemented by Apache FtpServer). + // Other commands (X) return the hash only. + ResponseText = ResponseText.Trim(); + intptr_t P = ResponseText.LastDelimiter(L" "); + if (P > 0) + { + ResponseText.Delete(1, P); + } + + Hash = ResponseText; + } + + if (Hash.IsEmpty()) + { + throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, CommandName.c_str(), ResponseText.c_str())); + } + + return LowerCase(Hash); +} + +void TFTPFileSystem::DoCalculateFilesChecksum(bool UsingHashCommand, + const UnicodeString & Alg, TStrings * FileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum, + TFileOperationProgressType * OperationProgress, bool FirstLevel) +{ + TOnceDoneOperation OnceDoneOperation; // not used + + intptr_t Index1 = 0; + while ((Index1 < FileList->GetCount()) && !OperationProgress->Cancel) + { + TRemoteFile * File = static_cast(FileList->GetObj(Index1)); + DebugAssert(File != nullptr); + + if (File->GetIsDirectory()) + { + if (FTerminal->CanRecurseToDirectory(File) && + !File->GetIsParentDirectory() && !File->GetIsThisDirectory() && + // recurse into subdirectories only if we have callback function + (OnCalculatedChecksum != nullptr)) + { + OperationProgress->SetFile(File->GetFileName()); + TRemoteFileList * SubFiles = + FTerminal->CustomReadDirectoryListing(File->GetFullFileName(), false); + + if (SubFiles != nullptr) + { + TStrings * SubFileList = new TStringList(); + bool Success = false; + try__finally + { + SCOPE_EXIT + { + delete SubFiles; + delete SubFileList; + + if (FirstLevel) + { + OperationProgress->Finish(File->GetFileName(), Success, OnceDoneOperation); + } + }; + + OperationProgress->SetFile(File->GetFileName()); + + for (intptr_t Index2 = 0; Index2 < SubFiles->GetCount(); Index2++) + { + TRemoteFile * SubFile = SubFiles->GetFile(Index2); + SubFileList->AddObject(SubFile->GetFullFileName(), SubFile); + } + + // do not collect checksums for files in subdirectories, + // only send back checksums via callback + DoCalculateFilesChecksum(UsingHashCommand, Alg, SubFileList, nullptr, + OnCalculatedChecksum, OperationProgress, false); + + Success = true; + } + __finally + { + delete SubFiles; + delete SubFileList; + + if (FirstLevel) + { + OperationProgress->Finish(File->GetFileName(), Success, OnceDoneOperation); + } + }; + } + } + } + else + { + TChecksumSessionAction Action(FTerminal->GetActionLog()); + try + { + OperationProgress->SetFile(File->GetFileName()); + Action.SetFileName(FTerminal->GetAbsolutePath(File->GetFullFileName(), true)); + + UnicodeString Checksum = DoCalculateFileChecksum(UsingHashCommand, Alg, File); + + if (OnCalculatedChecksum != nullptr) + { + OnCalculatedChecksum(File->GetFileName(), Alg, Checksum); + } + Action.Checksum(Alg, Checksum); + if (Checksums != nullptr) + { + Checksums->Add(Checksum); + } + } + catch (Exception & E) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + + // Error formatting expanded from inline to avoid strange exceptions + UnicodeString Error = + FMTLOAD(CHECKSUM_ERROR, + (File != nullptr ? File->GetFullFileName().c_str() : L"")); + FTerminal->CommandError(&E, Error); + // Abort loop. + TODO("retries? resume?"); + Index1 = FileList->GetCount(); + } + } + Index1++; + } +} + +void TFTPFileSystem::CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * FileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum) +{ + TFileOperationProgressType Progress(MAKE_CALLBACK(TTerminal::DoProgress, FTerminal), MAKE_CALLBACK(TTerminal::DoFinished, FTerminal)); + Progress.Start(foCalculateChecksum, osRemote, FileList->GetCount()); + + FTerminal->SetOperationProgress(&Progress); + + try__finally + { + SCOPE_EXIT + { + FTerminal->SetOperationProgress(nullptr); + Progress.Stop(); + }; + UnicodeString NormalizedAlg = FindIdent(FindIdent(Alg, FHashAlgs.get()), FChecksumAlgs.get()); + + bool UsingHashCommand = (FHashAlgs->IndexOf(NormalizedAlg) >= 0); + if (UsingHashCommand) + { + // The server should understand lowercase alg name by spec, + // but we should use uppercase anyway + SendCommand(FORMAT(L"OPTS %s %s", HashCommand.c_str(), UpperCase(NormalizedAlg).c_str())); + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } + else if (FChecksumAlgs->IndexOf(NormalizedAlg) >= 0) + { + // will use algorithm-specific command + } + else + { + throw Exception(FMTLOAD(UNKNOWN_CHECKSUM, Alg.c_str())); + } + + DoCalculateFilesChecksum(UsingHashCommand, NormalizedAlg, FileList, Checksums, OnCalculatedChecksum, + &Progress, true); + } + __finally + { + FTerminal->SetOperationProgress(nullptr); + Progress.Stop(); + }; +} + +bool TFTPFileSystem::ConfirmOverwrite( + const UnicodeString & ASourceFullFileName, + UnicodeString & ATargetFileName, + intptr_t Params, TFileOperationProgressType * OperationProgress, + bool AutoResume, + const TOverwriteFileParams * FileParams, + const TCopyParamType * CopyParam, + OUT TOverwriteMode & OverwriteMode) +{ + bool Result; + bool CanAutoResume = FLAGSET(Params, cpNoConfirmation) && AutoResume; + bool DestIsSmaller = (FileParams != nullptr) && (FileParams->DestSize < FileParams->SourceSize); + bool DestIsSame = (FileParams != nullptr) && (FileParams->DestSize == FileParams->SourceSize); + bool CanResume = + !OperationProgress->AsciiTransfer && + // when resuming transfer after interrupted connection, + // do nothing (dummy resume) when the files has the same size. + // this is workaround for servers that strangely fails just after successful + // upload. + (DestIsSmaller || (DestIsSame && CanAutoResume)); + + uintptr_t Answer; + if (CanAutoResume && CanResume) + { + if (DestIsSame) + { + DebugAssert(CanAutoResume); + Answer = qaSkip; + } + else + { + Answer = qaRetry; + } + } + else + { + // retry = "resume" + // all = "yes to newer" + // ignore = "rename" + uintptr_t Answers = qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll | qaAll | qaIgnore; + if (CanResume) + { + Answers |= qaRetry; + } + TQueryButtonAlias Aliases[5]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(RESUME_BUTTON); + Aliases[0].GroupWith = qaNo; + Aliases[0].GrouppedShiftState = ssAlt; + Aliases[1].Button = qaAll; + Aliases[1].Alias = LoadStr(YES_TO_NEWER_BUTTON); + Aliases[1].GroupWith = qaYes; + Aliases[1].GrouppedShiftState = ssCtrl; + Aliases[2].Button = qaIgnore; + Aliases[2].Alias = LoadStr(RENAME_BUTTON); + Aliases[2].GroupWith = qaNo; + Aliases[2].GrouppedShiftState = ssCtrl; + Aliases[3].Button = qaYesToAll; + Aliases[3].GroupWith = qaYes; + Aliases[3].GrouppedShiftState = ssShift; + Aliases[4].Button = qaNoToAll; + Aliases[4].GroupWith = qaNo; + Aliases[4].GrouppedShiftState = ssShift; + TQueryParams QueryParams(qpNeverAskAgainCheck); + QueryParams.Aliases = Aliases; + QueryParams.AliasesCount = _countof(Aliases); + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = FTerminal->ConfirmFileOverwrite( + ASourceFullFileName, ATargetFileName, FileParams, Answers, &QueryParams, + OperationProgress->Side == osLocal ? osRemote : osLocal, + CopyParam, Params, OperationProgress); + } + } + + Result = true; + + switch (Answer) + { + // resume + case qaRetry: + OverwriteMode = omResume; + DebugAssert(FileParams != nullptr); + DebugAssert(CanResume); + FFileTransferResumed = FileParams ? FileParams->DestSize : 0; + break; + + // rename + case qaIgnore: + if (FTerminal->PromptUser(FTerminal->GetSessionData(), pkFileName, + LoadStr(RENAME_TITLE), L"", LoadStr(RENAME_PROMPT2), true, 0, ATargetFileName)) + { + OverwriteMode = omOverwrite; + } + else + { + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + FFileTransferAbort = ftaCancel; + Result = false; + } + break; + + case qaYes: + OverwriteMode = omOverwrite; + break; + + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + FFileTransferAbort = ftaCancel; + Result = false; + break; + + case qaNo: + FFileTransferAbort = ftaSkip; + Result = false; + break; + + case qaSkip: + OverwriteMode = omComplete; + break; + + default: + DebugFail(); + Result = false; + break; + } + return Result; +} + +void TFTPFileSystem::ResetFileTransfer() +{ + FFileTransferAbort = ftaNone; + FFileTransferCancelled = false; + FFileTransferResumed = 0; +} + +void TFTPFileSystem::ReadDirectoryProgress(int64_t Bytes) +{ + // with FTP we do not know exactly how many entries we have received, + // instead we know number of bytes received only. + // so we report approximation based on average size of entry. + int Progress = static_cast(Bytes / 80); + if (Progress - FLastReadDirectoryProgress >= 10) + { + bool Cancel = false; + FLastReadDirectoryProgress = Progress; + FTerminal->DoReadDirectoryProgress(Progress, 0, Cancel); + if (Cancel) + { + FTerminal->DoReadDirectoryProgress(-2, 0, Cancel); + FFileZillaIntf->Cancel(); + } + } +} + +void TFTPFileSystem::DoFileTransferProgress(int64_t TransferSize, + int64_t Bytes) +{ + TFileOperationProgressType * OperationProgress = FTerminal->GetOperationProgress(); + + OperationProgress->SetTransferSize(TransferSize); + + if (FFileTransferResumed > 0) + { + OperationProgress->AddResumed(FFileTransferResumed); + FFileTransferResumed = 0; + } + + int64_t Diff = Bytes - OperationProgress->TransferedSize; + if (DebugAlwaysTrue(Diff >= 0)) + { + OperationProgress->AddTransfered(Diff); + } + + if (OperationProgress->Cancel == csCancel) + { + FFileTransferCancelled = true; + FFileTransferAbort = ftaCancel; + FFileZillaIntf->Cancel(); + } + + if (FFileTransferCPSLimit != OperationProgress->CPSLimit) + { + SetCPSLimit(OperationProgress); + } +} + +void TFTPFileSystem::SetCPSLimit(TFileOperationProgressType * OperationProgress) +{ + // Any reason we use separate field instead of directly using OperationProgress->CPSLimit? + // Maybe thread-safety? + FFileTransferCPSLimit = OperationProgress->CPSLimit; + OperationProgress->SetSpeedCounters(); +} + +void TFTPFileSystem::FileTransferProgress(int64_t TransferSize, + int64_t Bytes) +{ + TGuard Guard(FTransferStatusCriticalSection); + + DoFileTransferProgress(TransferSize, Bytes); +} + +void TFTPFileSystem::FileTransfer(const UnicodeString & AFileName, + const UnicodeString & LocalFile, const UnicodeString & RemoteFile, + const UnicodeString & RemotePath, bool Get, int64_t Size, intptr_t Type, + TFileTransferData & UserData, TFileOperationProgressType * OperationProgress) +{ + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(TRANSFER_ERROR, AFileName.c_str()), "", + [&]() + { + FFileZillaIntf->FileTransfer(ApiPath(LocalFile).c_str(), RemoteFile.c_str(), + RemotePath.c_str(), Get, Size, static_cast(Type), &UserData); + // we may actually catch response code of the listing + // command (when checking for existence of the remote file) + uintptr_t Reply = WaitForCommandReply(); + GotReply(Reply, FLAGMASK(FFileTransferCancelled, REPLY_ALLOW_CANCEL)); + }); + + switch (FFileTransferAbort) + { + case ftaSkip: + ThrowSkipFileNull(); + + case ftaCancel: + Abort(); + break; + } + + if (!FFileTransferCancelled) + { + // show completion of transfer + // call non-guarded variant to avoid deadlock with keepalives + // (we are not waiting for reply anymore so keepalives are free to proceed) + DoFileTransferProgress(OperationProgress->TransferSize, OperationProgress->TransferSize); + } +} + +void TFTPFileSystem::CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + Params &= ~cpAppend; + UnicodeString FullTargetDir = ::IncludeTrailingBackslash(TargetDir); + + intptr_t Index = 0; + while (Index < AFilesToCopy->GetCount() && !OperationProgress->Cancel) + { + UnicodeString FileName = AFilesToCopy->GetString(Index); + const TRemoteFile * File = NB_STATIC_DOWNCAST_CONST(TRemoteFile, AFilesToCopy->GetObj(Index)); + + bool Success = false; + try__finally + { + SCOPE_EXIT + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + UnicodeString AbsoluteFilePath = GetAbsolutePath(FileName, false); + UnicodeString TargetDirectory = CreateTargetDirectory(File->GetFileName(), FullTargetDir, CopyParam); + try + { + SinkRobust(AbsoluteFilePath, File, TargetDirectory, CopyParam, Params, + OperationProgress, tfFirstLevel); + Success = true; + FLastDataSent = Now(); + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + __finally + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TFTPFileSystem::SinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TSFTPFileSystem + + TDownloadSessionAction Action(FTerminal->GetActionLog()); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + + do + { + try + { + Sink(AFileName, AFile, TargetDir, CopyParam, Params, OperationProgress, + Flags, Action); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + DebugAssert(AFile != nullptr); + if (!AFile->GetIsDirectory()) + { + // prevent overwrite confirmations + Params |= cpNoConfirmation; + Flags |= tfAutoResume; + } + } + } + while (RobustLoop.Retry()); +} + +void TFTPFileSystem::Sink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t AParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action) +{ + DebugAssert(AFile); + UnicodeString OnlyFileName = base::UnixExtractFileName(AFileName); + + Action.SetFileName(AFileName); + + TFileMasks::TParams MaskParams; + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + + UnicodeString BaseFileName = FTerminal->GetBaseFileName(AFileName); + if (!CopyParam->AllowTransfer(BaseFileName, osRemote, AFile->GetIsDirectory(), MaskParams)) + { + FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", AFileName.c_str())); + ThrowSkipFileNull(); + } + + if (CopyParam->SkipTransfer(AFileName, AFile->GetIsDirectory())) + { + OperationProgress->AddSkippedFileSize(AFile->GetSize()); + ThrowSkipFileNull(); + } + + FTerminal->LogFileDetails(AFileName, AFile->GetModification(), AFile->GetSize()); + + OperationProgress->SetFile(OnlyFileName); + + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, OnlyFileName, osRemote, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = TargetDir + DestFileName; + + if (AFile->GetIsDirectory()) + { + Action.Cancel(); + if (FTerminal->CanRecurseToDirectory(AFile)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_DIRECTORY_ERROR, DestFullName.c_str()), "", + [&]() + { + DWORD LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if (FLAGCLEAR(LocalFileAttrs, faDirectory)) + { + ThrowExtException(); + } + }); + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CREATE_DIR_ERROR, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::ForceDirectories(DestFullName)); + }); + + TSinkFileParams SinkFileParams; + SinkFileParams.TargetDir = ::IncludeTrailingBackslash(DestFullName); + SinkFileParams.CopyParam = CopyParam; + SinkFileParams.Params = AParams; + SinkFileParams.OperationProgress = OperationProgress; + SinkFileParams.Skipped = false; + SinkFileParams.Flags = Flags & ~(tfFirstLevel | tfAutoResume); + + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TFTPFileSystem::SinkFile, this), &SinkFileParams); + + // Do not delete directory if some of its files were skipped. + // Throw "skip file" for the directory to avoid attempt to deletion + // of any parent directory + if (FLAGSET(AParams, cpDelete) && SinkFileParams.Skipped) + { + ThrowSkipFileNull(); + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", AFileName.c_str())); + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", AFileName.c_str())); + + // Will we use ASCII of BINARY file transfer? + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osRemote, MaskParams)); + FTerminal->LogEvent(UnicodeString(OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary") + + L" transfer mode selected."); + + // Suppose same data size to transfer as to write + OperationProgress->SetTransferSize(AFile->GetSize()); + OperationProgress->SetLocalSize(OperationProgress->TransferSize); + + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_FILE_ERROR, DestFullName.c_str()), "", + [&]() + { + LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && FLAGSET(LocalFileAttrs, faDirectory)) + { + ThrowExtException(); + } + }); + + OperationProgress->TransferingFile = false; // not set with FTP protocol + + ResetFileTransfer(); + + TFileTransferData UserData; + + UnicodeString FilePath = core::UnixExtractFilePath(AFileName); + if (FilePath.IsEmpty()) + { + FilePath = ROOTDIRECTORY; + } + uintptr_t TransferType = (OperationProgress->AsciiTransfer ? 1 : 2); + + { + // ignore file list + TFTPFileListHelper Helper(this, nullptr, true); + + SetCPSLimit(OperationProgress); + FFileTransferPreserveTime = CopyParam->GetPreserveTime(); + // not used for downloads anyway + FFileTransferRemoveBOM = CopyParam->GetRemoveBOM(); + FFileTransferNoList = CanTransferSkipList(AParams, Flags, CopyParam); + UserData.FileName = DestFileName; + UserData.Params = AParams; + UserData.AutoResume = FLAGSET(Flags, tfAutoResume); + UserData.CopyParam = CopyParam; + UserData.Modification = AFile->GetModification(); + FileTransfer(AFileName, DestFullName, OnlyFileName, + FilePath, true, AFile->GetSize(), TransferType, UserData, OperationProgress); + } + + // in case dest filename is changed from overwrite dialog + if (DestFileName != UserData.FileName) + { + DestFullName = TargetDir + UserData.FileName; + LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + } + + Action.Destination(::ExpandUNCFileName(DestFullName)); + + if (LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + LocalFileAttrs = faArchive; + } + DWORD NewAttrs = CopyParam->LocalFileAttrs(*AFile->GetRights()); + if ((NewAttrs & LocalFileAttrs) != NewAttrs) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(DestFullName), (LocalFileAttrs | NewAttrs)) == 0); + }); + } + + FTerminal->LogFileDone(OperationProgress); + } + + if (FLAGSET(AParams, cpDelete)) + { + // If file is directory, do not delete it recursively, because it should be + // empty already. If not, it should not be deleted (some files were + // skipped or some new files were copied to it, while we were downloading) + intptr_t Params = dfNoRecursive; + FTerminal->RemoteDeleteFile(AFileName, AFile, &Params); + } +} + +void TFTPFileSystem::SinkFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param) +{ + TSinkFileParams * Params = NB_STATIC_DOWNCAST(TSinkFileParams, Param); + DebugAssert(Params->OperationProgress); + try + { + SinkRobust(AFileName, AFile, Params->TargetDir, Params->CopyParam, + Params->Params, Params->OperationProgress, Params->Flags); + } + catch (ESkipFile & E) + { + TFileOperationProgressType * OperationProgress = Params->OperationProgress; + + Params->Skipped = true; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + if (OperationProgress->Cancel) + { + Abort(); + } + } +} + +void TFTPFileSystem::CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & ATargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + DebugAssert((AFilesToCopy != nullptr) && (OperationProgress != nullptr)); + + Params &= ~cpAppend; + UnicodeString FileName, FileNameOnly; + UnicodeString TargetDir = GetAbsolutePath(ATargetDir, false); + UnicodeString FullTargetDir = core::UnixIncludeTrailingBackslash(TargetDir); + intptr_t Index = 0; + while ((Index < AFilesToCopy->GetCount()) && !OperationProgress->Cancel) + { + bool Success = false; + FileName = AFilesToCopy->GetString(Index); + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(Index)); + UnicodeString RealFileName = File ? File->GetFileName() : FileName; + FileNameOnly = base::ExtractFileName(RealFileName, false); + + try__finally + { + SCOPE_EXIT + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + try + { + if (FTerminal->GetSessionData()->GetCacheDirectories()) + { + FTerminal->DirectoryModified(TargetDir, false); + + if (::DirectoryExists(ApiPath(::ExtractFilePath(FileName)))) + { + FTerminal->DirectoryModified(FullTargetDir + FileNameOnly, true); + } + } + SourceRobust(FileName, File, FullTargetDir, CopyParam, Params, OperationProgress, + tfFirstLevel); + Success = true; + FLastDataSent = Now(); + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + __finally + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TFTPFileSystem::SourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TSFTPFileSystem + + TUploadSessionAction Action(FTerminal->GetActionLog()); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + TOpenRemoteFileParams OpenParams; + OpenParams.OverwriteMode = omOverwrite; + TOverwriteFileParams FileParams; + + do + { + try + { + Source(AFileName, AFile, TargetDir, CopyParam, Params, &OpenParams, &FileParams, OperationProgress, + Flags, Action); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + // prevent overwrite confirmations + // (should not be set for directories!) + Params |= cpNoConfirmation; + Flags |= tfAutoResume; + } + } + while (RobustLoop.Retry()); +} + +bool TFTPFileSystem::CanTransferSkipList(intptr_t Params, uintptr_t Flags, const TCopyParamType * CopyParam) const +{ + bool Result = + FLAGSET(Params, cpNoConfirmation) && + // cpAppend is not supported with FTP + DebugAlwaysTrue(FLAGCLEAR(Params, cpAppend)) && + FLAGCLEAR(Params, cpResume) && + FLAGCLEAR(Flags, tfAutoResume) && + !CopyParam->GetNewerOnly(); + return Result; +} + +// Copy file to remote host +void TFTPFileSystem::Source(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TOpenRemoteFileParams * OpenParams, + TOverwriteFileParams * FileParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action) +{ + UnicodeString RealFileName = AFile ? AFile->GetFileName() : AFileName; + Action.SetFileName(::ExpandUNCFileName(AFileName)); + + OperationProgress->SetFile(RealFileName, false); + + if (!FTerminal->AllowLocalFileTransfer(AFileName, CopyParam, OperationProgress)) + { + // FTerminal->LogEvent(FORMAT("File \"%s\" excluded from transfer", RealFileName.c_str())); + ThrowSkipFileNull(); + } + + int64_t MTime = 0, ATime = 0; + int64_t Size = 0; + + FTerminal->TerminalOpenLocalFile(AFileName, GENERIC_READ, + nullptr, &OpenParams->LocalFileAttrs, nullptr, &MTime, &ATime, &Size); + + OperationProgress->SetFileInProgress(); + + bool Dir = FLAGSET(OpenParams->LocalFileAttrs, faDirectory); + if (Dir) + { + Action.Cancel(); + DirectorySource(::IncludeTrailingBackslash(RealFileName), TargetDir, + OpenParams->LocalFileAttrs, CopyParam, Params, OperationProgress, Flags); + } + else + { + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(RealFileName, false), osLocal, + FLAGSET(Flags, tfFirstLevel)); + + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", RealFileName.c_str())); + + OperationProgress->SetLocalSize(Size); + + // Suppose same data size to transfer as to read + // (not true with ASCII transfer) + OperationProgress->SetTransferSize(OperationProgress->LocalSize); + OperationProgress->TransferingFile = false; + + TDateTime Modification; + // Inspired by Sysutils::FileAge + WIN32_FIND_DATA FindData; + HANDLE LocalFileHandle = ::FindFirstFile(ApiPath(AFileName).c_str(), &FindData); + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + Modification = + ::UnixToDateTime( + ::ConvertTimestampToUnixSafe(FindData.ftLastWriteTime, dstmUnix), + dstmUnix); + ::FindClose(LocalFileHandle); + } + + // Will we use ASCII of BINARY file transfer? + TFileMasks::TParams MaskParams; + MaskParams.Size = Size; + MaskParams.Modification = Modification; + UnicodeString BaseFileName = FTerminal->GetBaseFileName(RealFileName); + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osLocal, MaskParams)); + FTerminal->LogEvent( + UnicodeString(OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary") + + L" transfer mode selected."); + + ResetFileTransfer(); + + TFileTransferData UserData; + + uintptr_t TransferType = (OperationProgress->AsciiTransfer ? 1 : 2); + + // should we check for interrupted transfer? + bool ResumeAllowed = !OperationProgress->AsciiTransfer && + CopyParam->AllowResume(OperationProgress->LocalSize) && + IsCapable(fcRename); +// OperationProgress->SetResumeStatus(ResumeAllowed ? rsEnabled : rsDisabled); + + FileParams->SourceSize = OperationProgress->LocalSize; + FileParams->SourceTimestamp = ::UnixToDateTime(MTime, + FTerminal->GetSessionData()->GetDSTMode()); + bool DoResume = (ResumeAllowed && (OpenParams->OverwriteMode == omOverwrite)); + { + // ignore file list + TFTPFileListHelper Helper(this, nullptr, true); + + SetCPSLimit(OperationProgress); + // not used for uploads anyway + FFileTransferPreserveTime = CopyParam->GetPreserveTime(); + FFileTransferRemoveBOM = CopyParam->GetRemoveBOM(); + FFileTransferNoList = CanTransferSkipList(Params, Flags, CopyParam); + // not used for uploads, but we get new name (if any) back in this field + UserData.FileName = DestFileName; + UserData.Params = Params; + UserData.AutoResume = FLAGSET(Flags, tfAutoResume) || DoResume; + UserData.CopyParam = CopyParam; + UserData.Modification = Modification; + FileTransfer(RealFileName, AFileName, DestFileName, + TargetDir, false, Size, TransferType, UserData, OperationProgress); + } + + UnicodeString DestFullName = TargetDir + UserData.FileName; + // only now, we know the final destination + Action.Destination(DestFullName); + + // We are not able to tell if setting timestamp succeeded, + // so we log it always (if supported). + // Support for MDTM does not necessarily mean that the server supports + // non-standard hack of setting timestamp using + // MFMT-like (two argument) call to MDTM. + // IIS definitely does. + if (FFileTransferPreserveTime && + ((FServerCapabilities->GetCapability(mfmt_command) == yes) || + ((FServerCapabilities->GetCapability(mdtm_command) == yes)))) + { + TTouchSessionAction TouchAction(FTerminal->GetActionLog(), DestFullName, Modification); + + if (!FFileZillaIntf->UsingMlsd()) + { + FUploadedTimes[DestFullName] = Modification; + if ((FTerminal->GetConfiguration()->GetActualLogProtocol() >= 2)) + { + FTerminal->LogEvent( + FORMAT(L"Remembering modification time of \"%s\" as [%s]", + DestFullName.c_str(), StandardTimestamp(FUploadedTimes[DestFullName]).c_str())); + } + } + } + + FTerminal->LogFileDone(OperationProgress); + } + + /* TODO : Delete also read-only files. */ + if (FLAGSET(Params, cpDelete)) + { + if (!Dir) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(AFileName)); + }); + } + } + else if (CopyParam->GetClearArchive() && FLAGSET(OpenParams->LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(AFileName, OpenParams->LocalFileAttrs & ~faArchive) == 0); + }); + } +} + +void TFTPFileSystem::DirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, intptr_t Attrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + UnicodeString DestDirectoryName = + FTerminal->ChangeFileName( + CopyParam, + base::ExtractFileName(::ExcludeTrailingBackslash(DirectoryName), false), + osLocal, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = core::UnixIncludeTrailingBackslash(TargetDir + DestDirectoryName); + + OperationProgress->SetFile(DirectoryName); + + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + TSearchRecChecked SearchRec; + bool FindOK = false; + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = + ::FindFirstChecked((DirectoryName + L"*.*").c_str(), + FindAttrs, SearchRec) == 0; + }); + + bool CreateDir = true; + + try__finally + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + while (FindOK && !OperationProgress->Cancel) + { + UnicodeString FileName = DirectoryName + SearchRec.Name; + try + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + { + SourceRobust(FileName, nullptr, DestFullName, CopyParam, Params, OperationProgress, + Flags & ~(tfFirstLevel | tfAutoResume)); + // if any file got uploaded (i.e. there were any file in the + // directory and at least one was not skipped), + // do not try to create the directory, + // as it should be already created by FZAPI during upload + CreateDir = false; + } + } + catch (ESkipFile & E) + { + // If ESkipFile occurs, just log it and continue with next file + TSuspendFileOperationProgress Suspend(OperationProgress); + // here a message to user was displayed, which was not appropriate + // when user refused to overwrite the file in subdirectory. + // hopefully it won't be missing in other situations. + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = (::FindNextChecked(SearchRec) == 0); + }); + } + } + __finally + { + FindClose(SearchRec); + }; + + if (CreateDir) + { + TRemoteProperties Properties; + if (CopyParam->GetPreserveRights()) + { + Properties.Valid = TValidProperties() << vpRights; + Properties.Rights = CopyParam->RemoteFileRights(Attrs); + } + + try + { + FTerminal->SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + FTerminal->SetExceptionOnFail(false); + }; + FTerminal->RemoteCreateDirectory(DestFullName, &Properties); + } + __finally + { + FTerminal->SetExceptionOnFail(false); + }; + } + catch (...) + { + TRemoteFile * File = nullptr; + // ignore non-fatal error when the directory already exists + UnicodeString Fn = core::UnixExcludeTrailingBackslash(DestFullName); + if (Fn.IsEmpty()) + { + Fn = ROOTDIRECTORY; + } + bool Rethrow = + !FTerminal->GetActive() || + !FTerminal->FileExists(Fn, &File) || + !File->GetIsDirectory(); + SAFE_DESTROY(File); + if (Rethrow) + { + throw; + } + } + } + + /* TODO : Delete also read-only directories. */ + /* TODO : Show error message on failure. */ + if (!OperationProgress->Cancel) + { + if (FLAGSET(Params, cpDelete)) + { + FTerminal->RemoveLocalDirectory(ApiPath(DirectoryName)); + } + else if (CopyParam->GetClearArchive() && FLAGSET(Attrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DirectoryName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(DirectoryName, Attrs & ~faArchive) == 0); + }); + } + } +} + +void TFTPFileSystem::RemoteCreateDirectory(const UnicodeString & ADirName) +{ + UnicodeString DirName = GetAbsolutePath(ADirName, false); + + { + // ignore file list + TFTPFileListHelper Helper(this, nullptr, true); + + FFileZillaIntf->MakeDir(DirName.c_str()); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } +} + +void TFTPFileSystem::CreateLink(const UnicodeString & AFileName, + const UnicodeString & PointTo, bool Symbolic) +{ + DebugAssert(SupportsSiteCommand(SymlinkSiteCommand)); + if (DebugAlwaysTrue(Symbolic)) + { + EnsureLocation(); + + UnicodeString Command = FORMAT(L"%s %s %s %s", SiteCommand.c_str(), SymlinkSiteCommand.c_str(), PointTo.c_str(), AFileName.c_str()); + SendCommand(Command); + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } +} + +void TFTPFileSystem::RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action) +{ + UnicodeString FileName = GetAbsolutePath(AFileName, false); + UnicodeString FileNameOnly = base::UnixExtractFileName(FileName); + UnicodeString FilePath = core::UnixExtractFilePath(FileName); + + bool Dir = (AFile != nullptr) && AFile->GetIsDirectory() && FTerminal->CanRecurseToDirectory(AFile); + + if (Dir && FLAGCLEAR(Params, dfNoRecursive)) + { + try + { + FTerminal->ProcessDirectory(FileName, MAKE_CALLBACK(TTerminal::RemoteDeleteFile, FTerminal), &Params); + } + catch (...) + { + Action.Cancel(); + throw; + } + } + + { + // ignore file list + TFTPFileListHelper Helper(this, nullptr, true); + + if (Dir) + { + // If current remote directory is in the directory being removed, + // some servers may refuse to delete it + // This is common as ProcessDirectory above would CWD to + // the directory to LIST it. + // EnsureLocation should reset actual current directory to user's working directory. + // If user's working directory is still below deleted directory, it is + // perfectly correct to report an error. + if (core::UnixIsChildPath(ActualCurrentDirectory(), FileName)) + { + EnsureLocation(); + } + FFileZillaIntf->RemoveDir(FileNameOnly.c_str(), FilePath.c_str()); + } + else + { + FFileZillaIntf->Delete(FileNameOnly.c_str(), FilePath.c_str()); + } + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } +} + +void TFTPFileSystem::CustomCommandOnFile(const UnicodeString & /*FileName*/, + const TRemoteFile * /*File*/, const UnicodeString & /*Command*/, intptr_t /*Params*/, + TCaptureOutputEvent /*OutputEvent*/) +{ + // if ever implemented, do not forget to add EnsureLocation, + // see AnyCommand for a reason why + DebugFail(); +} + +void TFTPFileSystem::DoStartup() +{ + std::unique_ptr PostLoginCommands(new TStringList()); + try__finally + { + PostLoginCommands->SetText(FTerminal->GetSessionData()->GetPostLoginCommands()); + for (intptr_t Index = 0; Index < PostLoginCommands->GetCount(); ++Index) + { + UnicodeString Command = PostLoginCommands->GetString(Index); + if (!Command.IsEmpty()) + { + SendCommand(Command); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE); + } + } + } + __finally + { +// delete PostLoginCommands; + }; + + // retrieve initialize working directory to save it as home directory + ReadCurrentDirectory(); + FHomeDirectory = FCurrentDirectory; +} + +void TFTPFileSystem::HomeDirectory() +{ + // FHomeDirectory is an absolute path, so avoid unnecessary overhead + // of ChangeDirectory, such as EnsureLocation + DoChangeDirectory(FHomeDirectory); + FCurrentDirectory = FHomeDirectory; + FReadCurrentDirectory = false; + // make sure FZAPI is aware that we changed current working directory + FFileZillaIntf->SetCurrentPath(FCurrentDirectory.c_str()); +} + +bool TFTPFileSystem::IsCapable(intptr_t Capability) const +{ + DebugAssert(FTerminal); + switch (Capability) + { + case fcResolveSymlink: // sic + case fcTextMode: + case fcModeChanging: // but not fcModeChangingUpload + case fcNewerOnlyUpload: + case fcAnyCommand: // but not fcShellAnyCommand + case fcRename: + case fcRemoteMove: + case fcRemoveBOMUpload: + case fcMoveToQueue: + return true; + + case fcPreservingTimestampUpload: + return (FServerCapabilities->GetCapability(mfmt_command) == yes); + + case fcRemoteCopy: + return SupportsSiteCommand(CopySiteCommand); + + case fcSymbolicLink: + return SupportsSiteCommand(SymlinkSiteCommand); + + case fcCalculatingChecksum: + return FSupportsAnyChecksumFeature; + + case fcCheckingSpaceAvailable: + return FBytesAvailableSupported || SupportsCommand(AvblCommand) || SupportsCommand(XQuotaCommand); + + case fcModeChangingUpload: + case fcLoadingAdditionalProperties: + case fcShellAnyCommand: + case fcHardLink: + case fcUserGroupListing: + case fcGroupChanging: + case fcOwnerChanging: + case fcGroupOwnerChangingByID: + case fcSecondaryShell: + case fcNativeTextMode: + case fcTimestampChanging: + case fcIgnorePermErrors: + case fcRemoveCtrlZUpload: + case fcLocking: + case fcPreservingTimestampDirs: + case fcResumeSupport: + return false; + + default: + DebugFail(); + return false; + } +} + +void TFTPFileSystem::LookupUsersGroups() +{ + DebugFail(); +} + +void TFTPFileSystem::ReadCurrentDirectory() +{ + // ask the server for current directory on startup only + // and immediately after call to CWD, + // later our current directory may be not synchronized with FZAPI current + // directory anyway, see comments in EnsureLocation + if (FReadCurrentDirectory || DebugAlwaysFalse(FCurrentDirectory.IsEmpty())) + { + UnicodeString Command = L"PWD"; + SendCommand(Command); + + uintptr_t Code = 0; + TStrings * Response = nullptr; + GotReply(WaitForCommandReply(), REPLY_2XX_CODE, L"", &Code, &Response); + + std::unique_ptr ResponsePtr(Response); + + try__finally + { + DebugAssert(ResponsePtr.get() != nullptr); + bool Result = false; + + // the only allowed 2XX codes to "PWD" + if (((Code == 257) && (ResponsePtr->GetCount() == 1)) || + (Code == 250)) // RTEMS FTP server sends 250 "/" is the current directory. http://bugs.farmanager.com/view.php?id=3090 + { + UnicodeString Path = ResponsePtr->GetText(); + + intptr_t P = Path.Pos(L"\""); + if (P == 0) + { + // some systems use single quotes, be tolerant + P = Path.Pos(L"'"); + } + if (P != 0) + { + Path.Delete(1, P - 1); + + if (Unquote(Path)) + { + Result = true; + } + } + else + { + P = Path.Pos(L" "); + Path.Delete(P, Path.Length() - P + 1); + Result = true; + } + + if (Result) + { + if ((Path.Length() > 0) && (Path[1] != L'/')) + { + Path = L"/" + Path; + } + FCurrentDirectory = core::AbsolutePath(ROOTDIRECTORY, core::UnixExcludeTrailingBackslash(Path)); + if (FCurrentDirectory.IsEmpty()) + { + FCurrentDirectory = ROOTDIRECTORY; + } + FReadCurrentDirectory = false; + } + } + + if (Result) + { + FFileZillaIntf->SetCurrentPath(FCurrentDirectory.c_str()); + } + else + { + throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, Command.c_str(), Response->GetText().c_str())); + } + } + __finally + { +// delete Response; + }; + } +} + +void TFTPFileSystem::DoReadDirectory(TRemoteFileList * FileList) +{ + FBytesAvailable = -1; + FileList->Reset(); + // FZAPI does not list parent directory, add it + FileList->AddFile(new TRemoteParentDirectory(FTerminal)); + + FLastReadDirectoryProgress = 0; + + TFTPFileListHelper Helper(this, FileList, false); + + // always specify path to list, do not attempt to list "current" dir as: + // 1) List() lists again the last listed directory, not the current working directory + // 2) we handle this way the cached directory change + UnicodeString Directory = GetAbsolutePath(FileList->GetDirectory(), false); + FFileZillaIntf->List(Directory.c_str()); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_ALLOW_CANCEL); + + AutoDetectTimeDifference(FileList); + + if ((FTimeDifference != 0) || !FUploadedTimes.empty())// optimization + { + for (intptr_t Index = 0; Index < FileList->GetCount(); ++Index) + { + ApplyTimeDifference(FileList->GetFile(Index)); + } + } + + FLastDataSent = Now(); +} + +void TFTPFileSystem::ApplyTimeDifference(TRemoteFile * File) +{ + if (!File) + return; + DebugAssert(File->GetModification() == File->GetLastAccess()); + File->ShiftTimeInSeconds(FTimeDifference); + + if (File->GetModificationFmt() != mfFull) + { + TUploadedTimes::iterator Iterator = FUploadedTimes.find(File->GetFullFileName()); + if (Iterator != FUploadedTimes.end()) + { + TDateTime UploadModification = Iterator->second; + TDateTime UploadModificationReduced = core::ReduceDateTimePrecision(UploadModification, File->GetModificationFmt()); + if (UploadModificationReduced == File->GetModification()) + { + if ((FTerminal->GetConfiguration()->GetActualLogProtocol() >= 2)) + { + FTerminal->LogEvent( + FORMAT(L"Enriching modification time of \"%s\" from [%s] to [%s]", + File->GetFullFileName().c_str(), StandardTimestamp(File->GetModification()).c_str(), StandardTimestamp(UploadModification).c_str())); + } + // implicitly sets ModificationFmt to mfFull + File->SetModification(UploadModification); + } + else + { + if ((FTerminal->GetConfiguration()->GetActualLogProtocol() >= 2)) + { + FTerminal->LogEvent( + FORMAT(L"Remembered modification time [%s]/[%s] of \"%s\" is obsolete, keeping [%s]", + StandardTimestamp(UploadModification).c_str(), StandardTimestamp(UploadModificationReduced).c_str(), File->GetFullFileName().c_str(), StandardTimestamp(File->GetModification()).c_str())); + } + FUploadedTimes.erase(File->GetFullFileName()); + } + } + } +} + +void TFTPFileSystem::AutoDetectTimeDifference(TRemoteFileList * FileList) +{ + if (FDetectTimeDifference && + // Does not support MLST/MLSD, but supports MDTM at least + !FFileZillaIntf->UsingMlsd() && SupportsReadingFile()) + { + FTerminal->LogEvent(L"Detecting timezone difference..."); + + for (intptr_t Index = 0; Index < FileList->GetCount(); ++Index) + { + TRemoteFile * File = FileList->GetFile(Index); + // For directories, we do not do MDTM in ReadFile + // (it should not be problem to use them otherwise). + // We are also not interested in files with day precision only. + if (!File->GetIsDirectory() && !File->GetIsSymLink() && + File->GetIsTimeShiftingApplicable()) + { + FDetectTimeDifference = false; + + std::unique_ptr UtcFilePtr; + try + { + TRemoteFile * UtcFile = nullptr; + ReadFile(File->GetFullFileName(), UtcFile); + UtcFilePtr.reset(UtcFile); + } + catch (Exception & /*E*/) + { + if (!FTerminal->GetActive()) + { + throw; + } + FTerminal->LogEvent(FORMAT(L"Failed to retrieve file %s attributes to detect timezone difference", File->GetFullFileName().c_str())); + break; + } + + TDateTime UtcModification = UtcFilePtr->GetModification(); + UtcFilePtr.release(); + + // MDTM returns seconds, trim those + UtcModification = core::ReduceDateTimePrecision(UtcModification, File->GetModificationFmt()); + + // Time difference between timestamp retrieved using MDTM (UTC converted to local timezone) + // and using LIST (no conversion, expecting the server uses the same timezone as the client). + // Note that FormatTimeZone reverses the value. + FTimeDifference = static_cast(SecsPerDay * (UtcModification - File->GetModification())); + + UnicodeString LogMessage; + if (FTimeDifference == 0) + { + LogMessage = FORMAT(L"No timezone difference detected using file %s", File->GetFullFileName().c_str()); + } + else + { + LogMessage = FORMAT(L"Timezone difference of %s detected using file %s", FormatTimeZone(static_cast(FTimeDifference)).c_str(), File->GetFullFileName().c_str()); + } + FTerminal->LogEvent(LogMessage); + + break; + } + } + + if (FDetectTimeDifference) + { + FTerminal->LogEvent(L"Found no file to use for detecting timezone difference"); + } + } +} + +void TFTPFileSystem::ReadDirectory(TRemoteFileList * FileList) +{ + // whole below "-a" logic is for LIST, + // if we know we are going to use MLSD, skip it + if (FTerminal->GetSessionData()->GetFtpUseMlsd() == asOn) + { + DoReadDirectory(FileList); + } + else + { + bool GotNoFilesForAll = false; + bool Repeat = false; + + do + { + Repeat = false; + try + { + FDoListAll = (FListAll == asAuto) || (FListAll == asOn); + DoReadDirectory(FileList); + + // We got no files with "-a", but again no files w/o "-a", + // so it was not "-a"'s problem, revert to auto and let it decide the next time + if (GotNoFilesForAll && (FileList->GetCount() == 0)) + { + DebugAssert(FListAll == asOff); + FListAll = asAuto; + } + else if (FListAll == asAuto) + { + // some servers take "-a" as a mask and return empty directory listing + // (note that it's actually never empty here, there's always at least parent directory, + // added explicitly by DoReadDirectory) + if ((FileList->GetCount() == 0) || + ((FileList->GetCount() == 1) && FileList->GetFile(0)->GetIsParentDirectory())) + { + Repeat = true; + FListAll = asOff; + GotNoFilesForAll = true; + FTerminal->LogEvent("LIST with -a switch returned empty directory listing, will try pure LIST"); + } + else + { + // reading first directory has succeeded, always use "-a" + FListAll = asOn; + } + } + + // use "-a" even for implicit directory reading by FZAPI? + // (e.g. before file transfer) + FDoListAll = (FListAll == asOn); + } + catch (Exception &) + { + FDoListAll = false; + // reading the first directory has failed, + // further try without "-a" only as the server may not support it + if (FListAll == asAuto) + { + FTerminal->LogEvent("LIST with -a failed, will try pure LIST"); + if (!FTerminal->GetActive()) + { + FTerminal->Reopen(ropNoReadDirectory); + } + + FListAll = asOff; + Repeat = true; + } + else + { + throw; + } + } + } + while (Repeat); + } +} + +void TFTPFileSystem::DoReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile) +{ + UnicodeString FileName = GetAbsolutePath(AFileName, false); + UnicodeString FileNameOnly; + UnicodeString FilePath; + if (core::IsUnixRootPath(FileName)) + { + FileNameOnly = FileName; + FilePath = FileName; + } + else + { + FileNameOnly = base::UnixExtractFileName(FileName); + FilePath = core::UnixExtractFilePath(FileName); + } + + std::unique_ptr FileList(new TRemoteFileList()); + try__finally + { + // Duplicate() call below would use this to compose FullFileName + FileList->SetDirectory(FilePath); + TFTPFileListHelper Helper(this, FileList.get(), false); + FFileZillaIntf->ListFile(FileNameOnly.c_str(), FilePath.c_str()); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_ALLOW_CANCEL); + TRemoteFile * File = FileList->FindFile(FileNameOnly.c_str()); + if (File != nullptr) + { + AFile = File->Duplicate(); + ApplyTimeDifference(AFile); + } + + FLastDataSent = Now(); + } + __finally + { +// delete FileList; + }; +} + +bool TFTPFileSystem::SupportsReadingFile() const +{ + return + FFileZillaIntf->UsingMlsd() || + ((FServerCapabilities->GetCapability(mdtm_command) == yes) && + (FServerCapabilities->GetCapability(size_command) == yes)); +} + +void TFTPFileSystem::ReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile) +{ + UnicodeString Path = core::UnixExtractFilePath(AFileName); + UnicodeString NameOnly = base::UnixExtractFileName(AFileName); + TRemoteFile * File = nullptr; + bool Own = false; + if (SupportsReadingFile()) + { + DoReadFile(AFileName, File); + Own = true; + } + else + { + if (core::IsUnixRootPath(AFileName)) + { + FTerminal->LogEvent(FORMAT(L"%s is a root path", AFileName.c_str())); + File = new TRemoteDirectoryFile(); + File->SetFullFileName(AFileName); + File->SetFileName(L""); + } + else + { + // FZAPI does not have efficient way to read properties of one file. + // In case we need properties of set of files from the same directory, + // cache the file list for future + if ((FFileListCache != nullptr) && + core::UnixSamePath(Path, FFileListCache->GetDirectory()) && + (core::UnixIsAbsolutePath(FFileListCache->GetDirectory()) || + (FFileListCachePath == GetCurrDirectory()))) + { + File = FFileListCache->FindFile(NameOnly); + } + // if cache is invalid or file is not in cache, (re)read the directory + if (File == nullptr) + { + std::unique_ptr FileListCache(new TRemoteFileList()); + FileListCache->SetDirectory(Path); + try__catch + { + ReadDirectory(FileListCache.get()); + } + /*catch (...) + { + delete FileListCache; + throw; + }*/ + // set only after we successfully read the directory, + // otherwise, when we reconnect from ReadDirectory, + // the FFileListCache is reset from ResetCache. + SAFE_DESTROY(FFileListCache); + FFileListCache = FileListCache.release(); + FFileListCachePath = GetCurrDirectory(); + + File = FFileListCache->FindFile(NameOnly); + } + + Own = false; + } + } + + if (File == nullptr) + { + AFile = nullptr; + throw Exception(FMTLOAD(FILE_NOT_EXISTS, AFileName.c_str())); + } + + DebugAssert(File != nullptr); + AFile = Own ? File : File->Duplicate(); +} + +void TFTPFileSystem::ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& AFile) +{ + // Resolving symlinks over FTP is big overhead + // (involves opening TCPIP connection for retrieving "directory listing"). + // Moreover FZAPI does not support that anyway. + // Though nowadays we could use MLST to read the symlink. + std::unique_ptr File(new TRemoteFile(SymlinkFile)); + try__catch + { + File->SetTerminal(FTerminal); + File->SetFileName(base::UnixExtractFileName(SymlinkFile->GetLinkTo())); + // FZAPI treats all symlink target as directories + File->SetType(FILETYPE_SYMLINK); + AFile = File.release(); + } + /*catch (...) + { + delete File; + File = NULL; + throw; + }*/ +} + +void TFTPFileSystem::RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + UnicodeString FileName = GetAbsolutePath(AFileName, false); + UnicodeString NewName = GetAbsolutePath(ANewName, false); + + UnicodeString FileNameOnly = base::UnixExtractFileName(FileName); + UnicodeString FilePathOnly = core::UnixExtractFilePath(FileName); + UnicodeString NewNameOnly = base::UnixExtractFileName(NewName); + UnicodeString NewPathOnly = core::UnixExtractFilePath(NewName); + + { + // ignore file list + TFTPFileListHelper Helper(this, nullptr, true); + + FFileZillaIntf->Rename(FileNameOnly.c_str(), NewNameOnly.c_str(), + FilePathOnly.c_str(), NewPathOnly.c_str()); + + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); + } +} + +void TFTPFileSystem::RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + DebugAssert(SupportsSiteCommand(CopySiteCommand)); + EnsureLocation(); + + UnicodeString Command; + + Command = FORMAT(L"%s CPFR %s", SiteCommand.c_str(), AFileName.c_str()); + SendCommand(Command); + GotReply(WaitForCommandReply(), REPLY_3XX_CODE); + + Command = FORMAT(L"%s CPTO %s", SiteCommand.c_str(), ANewName.c_str()); + SendCommand(Command); + GotReply(WaitForCommandReply(), REPLY_2XX_CODE); +} + +TStrings * TFTPFileSystem::GetFixedPaths() +{ + return nullptr; +} + +void TFTPFileSystem::SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable) +{ + if (FBytesAvailableSupported) + { + std::unique_ptr DummyFileList(new TRemoteFileList()); + DummyFileList->SetDirectory(APath); + ReadDirectory(DummyFileList.get()); + ASpaceAvailable.UnusedBytesAvailableToUser = FBytesAvailable; + } + else if (SupportsCommand(XQuotaCommand)) + { + // WS_FTP: + // XQUOTA + // 213-File and disk usage + // File count: 3 + // File limit: 10000 + // Disk usage: 1532791 + // Disk limit: 2048000 + // 213 File and disk usage end + + // XQUOTA is global not path-specific + UnicodeString Command = XQuotaCommand; + SendCommand(Command); + TStrings * Response = nullptr; + GotReply(WaitForCommandReply(), REPLY_2XX_CODE, L"", nullptr, &Response); + std::unique_ptr ResponseOwner(Response); + + int64_t UsedBytes = -1; + for (intptr_t Index = 0; Index < Response->GetCount(); Index++) + { + // trimming padding + UnicodeString Line = Trim(Response->GetString(Index)); + UnicodeString Label = CutToChar(Line, L':', true); + if (SameText(Label, L"Disk usage")) + { + UsedBytes = StrToInt64(Line); + } + else if (SameText(Label, L"Disk limit") && !SameText(Line, L"unlimited")) + { + ASpaceAvailable.BytesAvailableToUser = StrToInt64(Line); + } + } + + if ((UsedBytes >= 0) && (ASpaceAvailable.BytesAvailableToUser > 0)) + { + ASpaceAvailable.UnusedBytesAvailableToUser = ASpaceAvailable.BytesAvailableToUser - UsedBytes; + } + } + else if (SupportsCommand(AvblCommand)) + { + // draft-peterson-streamlined-ftp-command-extensions-10 + // Implemented by Serv-U. + UnicodeString Command = FORMAT(L"%s %s", AvblCommand.c_str(), APath.c_str()); + SendCommand(Command); + UnicodeString Response = GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_SINGLE_LINE); + ASpaceAvailable.UnusedBytesAvailableToUser = StrToInt64(Response); + } +} + +const TSessionInfo & TFTPFileSystem::GetSessionInfo() const +{ + return FSessionInfo; +} + +const TFileSystemInfo & TFTPFileSystem::GetFileSystemInfo(bool /*Retrieve*/) +{ + if (!FFileSystemInfoValid) + { + FFileSystemInfo.RemoteSystem = FSystem; + FFileSystemInfo.RemoteSystem.Unique(); + + if (FFeatures->GetCount() == 0) + { + FFileSystemInfo.AdditionalInfo = LoadStr(FTP_NO_FEATURE_INFO); + } + else + { + FFileSystemInfo.AdditionalInfo = + FORMAT(L"%s\r\n", LoadStr(FTP_FEATURE_INFO).c_str()); + for (intptr_t Index = 0; Index < FFeatures->GetCount(); ++Index) + { + // For TrimLeft, refer to HandleFeatReply + FFileSystemInfo.AdditionalInfo += FORMAT(L" %s\r\n", TrimLeft(FFeatures->GetString(Index)).c_str()); + } + } + + FTerminal->SaveCapabilities(FFileSystemInfo); + + FFileSystemInfoValid = true; + } + return FFileSystemInfo; +} + +bool TFTPFileSystem::TemporaryTransferFile(const UnicodeString & /*FileName*/) +{ + return false; +} + +bool TFTPFileSystem::GetStoredCredentialsTried() const +{ + return FStoredPasswordTried; +} + +UnicodeString TFTPFileSystem::FSGetUserName() const +{ + return FUserName; +} + +UnicodeString TFTPFileSystem::GetCurrDirectory() const +{ + return FCurrentDirectory; +} + +const wchar_t * TFTPFileSystem::GetOption(intptr_t OptionID) const +{ + TSessionData * Data = FTerminal->GetSessionData(); + + switch (OptionID) + { + case OPTION_PROXYHOST: + case OPTION_FWHOST: + FOptionScratch = Data->GetProxyHost(); + break; + + case OPTION_PROXYUSER: + case OPTION_FWUSER: + FOptionScratch = Data->GetProxyUsername(); + break; + + case OPTION_PROXYPASS: + case OPTION_FWPASS: + FOptionScratch = Data->GetProxyPassword(); + break; + + case OPTION_TRANSFERIP: + FOptionScratch = FTerminal->GetConfiguration()->GetExternalIpAddress(); + break; + + case OPTION_ANONPWD: + case OPTION_TRANSFERIP6: + FOptionScratch.Clear(); + break; + + default: + DebugFail(); + FOptionScratch.Clear(); + } + + return FOptionScratch.c_str(); +} + +intptr_t TFTPFileSystem::GetOptionVal(intptr_t OptionID) const +{ + TSessionData * Data = FTerminal->GetSessionData(); + intptr_t Result; + + switch (OptionID) + { + case OPTION_PROXYTYPE: + switch (Data->GetActualProxyMethod()) + { + case ::pmNone: + Result = 0; // PROXYTYPE_NOPROXY; + break; + + case pmSocks4: + Result = 2; // PROXYTYPE_SOCKS4A + break; + + case pmSocks5: + Result = 3; // PROXYTYPE_SOCKS5 + break; + + case pmHTTP: + Result = 4; // PROXYTYPE_HTTP11 + break; + + case pmTelnet: + case pmCmd: + default: + DebugFail(); + Result = 0; // PROXYTYPE_NOPROXY; + break; + } + break; + + case OPTION_PROXYPORT: + case OPTION_FWPORT: + Result = Data->GetProxyPort(); + break; + + case OPTION_PROXYUSELOGON: + Result = !Data->GetProxyUsername().IsEmpty(); + break; + + case OPTION_LOGONTYPE: + Result = Data->GetFtpProxyLogonType(); + break; + + case OPTION_TIMEOUTLENGTH: + Result = Data->GetTimeout(); + break; + + case OPTION_DEBUGSHOWLISTING: + Result = TRUE; + break; + + case OPTION_PASV: + // should never get here t_server.nPasv being nonzero + DebugFail(); + Result = FALSE; + break; + + case OPTION_PRESERVEDOWNLOADFILETIME: + case OPTION_MPEXT_PRESERVEUPLOADFILETIME: + Result = FFileTransferPreserveTime ? TRUE : FALSE; + break; + + case OPTION_LIMITPORTRANGE: + Result = FALSE; + break; + + case OPTION_PORTRANGELOW: + case OPTION_PORTRANGEHIGH: + // should never get here OPTION_LIMITPORTRANGE being zero + DebugFail(); + Result = 0; + break; + + case OPTION_ENABLE_IPV6: + Result = ((Data->GetAddressFamily() != afIPv4) ? TRUE : FALSE); + break; + + case OPTION_KEEPALIVE: + Result = ((Data->GetFtpPingType() != ptOff) ? TRUE : FALSE); + break; + + case OPTION_INTERVALLOW: + case OPTION_INTERVALHIGH: + Result = Data->GetFtpPingInterval(); + break; + + case OPTION_VMSALLREVISIONS: + Result = FALSE; + break; + + case OPTION_SPEEDLIMIT_DOWNLOAD_TYPE: + case OPTION_SPEEDLIMIT_UPLOAD_TYPE: + Result = (FFileTransferCPSLimit == 0 ? 0 : 1); + break; + + case OPTION_SPEEDLIMIT_DOWNLOAD_VALUE: + case OPTION_SPEEDLIMIT_UPLOAD_VALUE: + Result = static_cast((FFileTransferCPSLimit / 1024)); // FZAPI expects KB/s + break; + + case OPTION_MPEXT_SHOWHIDDEN: + Result = (FDoListAll ? TRUE : FALSE); + break; + + case OPTION_MPEXT_SSLSESSIONREUSE: + Result = (Data->GetSslSessionReuse() ? TRUE : FALSE); + break; + + case OPTION_MPEXT_MIN_TLS_VERSION: + Result = Data->GetMinTlsVersion(); + break; + + case OPTION_MPEXT_MAX_TLS_VERSION: + Result = Data->GetMaxTlsVersion(); + break; + + case OPTION_MPEXT_SNDBUF: + Result = Data->GetSendBuf(); + break; + + case OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY: + Result = FTransferActiveImmediately; + break; + + case OPTION_MPEXT_REMOVE_BOM: + Result = FFileTransferRemoveBOM ? TRUE : FALSE; + break; + + case OPTION_MPEXT_LOG_SENSITIVE: + Result = FTerminal->GetConfiguration()->GetLogSensitive() ? TRUE : FALSE; + break; + + case OPTION_MPEXT_HOST: + Result = (Data->GetFtpHost() == asOn); + break; + + case OPTION_MPEXT_NODELAY: + Result = Data->GetTcpNoDelay(); + break; + + case OPTION_MPEXT_NOLIST: + Result = FFileTransferNoList ? TRUE : FALSE; + break; + + default: + DebugFail(); + Result = FALSE; + break; + } + + return Result; +} + +bool TFTPFileSystem::FTPPostMessage(uintptr_t Type, WPARAM wParam, LPARAM lParam) +{ + if (Type == TFileZillaIntf::MSG_TRANSFERSTATUS) + { + // Stop here if FileTransferProgress is proceeding, + // it makes "pause" in queue work. + // Paused queue item stops in some of the TFileOperationProgressType + // methods called from FileTransferProgress + TGuard Guard(FTransferStatusCriticalSection); + } + + TGuard Guard(FQueueCriticalSection); + + FQueue.push_back(TMessageQueue::value_type(wParam, lParam)); + ::SetEvent(FQueueEvent); + + return true; +} + +bool TFTPFileSystem::ProcessMessage() +{ + bool Result; + TMessageQueue::value_type Message; + + { + TGuard Guard(FQueueCriticalSection); + + Result = !FQueue.empty(); + if (Result) + { + Message = FQueue.front(); + FQueue.erase(FQueue.begin()); + } + else + { + // now we are perfectly sure that the queue is empty as it is locked, + // so reset the event + ::ResetEvent(FQueueEvent); + } + } + + if (Result) + { + FFileZillaIntf->HandleMessage(Message.wparam, Message.lparam); + } + + return Result; +} + +void TFTPFileSystem::DiscardMessages() +{ + while (ProcessMessage()); +} + +void TFTPFileSystem::WaitForMessages() +{ + //if (FQueue.empty()) + // return; +// DWORD Result = ::WaitForSingleObject(FQueueEvent, INFINITE); + DWORD Result = 0; + do + { + Result = ::WaitForSingleObject(FQueueEvent, GUIUpdateInterval); + FTerminal->ProcessGUI(); + } + while (Result == WAIT_TIMEOUT); + + if (Result != WAIT_OBJECT_0) + { + FTerminal->FatalError(nullptr, FMTLOAD(INTERNAL_ERROR, L"ftp#1", ::IntToStr(Result).c_str())); + } +} + +void TFTPFileSystem::PoolForFatalNonCommandReply() +{ + DebugAssert(FReply == 0); + DebugAssert(FCommandReply == 0); + DebugAssert(!FWaitingForReply); + + FWaitingForReply = true; + + uintptr_t Reply = 0; + + try__finally + { + SCOPE_EXIT + { + FReply = 0; + DebugAssert(FCommandReply == 0); + FCommandReply = 0; + DebugAssert(FWaitingForReply); + FWaitingForReply = false; + }; + // discard up to one reply + // (it should not happen here that two replies are posted anyway) + while (ProcessMessage() && (FReply == 0)); + Reply = FReply; + } + __finally + { + FReply = 0; + DebugAssert(FCommandReply == 0); + FCommandReply = 0; + DebugAssert(FWaitingForReply); + FWaitingForReply = false; + }; + + if (Reply != 0) + { + // throws + GotNonCommandReply(Reply); + } +} + +bool TFTPFileSystem::NoFinalLastCode() const +{ + return (FLastCodeClass == 0) || (FLastCodeClass == 1); +} + +bool TFTPFileSystem::KeepWaitingForReply(uintptr_t & ReplyToAwait, bool WantLastCode) const +{ + // to keep waiting, + // non-command reply must be unset, + // the reply we wait for must be unset or + // last code must be unset (if we wait for it) + return + (FReply == 0) && + ((ReplyToAwait == 0) || + (WantLastCode && NoFinalLastCode())); +} + +void TFTPFileSystem::DoWaitForReply(uintptr_t & ReplyToAwait, bool WantLastCode) +{ + try + { + while (FTerminal && (FTerminal->GetStatus() != ssClosed) && KeepWaitingForReply(ReplyToAwait, WantLastCode)) + { + WaitForMessages(); + // wait for the first reply only, + // i.e. in case two replies are posted get the first only. + // e.g. when server closes the connection, but posts error message before, + // sometime it happens that command (like download) fails because of the error + // and does not catch the disconnection. then asynchronous "disconnect reply" + // is posted immediately afterwards. leave detection of that to Idle() + while (ProcessMessage() && KeepWaitingForReply(ReplyToAwait, WantLastCode)); + } + + if (FReply != 0) + { + // throws + GotNonCommandReply(FReply); + } + } + catch (...) + { + // even if non-fatal error happens, we must process pending message, + // so that we "eat" the reply message, so that it gets not mistakenly + // associated with future connect + if (FTerminal->GetActive()) + { + DoWaitForReply(ReplyToAwait, WantLastCode); + } + throw; + } +} + +uintptr_t TFTPFileSystem::WaitForReply(bool Command, bool WantLastCode) +{ + DebugAssert(FReply == 0); + DebugAssert(FCommandReply == 0); + DebugAssert(!FWaitingForReply); + DebugAssert(!FTransferStatusCriticalSection.GetAcquired()); + + ResetReply(); + FWaitingForReply = true; + + uintptr_t Reply = 0; + + try__finally + { + SCOPE_EXIT + { + FReply = 0; + FCommandReply = 0; + DebugAssert(FWaitingForReply); + FWaitingForReply = false; + }; + uintptr_t & ReplyToAwait = (Command ? FCommandReply : FReply); + DoWaitForReply(ReplyToAwait, WantLastCode); + + Reply = ReplyToAwait; + } + __finally + { + FReply = 0; + FCommandReply = 0; + DebugAssert(FWaitingForReply); + FWaitingForReply = false; + }; + + return Reply; +} + +uintptr_t TFTPFileSystem::WaitForCommandReply(bool WantLastCode) +{ + return WaitForReply(true, WantLastCode); +} + +void TFTPFileSystem::WaitForFatalNonCommandReply() +{ + WaitForReply(false, false); + DebugFail(); +} + +void TFTPFileSystem::ResetReply() +{ + FLastCode = 0; + FLastCodeClass = 0; + DebugAssert(FLastResponse != nullptr); + FLastResponse->Clear(); + DebugAssert(FLastErrorResponse != nullptr); + FLastErrorResponse->Clear(); + DebugAssert(FLastError != nullptr); + FLastError->Clear(); +} + +void TFTPFileSystem::GotNonCommandReply(uintptr_t Reply) +{ + DebugAssert(FLAGSET(Reply, TFileZillaIntf::REPLY_DISCONNECTED)); + GotReply(Reply); + // should never get here as GotReply should raise fatal exception + DebugFail(); +} + +UnicodeString TFTPFileSystem::GotReply(uintptr_t Reply, uintptr_t Flags, + const UnicodeString & Error, uintptr_t * Code, TStrings ** Response) +{ + UnicodeString Result; + try__finally + { + SCOPE_EXIT + { + ResetReply(); + }; + if (FLAGSET(Reply, TFileZillaIntf::REPLY_OK)) + { + DebugAssert(Reply == TFileZillaIntf::REPLY_OK); + + // With REPLY_2XX_CODE treat "OK" non-2xx code like an error. + // REPLY_3XX_CODE has to be always used along with REPLY_2XX_CODE. + if ((FLAGSET(Flags, REPLY_2XX_CODE) && (FLastCodeClass != 2)) && + ((FLAGCLEAR(Flags, REPLY_3XX_CODE) || (FLastCodeClass != 3)))) + { + GotReply(TFileZillaIntf::REPLY_ERROR, Flags, Error); + } + } + else if (FLAGSET(Reply, TFileZillaIntf::REPLY_CANCEL) && + FLAGSET(Flags, REPLY_ALLOW_CANCEL)) + { + DebugAssert( + (Reply == (TFileZillaIntf::REPLY_CANCEL | TFileZillaIntf::REPLY_ERROR)) || + (Reply == (TFileZillaIntf::REPLY_ABORTED | TFileZillaIntf::REPLY_CANCEL | TFileZillaIntf::REPLY_ERROR))); + // noop + } + // we do not expect these with our usage of FZ + else if (Reply & + (TFileZillaIntf::REPLY_WOULDBLOCK | TFileZillaIntf::REPLY_OWNERNOTSET | + TFileZillaIntf::REPLY_INVALIDPARAM | TFileZillaIntf::REPLY_ALREADYCONNECTED | + TFileZillaIntf::REPLY_IDLE | TFileZillaIntf::REPLY_NOTINITIALIZED | + TFileZillaIntf::REPLY_ALREADYINIZIALIZED)) + { + FTerminal->FatalError(nullptr, FMTLOAD(INTERNAL_ERROR, L"ftp#2", FORMAT(L"0x%x", static_cast(Reply)).c_str())); + } + else + { + // everything else must be an error or disconnect notification + DebugAssert( + FLAGSET(Reply, TFileZillaIntf::REPLY_ERROR) || + FLAGSET(Reply, TFileZillaIntf::REPLY_DISCONNECTED)); + + TODO("REPLY_CRITICALERROR ignored"); + + // REPLY_NOTCONNECTED happens if connection is closed between moment + // when FZAPI interface method dispatches the command to FZAPI thread + // and moment when FZAPI thread receives the command + bool Disconnected = + FLAGSET(Reply, TFileZillaIntf::REPLY_DISCONNECTED) || + FLAGSET(Reply, TFileZillaIntf::REPLY_NOTCONNECTED); + + UnicodeString HelpKeyword; + std::unique_ptr MoreMessages(new TStringList()); + try__catch + { + if (Disconnected) + { + if (FLAGCLEAR(Flags, REPLY_CONNECT)) + { + MoreMessages->Add(LoadStr(LOST_CONNECTION)); + Discard(); + FTerminal->Closed(); + } + else + { + // For connection failure, do not report that connection was lost, + // its obvious. + // Also do not report to terminal that we are closed as + // that turns terminal into closed mode, but we want to + // pretend (at least with failed authentication) to retry + // with the same connection (as with SSH), so we explicitly + // close terminal in Open() only after we give up + Discard(); + } + } + + if (FLAGSET(Reply, TFileZillaIntf::REPLY_ABORTED)) + { + MoreMessages->Add(LoadStr(USER_TERMINATED)); + } + + if (FLAGSET(Reply, TFileZillaIntf::REPLY_NOTSUPPORTED)) + { + MoreMessages->Add(LoadStr(FZ_NOTSUPPORTED)); + } + + if (FLastCode == 530) + { + // Serv-U also uses this code in response to "SITE PSWD" + MoreMessages->Add(LoadStr(AUTHENTICATION_FAILED)); + } + + if (FLastCode == 425) + { + if (!FTerminal->GetSessionData()->GetFtpPasvMode()) + { + MoreMessages->Add(LoadStr(FTP_CANNOT_OPEN_ACTIVE_CONNECTION2)); + HelpKeyword = HELP_FTP_CANNOT_OPEN_ACTIVE_CONNECTION; + } + } + if (FLastCode == 421) + { + Disconnected = true; + } + + if (FLastCode == DummyTimeoutCode) + { + HelpKeyword = HELP_ERRORMSG_TIMEOUT; + } + + if (FLastCode == DummyDisconnectCode) + { + HelpKeyword = HELP_STATUSMSG_DISCONNECTED; + } + + MoreMessages->AddStrings(FLastError); + // already cleared from WaitForReply, but GotReply can be also called + // from Closed. then make sure that error from previous command not + // associated with session closure is not reused + FLastError->Clear(); + + MoreMessages->AddStrings(FLastErrorResponse); + // see comment for FLastError + FLastResponse->Clear(); + FLastErrorResponse->Clear(); + + if (MoreMessages->GetCount() == 0) + { + MoreMessages.reset(); + } + } + /*catch(...) + { + delete MoreMessages; + throw; + }*/ + + UnicodeString ErrorStr = Error; + if (ErrorStr.IsEmpty() && (MoreMessages.get() != nullptr)) + { + DebugAssert(MoreMessages->GetCount() > 0); + // bit too generic assigning of main instructions, let's see how it works + ErrorStr = MainInstructions(MoreMessages->GetString(0)); + MoreMessages->Delete(0); + } + + if (Disconnected) + { + // for fatal error, it is essential that there is some message + DebugAssert(!ErrorStr.IsEmpty()); + std::unique_ptr E(new ExtException(ErrorStr, MoreMessages.release(), true)); + try__finally + { + FTerminal->FatalError(E.get(), L""); + } + __finally + { +// delete E; + }; + } + else + { + throw ExtException(ErrorStr, MoreMessages.release(), true, UnicodeString(HelpKeyword)); + } + } + + if ((Code != nullptr) && (FLastCodeClass != DummyCodeClass)) + { + *Code = static_cast(FLastCode); + } + + if (FLAGSET(Flags, REPLY_SINGLE_LINE)) + { + if (FLastResponse->GetCount() != 1) + { + throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, FLastCommandSent.c_str(), FLastResponse->GetText().c_str())); + } + Result = FLastResponse->GetString(0); + } + + if (Response != nullptr) + { + *Response = FLastResponse; + FLastResponse = new TStringList(); + // just to be consistent + SAFE_DESTROY(FLastErrorResponse); + FLastErrorResponse = new TStringList(); + } + } + __finally + { + ResetReply(); + }; + return Result; +} + +void TFTPFileSystem::SendCommand(const UnicodeString & Command) +{ + FFileZillaIntf->CustomCommand(Command.c_str()); + FLastCommandSent = CopyToChar(Command, L' ', false); +} + +void TFTPFileSystem::SetLastCode(intptr_t Code) +{ + FLastCode = Code; + FLastCodeClass = (Code / 100); +} + +void TFTPFileSystem::StoreLastResponse(const UnicodeString & Text) +{ + FLastResponse->Add(Text); + if (FLastCodeClass >= 4) + { + FLastErrorResponse->Add(Text); + } +} + +void TFTPFileSystem::HandleReplyStatus(const UnicodeString & Response) +{ + int64_t Code = 0; + + if (FOnCaptureOutput != nullptr) + { + FOnCaptureOutput(Response, cotOutput); + } + + if (FWelcomeMessage.IsEmpty() && StartsStr(L"SSH", Response)) + { + FLastErrorResponse->Add(LoadStr(SFTP_AS_FTP_ERROR)); + } + + // Two forms of multiline responses were observed + // (the first is according to the RFC 959): + + // 211-Features: + // MDTM + // REST STREAM + // SIZE + // 211 End + + // This format is according to RFC 2228. + // Is used by ProFTPD when MultilineRFC2228 is enabled + // http://www.proftpd.org/docs/directives/linked/config_ref_MultilineRFC2228.html + + // 211-Features: + // 211-MDTM + // 211-REST STREAM + // 211-SIZE + // 211-AUTH TLS + // 211-PBSZ + // 211-PROT + // 211 End + + // IIS 2003: + + // 211-FEAT + // SIZE + // MDTM + // 211 END + + // Partially duplicated in CFtpControlSocket::OnReceive + + bool HasCodePrefix = + (Response.Length() >= 3) && + ::TryStrToInt(Response.SubString(1, 3), Code) && + (Code >= 100) && (Code <= 599) && + ((Response.Length() == 3) || (Response[4] == L' ') || (Response[4] == L'-')); + + if (HasCodePrefix && !FMultineResponse) + { + FMultineResponse = (Response.Length() >= 4) && (Response[4] == L'-'); + FLastResponse->Clear(); + FLastErrorResponse->Clear(); + SetLastCode(static_cast(Code)); + if (Response.Length() >= 5) + { + StoreLastResponse(Response.SubString(5, Response.Length() - 4)); + } + } + else + { + intptr_t Start = 0; + // response with code prefix + if (HasCodePrefix && (FLastCode == Code)) + { + // End of multiline response? + if ((Response.Length() <= 3) || (Response[4] == L' ')) + { + FMultineResponse = false; + } + Start = 5; + } + else + { + Start = (((Response.Length() >= 1) && (Response[1] == L' ')) ? 2 : 1); + } + + // Intermediate empty lines are being added + if (FMultineResponse || (Response.Length() >= Start)) + { + StoreLastResponse(Response.SubString(Start, Response.Length() - Start + 1)); + } + } + + + if (StartsStr(DirectoryHasBytesPrefix, Response)) + { + UnicodeString Buf = Response; + Buf.Delete(1, DirectoryHasBytesPrefix.Length()); + Buf = Buf.TrimLeft(); + UnicodeString BytesStr = CutToChar(Buf, L' ', true); + BytesStr = ReplaceStr(BytesStr, L",", L""); + FBytesAvailable = StrToInt64Def(BytesStr, -1); + if (FBytesAvailable >= 0) + { + FBytesAvailableSupported = true; + } + } + + if (!FMultineResponse) + { + if (FLastCode == 220) + { + // HOST command also uses 220 response. + // Neither our use of welcome message is prepared for changing it + // during the session, so we keep the initial message only. + // Theoretically the welcome message can be host-specific, + // but IIS uses "220 Host accepted", and we are not interested in that anyway. + // Serv-U repeats the initial welcome message. + // WS_FTP uses "200 Command HOST succeed" + if (FWelcomeMessage.IsEmpty()) + { + FWelcomeMessage = FLastResponse->GetText(); + if (FTerminal->GetConfiguration()->GetShowFtpWelcomeMessage()) + { + FTerminal->DisplayBanner(FWelcomeMessage); + } + // Idea FTP Server v0.80 + if ((FTerminal->GetSessionData()->GetFtpTransferActiveImmediately() == asAuto) && + FWelcomeMessage.Pos(L"Idea FTP Server") > 0) + { + FTerminal->LogEvent(L"The server requires TLS/SSL handshake on transfer connection before responding 1yz to STOR/APPE"); + FTransferActiveImmediately = true; + } + } + } + else if (FLastCommand == PASS) + { + FStoredPasswordTried = true; + // 530 = "Not logged in." + if (FLastCode == 530) + { + FPasswordFailed = true; + } + } + else if (FLastCommand == SYST) + { + DebugAssert(FSystem.IsEmpty()); + // Positive reply to "SYST" must be 215, see RFC 959 + if (FLastCode == 215) + { + FSystem = FLastResponse->GetText().TrimRight(); + // full name is "Personal FTP Server PRO K6.0" + if ((FListAll == asAuto) && + (FSystem.Pos(L"Personal FTP Server") > 0)) + { + FTerminal->LogEvent("Server is known not to support LIST -a"); + FListAll = asOff; + } + // The FWelcomeMessage usually contains "Microsoft FTP Service" but can be empty + if (ContainsText(FSystem, L"Windows_NT")) + { + FTerminal->LogEvent(L"The server is probably running Windows, assuming that directory listing timestamps are affected by DST."); + FWindowsServer = true; + } + } + else + { + FSystem.Clear(); + } + } + else if (FLastCommand == FEAT) + { + HandleFeatReply(); + } + } +} + +void TFTPFileSystem::ResetFeatures() +{ + FFeatures->Clear(); + FSupportedCommands->Clear(); + FSupportedSiteCommands->Clear(); + FHashAlgs->Clear(); + FSupportsAnyChecksumFeature = false; +} + +void TFTPFileSystem::HandleFeatReply() +{ + ResetFeatures(); + // Response to FEAT must be multiline, where leading and trailing line + // is "meaningless". See RFC 2389. + if ((FLastCode == 211) && (FLastResponse->GetCount() > 2)) + { + FLastResponse->Delete(0); + FLastResponse->Delete(FLastResponse->GetCount() - 1); + FFeatures->Assign(FLastResponse); + for (intptr_t Index = 0; Index < FFeatures->GetCount(); Index++) + { + // IIS 2003 indents response by 4 spaces, instead of one, + // see example in HandleReplyStatus + UnicodeString Feature = TrimLeft(FFeatures->GetString(Index)); + + UnicodeString Args = Feature; + UnicodeString Command = CutToChar(Args, L' ', true); + + // Serv-U lists Xalg commands like: + // XSHA1 filename;start;end + FSupportedCommands->Add(Command); + + if (SameText(Command, SiteCommand)) + { + // Serv-U lists all SITE commands in one line like: + // SITE PSWD;SET;ZONE;CHMOD;MSG;EXEC;HELP + // But ProFTPD lists them separately: + // SITE UTIME + // SITE RMDIR + // SITE COPY + // SITE MKDIR + // SITE SYMLINK + while (!Args.IsEmpty()) + { + UnicodeString Arg = CutToChar(Args, L';', true); + FSupportedSiteCommands->Add(Arg); + } + } + else if (SameText(Command, HashCommand)) + { + while (!Args.IsEmpty()) + { + UnicodeString Alg = CutToChar(Args, L';', true); + if ((Alg.Length() > 0) && (Alg[Alg.Length()] == L'*')) + { + Alg.Delete(Alg.Length(), 1); + } + // FTP HASH alg names follow IANA as we do, + // but using uppercase and we use lowercase + FHashAlgs->Add(LowerCase(Alg)); + FSupportsAnyChecksumFeature = true; + } + } + + if (FChecksumCommands->IndexOf(Command) >= 0) + { + FSupportsAnyChecksumFeature = true; + } + } + } +} + +bool TFTPFileSystem::HandleStatus(const wchar_t * AStatus, int Type) +{ + TLogLineType LogType = static_cast(-1); + UnicodeString Status(AStatus); + + switch (Type) + { + case TFileZillaIntf::LOG_STATUS: + FTerminal->Information(Status, true); + LogType = llMessage; + break; + + case TFileZillaIntf::LOG_COMMAND: + if (Status == L"SYST") + { + // not to trigger the assert in HandleReplyStatus, + // when SYST command is used by the user + FSystem.Clear(); + FLastCommand = SYST; + } + else if (Status == L"FEAT") + { + FLastCommand = FEAT; + } + else if (Status.SubString(1, 5) == L"PASS ") + { + FLastCommand = PASS; + } + else + { + FLastCommand = CMD_UNKNOWN; + } + LogType = llInput; + break; + + case TFileZillaIntf::LOG_ERROR: + case TFileZillaIntf::LOG_APIERROR: + case TFileZillaIntf::LOG_WARNING: + // when timeout message occurs, break loop waiting for response code + // by setting dummy one + if (Type == TFileZillaIntf::LOG_ERROR) + { + if (StartsStr(FTimeoutStatus, Status)) + { + if (NoFinalLastCode()) + { + SetLastCode(DummyTimeoutCode); + } + } + else if (Status == FDisconnectStatus) + { + if (NoFinalLastCode()) + { + SetLastCode(DummyDisconnectCode); + } + } + } + // there can be multiple error messages associated with single failure + // (such as "cannot open local file..." followed by "download failed") + FLastError->Add(Status); + LogType = llMessage; + break; + + case TFileZillaIntf::LOG_PROGRESS: + LogType = llMessage; + break; + + case TFileZillaIntf::LOG_REPLY: + HandleReplyStatus(AStatus); + LogType = llOutput; + break; + + case TFileZillaIntf::LOG_INFO: + LogType = llMessage; + break; + + case TFileZillaIntf::LOG_DEBUG: + LogType = llMessage; + break; + + default: + DebugFail(); + break; + } + + if (FTerminal->GetLog()->GetLogging() && (LogType != static_cast(-1))) + { + FTerminal->GetLog()->Add(LogType, Status); + } + + return true; +} + +TDateTime TFTPFileSystem::ConvertLocalTimestamp(time_t Time) +{ + // This reverses how FZAPI converts FILETIME to time_t, + // before passing it to FZ_ASYNCREQUEST_OVERWRITE. + int64_t Timestamp; + tm * Tm = gmtime(&Time); // localtime(&Time); + if (Tm != nullptr) + { + SYSTEMTIME SystemTime; + SystemTime.wYear = ToWord(Tm->tm_year + 1900); + SystemTime.wMonth = ToWord(Tm->tm_mon + 1); + SystemTime.wDayOfWeek = 0; + SystemTime.wDay = ToWord(Tm->tm_mday); + SystemTime.wHour = ToWord(Tm->tm_hour); + SystemTime.wMinute = ToWord(Tm->tm_min); + SystemTime.wSecond = ToWord(Tm->tm_sec); + SystemTime.wMilliseconds = 0; + + FILETIME LocalTime; + SystemTimeToFileTime(&SystemTime, &LocalTime); + FILETIME FileTime; + ::LocalFileTimeToFileTime(&LocalTime, &FileTime); + Timestamp = ::ConvertTimestampToUnixSafe(FileTime, dstmUnix); + } + else + { + // incorrect, but at least something + Timestamp = Time; + } + + return ::UnixToDateTime(Timestamp, dstmUnix); +} + +bool TFTPFileSystem::HandleAsynchRequestOverwrite( + wchar_t * FileName1, size_t FileName1Len, const wchar_t * FileName2, + const wchar_t * Path1, const wchar_t * Path2, + int64_t Size1, int64_t Size2, time_t LocalTime, + bool /*HasLocalTime*/, const TRemoteFileTime & RemoteTime, void * AUserData, + HANDLE & LocalFileHandle, + int & RequestResult) +{ + if (!FActive) + { + return false; + } + else + { + UnicodeString DestFullName = Path1; + ::AppendPathDelimiterW(DestFullName); + DestFullName += FileName1; + TFileTransferData & UserData = *(NB_STATIC_DOWNCAST(TFileTransferData, AUserData)); + if (UserData.OverwriteResult >= 0) + { + // on retry, use the same answer as on the first attempt + RequestResult = UserData.OverwriteResult; + } + else + { + TFileOperationProgressType * OperationProgress = FTerminal->GetOperationProgress(); + UnicodeString TargetFileName = FileName1; + DebugAssert(UserData.FileName == TargetFileName); + TOverwriteMode OverwriteMode = omOverwrite; + TOverwriteFileParams FileParams; + bool NoFileParams = + (Size1 < 0) || (LocalTime == 0) || + (Size2 < 0) || !RemoteTime.HasDate; + if (!NoFileParams) + { + FileParams.SourceSize = Size2; + FileParams.DestSize = Size1; + + if (OperationProgress->Side == osLocal) + { + FileParams.SourceTimestamp = ConvertLocalTimestamp(LocalTime); + RemoteFileTimeToDateTimeAndPrecision(RemoteTime, FileParams.DestTimestamp, FileParams.DestPrecision); + } + else + { + FileParams.DestTimestamp = ConvertLocalTimestamp(LocalTime); + RemoteFileTimeToDateTimeAndPrecision(RemoteTime, FileParams.SourceTimestamp, FileParams.SourcePrecision); + } + } + + UnicodeString SourceFullFileName = Path2; + if (OperationProgress->Side == osLocal) + { + SourceFullFileName = ::IncludeTrailingBackslash(SourceFullFileName); + } + else + { + SourceFullFileName = core::UnixIncludeTrailingBackslash(SourceFullFileName); + } + SourceFullFileName += FileName2; + + if (ConfirmOverwrite(SourceFullFileName, TargetFileName, UserData.Params, OperationProgress, + UserData.AutoResume && UserData.CopyParam->AllowResume(FileParams.SourceSize), + NoFileParams ? nullptr : &FileParams, UserData.CopyParam, OverwriteMode)) + { + switch (OverwriteMode) + { + case omOverwrite: + if ((OperationProgress->Side == osRemote) && !FTerminal->TerminalCreateLocalFile(DestFullName, OperationProgress, + false, true, + &LocalFileHandle)) + { + RequestResult = TFileZillaIntf::FILEEXISTS_SKIP; + break; + } + if (TargetFileName != FileName1) + { + wcsncpy_s(FileName1, FileName1Len, TargetFileName.c_str(), FileName1Len); + FileName1[FileName1Len - 1] = L'\0'; + UserData.FileName = FileName1; + RequestResult = TFileZillaIntf::FILEEXISTS_RENAME; + } + else + { + RequestResult = TFileZillaIntf::FILEEXISTS_OVERWRITE; + } + break; + + case omResume: + if ((OperationProgress->Side == osRemote) && !FTerminal->TerminalCreateLocalFile(DestFullName, OperationProgress, + true, true, + &LocalFileHandle)) + { + // ThrowSkipFileNull(); + RequestResult = TFileZillaIntf::FILEEXISTS_SKIP; + } + else + RequestResult = TFileZillaIntf::FILEEXISTS_RESUME; + break; + + case omComplete: + FTerminal->LogEvent("File transfer was completed before disconnect"); + RequestResult = TFileZillaIntf::FILEEXISTS_COMPLETE; + break; + + default: + DebugFail(); + RequestResult = TFileZillaIntf::FILEEXISTS_OVERWRITE; + break; + } + } + else + { + RequestResult = TFileZillaIntf::FILEEXISTS_SKIP; + } + } + + // remember the answer for the retries + UserData.OverwriteResult = RequestResult; + + if (RequestResult == TFileZillaIntf::FILEEXISTS_SKIP) + { + // when user chooses not to overwrite, break loop waiting for response code + // by setting dummy one, as FZAPI won't do anything then + SetLastCode(DummyTimeoutCode); + } + + return true; + } +} + +static UnicodeString FormatContactList(const UnicodeString & Entry1, const UnicodeString & Entry2) +{ + if (!Entry1.IsEmpty() && !Entry2.IsEmpty()) + { + return FORMAT(L"%s, %s", Entry1.c_str(), Entry2.c_str()); + } + else + { + return Entry1 + Entry2; + } +} + +UnicodeString FormatContact(const TFtpsCertificateData::TContact & Contact) +{ + UnicodeString Result = + FORMAT(LoadStrPart(VERIFY_CERT_CONTACT, 1).c_str(), + FormatContactList(FormatContactList(FormatContactList( + Contact.Organization, Contact.Unit).c_str(), Contact.CommonName).c_str(), Contact.Mail).c_str()); + + if ((wcslen(Contact.Country) > 0) || + (wcslen(Contact.StateProvince) > 0) || + (wcslen(Contact.Town) > 0)) + { + Result += + FORMAT(LoadStrPart(VERIFY_CERT_CONTACT, 2).c_str(), + FormatContactList(FormatContactList( + Contact.Country, Contact.StateProvince).c_str(), Contact.Town).c_str()); + } + + if (wcslen(Contact.Other) > 0) + { + Result += FORMAT(LoadStrPart(VERIFY_CERT_CONTACT, 3).c_str(), Contact.Other); + } + + return Result; +} + +UnicodeString FormatValidityTime(const TFtpsCertificateData::TValidityTime & ValidityTime) +{ + /* + return FormatDateTime(L"ddddd tt", + EncodeDateVerbose( + static_cast(ValidityTime.Year), static_cast(ValidityTime.Month), + static_cast(ValidityTime.Day)) + + EncodeTimeVerbose( + static_cast(ValidityTime.Hour), static_cast(ValidityTime.Min), + static_cast(ValidityTime.Sec), 0)); + */ + TODO("use Sysutils::FormatDateTime"); + uint16_t Y, M, D, H, Mm, S, MS; + TDateTime DateTime = + EncodeDateVerbose( + static_cast(ValidityTime.Year), static_cast(ValidityTime.Month), + static_cast(ValidityTime.Day)) + + EncodeTimeVerbose( + static_cast(ValidityTime.Hour), static_cast(ValidityTime.Min), + static_cast(ValidityTime.Sec), 0); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, Mm, S, MS); + UnicodeString dt = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d ", D, M, Y, H, Mm, S); + return dt; +} + +static bool VerifyNameMask(const UnicodeString & AName, const UnicodeString & AMask) +{ + bool Result = true; + UnicodeString Name = AName; + UnicodeString Mask = AMask; + intptr_t Pos = 0; + while (Result && (Pos = Mask.Pos(L"*")) > 0) + { + // Pos will typically be 1 here, so not actual comparison is done + Result = ::SameText(Mask.SubString(1, Pos - 1), Name.SubString(1, Pos - 1)); + if (Result) + { + Mask.Delete(1, Pos); // including * + Name.Delete(1, Pos - 1); + // remove everything until the next dot + Pos = Name.Pos(L"."); + if (Pos == 0) + { + Pos = Name.Length() + 1; + } + Name.Delete(1, Pos - 1); + } + } + + if (Result) + { + Result = ::SameText(Mask, Name); + } + + return Result; +} + +bool TFTPFileSystem::VerifyCertificateHostName(const TFtpsCertificateData & Data) +{ + UnicodeString HostName = FTerminal->GetSessionData()->GetHostNameExpanded(); + + UnicodeString CommonName = Data.Subject.CommonName; + bool NoMask = CommonName.IsEmpty(); + bool Result = !NoMask && VerifyNameMask(HostName, CommonName); + if (Result) + { + FTerminal->LogEvent(FORMAT(L"Certificate common name \"%s\" matches hostname", CommonName.c_str())); + } + else + { + if (!NoMask && (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1)) + { + FTerminal->LogEvent(FORMAT(L"Certificate common name \"%s\" does not match hostname", CommonName.c_str())); + } + UnicodeString SubjectAltName = Data.SubjectAltName; + while (!Result && !SubjectAltName.IsEmpty()) + { + UnicodeString Entry = CutToChar(SubjectAltName, L',', true); + UnicodeString EntryName = CutToChar(Entry, L':', true); + if (::SameText(EntryName, L"DNS")) + { + NoMask = false; + Result = VerifyNameMask(HostName, Entry); + if (Result) + { + FTerminal->LogEvent(FORMAT(L"Certificate subject alternative name \"%s\" matches hostname", Entry.c_str())); + } + else + { + if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1) + { + FTerminal->LogEvent(FORMAT(L"Certificate subject alternative name \"%s\" does not match hostname", Entry.c_str())); + } + } + } + } + } + if (!Result && NoMask) + { + FTerminal->LogEvent("Certificate has no common name nor subject alternative name, not verifying hostname"); + Result = true; + } + return Result; +} + +static bool IsIPAddress(const UnicodeString & HostName) +{ + bool IPv4 = true; + bool IPv6 = true; + bool AnyColon = false; + + for (intptr_t Index = 1; Index <= HostName.Length(); Index++) + { + wchar_t C = HostName[Index]; + if (!IsDigit(C) && (C != L'.')) + { + IPv4 = false; + } + if (!IsHex(C) && (C != L':')) + { + IPv6 = false; + } + if (C == L':') + { + AnyColon = true; + } + } + + return IPv4 || (IPv6 && AnyColon); +} + +bool TFTPFileSystem::HandleAsynchRequestVerifyCertificate( + const TFtpsCertificateData & Data, int & RequestResult) +{ + if (!FActive) + { + return false; + } + else + { + FSessionInfo.CertificateFingerprint = + BytesToHex(RawByteString(reinterpret_cast(Data.Hash), Data.HashLen), false, L':'); + + if (FTerminal->GetSessionData()->GetFingerprintScan()) + { + RequestResult = 0; + } + else + { + UnicodeString CertificateSubject = Data.Subject.Organization; + FTerminal->LogEvent(FORMAT(L"Verifying certificate for \"%s\" with fingerprint %s and %d failures", CertificateSubject.c_str(), FSessionInfo.CertificateFingerprint.c_str(), Data.VerificationResult)); + + bool Trusted = false; + bool TryWindowsSystemCertificateStore = false; + UnicodeString VerificationResultStr; + switch (Data.VerificationResult) + { + case X509_V_OK: + Trusted = true; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + VerificationResultStr = LoadStr(CERT_ERR_UNABLE_TO_GET_ISSUER_CERT); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + VerificationResultStr = LoadStr(CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE); + break; + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + VerificationResultStr = LoadStr(CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY); + break; + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + VerificationResultStr = LoadStr(CERT_ERR_CERT_SIGNATURE_FAILURE); + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + VerificationResultStr = LoadStr(CERT_ERR_CERT_NOT_YET_VALID); + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + VerificationResultStr = LoadStr(CERT_ERR_CERT_HAS_EXPIRED); + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + VerificationResultStr = LoadStr(CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD); + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + VerificationResultStr = LoadStr(CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD); + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + VerificationResultStr = LoadStr(CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + VerificationResultStr = LoadStr(CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + VerificationResultStr = LoadStr(CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + VerificationResultStr = LoadStr(CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_INVALID_CA: + VerificationResultStr = LoadStr(CERT_ERR_INVALID_CA); + break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + VerificationResultStr = LoadStr(CERT_ERR_PATH_LENGTH_EXCEEDED); + break; + case X509_V_ERR_INVALID_PURPOSE: + VerificationResultStr = LoadStr(CERT_ERR_INVALID_PURPOSE); + break; + case X509_V_ERR_CERT_UNTRUSTED: + VerificationResultStr = LoadStr(CERT_ERR_CERT_UNTRUSTED); + TryWindowsSystemCertificateStore = true; + break; + case X509_V_ERR_CERT_REJECTED: + VerificationResultStr = LoadStr(CERT_ERR_CERT_REJECTED); + break; + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + VerificationResultStr = LoadStr(CERT_ERR_KEYUSAGE_NO_CERTSIGN); + break; + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + VerificationResultStr = LoadStr(CERT_ERR_CERT_CHAIN_TOO_LONG); + break; + default: + VerificationResultStr = + FORMAT(L"%s (%s)", + LoadStr(CERT_ERR_UNKNOWN).c_str(), UnicodeString(X509_verify_cert_error_string(Data.VerificationResult)).c_str()); + break; + } + + bool IsHostNameIPAddress = IsIPAddress(FTerminal->GetSessionData()->GetHostNameExpanded()); + bool CertificateHostNameVerified = !IsHostNameIPAddress && VerifyCertificateHostName(Data); + + bool VerificationResult = Trusted; + + if (IsHostNameIPAddress || !CertificateHostNameVerified) + { + VerificationResult = false; + TryWindowsSystemCertificateStore = false; + } + + UnicodeString SiteKey = FTerminal->GetSessionData()->GetSiteKey(); + + if (!VerificationResult) + { + if (FTerminal->VerifyCertificate(FtpsCertificateStorageKey, SiteKey, + FSessionInfo.CertificateFingerprint, CertificateSubject, Data.VerificationResult)) + { + // certificate is trusted, but for not purposes of info dialog + VerificationResult = true; + } + } + + // TryWindowsSystemCertificateStore is set for the same set of failures + // as trigger NE_SSL_UNTRUSTED flag in ne_openssl.c's verify_callback(). + // Use WindowsValidateCertificate only as a last resort (after checking the cached fiungerprint) + // as it can take a very long time (up to 1 minute). + if (!VerificationResult && TryWindowsSystemCertificateStore) + { + if (WindowsValidateCertificate(Data.Certificate, Data.CertificateLen)) + { + FTerminal->LogEvent("Certificate verified against Windows certificate store"); + VerificationResult = true; + // certificate is trusted for all purposes + Trusted = true; + } + } + + const UnicodeString SummarySeparator = L"\n\n"; + UnicodeString Summary; + // even if the fingerprint is cached, the certificate is still not trusted for a purposes of the info dialog. + if (!Trusted) + { + AddToList(Summary, VerificationResultStr + L" " + FMTLOAD(CERT_ERRDEPTH, (Data.VerificationDepth + 1)), SummarySeparator); + } + + if (IsHostNameIPAddress) + { + AddToList(Summary, FMTLOAD(CERT_IP_CANNOT_VERIFY, FTerminal->GetSessionData()->GetHostNameExpanded().c_str()), SummarySeparator); + } + else if (!CertificateHostNameVerified) + { + AddToList(Summary, FMTLOAD(CERT_NAME_MISMATCH, FTerminal->GetSessionData()->GetHostNameExpanded().c_str()), SummarySeparator); + } + + if (Summary.IsEmpty()) + { + Summary = LoadStr(CERT_OK); + } + + FSessionInfo.Certificate = + FMTLOAD(CERT_TEXT, + FormatContact(Data.Issuer).c_str(), + FormatContact(Data.Subject).c_str(), + FormatValidityTime(Data.ValidFrom).c_str(), + FormatValidityTime(Data.ValidUntil).c_str(), + FSessionInfo.CertificateFingerprint.c_str(), + Summary.c_str()); + + RequestResult = VerificationResult ? 1 : 0; + + if (RequestResult == 0) + { + TClipboardHandler ClipboardHandler; + ClipboardHandler.Text = FSessionInfo.CertificateFingerprint; + + TQueryButtonAlias Aliases[1]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON); + Aliases[0].OnClick = MAKE_CALLBACK(TClipboardHandler::Copy, &ClipboardHandler); + + TQueryParams Params(qpWaitInBatch); + Params.HelpKeyword = HELP_VERIFY_CERTIFICATE; + Params.NoBatchAnswers = qaYes | qaRetry; + Params.Aliases = Aliases; + Params.AliasesCount = _countof(Aliases); + uintptr_t Answer = FTerminal->QueryUser( + FMTLOAD(VERIFY_CERT_PROMPT3, FSessionInfo.Certificate.c_str()), + nullptr, qaYes | qaNo | qaCancel | qaRetry, &Params, qtWarning); + + switch (Answer) + { + case qaYes: + // 2 = always, as used by FZ's VerifyCertDlg.cpp, + // however FZAPI takes all non-zero values equally + RequestResult = 2; + break; + + case qaNo: + RequestResult = 1; + break; + + case qaCancel: + // FTerminal->Configuration->Usage->Inc(L"HostNotVerified"); + RequestResult = 0; + break; + + default: + DebugFail(); + RequestResult = 0; + break; + } + + if (RequestResult == 2) + { + FTerminal->CacheCertificate( + FtpsCertificateStorageKey, SiteKey, + FSessionInfo.CertificateFingerprint, Data.VerificationResult); + } + } + + // Cache only if the certificate was accepted manually + if (!VerificationResult && (RequestResult != 0)) + { + FTerminal->GetConfiguration()->RememberLastFingerprint( + FTerminal->GetSessionData()->GetSiteKey(), TlsFingerprintType, FSessionInfo.CertificateFingerprint); + } + } + + return true; + } +} + +bool TFTPFileSystem::HandleAsynchRequestNeedPass( + struct TNeedPassRequestData & Data, int & RequestResult) const +{ + if (!FActive) + { + return false; + } + else + { + UnicodeString Password; + if (FCertificate != nullptr) + { + FTerminal->LogEvent(L"Server asked for password, but we are using certificate, and no password was specified upfront, using fake password"); + Password = L"USINGCERT"; + RequestResult = TFileZillaIntf::REPLY_OK; + } + else + { + if (!FPasswordFailed && FTerminal->GetSessionData()->GetLoginType() == ltAnonymous) + { + RequestResult = TFileZillaIntf::REPLY_OK; + } + else if (FTerminal->PromptUser(FTerminal->GetSessionData(), pkPassword, LoadStr(PASSWORD_TITLE), L"", + LoadStr(PASSWORD_PROMPT), false, 0, Password)) + { + RequestResult = TFileZillaIntf::REPLY_OK; + } + else + { + RequestResult = TFileZillaIntf::REPLY_ABORTED; + } + } + + Data.Password = nullptr; + // When returning REPLY_OK, we need to return an allocated password, + // even if we were returning and empty string we got on input. + if (RequestResult == TFileZillaIntf::REPLY_OK) + { + Data.Password = _wcsdup(Password.c_str()); + } + + return true; + } +} + +void TFTPFileSystem::RemoteFileTimeToDateTimeAndPrecision(const TRemoteFileTime & Source, TDateTime & DateTime, TModificationFmt & ModificationFmt) +{ + // ModificationFmt must be set after Modification + if (Source.HasDate) + { + DateTime = + EncodeDateVerbose(Source.Year, Source.Month, Source.Day); + if (Source.HasTime) + { + DateTime = DateTime + + EncodeTimeVerbose(Source.Hour, Source.Minute, Source.Second, 0); + // not exact as we got year as well, but it is most probably + // guessed by FZAPI anyway + ModificationFmt = Source.HasSeconds ? mfFull : mfMDHM; + + // With IIS, the Utc should be false only for MDTM + if (FWindowsServer && !Source.Utc) + { + DateTime -= DSTDifferenceForTime(DateTime); + } + } + else + { + ModificationFmt = mfMDY; + } + + if (Source.Utc) + { + DateTime = ::ConvertTimestampFromUTC(DateTime); + } + + } + else + { + // With SCP we estimate date to be today, if we have at least time + + DateTime = double(0.0); + ModificationFmt = mfNone; + } +} + +bool TFTPFileSystem::HandleListData(const wchar_t * Path, + const TListDataEntry * Entries, uintptr_t Count) +{ + if (!FActive) + { + return false; + } + else if (FIgnoreFileList) + { + // directory listing provided implicitly by FZAPI during certain operations is ignored + DebugAssert(FFileList == nullptr); + return false; + } + else + { + DebugAssert(FFileList != nullptr); + // This can actually fail in real life, + // when connected to server with case insensitive paths + // Is empty when called from DoReadFile + UnicodeString AbsPath = GetAbsolutePath(FFileList->GetDirectory(), false); + DebugAssert(FFileList->GetDirectory().IsEmpty() || core::UnixSamePath(AbsPath, Path)); + DebugUsedParam(Path); + + for (uintptr_t Index = 0; Index < Count; ++Index) + { + const TListDataEntry * Entry = &Entries[Index]; + std::unique_ptr File(new TRemoteFile()); + try + { + File->SetTerminal(FTerminal); + + File->SetFileName(Entry->Name); + try + { + intptr_t PermissionsLen = wcslen(Entry->Permissions); + if (PermissionsLen >= 10) + { + File->GetRights()->SetText(Entry->Permissions + 1); + } + else if ((PermissionsLen == 3) || (PermissionsLen == 4)) + { + File->GetRights()->SetOctal(Entry->Permissions); + } + } + catch (...) + { + // ignore permissions errors with FTP + } + + File->SetHumanRights(Entry->HumanPerm); + + const wchar_t * Space = wcschr(Entry->OwnerGroup, L' '); + if (Space != nullptr) + { + File->GetFileOwner().SetName(UnicodeString(Entry->OwnerGroup, Space - Entry->OwnerGroup)); + File->GetFileGroup().SetName(Space + 1); + } + else + { + File->GetFileOwner().SetName(Entry->OwnerGroup); + } + + File->SetSize(Entry->Size); + + if (Entry->Link) + { + File->SetType(FILETYPE_SYMLINK); + } + else if (Entry->Dir) + { + File->SetType(FILETYPE_DIRECTORY); + } + else + { + File->SetType(FILETYPE_DEFAULT); + } + + TDateTime Modification; + TModificationFmt ModificationFmt; + RemoteFileTimeToDateTimeAndPrecision(Entry->Time, Modification, ModificationFmt); + File->SetModification(Modification); + File->SetModificationFmt(ModificationFmt); + File->SetLastAccess(File->GetModification()); + + File->SetLinkTo(Entry->LinkTarget); + + File->Complete(); + } + catch (Exception & E) + { +// delete File; + UnicodeString EntryData = + FORMAT(L"%s/%s/%s/%s/%s/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d", + Entry->Name, Entry->Permissions, Entry->HumanPerm, Entry->OwnerGroup, ::Int64ToStr(Entry->Size).c_str(), + int(Entry->Dir), int(Entry->Link), Entry->Time.Year, Entry->Time.Month, Entry->Time.Day, + Entry->Time.Hour, Entry->Time.Minute, int(Entry->Time.HasTime), + int(Entry->Time.HasSeconds), int(Entry->Time.HasDate)); + throw ETerminal(&E, FMTLOAD(LIST_LINE_ERROR, EntryData.c_str()), HELP_LIST_LINE_ERROR); + } + + FFileList->AddFile(File.release()); + } + return true; + } +} + +bool TFTPFileSystem::HandleTransferStatus(bool Valid, int64_t TransferSize, + int64_t Bytes, bool FileTransfer) +{ + if (!FActive) + { + return false; + } + else if (!Valid) + { + } + else if (FileTransfer) + { + FileTransferProgress(TransferSize, Bytes); + } + else + { + ReadDirectoryProgress(Bytes); + } + return true; +} + +bool TFTPFileSystem::HandleReply(intptr_t Command, uintptr_t Reply) +{ + if (!FActive) + { + return false; + } + else + { + if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1) + { + FTerminal->LogEvent(FORMAT(L"Got reply %x to the command %d", static_cast(Reply), Command)); + } + + // reply with Command 0 is not associated with current operation + // so do not treat is as a reply + // (it is typically used asynchronously to notify about disconnects) + if (Command != 0) + { + DebugAssert(FCommandReply == 0); + FCommandReply = Reply; + } + else + { + DebugAssert(FReply == 0); + FReply = Reply; + } + return true; + } +} + +bool TFTPFileSystem::HandleCapabilities( + TFTPServerCapabilities * ServerCapabilities) +{ + FServerCapabilities->Assign(ServerCapabilities); + FFileSystemInfoValid = false; + return true; +} + +bool TFTPFileSystem::CheckError(intptr_t ReturnCode, const wchar_t * Context) +{ + // we do not expect any FZAPI call to fail as it generally can fail only due to: + // - invalid parameters + // - busy FZAPI core + // the only exception is REPLY_NOTCONNECTED that can happen if + // connection is closed just between the last call to Idle() + // and call to any FZAPI command + // in such case reply without associated command is posted, + // which we are going to wait for unless we are already waiting + // on higher level (this typically happens if connection is lost while + // waiting for user interaction and is detected within call to + // SetAsyncRequestResult) + if (FLAGSET(ReturnCode, TFileZillaIntf::REPLY_NOTCONNECTED)) + { + if (!FWaitingForReply) + { + // throws + WaitForFatalNonCommandReply(); + } + } + else + { + FTerminal->FatalError(nullptr, + FMTLOAD(INTERNAL_ERROR, FORMAT(L"fz#%s", Context).c_str(), ::IntToHex(ReturnCode, 4).c_str())); + DebugFail(); + } + + return false; +} + +bool TFTPFileSystem::Unquote(UnicodeString & Str) +{ + enum + { + STATE_INIT, + STATE_QUOTE, + STATE_QUOTED, + STATE_DONE + } State; + + State = STATE_INIT; + DebugAssert((Str.Length() > 0) && ((Str[1] == L'"') || (Str[1] == L'\''))); + + intptr_t Index = 1; + wchar_t Quote = 0; + while (Index <= Str.Length()) + { + switch (State) + { + case STATE_INIT: + if ((Str[Index] == L'"') || (Str[Index] == L'\'')) + { + Quote = Str[Index]; + State = STATE_QUOTED; + Str.Delete(Index, 1); + } + else + { + DebugFail(); + // no quoted string + Str.SetLength(0); + } + break; + + case STATE_QUOTED: + if (Str[Index] == Quote) + { + State = STATE_QUOTE; + Str.Delete(Index, 1); + } + else + { + ++Index; + } + break; + + case STATE_QUOTE: + if (Str[Index] == Quote) + { + ++Index; + } + else + { + // end of quoted string, trim the rest + Str.SetLength(Index - 1); + State = STATE_DONE; + } + break; + } + } + + return (State == STATE_DONE); +} + +void TFTPFileSystem::PreserveDownloadFileTime(HANDLE AHandle, void * UserData) +{ + TFileTransferData * Data = NB_STATIC_DOWNCAST(TFileTransferData, UserData); + FILETIME WrTime = ::DateTimeToFileTime(Data->Modification, dstmUnix); + SetFileTime(AHandle, nullptr, nullptr, &WrTime); +} + +bool TFTPFileSystem::GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time) +{ + bool Result; + try + { + // error-handling-free and DST-mode-unaware copy of TTerminal::OpenLocalFile + HANDLE LocalFileHandle = ::CreateFile(ApiPath(FileName).c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, 0); + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + Result = false; + } + else + { + FILETIME MTime; + if (!GetFileTime(LocalFileHandle, nullptr, nullptr, &MTime)) + { + Result = false; + } + else + { + TDateTime Modification = ::ConvertTimestampToUTC(::FileTimeToDateTime(MTime)); + + uint16_t Year; + uint16_t Month; + uint16_t Day; + Modification.DecodeDate(Year, Month, Day); + Time.tm_year = Year - 1900; + Time.tm_mon = Month - 1; + Time.tm_mday = Day; + + uint16_t Hour; + uint16_t Min; + uint16_t Sec; + uint16_t MSec; + Modification.DecodeTime(Hour, Min, Sec, MSec); + Time.tm_hour = Hour; + Time.tm_min = Min; + Time.tm_sec = Sec; + + Result = true; + } + + ::CloseHandle(LocalFileHandle); + } + } + catch (...) + { + Result = false; + } + return Result; +} + +void TFTPFileSystem::RegisterChecksumAlgCommand(const UnicodeString & Alg, const UnicodeString & Command) +{ + FChecksumAlgs->Add(Alg); + FChecksumCommands->Add(Command); +} + +void TFTPFileSystem::GetSupportedChecksumAlgs(TStrings * Algs) +{ + for (intptr_t Index = 0; Index < FHashAlgs->GetCount(); Index++) + { + Algs->Add(FHashAlgs->GetString(Index)); + } + + for (intptr_t Index = 0; Index < FChecksumAlgs->GetCount(); Index++) + { + UnicodeString Alg = FChecksumAlgs->GetString(Index); + UnicodeString Command = FChecksumCommands->GetString(Index); + + if (SupportsCommand(Command) && (Algs->IndexOf(Alg) < 0)) + { + Algs->Add(Alg); + } + } +} + +bool TFTPFileSystem::SupportsSiteCommand(const UnicodeString & Command) const +{ + return (FSupportedSiteCommands->IndexOf(Command) >= 0); +} + +bool TFTPFileSystem::SupportsCommand(const UnicodeString & Command) const +{ + return (FSupportedCommands->IndexOf(Command) >= 0); +} + +void TFTPFileSystem::LockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TFTPFileSystem::UnlockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TFTPFileSystem::UpdateFromMain(TCustomFileSystem * /*MainFileSystem*/) +{ + // noop +} + +UnicodeString GetOpenSSLVersionText() +{ + return OPENSSL_VERSION_TEXT; +} + +#endif // NO_FILEZILLA diff --git a/netbox/src/core/FtpFileSystem.h b/netbox/src/core/FtpFileSystem.h new file mode 100644 index 000000000..e3bd9a1f9 --- /dev/null +++ b/netbox/src/core/FtpFileSystem.h @@ -0,0 +1,324 @@ +#pragma once + +#ifndef NO_FILEZILLA + +#include +#include +#include +#include + +class TFileZillaIntf; +class TFileZillaImpl; +class TFTPServerCapabilities; +struct TOverwriteFileParams; +struct TListDataEntry; +struct TFileTransferData; +struct TFtpsCertificateData; +struct TRemoteFileTime; + +struct message_t +{ + message_t() : wparam(0), lparam(0) + {} + message_t(WPARAM w, LPARAM l) : wparam(w), lparam(l) + {} + WPARAM wparam; + LPARAM lparam; +}; + +class TMessageQueue : public TObject, public std::vector +{ +public: + typedef message_t value_type; +}; + +class TFTPFileSystem : public TCustomFileSystem +{ +friend class TFileZillaImpl; +friend class TFTPFileListHelper; +NB_DISABLE_COPY(TFTPFileSystem) +public: + explicit TFTPFileSystem(TTerminal * ATerminal); + virtual ~TFTPFileSystem(); + + virtual void Init(void *); + + virtual void Open(); + virtual void Close(); + virtual bool GetActive() const; + virtual void CollectUsage(); + virtual void Idle(); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const; + virtual void AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent); + virtual void ChangeDirectory(const UnicodeString & Directory); + virtual void CachedChangeDirectory(const UnicodeString & Directory); + virtual void AnnounceFileListOperation(); + virtual void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action); + virtual bool LoadFilesProperties(TStrings * AFileList); + virtual void CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum); + virtual void CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void RemoteCreateDirectory(const UnicodeString & ADirName); + virtual void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic); + virtual void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action); + virtual void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent); + virtual void DoStartup(); + virtual void HomeDirectory(); + virtual bool IsCapable(intptr_t Capability) const; + virtual void LookupUsersGroups(); + virtual void ReadCurrentDirectory(); + virtual void ReadDirectory(TRemoteFileList * FileList); + virtual void ReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile); + virtual void ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& AFile); + virtual void RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual void RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual TStrings * GetFixedPaths(); + virtual void SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable); + virtual const TSessionInfo & GetSessionInfo() const; + virtual const TFileSystemInfo & GetFileSystemInfo(bool Retrieve); + virtual bool TemporaryTransferFile(const UnicodeString & AFileName); + virtual bool GetStoredCredentialsTried() const; + virtual UnicodeString FSGetUserName() const; + virtual void GetSupportedChecksumAlgs(TStrings * Algs); + virtual void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UpdateFromMain(TCustomFileSystem * MainFileSystem); + +protected: + // enum TOverwriteMode { omOverwrite, omResume, omComplete }; + + virtual UnicodeString GetCurrDirectory() const; + + const wchar_t * GetOption(intptr_t OptionID) const; + intptr_t GetOptionVal(intptr_t OptionID) const; + + enum + { + REPLY_CONNECT = 0x01, + REPLY_2XX_CODE = 0x02, + REPLY_ALLOW_CANCEL = 0x04, + REPLY_3XX_CODE = 0x08, + REPLY_SINGLE_LINE = 0x10, + }; + + bool FTPPostMessage(uintptr_t Type, WPARAM wParam, LPARAM lParam); + bool ProcessMessage(); + void DiscardMessages(); + void WaitForMessages(); + uintptr_t WaitForReply(bool Command, bool WantLastCode); + uintptr_t WaitForCommandReply(bool WantLastCode = true); + void WaitForFatalNonCommandReply(); + void PoolForFatalNonCommandReply(); + void GotNonCommandReply(uintptr_t Reply); + UnicodeString GotReply(uintptr_t Reply, uintptr_t Flags = 0, + const UnicodeString & Error = L"", uintptr_t * Code = nullptr, + TStrings ** Response = nullptr); + void ResetReply(); + void HandleReplyStatus(const UnicodeString & Response); + void DoWaitForReply(uintptr_t &ReplyToAwait, bool WantLastCode); + bool KeepWaitingForReply(uintptr_t &ReplyToAwait, bool WantLastCode) const; + inline bool NoFinalLastCode() const; + + bool HandleStatus(const wchar_t * Status, int Type); + bool HandleAsynchRequestOverwrite( + wchar_t * FileName1, size_t FileName1Len, const wchar_t * FileName2, + const wchar_t * Path1, const wchar_t * Path2, + int64_t Size1, int64_t Size2, time_t LocalTime, + bool HasLocalTime, const TRemoteFileTime & RemoteTime, void * UserData, + HANDLE & LocalFileHandle, + int & RequestResult); + bool HandleAsynchRequestVerifyCertificate( + const TFtpsCertificateData & Data, int & RequestResult); + bool HandleAsynchRequestNeedPass( + struct TNeedPassRequestData & Data, int & RequestResult) const; + bool HandleListData(const wchar_t * Path, const TListDataEntry * Entries, + uintptr_t Count); + bool HandleTransferStatus(bool Valid, int64_t TransferSize, + int64_t Bytes, bool FileTransfer); + bool HandleReply(intptr_t Command, uintptr_t Reply); + bool HandleCapabilities(TFTPServerCapabilities * ServerCapabilities); + bool CheckError(intptr_t ReturnCode, const wchar_t * Context); + void PreserveDownloadFileTime(HANDLE AHandle, void * UserData); + bool GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time); + void EnsureLocation(); + UnicodeString ActualCurrentDirectory(); + void Discard(); + void DoChangeDirectory(const UnicodeString & Directory); + + void Sink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t AParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action); + void SinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void SinkFile(const UnicodeString & AFileName, const TRemoteFile * AFile, void * Param); + void SourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void Source(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TOpenRemoteFileParams * OpenParams, + TOverwriteFileParams * FileParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action); + void DirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, intptr_t Attrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags); + bool ConfirmOverwrite(const UnicodeString & ASourceFullFileName, UnicodeString & ATargetFileName, + intptr_t Params, TFileOperationProgressType * OperationProgress, + bool AutoResume, + const TOverwriteFileParams * FileParams, + const TCopyParamType * CopyParam, + OUT TOverwriteMode & OverwriteMode); + void ReadDirectoryProgress(int64_t Bytes); + void ResetFileTransfer(); + virtual void DoFileTransferProgress(int64_t TransferSize, int64_t Bytes); + virtual void FileTransferProgress(int64_t TransferSize, int64_t Bytes); + void ResetCaches(); + void CaptureOutput(const UnicodeString & Str); + void DoReadDirectory(TRemoteFileList * FileList); + void DoReadFile(const UnicodeString & AFileName, TRemoteFile *& AFile); + void FileTransfer(const UnicodeString & AFileName, const UnicodeString & LocalFile, + const UnicodeString & RemoteFile, const UnicodeString & RemotePath, bool Get, + int64_t Size, intptr_t Type, TFileTransferData & UserData, + TFileOperationProgressType * OperationProgress); + TDateTime ConvertLocalTimestamp(time_t Time); + void RemoteFileTimeToDateTimeAndPrecision(const TRemoteFileTime & Source, + TDateTime & DateTime, TModificationFmt & ModificationFmt); + void SetLastCode(intptr_t Code); + void StoreLastResponse(const UnicodeString & Text); + void SetCPSLimit(TFileOperationProgressType * OperationProgress); + bool VerifyCertificateHostName(const TFtpsCertificateData & Data); + bool SupportsReadingFile() const; + void AutoDetectTimeDifference(TRemoteFileList * FileList); + void ApplyTimeDifference(TRemoteFile * File); + void ApplyTimeDifference( + const UnicodeString & FileName, TDateTime & Modification, TModificationFmt & ModificationFmt); + bool LookupUploadModificationTime( + const UnicodeString & FileName, TDateTime & Modification, TModificationFmt ModificationFmt); + UnicodeString DoCalculateFileChecksum(bool UsingHashCommand, const UnicodeString & Alg, TRemoteFile * File); + void DoCalculateFilesChecksum(bool UsingHashCommand, const UnicodeString & Alg, + TStrings * FileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum, + TFileOperationProgressType * OperationProgress, bool FirstLevel); + void HandleFeatReply(); + void ResetFeatures(); + bool SupportsSiteCommand(const UnicodeString & Command) const; + bool SupportsCommand(const UnicodeString & Command) const; + void RegisterChecksumAlgCommand(const UnicodeString & Alg, const UnicodeString & Command); + void SendCommand(const UnicodeString & Command); + bool CanTransferSkipList(intptr_t Params, uintptr_t Flags, const TCopyParamType * CopyParam) const; + + static bool Unquote(UnicodeString & Str); + +private: + enum TCommand + { + CMD_UNKNOWN, + PASS, + SYST, + FEAT + }; + + TFileZillaIntf * FFileZillaIntf; + TCriticalSection FQueueCriticalSection; + TCriticalSection FTransferStatusCriticalSection; + TMessageQueue FQueue; + HANDLE FQueueEvent; + TSessionInfo FSessionInfo; + TFileSystemInfo FFileSystemInfo; + bool FFileSystemInfoValid; + uintptr_t FReply; + uintptr_t FCommandReply; + TCommand FLastCommand; + bool FPasswordFailed; + bool FStoredPasswordTried; + bool FMultineResponse; + intptr_t FLastCode; + intptr_t FLastCodeClass; + intptr_t FLastReadDirectoryProgress; + UnicodeString FTimeoutStatus; + UnicodeString FDisconnectStatus; + TStrings * FLastResponse; + TStrings * FLastErrorResponse; + TStrings * FLastError; + UnicodeString FSystem; + TStrings * FFeatures; + UnicodeString FCurrentDirectory; + bool FReadCurrentDirectory; + UnicodeString FHomeDirectory; + TRemoteFileList * FFileList; + TRemoteFileList * FFileListCache; + UnicodeString FFileListCachePath; + UnicodeString FWelcomeMessage; + bool FActive; + bool FOpening; + bool FWaitingForReply; + enum + { + ftaNone, + ftaSkip, + ftaCancel, + } FFileTransferAbort; + bool FIgnoreFileList; + bool FFileTransferCancelled; + int64_t FFileTransferResumed; + bool FFileTransferPreserveTime; + bool FFileTransferRemoveBOM; + bool FFileTransferNoList; + uintptr_t FFileTransferCPSLimit; + bool FAwaitingProgress; + TCaptureOutputEvent FOnCaptureOutput; + UnicodeString FUserName; + TAutoSwitch FListAll; + bool FDoListAll; + TFTPServerCapabilities * FServerCapabilities; + TDateTime FLastDataSent; + bool FDetectTimeDifference; + int64_t FTimeDifference; + std::unique_ptr FChecksumAlgs; + std::unique_ptr FChecksumCommands; + std::unique_ptr FSupportedCommands; + std::unique_ptr FSupportedSiteCommands; + std::unique_ptr FHashAlgs; + typedef std::map TUploadedTimes; + TUploadedTimes FUploadedTimes; + bool FSupportsAnyChecksumFeature; + UnicodeString FLastCommandSent; + X509 * FCertificate; + EVP_PKEY * FPrivateKey; + bool FTransferActiveImmediately; + bool FWindowsServer; + int64_t FBytesAvailable; + bool FBytesAvailableSupported; + mutable UnicodeString FOptionScratch; +}; + +UnicodeString GetOpenSSLVersionText(); + +#endif // NO_FILEZILLA + diff --git a/netbox/src/core/HierarchicalStorage.cpp b/netbox/src/core/HierarchicalStorage.cpp new file mode 100644 index 000000000..4d5220415 --- /dev/null +++ b/netbox/src/core/HierarchicalStorage.cpp @@ -0,0 +1,1547 @@ +#include +#pragma hdrstop + +#include +#include +#include +#include +#include + +#include "PuttyIntf.h" +#include "HierarchicalStorage.h" + +#define READ_REGISTRY(Method) \ + if (FRegistry->ValueExists(Name)) \ + try { return FRegistry->Method(Name); } catch (...) { FFailed++; return Default; } \ + else return Default; +#define WRITE_REGISTRY(Method) \ + try { FRegistry->Method(Name, Value); } catch (...) { FFailed++; } + +UnicodeString MungeStr(const UnicodeString & Str, bool ForceAnsi) +{ + RawByteString Source; + if (ForceAnsi) + { + Source = RawByteString(AnsiString(Str)); + } + else + { + Source = RawByteString(UTF8String(Str)); + if (Source.Length() > Str.Length()) + { + Source.Insert(CONST_BOM, 1); + } + } + // should contain ASCII characters only + RawByteString Dest; + Dest.SetLength(Source.Length() * 3 + 1); + putty_mungestr(Source.c_str(), const_cast(Dest.c_str())); + PackStr(Dest); + return UnicodeString(Dest.c_str(), Dest.Length()); +} + +UnicodeString UnMungeStr(const UnicodeString & Str) +{ + // Str should contain ASCII characters only + RawByteString Source = Str; + RawByteString Dest; + Dest.SetLength(Source.Length() + 1); + putty_unmungestr(Source.c_str(), const_cast(Dest.c_str()), static_cast(Dest.Length())); + // Cut the string at null character + PackStr(Dest); + UnicodeString Result; + const std::string Bom(CONST_BOM); + if (Dest.Pos(Bom.c_str()) == 1) + { + Dest.Delete(1, Bom.size()); + Result = UTF8ToString(Dest); + } + else + { + Result = AnsiToString(Dest); + } + return Result; +} + +UnicodeString PuttyMungeStr(const UnicodeString & Str) +{ + return MungeStr(Str, false); +} + +UnicodeString PuttyUnMungeStr(const UnicodeString & Str) +{ + return UnMungeStr(Str); +} + +UnicodeString MungeIniName(const UnicodeString & Str) +{ + intptr_t P = Str.Pos(L"="); + // make this fast for now + if (P > 0) + { + return ReplaceStr(Str, L"=", L"%3D"); + } + else + { + return Str; + } +} + +UnicodeString UnMungeIniName(const UnicodeString & Str) +{ + intptr_t P = Str.Pos(L"%3D"); + // make this fast for now + if (P > 0) + { + return ReplaceStr(Str, L"%3D", L"="); + } + else + { + return Str; + } +} + +THierarchicalStorage::THierarchicalStorage(const UnicodeString & AStorage) : + FStorage(AStorage), + FKeyHistory(new TStringList()) +{ + SetAccessMode(smRead); + SetExplicit(false); + // While this was implemented in 5.0 already, for some reason + // it was disabled (by mistake?). So although enabled for 5.6.1 only, + // data written in Unicode/UTF8 can be read by all versions back to 5.0. + SetForceAnsi(false); + SetMungeStringValues(true); +} + +THierarchicalStorage::~THierarchicalStorage() +{ + SAFE_DESTROY(FKeyHistory); +} + +void THierarchicalStorage::Flush() +{ +} + +void THierarchicalStorage::SetAccessMode(TStorageAccessMode Value) +{ + FAccessMode = Value; +} + +UnicodeString THierarchicalStorage::GetCurrentSubKeyMunged() const +{ + if (FKeyHistory->GetCount()) + { + return FKeyHistory->GetString(FKeyHistory->GetCount() - 1); + } + else + { + return L""; + } +} + +UnicodeString THierarchicalStorage::GetCurrentSubKey() const +{ + return UnMungeStr(GetCurrentSubKeyMunged()); +} + +bool THierarchicalStorage::OpenRootKey(bool CanCreate) +{ + return OpenSubKey(L"", CanCreate); +} + +UnicodeString THierarchicalStorage::MungeKeyName(const UnicodeString & Key) +{ + UnicodeString Result = MungeStr(Key, GetForceAnsi()); + // if there's already ANSI-munged subkey, keep ANSI munging + if ((Result != Key) && !GetForceAnsi() && DoKeyExists(Key, true)) + { + Result = MungeStr(Key, true); + } + return Result; +} + +bool THierarchicalStorage::OpenSubKey(const UnicodeString & ASubKey, bool CanCreate, bool Path) +{ + bool Result; + UnicodeString MungedKey; + UnicodeString Key = ASubKey; + if (Path) + { + DebugAssert(Key.IsEmpty() || (Key[Key.Length()] != L'\\')); + Result = true; + while (!Key.IsEmpty() && Result) + { + if (!MungedKey.IsEmpty()) + { + MungedKey += L'\\'; + } + MungedKey += MungeKeyName(CutToChar(Key, L'\\', false)); + Result = DoOpenSubKey(MungedKey, CanCreate); + } + + // hack to restore last opened key for registry storage + if (!Result) + { + FKeyHistory->Add(::IncludeTrailingBackslash(GetCurrentSubKey() + MungedKey)); + CloseSubKey(); + } + } + else + { + MungedKey = MungeKeyName(Key); + Result = DoOpenSubKey(MungedKey, CanCreate); + } + + if (Result) + { + FKeyHistory->Add(::IncludeTrailingBackslash(GetCurrentSubKey() + MungedKey)); + } + + return Result; +} + +void THierarchicalStorage::CloseSubKey() +{ + if (FKeyHistory->GetCount() == 0) + throw Exception(L""); + else + FKeyHistory->Delete(FKeyHistory->GetCount() - 1); +} + +void THierarchicalStorage::CloseAll() +{ + while (!GetCurrentSubKey().IsEmpty()) + { + CloseSubKey(); + } +} + +void THierarchicalStorage::ClearSubKeys() +{ + std::unique_ptr SubKeys(new TStringList()); + try__finally + { + GetSubKeyNames(SubKeys.get()); + for (intptr_t Index = 0; Index < SubKeys->GetCount(); ++Index) + { + RecursiveDeleteSubKey(SubKeys->GetString(Index)); + } + } + __finally + { +// delete SubKeys; + }; +} + +void THierarchicalStorage::RecursiveDeleteSubKey(const UnicodeString & Key) +{ + if (OpenSubKey(Key, false)) + { + ClearSubKeys(); + CloseSubKey(); + } + DeleteSubKey(Key); +} + +bool THierarchicalStorage::HasSubKeys() +{ + bool Result = false; + std::unique_ptr SubKeys(new TStringList()); + try__finally + { + GetSubKeyNames(SubKeys.get()); + Result = SubKeys->GetCount() > 0; + } + __finally + { +// delete SubKeys; + }; + return Result; +} + +bool THierarchicalStorage::HasSubKey(const UnicodeString & SubKey) +{ + bool Result = OpenSubKey(SubKey, false); + if (Result) + { + CloseSubKey(); + } + return Result; +} + +bool THierarchicalStorage::KeyExists(const UnicodeString & SubKey) +{ + return DoKeyExists(SubKey, GetForceAnsi()); +} + +void THierarchicalStorage::ReadValues(TStrings * Strings, + bool MaintainKeys) +{ + std::unique_ptr Names(new TStringList()); + try__finally + { + GetValueNames(Names.get()); + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + if (MaintainKeys) + { + Strings->Add(FORMAT(L"%s=%s", Names->GetString(Index).c_str(), + ReadString(Names->GetString(Index), L"").c_str())); + } + else + { + Strings->Add(ReadString(Names->GetString(Index), L"")); + } + } + } + __finally + { +// delete Names; + }; +} + +void THierarchicalStorage::ClearValues() +{ + std::unique_ptr Names(new TStringList()); + try__finally + { + GetValueNames(Names.get()); + for (intptr_t Index = 0; Index < Names->GetCount(); ++Index) + { + DeleteValue(Names->GetString(Index)); + } + } + __finally + { +// delete Names; + }; +} + +void THierarchicalStorage::WriteValues(TStrings * Strings, + bool MaintainKeys) +{ + ClearValues(); + + if (Strings) + { + for (intptr_t Index = 0; Index < Strings->GetCount(); ++Index) + { + if (MaintainKeys) + { + DebugAssert(Strings->GetString(Index).Pos(L"=") > 1); + WriteString(Strings->GetName(Index), Strings->GetValue(Strings->GetName(Index))); + } + else + { + WriteString(::IntToStr(Index), Strings->GetString(Index)); + } + } + } +} + +UnicodeString THierarchicalStorage::ReadString(const UnicodeString & Name, const UnicodeString & Default) const +{ + UnicodeString Result; + if (GetMungeStringValues()) + { + Result = UnMungeStr(ReadStringRaw(Name, MungeStr(Default, GetForceAnsi()))); + } + else + { + Result = ReadStringRaw(Name, Default); + } + return Result; +} + +RawByteString THierarchicalStorage::ReadBinaryData(const UnicodeString & Name) const +{ + size_t Size = BinaryDataSize(Name); + RawByteString Value; + Value.SetLength(Size); + ReadBinaryData(Name, static_cast(const_cast(Value.c_str())), Size); + return Value; +} + +RawByteString THierarchicalStorage::ReadStringAsBinaryData(const UnicodeString & Name, const RawByteString & Default) const +{ + UnicodeString UnicodeDefault = AnsiToString(Default); + // This should be exactly the same operation as calling ReadString in + // C++Builder 6 (non-Unicode) on Unicode-based OS + // (conversion is done by Ansi layer of the OS) + UnicodeString String = ReadString(Name, UnicodeDefault); + AnsiString Ansi = AnsiString(String); + RawByteString Result = RawByteString(Ansi.c_str(), Ansi.Length()); + return Result; +} + +void THierarchicalStorage::WriteString(const UnicodeString & Name, const UnicodeString & Value) +{ + if (GetMungeStringValues()) + { + WriteStringRaw(Name, MungeStr(Value, GetForceAnsi())); + } + else + { + WriteStringRaw(Name, Value); + } +} + +void THierarchicalStorage::WriteBinaryData(const UnicodeString & Name, + const RawByteString & Value) +{ + WriteBinaryData(Name, Value.c_str(), Value.Length()); +} + +void THierarchicalStorage::WriteBinaryDataAsString(const UnicodeString & Name, const RawByteString & Value) +{ + // This should be exactly the same operation as calling WriteString in + // C++Builder 6 (non-Unicode) on Unicode-based OS + // (conversion is done by Ansi layer of the OS) + WriteString(Name, AnsiToString(Value)); +} + +UnicodeString THierarchicalStorage::IncludeTrailingBackslash(const UnicodeString & S) +{ + // expanded from ?: as it caused memory leaks + if (S.IsEmpty()) + { + return S; + } + else + { + return ::IncludeTrailingBackslash(S); + } +} + +UnicodeString THierarchicalStorage::ExcludeTrailingBackslash(const UnicodeString & S) +{ + // expanded from ?: as it caused memory leaks + if (S.IsEmpty()) + { + return S; + } + else + { + return ::ExcludeTrailingBackslash(S); + } +} + +bool THierarchicalStorage::GetTemporary() const +{ + return false; +} + +TRegistryStorage::TRegistryStorage(const UnicodeString & AStorage) : + THierarchicalStorage(IncludeTrailingBackslash(AStorage)), + FRegistry(nullptr) +{ + Init(); +} + +TRegistryStorage::TRegistryStorage(const UnicodeString & AStorage, HKEY ARootKey) : + THierarchicalStorage(IncludeTrailingBackslash(AStorage)), + FRegistry(nullptr), + FFailed(0) +{ + Init(); + FRegistry->SetRootKey(ARootKey); +} + +void TRegistryStorage::Init() +{ + FFailed = 0; + FRegistry = new TRegistry(); + FRegistry->SetAccess(KEY_READ); +} + +TRegistryStorage::~TRegistryStorage() +{ + SAFE_DESTROY(FRegistry); +} + +bool TRegistryStorage::Copy(TRegistryStorage * Storage) +{ + TRegistry * Registry = Storage->FRegistry; + bool Result = true; + std::unique_ptr Names(new TStringList()); + try__finally + { + std::vector Buffer(1024); + Registry->GetValueNames(Names.get()); + intptr_t Index = 0; + while ((Index < Names->GetCount()) && Result) + { + UnicodeString Name = MungeStr(Names->GetString(Index), GetForceAnsi()); + DWORD Size = static_cast(Buffer.size()); + DWORD Type; + int RegResult = 0; + do + { + RegResult = ::RegQueryValueEx(Registry->GetCurrentKey(), Name.c_str(), nullptr, + &Type, &Buffer[0], &Size); + if (RegResult == ERROR_MORE_DATA) + { + Buffer.resize(Size); + } + } + while (RegResult == ERROR_MORE_DATA); + + Result = (RegResult == ERROR_SUCCESS); + if (Result) + { + RegResult = ::RegSetValueEx(FRegistry->GetCurrentKey(), Name.c_str(), 0, Type, + &Buffer[0], Size); + Result = (RegResult == ERROR_SUCCESS); + } + + ++Index; + } + } + __finally + { +// delete Names; + }; + return Result; +} + +UnicodeString TRegistryStorage::GetSource() const +{ + return RootKeyToStr(FRegistry->GetRootKey()) + L"\\" + GetStorage(); +} + +UnicodeString TRegistryStorage::GetSource() +{ + return RootKeyToStr(FRegistry->GetRootKey()) + L"\\" + GetStorage(); +} + +void TRegistryStorage::SetAccessMode(TStorageAccessMode Value) +{ + THierarchicalStorage::SetAccessMode(Value); + if (FRegistry) + { + switch (GetAccessMode()) + { + case smRead: + FRegistry->SetAccess(KEY_READ); + break; + + case smReadWrite: + default: + FRegistry->SetAccess(KEY_READ | KEY_WRITE); + break; + } + } +} + +bool TRegistryStorage::DoOpenSubKey(const UnicodeString & SubKey, bool CanCreate) +{ + if (FKeyHistory->GetCount() > 0) + { + FRegistry->CloseKey(); + } + UnicodeString Key = ::ExcludeTrailingBackslash(GetStorage() + GetCurrentSubKey() + SubKey); + return FRegistry->OpenKey(Key, CanCreate); +} + +void TRegistryStorage::CloseSubKey() +{ + FRegistry->CloseKey(); + THierarchicalStorage::CloseSubKey(); + if (FKeyHistory->GetCount()) + { + FRegistry->OpenKey(GetStorage() + GetCurrentSubKeyMunged(), True); + } +} + +bool TRegistryStorage::DeleteSubKey(const UnicodeString & SubKey) +{ + UnicodeString Key; + if (FKeyHistory->GetCount() == 0) + { + Key = GetStorage() + GetCurrentSubKey(); + } + Key += MungeKeyName(SubKey); + return FRegistry->DeleteKey(Key); +} + +void TRegistryStorage::GetSubKeyNames(TStrings * Strings) +{ + FRegistry->GetKeyNames(Strings); + for (intptr_t Index = 0; Index < Strings->GetCount(); ++Index) + { + Strings->SetString(Index, UnMungeStr(Strings->GetString(Index))); + } +} + +void TRegistryStorage::GetValueNames(TStrings * Strings) const +{ + FRegistry->GetValueNames(Strings); +} + +bool TRegistryStorage::DeleteValue(const UnicodeString & Name) +{ + return FRegistry->DeleteValue(Name); +} + +bool TRegistryStorage::DoKeyExists(const UnicodeString & SubKey, bool AForceAnsi) +{ + UnicodeString Key = MungeStr(SubKey, AForceAnsi); + bool Result = FRegistry->KeyExists(Key); + return Result; +} + +bool TRegistryStorage::ValueExists(const UnicodeString & Value) const +{ + bool Result = FRegistry->ValueExists(Value); + return Result; +} + +size_t TRegistryStorage::BinaryDataSize(const UnicodeString & Name) const +{ + size_t Result = FRegistry->GetDataSize(Name); + return Result; +} + +bool TRegistryStorage::ReadBool(const UnicodeString & Name, bool Default) const +{ + READ_REGISTRY(ReadBool); +} + +TDateTime TRegistryStorage::ReadDateTime(const UnicodeString & Name, const TDateTime & Default) const +{ + READ_REGISTRY(ReadDateTime); +} + +double TRegistryStorage::ReadFloat(const UnicodeString & Name, double Default) const +{ + READ_REGISTRY(ReadFloat); +} + +intptr_t TRegistryStorage::ReadInteger(const UnicodeString & Name, intptr_t Default) const +{ + READ_REGISTRY(ReadInteger); +} + +int64_t TRegistryStorage::ReadInt64(const UnicodeString & Name, int64_t Default) const +{ + int64_t Result = Default; + if (FRegistry->ValueExists(Name)) + { + try + { + FRegistry->ReadBinaryData(Name, &Result, sizeof(Result)); + } + catch (...) + { + FFailed++; + } + } + return Result; +} + +UnicodeString TRegistryStorage::ReadStringRaw(const UnicodeString & Name, const UnicodeString & Default) const +{ + READ_REGISTRY(ReadString); +} + +size_t TRegistryStorage::ReadBinaryData(const UnicodeString & Name, + void * Buffer, size_t Size) const +{ + size_t Result; + if (FRegistry->ValueExists(Name)) + { + try + { + Result = FRegistry->ReadBinaryData(Name, Buffer, Size); + } + catch (...) + { + Result = 0; + FFailed++; + } + } + else + { + Result = 0; + } + return Result; +} + +void TRegistryStorage::WriteBool(const UnicodeString & Name, bool Value) +{ + WRITE_REGISTRY(WriteBool); +} + +void TRegistryStorage::WriteDateTime(const UnicodeString & Name, const TDateTime & Value) +{ + WRITE_REGISTRY(WriteDateTime); +} + +void TRegistryStorage::WriteFloat(const UnicodeString & Name, double Value) +{ + WRITE_REGISTRY(WriteFloat); +} + +void TRegistryStorage::WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value) +{ + WRITE_REGISTRY(WriteString); +} + +void TRegistryStorage::WriteInteger(const UnicodeString & Name, intptr_t Value) +{ + WRITE_REGISTRY(WriteInteger); +} + +void TRegistryStorage::WriteInt64(const UnicodeString & Name, int64_t Value) +{ + try + { + FRegistry->WriteBinaryData(Name, &Value, sizeof(Value)); + } + catch (...) + { + FFailed++; + } +} + +void TRegistryStorage::WriteBinaryData(const UnicodeString & Name, + const void * Buffer, size_t Size) +{ + try + { + FRegistry->WriteBinaryData(Name, const_cast(Buffer), Size); + } + catch (...) + { + FFailed++; + } +} + +intptr_t TRegistryStorage::GetFailed() const +{ + intptr_t Result = FFailed; + FFailed = 0; + return Result; +} + +/* +__fastcall TCustomIniFileStorage::TCustomIniFileStorage(const UnicodeString Storage, TCustomIniFile * IniFile) : + THierarchicalStorage(Storage), + FIniFile(IniFile), + FMasterStorageOpenFailures(0), + FOpeningSubKey(false) +{ +} +//--------------------------------------------------------------------------- +__fastcall TCustomIniFileStorage::~TCustomIniFileStorage() +{ + delete FIniFile; +} +//--------------------------------------------------------------------------- +UnicodeString __fastcall TCustomIniFileStorage::GetSource() +{ + return Storage; +} +//--------------------------------------------------------------------------- +UnicodeString __fastcall TCustomIniFileStorage::GetCurrentSection() +{ + return ExcludeTrailingBackslash(GetCurrentSubKeyMunged()); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::CacheSections() +{ + if (FSections.get() == nullptr) + { + FSections.reset(new TStringList()); + FIniFile->ReadSections(FSections.get()); + FSections->Sorted = true; // has to set only after reading as ReadSections reset it to false + } +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::ResetCache() +{ + FSections.reset(nullptr); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::SetAccessMode(TStorageAccessMode value) +{ + if (FMasterStorage.get() != nullptr) + { + FMasterStorage->AccessMode = value; + } + THierarchicalStorage::SetAccessMode(value); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::DoOpenSubKey(const UnicodeString SubKey, bool CanCreate) +{ + bool Result = CanCreate; + + if (!Result) + { + CacheSections(); + UnicodeString NewKey = ExcludeTrailingBackslash(CurrentSubKey+SubKey); + if (FSections->Count > 0) + { + int Index = -1; + Result = FSections->Find(NewKey, Index); + if (!Result && + (Index < FSections->Count) && + (FSections->Strings[Index].SubString(1, NewKey.Length()+1) == NewKey + L"\\")) + { + Result = true; + } + } + } + return Result; +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::OpenRootKey(bool CanCreate) +{ + // Not supported with master storage. + // Actually currently, we use OpenRootKey with TRegistryStorage only. + DebugAssert(FMasterStorage.get() == NULL); + + return THierarchicalStorage::OpenRootKey(CanCreate); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::OpenSubKey(UnicodeString Key, bool CanCreate, bool Path) +{ + bool Result; + + { + TAutoFlag Flag(FOpeningSubKey); + Result = THierarchicalStorage::OpenSubKey(Key, CanCreate, Path); + } + + if (FMasterStorage.get() != nullptr) + { + if (FMasterStorageOpenFailures > 0) + { + FMasterStorageOpenFailures++; + } + else + { + bool MasterResult = FMasterStorage->OpenSubKey(Key, CanCreate, Path); + if (!Result && MasterResult) + { + Result = THierarchicalStorage::OpenSubKey(Key, true, Path); + DebugAssert(Result); + } + else if (Result && !MasterResult) + { + FMasterStorageOpenFailures++; + } + } + } + + return Result; +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::CloseSubKey() +{ + THierarchicalStorage::CloseSubKey(); + + // What we are called to restore previous key from OpenSubKey, + // when opening path component fails, the master storage was not involved yet + if (!FOpeningSubKey && (FMasterStorage.get() != nullptr)) + { + if (FMasterStorageOpenFailures > 0) + { + FMasterStorageOpenFailures--; + } + else + { + FMasterStorage->CloseSubKey(); + } + } +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::DeleteSubKey(const UnicodeString SubKey) +{ + bool Result; + try + { + ResetCache(); + FIniFile->EraseSection(CurrentSubKey + MungeKeyName(SubKey)); + Result = true; + } + catch (...) + { + Result = false; + } + if (HandleByMasterStorage()) + { + Result = FMasterStorage->DeleteSubKey(SubKey); + } + return Result; +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::GetSubKeyNames(Classes::TStrings* Strings) +{ + Strings->Clear(); + if (HandleByMasterStorage()) + { + FMasterStorage->GetSubKeyNames(Strings); + } + CacheSections(); + for (int i = 0; i < FSections->Count; i++) + { + UnicodeString Section = FSections->Strings[i]; + if (AnsiCompareText(CurrentSubKey, + Section.SubString(1, CurrentSubKey.Length())) == 0) + { + UnicodeString SubSection = Section.SubString(CurrentSubKey.Length() + 1, + Section.Length() - CurrentSubKey.Length()); + int P = SubSection.Pos(L"\\"); + if (P) + { + SubSection.SetLength(P - 1); + } + if (Strings->IndexOf(SubSection) < 0) + { + Strings->Add(UnMungeStr(SubSection)); + } + } + } +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::GetValueNames(Classes::TStrings* Strings) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->GetValueNames(Strings); + } + FIniFile->ReadSection(CurrentSection, Strings); + for (int Index = 0; Index < Strings->Count; Index++) + { + Strings->Strings[Index] = UnMungeIniName(Strings->Strings[Index]); + } +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::DoKeyExists(const UnicodeString SubKey, bool AForceAnsi) +{ + return + (HandleByMasterStorage() && FMasterStorage->DoKeyExists(SubKey, AForceAnsi)) || + FIniFile->SectionExists(CurrentSubKey + MungeStr(SubKey, AForceAnsi)); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::DoValueExists(const UnicodeString & Value) +{ + return FIniFile->ValueExists(CurrentSection, MungeIniName(Value)); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::ValueExists(const UnicodeString Value) +{ + return + (HandleByMasterStorage() && FMasterStorage->ValueExists(Value)) || + DoValueExists(Value); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::DeleteValue(const UnicodeString Name) +{ + bool Result = true; + if (HandleByMasterStorage()) + { + Result = FMasterStorage->DeleteValue(Name); + } + ResetCache(); + FIniFile->DeleteKey(CurrentSection, MungeIniName(Name)); + return Result; +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::HandleByMasterStorage() +{ + return + (FMasterStorage.get() != nullptr) && + (FMasterStorageOpenFailures == 0); +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::HandleReadByMasterStorage(const UnicodeString & Name) +{ + return HandleByMasterStorage() && !DoValueExists(Name); +} +//--------------------------------------------------------------------------- +size_t __fastcall TCustomIniFileStorage::BinaryDataSize(const UnicodeString Name) +{ + if (HandleReadByMasterStorage(Name)) + { + return FMasterStorage->BinaryDataSize(Name); + } + else + { + return ReadStringRaw(Name, L"").Length() / 2; + } +} +//--------------------------------------------------------------------------- +bool __fastcall TCustomIniFileStorage::ReadBool(const UnicodeString Name, bool Default) +{ + if (HandleReadByMasterStorage(Name)) + { + return FMasterStorage->ReadBool(Name, Default); + } + else + { + return FIniFile->ReadBool(CurrentSection, MungeIniName(Name), Default); + } +} +//--------------------------------------------------------------------------- +int __fastcall TCustomIniFileStorage::ReadInteger(const UnicodeString Name, int Default) +{ + int Result; + if (HandleReadByMasterStorage(Name)) + { + Result = FMasterStorage->ReadInteger(Name, Default); + } + else + { + Result = FIniFile->ReadInteger(CurrentSection, MungeIniName(Name), Default); + } + return Result; +} +//--------------------------------------------------------------------------- +__int64 __fastcall TCustomIniFileStorage::ReadInt64(const UnicodeString Name, __int64 Default) +{ + __int64 Result; + if (HandleReadByMasterStorage(Name)) + { + Result = FMasterStorage->ReadInt64(Name, Default); + } + else + { + Result = Default; + UnicodeString Str; + Str = ReadStringRaw(Name, L""); + if (!Str.IsEmpty()) + { + Result = StrToInt64Def(Str, Default); + } + } + return Result; +} +//--------------------------------------------------------------------------- +TDateTime __fastcall TCustomIniFileStorage::ReadDateTime(const UnicodeString Name, TDateTime Default) +{ + TDateTime Result; + if (HandleReadByMasterStorage(Name)) + { + Result = FMasterStorage->ReadDateTime(Name, Default); + } + else + { + UnicodeString Value = FIniFile->ReadString(CurrentSection, MungeIniName(Name), L""); + if (Value.IsEmpty()) + { + Result = Default; + } + else + { + try + { + RawByteString Raw = HexToBytes(Value); + if (static_cast(Raw.Length()) == sizeof(Result)) + { + memcpy(&Result, Raw.c_str(), sizeof(Result)); + } + else + { + Result = StrToDateTime(Value); + } + } + catch(...) + { + Result = Default; + } + } + } + + return Result; +} +//--------------------------------------------------------------------------- +double __fastcall TCustomIniFileStorage::ReadFloat(const UnicodeString Name, double Default) +{ + double Result; + if (HandleReadByMasterStorage(Name)) + { + Result = FMasterStorage->ReadFloat(Name, Default); + } + else + { + UnicodeString Value = FIniFile->ReadString(CurrentSection, MungeIniName(Name), L""); + if (Value.IsEmpty()) + { + Result = Default; + } + else + { + try + { + RawByteString Raw = HexToBytes(Value); + if (static_cast(Raw.Length()) == sizeof(Result)) + { + memcpy(&Result, Raw.c_str(), sizeof(Result)); + } + else + { + Result = static_cast(StrToFloat(Value)); + } + } + catch(...) + { + Result = Default; + } + } + } + + return Result; +} +//--------------------------------------------------------------------------- +UnicodeString __fastcall TCustomIniFileStorage::ReadStringRaw(const UnicodeString Name, UnicodeString Default) +{ + UnicodeString Result; + if (HandleReadByMasterStorage(Name)) + { + Result = FMasterStorage->ReadStringRaw(Name, Default); + } + else + { + Result = FIniFile->ReadString(CurrentSection, MungeIniName(Name), Default); + } + return Result; +} +//--------------------------------------------------------------------------- +size_t __fastcall TCustomIniFileStorage::ReadBinaryData(const UnicodeString Name, + void * Buffer, size_t Size) +{ + size_t Len; + if (HandleReadByMasterStorage(Name)) + { + FMasterStorage->ReadBinaryData(Name, Buffer, Size); + } + else + { + RawByteString Value = HexToBytes(ReadStringRaw(Name, L"")); + Len = Value.Length(); + if (Size > Len) + { + Size = Len; + } + DebugAssert(Buffer); + memcpy(Buffer, Value.c_str(), Size); + } + return Size; +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteBool(const UnicodeString Name, bool Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteBool(Name, Value); + } + ResetCache(); + FIniFile->WriteBool(CurrentSection, MungeIniName(Name), Value); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteInteger(const UnicodeString Name, int Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteInteger(Name, Value); + } + ResetCache(); + FIniFile->WriteInteger(CurrentSection, MungeIniName(Name), Value); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteInt64(const UnicodeString Name, __int64 Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteInt64(Name, Value); + } + DoWriteStringRaw(Name, IntToStr(Value)); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteDateTime(const UnicodeString Name, TDateTime Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteDateTime(Name, Value); + } + DoWriteBinaryData(Name, &Value, sizeof(Value)); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteFloat(const UnicodeString Name, double Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteFloat(Name, Value); + } + DoWriteBinaryData(Name, &Value, sizeof(Value)); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::DoWriteStringRaw(const UnicodeString & Name, const UnicodeString & Value) +{ + ResetCache(); + FIniFile->WriteString(CurrentSection, MungeIniName(Name), Value); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteStringRaw(const UnicodeString Name, const UnicodeString Value) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteStringRaw(Name, Value); + } + DoWriteStringRaw(Name, Value); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::DoWriteBinaryData(const UnicodeString & Name, + const void * Buffer, int Size) +{ + DoWriteStringRaw(Name, BytesToHex(RawByteString(static_cast(Buffer), Size))); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomIniFileStorage::WriteBinaryData(const UnicodeString Name, + const void * Buffer, int Size) +{ + if (HandleByMasterStorage()) + { + FMasterStorage->WriteBinaryData(Name, Buffer, Size); + } + DoWriteBinaryData(Name, Buffer, Size); +} +//=========================================================================== +__fastcall TIniFileStorage::TIniFileStorage(const UnicodeString AStorage): + TCustomIniFileStorage(AStorage, new TMemIniFile(AStorage)) +{ + FOriginal = new TStringList(); + dynamic_cast(FIniFile)->GetStrings(FOriginal); + ApplyOverrides(); +} +//--------------------------------------------------------------------------- +void __fastcall TIniFileStorage::Flush() +{ + if (FMasterStorage.get() != nullptr) + { + FMasterStorage->Flush(); + } + if (FOriginal != nullptr) + { + TStrings * Strings = new TStringList; + try + { + dynamic_cast(FIniFile)->GetStrings(Strings); + if (!Strings->Equals(FOriginal)) + { + int Attr; + // preserve attributes (especially hidden) + bool Exists = FileExists(ApiPath(Storage)); + if (Exists) + { + Attr = GetFileAttributes(ApiPath(Storage).c_str()); + } + else + { + Attr = FILE_ATTRIBUTE_NORMAL; + } + + HANDLE Handle = CreateFile(ApiPath(Storage).c_str(), GENERIC_READ | GENERIC_WRITE, + 0, nullptr, CREATE_ALWAYS, Attr, 0); + + if (Handle == INVALID_HANDLE_VALUE) + { + // "access denied" errors upon implicit saves to existing file are ignored + if (Explicit || !Exists || (GetLastError() != ERROR_ACCESS_DENIED)) + { + throw EOSExtException(FMTLOAD((Exists ? WRITE_ERROR : CREATE_FILE_ERROR), (Storage))); + } + } + else + { + TStream * Stream = new THandleStream(int(Handle)); + try + { + Strings->SaveToStream(Stream); + } + __finally + { + CloseHandle(Handle); + delete Stream; + } + } + } + } + __finally + { + delete FOriginal; + FOriginal = nullptr; + delete Strings; + } + } +} +//--------------------------------------------------------------------------- +__fastcall TIniFileStorage::~TIniFileStorage() +{ + Flush(); +} +//--------------------------------------------------------------------------- +void __fastcall TIniFileStorage::ApplyOverrides() +{ + UnicodeString OverridesKey = IncludeTrailingBackslash(L"Override"); + + CacheSections(); + for (int i = 0; i < FSections->Count; i++) + { + UnicodeString Section = FSections->Strings[i]; + + if (AnsiSameText(OverridesKey, + Section.SubString(1, OverridesKey.Length()))) + { + UnicodeString SubKey = Section.SubString(OverridesKey.Length() + 1, + Section.Length() - OverridesKey.Length()); + + // this all uses raw names (munged) + TStrings * Names = new TStringList; + try + { + FIniFile->ReadSection(Section, Names); + + for (int ii = 0; ii < Names->Count; ii++) + { + UnicodeString Name = Names->Strings[ii]; + UnicodeString Value = FIniFile->ReadString(Section, Name, L""); + FIniFile->WriteString(SubKey, Name, Value); + } + } + __finally + { + delete Names; + } + + FIniFile->EraseSection(Section); + ResetCache(); + } + } +} +//=========================================================================== +enum TWriteMode { wmAllow, wmFail, wmIgnore }; +//--------------------------------------------------------------------------- +class TOptionsIniFile : public TCustomIniFile +{ +public: + __fastcall TOptionsIniFile(TStrings * Options, TWriteMode WriteMode, const UnicodeString & RootKey); + + virtual UnicodeString __fastcall ReadString(const UnicodeString Section, const UnicodeString Ident, const UnicodeString Default); + virtual void __fastcall WriteString(const UnicodeString Section, const UnicodeString Ident, const UnicodeString Value); + virtual void __fastcall ReadSection(const UnicodeString Section, TStrings * Strings); + virtual void __fastcall ReadSections(TStrings* Strings); + virtual void __fastcall ReadSectionValues(const UnicodeString Section, TStrings* Strings); + virtual void __fastcall EraseSection(const UnicodeString Section); + virtual void __fastcall DeleteKey(const UnicodeString Section, const UnicodeString Ident); + virtual void __fastcall UpdateFile(); + // Hoisted overload + void __fastcall ReadSections(const UnicodeString Section, TStrings* Strings); + // Ntb, we can implement ValueExists more efficiently than the TCustomIniFile.ValueExists + +private: + TStrings * FOptions; + TWriteMode FWriteMode; + UnicodeString FRootKey; + + bool __fastcall AllowWrite(); + void __fastcall NotImplemented(); +}; +//--------------------------------------------------------------------------- +__fastcall TOptionsIniFile::TOptionsIniFile(TStrings * Options, TWriteMode WriteMode, const UnicodeString & RootKey) : + TCustomIniFile(UnicodeString()) +{ + FOptions = Options; + FWriteMode = WriteMode; + FRootKey = RootKey; + if (!FRootKey.IsEmpty()) + { + FRootKey += PathDelim; + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::NotImplemented() +{ + throw Exception(L"Not implemented"); +} +//--------------------------------------------------------------------------- +bool __fastcall TOptionsIniFile::AllowWrite() +{ + switch (FWriteMode) + { + case wmAllow: + return true; + + case wmFail: + NotImplemented(); + return false; // never gets here + + case wmIgnore: + return false; + + default: + DebugFail(); + return false; + } +} +//--------------------------------------------------------------------------- +UnicodeString __fastcall TOptionsIniFile::ReadString(const UnicodeString Section, const UnicodeString Ident, const UnicodeString Default) +{ + UnicodeString Name = Section; + if (!Name.IsEmpty()) + { + Name += PathDelim; + } + UnicodeString Value; + if (!SameText(Name.SubString(1, FRootKey.Length()), FRootKey)) + { + Value = Default; + } + else + { + Name.Delete(1, FRootKey.Length()); + Name += Ident; + + int Index = FOptions->IndexOfName(Name); + if (Index >= 0) + { + Value = FOptions->ValueFromIndex[Index]; + } + else + { + Value = Default; + } + } + return Value; +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::WriteString(const UnicodeString Section, const UnicodeString Ident, const UnicodeString Value) +{ + if (AllowWrite() && + // Implemented for TSessionData.DoSave only + DebugAlwaysTrue(Section.IsEmpty() && FRootKey.IsEmpty())) + { + FOptions->Values[Ident] = Value; + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::ReadSection(const UnicodeString Section, TStrings * Strings) +{ + + UnicodeString SectionPrefix = Section; + if (!SectionPrefix.IsEmpty()) + { + SectionPrefix += PathDelim; + } + + if (SameText(SectionPrefix.SubString(1, FRootKey.Length()), FRootKey)) + { + SectionPrefix.Delete(1, FRootKey.Length()); + + Strings->BeginUpdate(); + try + { + for (int Index = 0; Index < FOptions->Count; Index++) + { + UnicodeString Name = FOptions->Names[Index]; + if (SameText(Name.SubString(1, SectionPrefix.Length()), SectionPrefix) && + (LastDelimiter(PathDelim, Name) <= SectionPrefix.Length())) + { + Strings->Add(Name.SubString(SectionPrefix.Length() + 1, Name.Length() - SectionPrefix.Length())); + } + } + } + __finally + { + Strings->EndUpdate(); + } + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::ReadSections(TStrings * Strings) +{ + std::unique_ptr Sections(CreateSortedStringList()); + + for (int Index = 0; Index < FOptions->Count; Index++) + { + UnicodeString Name = FOptions->Names[Index]; + int P = LastDelimiter(PathDelim, Name); + if (P > 0) + { + UnicodeString Section = Name.SubString(1, P - 1); + if (Sections->IndexOf(Section) < 0) + { + Sections->Add(Section); + } + } + } + + for (int Index = 0; Index < Sections->Count; Index++) + { + Strings->Add(FRootKey + Sections->Strings[Index]); + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::ReadSectionValues(const UnicodeString Section, TStrings * / *Strings* /) +{ + NotImplemented(); +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::EraseSection(const UnicodeString Section) +{ + if (AllowWrite()) + { + NotImplemented(); + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::DeleteKey(const UnicodeString Section, const UnicodeString Ident) +{ + if (AllowWrite() && + // Implemented for TSessionData.DoSave only + DebugAlwaysTrue(Section.IsEmpty() && FRootKey.IsEmpty())) + { + int Index = FOptions->IndexOfName(Ident); + if (Index >= 0) + { + FOptions->Delete(Index); + } + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::UpdateFile() +{ + if (AllowWrite()) + { + // noop + } +} +//--------------------------------------------------------------------------- +void __fastcall TOptionsIniFile::ReadSections(const UnicodeString / *Section* /, TStrings * / * Strings* /) +{ + NotImplemented(); +} +//=========================================================================== +__fastcall TOptionsStorage::TOptionsStorage(TStrings * Options, bool AllowWrite): + TCustomIniFileStorage( + UnicodeString(L"Command-line options"), + new TOptionsIniFile(Options, (AllowWrite ? wmAllow : wmFail), UnicodeString())) +{ +} +//--------------------------------------------------------------------------- +__fastcall TOptionsStorage::TOptionsStorage(TStrings * Options, const UnicodeString & RootKey, THierarchicalStorage * MasterStorage) : + TCustomIniFileStorage( + UnicodeString(L"Command-line options overriding " + MasterStorage->Source), + new TOptionsIniFile(Options, wmIgnore, RootKey)) +{ + FMasterStorage.reset(MasterStorage); +} +//--------------------------------------------------------------------------- +bool __fastcall TOptionsStorage::GetTemporary() +{ + return true; +} +*/ diff --git a/netbox/src/core/HierarchicalStorage.h b/netbox/src/core/HierarchicalStorage.h new file mode 100644 index 000000000..39eda0562 --- /dev/null +++ b/netbox/src/core/HierarchicalStorage.h @@ -0,0 +1,255 @@ +#pragma once + +#include + +enum TStorage +{ + stDetect, + stRegistry, + stIniFile, + stNul, + stXmlFile, +}; + +enum TStorageAccessMode +{ + smRead, + smReadWrite, +}; +//--------------------------------------------------------------------------- +class THierarchicalStorage : public TObject +{ +NB_DISABLE_COPY(THierarchicalStorage) +public: + explicit THierarchicalStorage(const UnicodeString & AStorage); + virtual ~THierarchicalStorage(); + virtual void Init() {} + virtual bool OpenRootKey(bool CanCreate); + virtual bool OpenSubKey(const UnicodeString & ASubKey, bool CanCreate, bool Path = false); + virtual void CloseSubKey(); + void CloseAll(); + virtual bool DeleteSubKey(const UnicodeString & SubKey) = 0; + virtual void GetSubKeyNames(TStrings * Strings) = 0; + virtual void GetValueNames(TStrings * Strings) const = 0; + bool HasSubKeys(); + bool HasSubKey(const UnicodeString & SubKey); + bool KeyExists(const UnicodeString & SubKey); + virtual bool ValueExists(const UnicodeString & Value) const = 0; + virtual void RecursiveDeleteSubKey(const UnicodeString & Key); + virtual void ClearSubKeys(); + virtual void ReadValues(TStrings * Strings, bool MaintainKeys = false); + virtual void WriteValues(TStrings * Strings, bool MaintainKeys = false); + virtual void ClearValues(); + virtual bool DeleteValue(const UnicodeString & Name) = 0; + virtual size_t BinaryDataSize(const UnicodeString & Name) const = 0; + + virtual bool ReadBool(const UnicodeString & Name, bool Default) const = 0; + virtual intptr_t ReadInteger(const UnicodeString & Name, intptr_t Default) const = 0; + virtual int64_t ReadInt64(const UnicodeString & Name, int64_t Default) const = 0; + virtual TDateTime ReadDateTime(const UnicodeString & Name, const TDateTime & Default) const = 0; + virtual double ReadFloat(const UnicodeString & Name, double Default) const = 0; + virtual UnicodeString ReadStringRaw(const UnicodeString & Name, const UnicodeString & Default) const = 0; + virtual size_t ReadBinaryData(const UnicodeString & Name, void * Buffer, size_t Size) const = 0; + + virtual UnicodeString ReadString(const UnicodeString & Name, const UnicodeString & Default) const; + virtual RawByteString ReadBinaryData(const UnicodeString & Name) const; + virtual RawByteString ReadStringAsBinaryData(const UnicodeString & Name, const RawByteString & Default) const; + + virtual void WriteBool(const UnicodeString & Name, bool Value) = 0; + virtual void WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value) = 0; + virtual void WriteInteger(const UnicodeString & Name, intptr_t Value) = 0; + virtual void WriteInt64(const UnicodeString & Name, int64_t Value) = 0; + virtual void WriteDateTime(const UnicodeString & Name, const TDateTime & Value) = 0; + virtual void WriteFloat(const UnicodeString & Name, double Value) = 0; + virtual void WriteBinaryData(const UnicodeString & Name, const void * Buffer, size_t Size) = 0; + + virtual void WriteString(const UnicodeString & Name, const UnicodeString & Value); + virtual void WriteBinaryData(const UnicodeString & Name, const RawByteString & Value); + virtual void WriteBinaryDataAsString(const UnicodeString & Name, const RawByteString & Value); + + virtual void Flush(); + + /*__property UnicodeString Storage = { read=FStorage }; + __property UnicodeString CurrentSubKey = { read=GetCurrentSubKey }; + __property TStorageAccessMode AccessMode = { read=FAccessMode, write=SetAccessMode }; + __property bool Explicit = { read = FExplicit, write = FExplicit }; + __property bool ForceAnsi = { read = FForceAnsi, write = FForceAnsi }; + __property bool MungeStringValues = { read = FMungeStringValues, write = FMungeStringValues }; + __property UnicodeString Source = { read = GetSource }; + __property bool Temporary = { read = GetTemporary };*/ + + UnicodeString GetStorage() const { return FStorage; } + TStorageAccessMode GetAccessMode() const { return FAccessMode; } + bool GetExplicit() const { return FExplicit; } + void SetExplicit(bool Value) { FExplicit = Value; } + bool GetForceAnsi() const { return FForceAnsi; } + void SetForceAnsi(bool Value) { FForceAnsi = Value; } + bool GetMungeStringValues() const { return FMungeStringValues; } + void SetMungeStringValues(bool Value) { FMungeStringValues = Value; } + + virtual void SetAccessMode(TStorageAccessMode Value); + +protected: + UnicodeString FStorage; + TStrings * FKeyHistory; + TStorageAccessMode FAccessMode; + bool FExplicit; + bool FMungeStringValues; + bool FForceAnsi; + +public: + UnicodeString GetCurrentSubKey() const; + UnicodeString GetCurrentSubKeyMunged() const; + virtual bool DoKeyExists(const UnicodeString & SubKey, bool ForceAnsi) = 0; + static UnicodeString IncludeTrailingBackslash(const UnicodeString & S); + static UnicodeString ExcludeTrailingBackslash(const UnicodeString & S); + virtual bool DoOpenSubKey(const UnicodeString & SubKey, bool CanCreate) = 0; + UnicodeString MungeKeyName(const UnicodeString & Key); + + virtual bool GetTemporary() const; + +public: + virtual UnicodeString GetSource() const = 0; + virtual UnicodeString GetSource() = 0; +}; + +class TRegistryStorage : public THierarchicalStorage +{ +NB_DISABLE_COPY(TRegistryStorage) +public: + explicit TRegistryStorage(const UnicodeString & AStorage, HKEY ARootKey); + explicit TRegistryStorage(const UnicodeString & AStorage); + virtual void Init(); + virtual ~TRegistryStorage(); + + bool Copy(TRegistryStorage * Storage); + + virtual void CloseSubKey(); + virtual bool DeleteSubKey(const UnicodeString & SubKey); + virtual bool DeleteValue(const UnicodeString & Name); + virtual void GetSubKeyNames(TStrings * Strings); + virtual bool ValueExists(const UnicodeString & Value) const; + + virtual size_t BinaryDataSize(const UnicodeString & Name) const; + + virtual bool ReadBool(const UnicodeString & Name, bool Default) const; + virtual intptr_t ReadInteger(const UnicodeString & Name, intptr_t Default) const; + virtual int64_t ReadInt64(const UnicodeString & Name, int64_t Default) const; + virtual TDateTime ReadDateTime(const UnicodeString & Name, const TDateTime & Default) const; + virtual double ReadFloat(const UnicodeString & Name, double Default) const; + virtual UnicodeString ReadStringRaw(const UnicodeString & Name, const UnicodeString & Default) const; + virtual size_t ReadBinaryData(const UnicodeString & Name, void * Buffer, size_t Size) const; + + virtual void WriteBool(const UnicodeString & Name, bool Value); + virtual void WriteInteger(const UnicodeString & Name, intptr_t Value); + virtual void WriteInt64(const UnicodeString & Name, int64_t Value); + virtual void WriteDateTime(const UnicodeString & Name, const TDateTime & Value); + virtual void WriteFloat(const UnicodeString & Name, double Value); + virtual void WriteStringRaw(const UnicodeString & Name, const UnicodeString & Value); + virtual void WriteBinaryData(const UnicodeString & Name, const void * Buffer, size_t Size); + + virtual void GetValueNames(TStrings * Strings) const; + +protected: + virtual bool DoKeyExists(const UnicodeString & SubKey, bool ForceAnsi); + virtual bool DoOpenSubKey(const UnicodeString & SubKey, bool CanCreate); + virtual UnicodeString GetSource() const; + virtual UnicodeString GetSource(); + +public: +// __property int Failed = { read=GetFailed, write=FFailed }; + intptr_t GetFailed() const; + void SetFailed(intptr_t Value) { FFailed = Value; } + virtual void SetAccessMode(TStorageAccessMode Value); + +private: + TRegistry * FRegistry; + mutable intptr_t FFailed; +}; + +/* +class TCustomIniFileStorage : public THierarchicalStorage +{ +public: + __fastcall TCustomIniFileStorage(const UnicodeString Storage, TCustomIniFile * IniFile); + virtual __fastcall ~TCustomIniFileStorage(); + + virtual bool __fastcall OpenRootKey(bool CanCreate); + virtual bool __fastcall OpenSubKey(UnicodeString SubKey, bool CanCreate, bool Path = false); + virtual void __fastcall CloseSubKey(); + virtual bool __fastcall DeleteSubKey(const UnicodeString SubKey); + virtual bool __fastcall DeleteValue(const UnicodeString Name); + virtual void __fastcall GetSubKeyNames(Classes::TStrings* Strings); + virtual bool __fastcall ValueExists(const UnicodeString Value); + + virtual size_t __fastcall BinaryDataSize(const UnicodeString Name); + + virtual bool __fastcall ReadBool(const UnicodeString Name, bool Default); + virtual int __fastcall ReadInteger(const UnicodeString Name, int Default); + virtual __int64 __fastcall ReadInt64(const UnicodeString Name, __int64 Default); + virtual TDateTime __fastcall ReadDateTime(const UnicodeString Name, TDateTime Default); + virtual double __fastcall ReadFloat(const UnicodeString Name, double Default); + virtual UnicodeString __fastcall ReadStringRaw(const UnicodeString Name, const UnicodeString Default); + virtual size_t __fastcall ReadBinaryData(const UnicodeString Name, void * Buffer, size_t Size); + + virtual void __fastcall WriteBool(const UnicodeString Name, bool Value); + virtual void __fastcall WriteInteger(const UnicodeString Name, int Value); + virtual void __fastcall WriteInt64(const UnicodeString Name, __int64 Value); + virtual void __fastcall WriteDateTime(const UnicodeString Name, TDateTime Value); + virtual void __fastcall WriteFloat(const UnicodeString Name, double Value); + virtual void __fastcall WriteStringRaw(const UnicodeString Name, const UnicodeString Value); + virtual void __fastcall WriteBinaryData(const UnicodeString Name, const void * Buffer, int Size); + + virtual void __fastcall GetValueNames(Classes::TStrings* Strings); + +private: + UnicodeString __fastcall GetCurrentSection(); + inline bool __fastcall HandleByMasterStorage(); + inline bool __fastcall HandleReadByMasterStorage(const UnicodeString & Name); + inline bool __fastcall DoValueExists(const UnicodeString & Value); + void __fastcall DoWriteStringRaw(const UnicodeString & Name, const UnicodeString & Value); + void __fastcall DoWriteBinaryData(const UnicodeString & Name, const void * Buffer, int Size); + +protected: + TCustomIniFile * FIniFile; + std::unique_ptr FSections; + std::unique_ptr FMasterStorage; + int FMasterStorageOpenFailures; + bool FOpeningSubKey; + + __property UnicodeString CurrentSection = { read=GetCurrentSection }; + virtual void __fastcall SetAccessMode(TStorageAccessMode value); + virtual bool __fastcall DoKeyExists(const UnicodeString SubKey, bool ForceAnsi); + virtual bool __fastcall DoOpenSubKey(const UnicodeString SubKey, bool CanCreate); + virtual UnicodeString __fastcall GetSource(); + void __fastcall CacheSections(); + void __fastcall ResetCache(); +}; +//--------------------------------------------------------------------------- +class TIniFileStorage : public TCustomIniFileStorage +{ +public: + __fastcall TIniFileStorage(const UnicodeString FileName); + virtual __fastcall ~TIniFileStorage(); + + virtual void __fastcall Flush(); + +private: + TStrings * FOriginal; + void __fastcall ApplyOverrides(); +}; +//--------------------------------------------------------------------------- +class TOptionsStorage : public TCustomIniFileStorage +{ +public: + __fastcall TOptionsStorage(TStrings * Options, bool AllowWrite); + __fastcall TOptionsStorage(TStrings * Options, const UnicodeString & RootKey, THierarchicalStorage * MasterStorage); + +protected: + virtual bool __fastcall GetTemporary(); +}; +*/ + +UnicodeString PuttyMungeStr(const UnicodeString & Str); +UnicodeString PuttyUnMungeStr(const UnicodeString & Str); + diff --git a/netbox/src/core/Http.cpp b/netbox/src/core/Http.cpp new file mode 100644 index 000000000..5064c73ef --- /dev/null +++ b/netbox/src/core/Http.cpp @@ -0,0 +1,263 @@ + +#include +#pragma hdrstop + +#include +#include + +#include "Http.h" +#include "NeonIntf.h" +#include "Exceptions.h" +#include "TextsCore.h" + +THttp::THttp() : + FProxyPort(0), + FOnDownload(nullptr), + FResponseLimit(-1), + FRequestHeaders(nullptr), + FResponseHeaders(new TStringList()) +{ +} + +THttp::~THttp() +{ + delete FResponseHeaders; + delete FRequestHeaders; +} + +void THttp::SendRequest(const char * Method, const UnicodeString & Request) +{ + std::unique_ptr AttemptedUrls(CreateSortedStringList()); + AttemptedUrls->Add(GetURL()); + UnicodeString RequestUrl = GetURL(); + bool WasTlsUri = false; // shut up + + bool Retry; + + do + { + ne_uri uri; + NeonParseUrl(RequestUrl, uri); + + bool IsTls = IsTlsUri(uri); + if (RequestUrl == GetURL()) + { + WasTlsUri = IsTls; + } + else + { + if (!IsTls && WasTlsUri) + { + throw Exception(LoadStr(UNENCRYPTED_REDIRECT)); + } + } + + FHostName = StrFromNeon(uri.host); + + UnicodeString Uri = StrFromNeon(uri.path); + if (uri.query != nullptr) + { + Uri += L"?" + StrFromNeon(uri.query); + } + + FResponse.SetLength(0); + FCertificateError.SetLength(0); + FException.reset(nullptr); + + TProxyMethod ProxyMethod = GetProxyHost().IsEmpty() ? ::pmNone : pmHTTP; + + ne_session_s * NeonSession = + CreateNeonSession( + uri, ProxyMethod, GetProxyHost(), GetProxyPort(), UnicodeString(), UnicodeString()); + + try__finally + { + SCOPE_EXIT + { + DestroyNeonSession(NeonSession); + ne_uri_free(&uri); + }; + if (IsTls) + { + SetNeonTlsInit(NeonSession, InitSslSession); + + ne_ssl_set_verify(NeonSession, NeonServerSSLCallback, this); + + ne_ssl_trust_default_ca(NeonSession); + } + + ne_request_s * NeonRequest = ne_request_create(NeonSession, Method, StrToNeon(Uri)); + try__finally + { + SCOPE_EXIT + { + ne_request_destroy(NeonRequest); + }; + if (FRequestHeaders != nullptr) + { + for (intptr_t Index = 0; Index < FRequestHeaders->GetCount(); Index++) + { + ne_add_request_header( + NeonRequest, StrToNeon(FRequestHeaders->GetName(Index)), StrToNeon(FRequestHeaders->GetValueFromIndex(Index))); + } + } + + UTF8String RequestUtf; + if (!Request.IsEmpty()) + { + RequestUtf = UTF8String(Request); + ne_set_request_body_buffer(NeonRequest, RequestUtf.c_str(), RequestUtf.Length()); + } + + ne_add_response_body_reader(NeonRequest, ne_accept_2xx, NeonBodyReader, this); + + int Status = ne_request_dispatch(NeonRequest); + + // Exception has precedence over status as status will always be NE_ERROR, + // as we returned 1 from NeonBodyReader + if (FException.get() != nullptr) + { + RethrowException(FException.get()); + } + + if (Status == NE_REDIRECT) + { + Retry = true; + RequestUrl = GetNeonRedirectUrl(NeonSession); + CheckRedirectLoop(RequestUrl, AttemptedUrls.get()); + } + else + { + Retry = false; + CheckNeonStatus(NeonSession, Status, FHostName, FCertificateError); + + const ne_status * NeonStatus = ne_get_status(NeonRequest); + if (NeonStatus->klass != 2) + { + throw Exception(FMTLOAD(HTTP_ERROR2, NeonStatus->code, StrFromNeon(NeonStatus->reason_phrase).c_str(), FHostName.c_str())); + } + + void * Cursor = NULL; + const char * HeaderName; + const char * HeaderValue; + while ((Cursor = ne_response_header_iterate(NeonRequest, Cursor, &HeaderName, &HeaderValue)) != nullptr) + { + FResponseHeaders->SetValue(StrFromNeon(HeaderName), StrFromNeon(HeaderValue)); + } + } + } + __finally + { + ne_request_destroy(NeonRequest); + }; + } + __finally + { + DestroyNeonSession(NeonSession); + ne_uri_free(&uri); + }; + } + while (Retry); +} + +void THttp::Get() +{ + SendRequest("GET", UnicodeString()); +} + +void THttp::Post(const UnicodeString & Request) +{ + SendRequest("POST", Request); +} + +UnicodeString THttp::GetResponse() const +{ + UTF8String UtfResponse(FResponse.c_str()); + return UnicodeString(UtfResponse); +} + +int THttp::NeonBodyReaderImpl(const char * Buf, size_t Len) +{ + bool Result = true; + if ((FResponseLimit < 0) || + (FResponse.Length() + static_cast(Len) <= FResponseLimit)) + { + FResponse += RawByteString(Buf, Len); + + if (FOnDownload != nullptr) + { + bool Cancel = false; + + try + { + FOnDownload(this, GetResponseLength(), Cancel); + } + catch (Exception & E) + { + FException.reset(CloneException(&E)); + Result = false; + } + + if (Cancel) + { + FException.reset(new EAbort(UnicodeString())); + Result = false; + } + } + } + + // neon wants 0 for success + return Result ? 0 : 1; +} + +int THttp::NeonBodyReader(void * UserData, const char * Buf, size_t Len) +{ + THttp * Http = static_cast(UserData); + return Http->NeonBodyReaderImpl(Buf, Len); +} + +int64_t THttp::GetResponseLength() const +{ + return FResponse.Length(); +} + +void THttp::InitSslSession(ssl_st * Ssl, ne_session * /*Session*/) +{ + int Options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; + SSL_ctrl(Ssl, SSL_CTRL_OPTIONS, Options, nullptr); +} + +int THttp::NeonServerSSLCallback(void * UserData, int Failures, const ne_ssl_certificate * Certificate) +{ + THttp * Http = static_cast(UserData); + return Http->NeonServerSSLCallbackImpl(Failures, Certificate); +} + +int THttp::NeonServerSSLCallbackImpl(int Failures, const ne_ssl_certificate * Certificate) +{ + AnsiString AsciiCert = NeonExportCertificate(Certificate); + + // winscp.net 31.05.2015 - 02.06.2016 + const AnsiString WebCert = "MIIEqzCCA5OgAwIBAgIDBMLgMA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMSAwHgYDVQQDExdSYXBpZFNTTCBTSEEyNTYgQ0EgLSBHMzAeFw0xNTA1MzEwMDA1NTRaFw0xNjA2MDIwNTUxNTNaMIGSMRMwEQYDVQQLEwpHVDUyNTA2NDcyMTEwLwYDVQQLEyhTZWUgd3d3LnJhcGlkc3NsLmNvbS9yZXNvdXJjZXMvY3BzIChjKTE1MS8wLQYDVQQLEyZEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQgLSBSYXBpZFNTTChSKTEXMBUGA1UEAxMOd3d3LndpbnNjcC5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRHP+5FLE+q+oeu0Qlx2eX1nV8Vc0jOkwam95Vr6JV9Ar5ZZIbR8a/jBJv/tDlQ8nvx38gPoFCcPX3qBjRhxIBuNETbLi+7Yd69CXyZKNrbo0pxAbn5C/JKFgog5EkSdR65gia0J6YGPDw/iq180MpkNIBAFInq8Pc36hLz4jrAunFjxJ18pkmBlOVSr7/4Ppd15Co9fhF4A3QI2wcrAOjEfGhssfk7aRp8x36/GI3rs/Or1SbO6/ZSg9h7a5uvJFgQPDVRmqytd03EC9HLVOvRK/70gcQfYcOSEWkEkEO8jQ7eXv0u1y5E20Te0Zk9hVXll2RpCO8u5RyyzhSKW1DAgMBAAGjggFSMIIBTjAfBgNVHSMEGDAWgBTDnPP800YINLvORn+gfFvz4gjLWTBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly9ndi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly9ndi5zeW1jYi5jb20vZ3YuY3J0MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwJQYDVR0RBB4wHIIOd3d3LndpbnNjcC5uZXSCCndpbnNjcC5uZXQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNvbS9ndi5jcmwwDAYDVR0TAQH/BAIwADBBBgNVHSAEOjA4MDYGBmeBDAECATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5yYXBpZHNzbC5jb20vbGVnYWwwDQYJKoZIhvcNAQELBQADggEBAHzQ0JNQOtaYUbcw6OCyyiV5O5VemJSDagD5VG5W2gkLfxa9v1ly9hxbdlkKv4d9ruq36ZiHxqKBLoBzw68RNOG/CFY85Xam5Wygo8afSIuhLOYgSUMriH8aoBUXssG15t54z1em58Qh5xMp0crQAbY7D4opo0pEzkaTaS8ulwOxu5SSpK3DF12VKUdKhBs7T0QY+oOJZsqkx7GdmeKxoKRpBQvnRegCszR7vkdWsY0fFZHeNdThkY1iRxI6b4tyDYgZ0COj8WXCBia9wZm/czMmq/d7KED2V06w3Ttc4hDOHl+Onm/gQFLs++1f2Z/X6iPNhIEMNKKH51y/eHJQMxE="; + // cdn.winscp.net 02.06.2015 - 04.06.2016 + const AnsiString CdnCert = "MIIEnzCCA4egAwIBAgIDBMxPMA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMSAwHgYDVQQDExdSYXBpZFNTTCBTSEEyNTYgQ0EgLSBHMzAeFw0xNTA2MDIwNjIzMDFaFw0xNjA2MDQwMDU0NTVaMIGSMRMwEQYDVQQLEwpHVDUwMzQ0NzgyMTEwLwYDVQQLEyhTZWUgd3d3LnJhcGlkc3NsLmNvbS9yZXNvdXJjZXMvY3BzIChjKTE1MS8wLQYDVQQLEyZEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQgLSBSYXBpZFNTTChSKTEXMBUGA1UEAxMOY2RuLndpbnNjcC5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5ykjjmhjnd6hMmb7WNlQwSzjGCiLA3b+U2xfUYGsR+0o8frU3bv50vNp+IlTFXn32Qt3QtskuoLdi/nip0ABBtgEUt4FAoZ/AMKPyT0hD95ZEFlaclCzUANmSJswWdQIZltbLulMAqt618Snkog3Z3nkjzAMZuKvEYvPV9ujuM2kzkWZr0/OoPHINUkaI6mZwTxCvxwmazBtyIx5tXqmfiOJ1R+viAZm4Av5nqaXz1dTqSjrgpvFtwkTu8YQpK3qrpjN+H67PnClOAS1GY0oaSMccxq0bYL1w0hORa+NyQ+ZTIiXVwiXFJ0w23qHadaoKOiG8WnQ49YvCpbDhMU+3AgMBAAGjggFGMIIBQjAfBgNVHSMEGDAWgBTDnPP800YINLvORn+gfFvz4gjLWTBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly9ndi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly9ndi5zeW1jYi5jb20vZ3YuY3J0MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGQYDVR0RBBIwEIIOY2RuLndpbnNjcC5uZXQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNvbS9ndi5jcmwwDAYDVR0TAQH/BAIwADBBBgNVHSAEOjA4MDYGBmeBDAECATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5yYXBpZHNzbC5jb20vbGVnYWwwDQYJKoZIhvcNAQELBQADggEBAF9BN1+whMrK/HCDggLi/76+zDkzqVvNjdTgOLLSBCZaTJV1xNwIfxrPVwECEKkqV0D3eYx53aMxudVq+QuQ7VlS2k0Nu12Bfr0LFcUIOOO55jfjXUnJ8QLWoZQIJ6spOk0Bilxos4yzcORxOfASjkECEg1XUK3THnNgVLKZS92JSdxzsRkZvkpKCSNlX4ftoaPyDsgYWv0gBBA4RPAjAJB5qQTUeu0xIO/r1IhqLqnhlnJ3ewE68ScHbE5sMpl5RLEiaVRBeNw/whVEDPe2TY+JNzk6NdsGWq6uPCFsCXTGzX2QkAwR+rk2y4PPoY2vnj2cQoYWXF5pBjpMPYyRu8Q="; + + if ((AsciiCert == WebCert) || + (AsciiCert == CdnCert)) + { + Failures &= ~NE_SSL_UNTRUSTED; + } + + if (Failures != 0) + { + NeonWindowsValidateCertificate(Failures, AsciiCert); + } + + if (Failures != 0) + { + FCertificateError = NeonCertificateFailuresErrorStr(Failures, FHostName); + } + + return (Failures == 0) ? NE_OK : NE_ERROR; +} + diff --git a/netbox/src/core/Http.h b/netbox/src/core/Http.h new file mode 100644 index 000000000..846af87b1 --- /dev/null +++ b/netbox/src/core/Http.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +struct ne_session_s; +struct ne_request_s; +struct ne_ssl_certificate_s; +struct ssl_st; + +class THttp; +//typedef void (__closure * THttpDownloadEvent)(THttp * Sender, int64_t Size, bool & Cancel); +DEFINE_CALLBACK_TYPE3(THttpDownloadEvent, void, + THttp * /*Sender*/, int64_t /*Size*/, bool & /*Cancel*/); + +class THttp : public TObject +{ +public: + THttp(); + ~THttp(); + + void Get(); + void Post(const UnicodeString & Request); + + /*__property UnicodeString URL = { read = FURL, write = FURL }; + __property UnicodeString ProxyHost = { read = FProxyHost, write = FProxyHost }; + __property int ProxyPort = { read = FProxyPort, write = FProxyPort }; + __property TStrings * RequestHeaders = { read = FRequestHeaders, write = FRequestHeaders }; + __property UnicodeString Response = { read = GetResponse }; + __property RawByteString ResponseRaw = { read = FResponse }; + __property TStrings * ResponseHeaders = { read = FResponseHeaders }; + __property __int64 ResponseLength = { read = GetResponseLength }; + __property __int64 ResponseLimit = { read = FResponseLimit, write = FResponseLimit }; + __property THttpDownloadEvent OnDownload = { read = FOnDownload, write = FOnDownload};*/ + + UnicodeString GetURL() const { return FURL; } + void SetURL(const UnicodeString & Value) { FURL = Value; } + UnicodeString GetProxyHost() const { return FProxyHost; } + void SetProxyHost(const UnicodeString & Value) { FProxyHost = Value; } + intptr_t GetProxyPort() const { return FProxyPort; } + void SetProxyPort(intptr_t Value) { FProxyPort = Value; } + TStrings * GetRequestHeaders() const { return FRequestHeaders; } + void SetRequestHeaders(TStrings * Value) { FRequestHeaders = Value; } + RawByteString GetResponseRaw() const { return FResponse; } + TStrings * GetResponseHeaders() const { return FResponseHeaders; } + int64_t GetResponseLimit() const { return FResponseLimit; } + void SetResponseLimit(int64_t Value) { FResponseLimit = Value; } + THttpDownloadEvent GetOnDownload() const { return FOnDownload; } + void SetOnDownload(THttpDownloadEvent Value) { FOnDownload = Value; } + +private: + UnicodeString FURL; + UnicodeString FProxyHost; + intptr_t FProxyPort; + RawByteString FResponse; + int64_t FResponseLimit; + std::unique_ptr FException; + THttpDownloadEvent FOnDownload; + UnicodeString FHostName; + UnicodeString FCertificateError; + TStrings * FRequestHeaders; + TStrings * FResponseHeaders; + + static int NeonBodyReader(void * UserData, const char * Buf, size_t Len); + int NeonBodyReaderImpl(const char * Buf, size_t Len); + void SendRequest(const char * Method, const UnicodeString & Request); + UnicodeString GetResponse() const; + int64_t GetResponseLength() const; + static void InitSslSession(ssl_st * Ssl, ne_session_s * Session); + static int NeonServerSSLCallback(void * UserData, int Failures, const ne_ssl_certificate_s * Certificate); + int NeonServerSSLCallbackImpl(int Failures, const ne_ssl_certificate_s * Certificate); +}; + diff --git a/netbox/src/core/Interface.h b/netbox/src/core/Interface.h new file mode 100644 index 000000000..ed644fb21 --- /dev/null +++ b/netbox/src/core/Interface.h @@ -0,0 +1,172 @@ + +#pragma once + +#include +#include +#include "Configuration.h" +#include "SessionData.h" +#define HELP_NONE L"" +#define SCRIPT_SWITCH "script" +#define COMMAND_SWITCH L"Command" +#define SESSIONNAME_SWITCH L"SessionName" +#define INI_NUL L"nul" +#define PRESERVETIME_SWITCH L"preservetime" +#define PRESERVETIMEDIRS_SWITCH_VALUE L"all" +#define NOPRESERVETIME_SWITCH L"nopreservetime" +#define PERMISSIONS_SWITCH L"permissions" +#define NOPERMISSIONS_SWITCH L"nopermissions" +#define SPEED_SWITCH L"speed" +#define TRANSFER_SWITCH L"transfer" +#define FILEMASK_SWITCH L"filemask" +#define RESUMESUPPORT_SWITCH L"resumesupport" +#define NEWERONLY_SWICH L"neweronly" +#define NONEWERONLY_SWICH L"noneweronly" +#define DELETE_SWITCH L"delete" +extern const wchar_t * TransferModeNames[]; +extern const int TransferModeNamesCount; +extern const wchar_t * ToggleNames[]; +enum TToggle { ToggleOff, ToggleOn }; + +TConfiguration * CreateConfiguration(); +class TOptions; +TOptions * GetGlobalOptions(); + +void ShowExtendedException(Exception * E); +bool AppendExceptionStackTraceAndForget(TStrings *& MoreMessages); +void IgnoreException(const std::type_info & ExceptionType); +UnicodeString GetExceptionDebugInfo(); + +UnicodeString GetCompanyRegistryKey(); +UnicodeString GetRegistryKey(); +void * BusyStart(); +void BusyEnd(void * Token); +const uint32_t GUIUpdateInterval = 100; +void SetNoGUI(); +bool ProcessGUI(bool Force = false); +UnicodeString GetAppNameString(); +UnicodeString GetSshVersionString(); +void CopyToClipboard(const UnicodeString & Text); +HANDLE StartThread(void * SecurityAttributes, DWORD StackSize, + /*TThreadFunc ThreadFunc,*/ void * Parameter, DWORD CreationFlags, + TThreadID & ThreadId); + +void WinInitialize(); +void WinFinalize(); + +struct TQueryButtonAlias : public TObject +{ + TQueryButtonAlias(); + + uintptr_t Button; + UnicodeString Alias; + TNotifyEvent OnClick; + int GroupWith; + bool Default; + TShiftStateFlag GrouppedShiftState; + bool ElevationRequired; +}; + +// typedef void __fastcall (__closure *TQueryParamsTimerEvent)(unsigned int & Result); +DEFINE_CALLBACK_TYPE1(TQueryParamsTimerEvent, void, + intptr_t & /*Result*/); + +struct TQueryParams : public TObject +{ + explicit TQueryParams(uintptr_t AParams = 0, const UnicodeString & AHelpKeyword = HELP_NONE); + explicit TQueryParams(const TQueryParams & Source); + + void Assign(const TQueryParams & Source); + + const TQueryButtonAlias * Aliases; + uintptr_t AliasesCount; + uintptr_t Params; + uintptr_t Timer; + TQueryParamsTimerEvent TimerEvent; + UnicodeString TimerMessage; + uintptr_t TimerAnswers; + TQueryType TimerQueryType; + uintptr_t Timeout; + uintptr_t TimeoutAnswer; + uintptr_t NoBatchAnswers; + UnicodeString HelpKeyword; + +public: + TQueryParams & operator=(const TQueryParams & other); + +private: + // NB_DISABLE_COPY(TQueryParams) +}; + +enum TPromptKind +{ + pkPrompt, + pkFileName, + pkUserName, + pkPassphrase, + pkTIS, + pkCryptoCard, + pkKeybInteractive, + pkPassword, + pkNewPassword +}; + +enum TPromptUserParam +{ + pupEcho = 0x01, + pupRemember = 0x02, +}; + +bool IsAuthenticationPrompt(TPromptKind Kind); +bool IsPasswordOrPassphrasePrompt(TPromptKind Kind, TStrings * Prompts); +bool IsPasswordPrompt(TPromptKind Kind, TStrings * Prompts); +//--------------------------------------------------------------------------- +//typedef void __fastcall (__closure *TFileFoundEvent) +// (TTerminal * Terminal, const UnicodeString FileName, const TRemoteFile * File, +// bool & Cancel); +DEFINE_CALLBACK_TYPE4(TFileFoundEvent, void, + TTerminal * /*Terminal*/, const UnicodeString & /*FileName*/, + const TRemoteFile * /*File*/, + bool & /*Cancel*/); +//typedef void __fastcall (__closure *TFindingFileEvent) +// (TTerminal * Terminal, const UnicodeString Directory, bool & Cancel); +DEFINE_CALLBACK_TYPE3(TFindingFileEvent, void, + TTerminal * /*Terminal*/, const UnicodeString & /*Directory*/, bool & /*Cancel*/); + +class TOperationVisualizer +{ +NB_DISABLE_COPY(TOperationVisualizer) +public: + explicit TOperationVisualizer(bool UseBusyCursor = true); + ~TOperationVisualizer(); + +private: + bool FUseBusyCursor; + void * FToken; +}; + +class TInstantOperationVisualizer : public TOperationVisualizer +{ +public: + TInstantOperationVisualizer(); + ~TInstantOperationVisualizer(); + +private: + TDateTime FStart; +}; + +struct TClipboardHandler +{ +NB_DECLARE_CLASS(TClipboardHandler) +NB_DISABLE_COPY(TClipboardHandler) +public: + TClipboardHandler() {} + + UnicodeString Text; + + void Copy(TObject * /*Sender*/) + { + TInstantOperationVisualizer Visualizer; + CopyToClipboard(Text); + } +}; + diff --git a/netbox/src/core/NamedObjs.cpp b/netbox/src/core/NamedObjs.cpp new file mode 100644 index 000000000..7204a0bcc --- /dev/null +++ b/netbox/src/core/NamedObjs.cpp @@ -0,0 +1,194 @@ +#include +#pragma hdrstop + +#include +#include + +#include "NamedObjs.h" + +static intptr_t NamedObjectSortProc(const void * Item1, const void * Item2) +{ + return static_cast(Item1)->Compare(static_cast(Item2)); +} +//--- TNamedObject ---------------------------------------------------------- +TNamedObject::TNamedObject(const UnicodeString & AName) : + FHidden(false) +{ + SetName(AName); +} + +void TNamedObject::SetName(const UnicodeString & Value) +{ + UnicodeString HiddenPrefix(CONST_HIDDEN_PREFIX); + FHidden = (Value.SubString(1, HiddenPrefix.Length()) == HiddenPrefix); + FName = Value; +} + +intptr_t TNamedObject::Compare(const TNamedObject * Other) const +{ + intptr_t Result; + if (GetHidden() && !Other->GetHidden()) + { + Result = -1; + } + else if (!GetHidden() && Other->GetHidden()) + { + Result = 1; + } + else + { + Result = CompareLogicalText(GetName(), Other->GetName()); + } + return Result; +} + +bool TNamedObject::IsSameName(const UnicodeString & AName) const +{ + return (GetName().CompareIC(AName) == 0); +} + +void TNamedObject::MakeUniqueIn(TNamedObjectList * List) +{ + // This object can't be item of list, it would create infinite loop + if (List && (List->IndexOf(this) == -1)) + while (List->FindByName(GetName())) + { + int64_t N = 0; + intptr_t P = 0; + // If name already contains number parenthesis remove it (and remember it) + UnicodeString Name = GetName(); + if ((Name[Name.Length()] == L')') && ((P = Name.LastDelimiter(L'(')) > 0)) + { + try + { + N = ::StrToInt64(Name.SubString(P + 1, Name.Length() - P - 1)); + Name.Delete(P, Name.Length() - P + 1); + SetName(Name.TrimRight()); + } + catch (Exception & E) + { + (void)E; + N = 0; + } + } + SetName(Name + L" (" + ::Int64ToStr(N+1) + L")"); + } +} +//--- TNamedObjectList ------------------------------------------------------ +TNamedObjectList::TNamedObjectList() : + TObjectList(), + AutoSort(true), + FHiddenCount(0), + FControlledAdd(false) +{ +} +//--------------------------------------------------------------------------- +const TNamedObject * TNamedObjectList::AtObject(intptr_t Index) const +{ + return const_cast(this)->AtObject(Index); +} +//--------------------------------------------------------------------------- +TNamedObject * TNamedObjectList::AtObject(intptr_t Index) +{ + return NB_STATIC_DOWNCAST(TNamedObject, GetObj(Index + FHiddenCount)); +} +//--------------------------------------------------------------------------- +void TNamedObjectList::Recount() +{ + intptr_t Index = 0; + while ((Index < TObjectList::GetCount()) && (NB_STATIC_DOWNCAST(TNamedObject, GetObj(Index))->GetHidden())) + { + ++Index; + } + FHiddenCount = Index; +} +//--------------------------------------------------------------------------- +void TNamedObjectList::AlphaSort() +{ + Sort(NamedObjectSortProc); + Recount(); +} +//--------------------------------------------------------------------------- +intptr_t TNamedObjectList::Add(TObject * AObject) +{ + intptr_t Result; + TAutoFlag ControlledAddFlag(FControlledAdd); + TNamedObject * NamedObject = static_cast(AObject); + // If temporarily not auto-sorting (when loading session list), + // keep the hidden objects in front, so that HiddenCount is correct + if (!AutoSort && NamedObject->GetHidden()) + { + Result = 0; + Insert(Result, AObject); + FHiddenCount++; + } + else + { + Result = TObjectList::Add(AObject); + } + return Result; +} +//--------------------------------------------------------------------------- +void TNamedObjectList::Notify(void * Ptr, TListNotification Action) +{ + if (Action == lnDeleted) + { + TNamedObject * NamedObject = static_cast(Ptr); + if (NamedObject->GetHidden() && (FHiddenCount >= 0)) + { + FHiddenCount--; + } + } + TObjectList::Notify(Ptr, Action); + if (Action == lnAdded) + { + if (!FControlledAdd) + { + FHiddenCount = -1; + } + if (AutoSort) + { + AlphaSort(); + } + } +} +//--------------------------------------------------------------------------- +const TNamedObject * TNamedObjectList::FindByName(const UnicodeString & Name) const +{ + return const_cast(this)->FindByName(Name); +} + +TNamedObject * TNamedObjectList::FindByName(const UnicodeString & Name) +{ + // This should/can be optimized when list is sorted + for (Integer Index = 0; Index < GetCountIncludingHidden(); ++Index) + { + // Not using AtObject as we iterate even hidden objects here + TNamedObject * NamedObject = static_cast(GetObj(Index)); + if (NamedObject->IsSameName(Name)) + { + return NamedObject; + } + } + return nullptr; +} +//--------------------------------------------------------------------------- +void TNamedObjectList::SetCount(intptr_t Value) +{ + TObjectList::SetCount(Value/*+HiddenCount*/); +} + +intptr_t TNamedObjectList::GetCount() const +{ + DebugAssert(FHiddenCount >= 0); + return TObjectList::GetCount() - FHiddenCount; +} + +intptr_t TNamedObjectList::GetCountIncludingHidden() const +{ + DebugAssert(FHiddenCount >= 0); + return TObjectList::GetCount(); +} + +NB_IMPLEMENT_CLASS(TNamedObject, NB_GET_CLASS_INFO(TPersistent), nullptr) + diff --git a/netbox/src/core/NamedObjs.h b/netbox/src/core/NamedObjs.h new file mode 100644 index 000000000..53142b42f --- /dev/null +++ b/netbox/src/core/NamedObjs.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#define CONST_HIDDEN_PREFIX L"_!_" + +class TNamedObjectList; +class TNamedObject : public TPersistent +{ +NB_DECLARE_CLASS(TNamedObject) +public: + /*__property UnicodeString Name = { read = FName, write = SetName }; + __property bool Hidden = { read = FHidden };*/ + UnicodeString GetName() const { return FName; } + void SetName(const UnicodeString & Value); + bool GetHidden() const { return FHidden; } + + explicit TNamedObject() : TPersistent(), FHidden(false) {} + virtual ~TNamedObject() {} + + bool IsSameName(const UnicodeString & Name) const; + virtual intptr_t Compare(const TNamedObject * Other) const; + explicit TNamedObject(const UnicodeString & AName); + void MakeUniqueIn(TNamedObjectList * List); +private: + UnicodeString FName; + bool FHidden; +}; + +class TNamedObjectList : public TObjectList +{ +public: + intptr_t GetCount() const; + intptr_t GetCountIncludingHidden() const; + virtual void Notify(void * Ptr, TListNotification Action); + void SetCount(intptr_t Value); +protected: + intptr_t FHiddenCount; + bool FControlledAdd; + void Recount(); + +public: + bool AutoSort; + + TNamedObjectList(); + void AlphaSort(); + intptr_t Add(TObject * AObject); + virtual const TNamedObject * AtObject(intptr_t Index) const; + virtual TNamedObject * AtObject(intptr_t Index); + const TNamedObject * FindByName(const UnicodeString & Name) const; + TNamedObject * FindByName(const UnicodeString & Name); + /*__property int Count = { read = GetCount, write = SetCount }; + __property int CountIncludingHidden = { read = GetCountIncludingHidden };*/ +private: +}; + +int NamedObjectSortProc(void * Item1, void * Item2); + diff --git a/netbox/src/core/NeonIntf.cpp b/netbox/src/core/NeonIntf.cpp new file mode 100644 index 000000000..3515e0960 --- /dev/null +++ b/netbox/src/core/NeonIntf.cpp @@ -0,0 +1,307 @@ + +#include +#pragma hdrstop + +#include +#include + +#include "NeonIntf.h" +#include "Interface.h" +#include "CoreMain.h" +#include "Exceptions.h" +#include "WinSCPSecurity.h" +#include +#include + +#define SESSION_PROXY_AUTH_KEY "proxyauth" +#define SESSION_TLS_INIT_KEY "tlsinit" + +void NeonParseUrl(const UnicodeString & Url, ne_uri & uri) +{ + if (ne_uri_parse(StrToNeon(Url), &uri) != 0) + { + // should never happen + throw Exception(FMTLOAD(INVALID_URL, Url.c_str())); + } + + // Will never happen for initial URL, but may happen for redirect URLs + if (uri.port == 0) + { + uri.port = ne_uri_defaultport(uri.scheme); + } +} + +bool IsTlsUri(const ne_uri & uri) +{ + return SameText(StrFromNeon(uri.scheme), WebDAVSProtocol); +} + +struct TProxyAuthData +{ + UnicodeString UserName; + UnicodeString Password; +}; + +static int NeonProxyAuth( + void * UserData, const char * /*Realm*/, int Attempt, char * UserName, char * Password) +{ + TProxyAuthData * ProxyAuthData = static_cast(UserData); + + int Result; + // no point trying too many times as we always return the same credentials + // (maybe just one would be enough) + if (Attempt >= 2) + { + Result = 1; + } + else + { + strncpy(UserName, StrToNeon(ProxyAuthData->UserName), NE_ABUFSIZ); + strncpy(Password, StrToNeon(ProxyAuthData->Password), NE_ABUFSIZ); + Result = 0; + } + + return Result; +} + +ne_session * CreateNeonSession( + const ne_uri & uri, TProxyMethod ProxyMethod, const UnicodeString & ProxyHost, + int ProxyPort, const UnicodeString & ProxyUsername, const UnicodeString & ProxyPassword) +{ + ne_session * Session = ne_session_create(uri.scheme, uri.host, uri.port); + + if (ProxyMethod != ::pmNone) + { + if ((ProxyMethod == pmSocks4) || (ProxyMethod == pmSocks5)) + { + enum ne_sock_sversion vers = (ProxyMethod == pmSocks4) ? NE_SOCK_SOCKSV4A : NE_SOCK_SOCKSV5; + ne_session_socks_proxy(Session, vers, StrToNeon(ProxyHost), ProxyPort, StrToNeon(ProxyUsername), StrToNeon(ProxyPassword)); + } + else if (!ProxyHost.IsEmpty()) + { + ne_session_proxy(Session, StrToNeon(ProxyHost), ProxyPort); + + if (!ProxyUsername.IsEmpty()) + { + TProxyAuthData * ProxyAuthData = new TProxyAuthData(); + ProxyAuthData->UserName = ProxyUsername; + ProxyAuthData->Password = ProxyPassword; + ne_set_session_private(Session, SESSION_PROXY_AUTH_KEY, ProxyAuthData); + ne_set_proxy_auth(Session, NeonProxyAuth, ProxyAuthData); + } + else + { + // Enable (only) the Negotiate scheme for proxy + // authentication, if no username/password is + // configured. + ne_add_proxy_auth(Session, NE_AUTH_NEGOTIATE, nullptr, nullptr); + } + } + } + + ne_redirect_register(Session); + ne_set_useragent(Session, StrToNeon(FORMAT(L"%s/%s", GetAppNameString().c_str(), GetConfiguration()->GetVersion().c_str()))); + + return Session; +} + +void DestroyNeonSession(ne_session * Session) +{ + TProxyAuthData * ProxyAuthData = + static_cast(ne_get_session_private(Session, SESSION_PROXY_AUTH_KEY)); + if (ProxyAuthData != nullptr) + { + delete ProxyAuthData; + } + ne_session_destroy(Session); +} + +UnicodeString GetNeonError(ne_session * Session) +{ + return StrFromNeon(ne_get_error(Session)); +} + +void CheckNeonStatus(ne_session * Session, int NeonStatus, + const UnicodeString & HostName, const UnicodeString & CustomError) +{ + if (NeonStatus == NE_OK) + { + // noop + } + else + { + UnicodeString NeonError = GetNeonError(Session); + UnicodeString Error; + if (!CustomError.IsEmpty()) + { + Error = CustomError; + } + else + { + switch (NeonStatus) + { + case NE_ERROR: + // noop + DebugAssert(!NeonError.IsEmpty()); + Error = NeonError; + NeonError = L""; + break; + + case NE_LOOKUP: + Error = ReplaceStr(LoadStr(NET_TRANSL_HOST_NOT_EXIST2), L"%HOST%", HostName); + break; + + case NE_AUTH: + Error = LoadStr(AUTHENTICATION_FAILED); + break; + + case NE_PROXYAUTH: + Error = LoadStr(PROXY_AUTHENTICATION_FAILED); + break; + + case NE_CONNECT: + Error = LoadStr(CONNECTION_FAILED); + break; + + case NE_TIMEOUT: + Error = ReplaceStr(LoadStr(NET_TRANSL_TIMEOUT2), L"%HOST%", HostName); + break; + + case NE_REDIRECT: + { + char * Uri = ne_uri_unparse(ne_redirect_location(Session)); + Error = FMTLOAD(REQUEST_REDIRECTED, Uri); + ne_free(Uri); + } + break; + + case NE_FAILED: // never used by neon as of 0.30.0 + case NE_RETRY: // not sure if this is a public API + default: + DebugFail(); + Error = FORMAT(L"Unexpected neon error %d", NeonStatus); + break; + } + } + + throw ExtException(Error, NeonError); + } +} + +UnicodeString GetNeonRedirectUrl(ne_session * Session) +{ + const ne_uri * RedirectUri = ne_redirect_location(Session); + char * RedirectUriStr = ne_uri_unparse(RedirectUri); + UnicodeString Result = StrFromNeon(RedirectUriStr); + ne_free(RedirectUriStr); + return Result; +} + +#define MAX_REDIRECT_ATTEMPTS 5 + +void CheckRedirectLoop(const UnicodeString & RedirectUrl, TStrings * AttemptedUrls) +{ + if (AttemptedUrls->GetCount() > MAX_REDIRECT_ATTEMPTS) + { + throw Exception(LoadStr(TOO_MANY_REDIRECTS)); + } + else + { + // Make sure we've not attempted this URL before. + if (AttemptedUrls->IndexOf(RedirectUrl) >= 0) + { + throw Exception(LoadStr(REDIRECT_LOOP)); + } + AttemptedUrls->Add(RedirectUrl); + } +} + +extern "C" +{ + +void ne_init_ssl_session(struct ssl_st * Ssl, ne_session * Session) +{ + TNeonTlsInit OnNeonTlsInit = + reinterpret_cast(ne_get_session_private(Session, SESSION_TLS_INIT_KEY)); + if (DebugAlwaysTrue(OnNeonTlsInit != nullptr)) + { + OnNeonTlsInit(Ssl, Session); + } +} + +} // extern "C" + +void SetNeonTlsInit(ne_session * Session, TNeonTlsInit OnNeonTlsInit) +{ + ne_set_session_private(Session, SESSION_TLS_INIT_KEY, (void*)OnNeonTlsInit); +} + +AnsiString NeonExportCertificate(const ne_ssl_certificate * Certificate) +{ + char * AsciiCert = ne_ssl_cert_export(Certificate); + AnsiString Result = AsciiCert; + ne_free(AsciiCert); + return Result; +} + +bool NeonWindowsValidateCertificate(int & Failures, const AnsiString & AsciiCert) +{ + bool Result = false; + // We can accept only unknown certificate authority. + if (FLAGSET(Failures, NE_SSL_UNTRUSTED)) + { + unsigned char * Certificate; + size_t CertificateLen = ne_unbase64(AsciiCert.c_str(), &Certificate); +#ifndef __linux__ + if (CertificateLen > 0) + { + if (WindowsValidateCertificate(Certificate, CertificateLen)) + { + Failures &= ~NE_SSL_UNTRUSTED; + Result = true; + } + ne_free(Certificate); + } +#endif + } + return Result; +} + +UnicodeString NeonCertificateFailuresErrorStr(int Failures, const UnicodeString & HostName) +{ + int FailuresToList = Failures; + + UnicodeString Result; + if (FLAGSET(FailuresToList, NE_SSL_NOTYETVALID)) + { + AddToList(Result, LoadStr(CERT_ERR_CERT_NOT_YET_VALID), L" "); + FailuresToList &= ~NE_SSL_NOTYETVALID; + } + if (FLAGSET(FailuresToList, NE_SSL_EXPIRED)) + { + AddToList(Result, LoadStr(CERT_ERR_CERT_HAS_EXPIRED), L" "); + FailuresToList &= ~NE_SSL_EXPIRED; + } + // NEON checks certificate host name on its own + if (FLAGSET(FailuresToList, NE_SSL_IDMISMATCH)) + { + AddToList(Result, FMTLOAD(CERT_NAME_MISMATCH, HostName.c_str()), L" "); + FailuresToList &= ~NE_SSL_IDMISMATCH; + } + if (FLAGSET(FailuresToList, NE_SSL_UNTRUSTED)) + { + AddToList(Result, LoadStr(CERT_ERR_CERT_UNTRUSTED), L" "); + FailuresToList &= ~NE_SSL_UNTRUSTED; + } + if (FLAGSET(FailuresToList, NE_SSL_BADCHAIN)) + { + AddToList(Result, LoadStr(CERT_ERR_BAD_CHAIN), L" "); + FailuresToList &= ~NE_SSL_BADCHAIN; + } + // nb, NE_SSL_REVOKED is never used by OpenSSL implementation + if (FailuresToList != 0) + { + AddToList(Result, LoadStr(CERT_ERR_UNKNOWN), L" "); + } + return Result; +} diff --git a/netbox/src/core/NeonIntf.h b/netbox/src/core/NeonIntf.h new file mode 100644 index 000000000..8218748a1 --- /dev/null +++ b/netbox/src/core/NeonIntf.h @@ -0,0 +1,28 @@ + +#ifndef NeonIntfH +#define NeonIntfH + +#include +#include +#include + +#define StrToNeon(S) UTF8String(S).c_str() +#define StrFromNeon(S) UnicodeString(UTF8String(S)) + +void NeonParseUrl(const UnicodeString & Url, ne_uri & uri); +bool IsTlsUri(const ne_uri & uri); +ne_session * CreateNeonSession(const ne_uri & uri, TProxyMethod ProxyMethod, const UnicodeString & ProxyHost, + int ProxyPort, const UnicodeString & ProxyUsername, const UnicodeString & ProxyPassword); +void DestroyNeonSession(ne_session * Session); +UnicodeString GetNeonError(ne_session * Session); +void CheckNeonStatus(ne_session * Session, int NeonStatus, + const UnicodeString & HostName, const UnicodeString & CustomError = L""); +UnicodeString GetNeonRedirectUrl(ne_session * Session); +void CheckRedirectLoop(const UnicodeString & RedirectUrl, TStrings * AttemptedUrls); +typedef void (*TNeonTlsInit)(struct ssl_st * Ssl, ne_session * Session); +void SetNeonTlsInit(ne_session * Session, TNeonTlsInit OnNeonTlsInit); +AnsiString NeonExportCertificate(const ne_ssl_certificate * Certificate); +bool NeonWindowsValidateCertificate(int & Failures, const AnsiString & AsciiCert); +UnicodeString NeonCertificateFailuresErrorStr(int Failures, const UnicodeString & HostName); + +#endif diff --git a/netbox/src/core/Option.cpp b/netbox/src/core/Option.cpp new file mode 100644 index 000000000..c7656e5ad --- /dev/null +++ b/netbox/src/core/Option.cpp @@ -0,0 +1,357 @@ +#include +#pragma hdrstop + +#include +#include "Option.h" +#include "TextsCore.h" + +TOptions::TOptions() : + FSwitchMarks(L"/-"), + FSwitchValueDelimiters(L"=:"), + FParamCount(0), + FNoMoreSwitches(false) +{ +} + +void TOptions::Add(const UnicodeString & Value) +{ + if (!FNoMoreSwitches && + (Value.Length() == 2) && + (Value[1] == Value[2]) && + (FSwitchMarks.Pos(Value[1]) > 0)) + { + FNoMoreSwitches = true; + } + else + { + bool Switch = false; + intptr_t Index = 0; // shut up + wchar_t SwitchMark = L'\0'; + if (!FNoMoreSwitches && + (Value.Length() >= 2) && + (FSwitchMarks.Pos(Value[1]) > 0)) + { + Index = 2; + Switch = true; + SwitchMark = Value[1]; + while (Switch && (Index <= Value.Length())) + { + if (Value.IsDelimiter(FSwitchValueDelimiters, Index)) + { + break; + } + // this is to treat /home/martin as parameter, not as switch + else if ((Value[Index] != L'?') && !IsLetter(Value[Index])) + { + Switch = false; + break; + } + ++Index; + } + } + + TOption Option; + + if (Switch) + { + Option.Type = otSwitch; + Option.Name = Value.SubString(2, Index - 2); + Option.Value = Value.SubString(Index + 1, Value.Length()); + Option.ValueSet = (Index <= Value.Length()); + } + else + { + Option.Type = otParam; + Option.Value = Value; + Option.ValueSet = false; // unused + ++FParamCount; + } + + Option.Used = false; + Option.SwitchMark = SwitchMark; + + FOptions.push_back(Option); + } + + FOriginalOptions = FOptions; +} + +UnicodeString TOptions::GetParam(intptr_t AIndex) +{ + DebugAssert((AIndex >= 1) && (AIndex <= FParamCount)); + + UnicodeString Result; + size_t Idx = 0; + while ((Idx < FOptions.size()) && (AIndex > 0)) + { + if (FOptions[Idx].Type == otParam) + { + --AIndex; + if (AIndex == 0) + { + Result = FOptions[Idx].Value; + FOptions[Idx].Used = true; + } + } + ++Idx; + } + + return Result; +} + +bool TOptions::GetEmpty() const +{ + return FOptions.empty(); +} + +bool TOptions::FindSwitch(const UnicodeString & Switch, + UnicodeString & Value, intptr_t & ParamsStart, intptr_t & ParamsCount, bool CaseSensitive, bool & ValueSet) +{ + ParamsStart = 0; + ValueSet = false; + intptr_t Index = 0; + bool Found = false; + while ((Index < static_cast(FOptions.size())) && !Found) + { + if (FOptions[Index].Type == otParam) + { + ParamsStart++; + } + else if (FOptions[Index].Type == otSwitch) + { + if ((!CaseSensitive && ::AnsiSameText(FOptions[Index].Name, Switch)) || + (CaseSensitive && ::AnsiSameStr(FOptions[Index].Name, Switch))) + { + Found = true; + Value = FOptions[Index].Value; + ValueSet = FOptions[Index].ValueSet; + FOptions[Index].Used = true; + } + } + ++Index; + } + + ParamsCount = 0; + if (Found) + { + ParamsStart++; + while ((Index + ParamsCount < static_cast(FOptions.size())) && + (FOptions[Index + ParamsCount].Type == otParam)) + { + ParamsCount++; + } + } + else + { + ParamsStart = 0; + } + + return Found; +} + +bool TOptions::FindSwitch(const UnicodeString & Switch, UnicodeString & Value) +{ + bool ValueSet; + return FindSwitch(Switch, Value, ValueSet); +} + +bool TOptions::FindSwitch(const UnicodeString & Switch, UnicodeString & Value, bool & ValueSet) +{ + intptr_t ParamsStart; + intptr_t ParamsCount; + return FindSwitch(Switch, Value, ParamsStart, ParamsCount, false, ValueSet); +} + +bool TOptions::FindSwitch(const UnicodeString & Switch) +{ + UnicodeString Value; + intptr_t ParamsStart; + intptr_t ParamsCount; + bool ValueSet; + return FindSwitch(Switch, Value, ParamsStart, ParamsCount, false, ValueSet); +} + +bool TOptions::FindSwitchCaseSensitive(const UnicodeString & Switch) +{ + UnicodeString Value; + intptr_t ParamsStart; + intptr_t ParamsCount; + bool ValueSet; + return FindSwitch(Switch, Value, ParamsStart, ParamsCount, true, ValueSet); +} + +bool TOptions::FindSwitch(const UnicodeString & Switch, + TStrings * Params, intptr_t ParamsMax) +{ + return DoFindSwitch(Switch, Params, ParamsMax, false); +} +//--------------------------------------------------------------------------- +bool TOptions::FindSwitchCaseSensitive(const UnicodeString & Switch, + TStrings * Params, int ParamsMax) +{ + return DoFindSwitch(Switch, Params, ParamsMax, true); +} + +bool TOptions::DoFindSwitch(const UnicodeString & Switch, + TStrings * Params, intptr_t ParamsMax, bool CaseSensitive) +{ + UnicodeString Value; + intptr_t ParamsStart; + intptr_t ParamsCount; + bool ValueSet; + bool Result = FindSwitch(Switch, Value, ParamsStart, ParamsCount, CaseSensitive, ValueSet); + if (Result) + { + if ((ParamsMax >= 0) && (ParamsCount > ParamsMax)) + { + ParamsCount = ParamsMax; + } + + intptr_t Index = 0; + while (Index < ParamsCount) + { + Params->Add(GetParam(ParamsStart + Index)); + ++Index; + } + ParamsProcessed(ParamsStart, ParamsCount); + } + return Result; +} + +UnicodeString TOptions::SwitchValue(const UnicodeString & Switch, + const UnicodeString & Default) +{ + UnicodeString Value; + FindSwitch(Switch, Value); + if (Value.IsEmpty()) + { + Value = Default; + } + return Value; +} + +bool TOptions::SwitchValue(const UnicodeString & Switch, bool Default, bool DefaultOnNonExistence) +{ + bool Result = false; + int64_t IntValue = 0; + UnicodeString Value; + if (!FindSwitch(Switch, Value)) + { + Result = DefaultOnNonExistence; + } + else if (Value.IsEmpty()) + { + Result = Default; + } + else if (::SameText(Value, L"on")) + { + Result = true; + } + else if (::SameText(Value, L"off")) + { + Result = false; + } + else if (::TryStrToInt(Value, IntValue)) + { + Result = (IntValue != 0); + } + else + { + throw Exception(FMTLOAD(URL_OPTION_BOOL_VALUE_ERROR, Value.c_str())); + } + return Result; +} + +bool TOptions::SwitchValue(const UnicodeString & Switch, bool Default) +{ + return SwitchValue(Switch, Default, Default); +} + +bool TOptions::UnusedSwitch(UnicodeString & Switch) const +{ + bool Result = false; + size_t Index = 0; + while (!Result && (Index < FOptions.size())) + { + if ((FOptions[Index].Type == otSwitch) && + !FOptions[Index].Used) + { + Switch = FOptions[Index].Name; + Result = true; + } + ++Index; + } + + return Result; +} + +bool TOptions::WasSwitchAdded(UnicodeString & Switch, wchar_t & SwitchMark) const +{ + bool Result = + DebugAlwaysTrue(FOptions.size() > 0) && + (FOptions.back().Type == otSwitch); + if (Result) + { + const TOption & Option = FOptions.back(); + Switch = Option.Name; + SwitchMark = Option.SwitchMark; + } + return Result; +} + +void TOptions::ParamsProcessed(intptr_t ParamsStart, intptr_t ParamsCount) +{ + if (ParamsCount > 0) + { + DebugAssert((ParamsStart >= 0) && ((ParamsStart - ParamsCount + 1) <= FParamCount)); + + size_t Index = 0; + while ((Index < FOptions.size()) && (ParamsStart > 0)) + { + if (FOptions[Index].Type == otParam) + { + --ParamsStart; + + if (ParamsStart == 0) + { + while (ParamsCount > 0) + { + DebugAssert(Index < FOptions.size()); + DebugAssert(FOptions[Index].Type == otParam); + FOptions.erase(FOptions.begin() + Index); + --FParamCount; + --ParamsCount; + } + } + } + ++Index; + } + } +} + +void TOptions::LogOptions(TLogOptionEvent OnLogOption) +{ + for (size_t Index = 0; Index < FOriginalOptions.size(); ++Index) + { + const TOption & Option = FOriginalOptions[Index]; + UnicodeString LogStr; + switch (Option.Type) + { + case otParam: + LogStr = FORMAT(L"Parameter: %s", Option.Value.c_str()); + DebugAssert(Option.Name.IsEmpty()); + break; + + case otSwitch: + LogStr = + FORMAT(L"Switch: %c%s%s%s", + FSwitchMarks[1], Option.Name.c_str(), (Option.Value.IsEmpty() ? UnicodeString() : FSwitchValueDelimiters.SubString(1, 1)).c_str(), Option.Value.c_str()); + break; + + default: + DebugFail(); + break; + } + OnLogOption(LogStr); + } +} diff --git a/netbox/src/core/Option.h b/netbox/src/core/Option.h new file mode 100644 index 000000000..5b3b9ab45 --- /dev/null +++ b/netbox/src/core/Option.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +enum TOptionType +{ + otParam, + otSwitch +}; + +// typedef void (__closure *TLogOptionEvent)(const UnicodeString & LogStr); +DEFINE_CALLBACK_TYPE1(TLogOptionEvent, void, const UnicodeString & /*LogStr*/); + +class TOptions : public TObject +{ +public: + TOptions(); + + void Add(const UnicodeString & Option); + + // void ParseParams(const UnicodeString & Params); + + bool FindSwitch(const UnicodeString & Switch); + bool FindSwitch(const UnicodeString & Switch, UnicodeString & Value); + bool FindSwitch(const UnicodeString & Switch, UnicodeString & Value, bool & ValueSet); + bool FindSwitch(const UnicodeString & Switch, intptr_t & ParamsStart, + intptr_t & ParamsCount); + bool FindSwitch(const UnicodeString & Switch, TStrings * Params, + intptr_t ParamsMax = -1); + bool FindSwitchCaseSensitive(const UnicodeString & Switch); + bool FindSwitchCaseSensitive(const UnicodeString & Switch, TStrings * Params, + int ParamsMax = -1); + void ParamsProcessed(intptr_t Position, intptr_t Count); + UnicodeString SwitchValue(const UnicodeString & Switch, const UnicodeString & Default = L""); + bool SwitchValue(const UnicodeString & Switch, bool Default); + bool SwitchValue(const UnicodeString & Switch, bool Default, bool DefaultOnNonExistence); + bool UnusedSwitch(UnicodeString & Switch) const; + bool WasSwitchAdded(UnicodeString & Switch, wchar_t & SwitchMark) const; + + void LogOptions(TLogOptionEvent OnEnumOption); + + /*__property int ParamCount = { read = FParamCount }; + __property UnicodeString Param[int Index] = { read = GetParam }; + __property bool Empty = { read = GetEmpty };*/ + + intptr_t GetParamCount() const { return FParamCount; } + UnicodeString GetParam(intptr_t AIndex); + void Clear() { FOptions.resize(0); FNoMoreSwitches = false; FParamCount = 0; } + bool GetEmpty() const; + UnicodeString GetSwitchMarks() const { return FSwitchMarks; } + +protected: + UnicodeString FSwitchMarks; + UnicodeString FSwitchValueDelimiters; + + bool FindSwitch(const UnicodeString & Switch, + UnicodeString & Value, intptr_t & ParamsStart, intptr_t & ParamsCount, bool CaseSensitive, bool & ValueSet); + bool DoFindSwitch(const UnicodeString & Switch, TStrings * Params, + intptr_t ParamsMax, bool CaseInsensitive); + +private: + struct TOption : public TObject + { + TOption() : Type(otParam), ValueSet(false), Used(false), SwitchMark(0) {} + UnicodeString Name; + UnicodeString Value; + TOptionType Type; + bool ValueSet; + bool Used; + wchar_t SwitchMark; + }; + + typedef std::vector TOptionsVector; + TOptionsVector FOptions; + TOptionsVector FOriginalOptions; + intptr_t FParamCount; + bool FNoMoreSwitches; + + /*UnicodeString __fastcall GetParam(int Index); + bool __fastcall GetEmpty();*/ +}; + diff --git a/netbox/src/core/PuttyIntf.cpp b/netbox/src/core/PuttyIntf.cpp new file mode 100644 index 000000000..fe4df2726 --- /dev/null +++ b/netbox/src/core/PuttyIntf.cpp @@ -0,0 +1,802 @@ +#include +#pragma hdrstop + +#define PUTTY_DO_GLOBALS +#include +#include + +#include "PuttyIntf.h" +#include "Interface.h" +#include "SecureShell.h" +#include "CoreMain.h" +#include "TextsCore.h" + +#ifndef __linux__ +extern "C" +{ +#include +} +#endif +char sshver[50]; +const int platform_uses_x11_unix_by_default = TRUE; +std::recursive_mutex putty_section; +bool SaveRandomSeed; +char appname_[50]; +const char * const appname = appname_; +extern const int share_can_be_downstream = FALSE; +extern const int share_can_be_upstream = FALSE; + +const UnicodeString OriginalPuttyRegistryStorageKey(L"Software\\SimonTatham\\PuTTY"); +const UnicodeString KittyRegistryStorageKey(L"Software\\9bis.com\\KiTTY"); +const UnicodeString OriginalPuttyExecutable("putty.exe"); +const UnicodeString KittyExecutable("kitty.exe"); + +void PuttyInitialize() +{ + SaveRandomSeed = true; + + // make sure random generator is initialised, so random_save_seed() + // in destructor can proceed + random_ref(); + + flags = FLAG_VERBOSE/* | FLAG_SYNCAGENT*/; // verbose log + + sk_init(); + + AnsiString VersionString = AnsiString(GetSshVersionString()); + DebugAssert(!VersionString.IsEmpty() && (static_cast(VersionString.Length()) < _countof(sshver))); + strncpy(sshver, VersionString.c_str(), sizeof(sshver)); + AnsiString AppName = AnsiString(GetAppNameString()); + DebugAssert(!AppName.IsEmpty() && (static_cast(AppName.Length()) < _countof(appname_))); + strncpy(appname_, AppName.c_str(), sizeof(appname_)); +} + +void PuttyFinalize() +{ + if (SaveRandomSeed) + { + random_save_seed(); + } + random_unref(); + + sk_cleanup(); +#ifndef __linux__ + win_misc_cleanup(); + win_secur_cleanup(); +#endif + ec_cleanup(); +} + +void DontSaveRandomSeed() +{ + SaveRandomSeed = false; +} + +extern "C" char * do_select(Plug plug, SOCKET skt, int startup) +{ + void * frontend = nullptr; + + if (!is_ssh(plug) && !is_pfwd(plug)) + { + // If it is not SSH/PFwd plug, then it must be Proxy plug. + // Get SSH/PFwd plug which it wraps. + Proxy_Socket ProxySocket = (reinterpret_cast(plug))->proxy_socket; + plug = ProxySocket->plug; + } + + bool pfwd = is_pfwd(plug) != 0; + if (pfwd) + { + plug = static_cast(get_pfwd_backend(plug)); + } + + frontend = get_ssh_frontend(plug); + DebugAssert(frontend); + + TSecureShell * SecureShell = NB_STATIC_DOWNCAST(TSecureShell, frontend); + if (!pfwd) + { + SecureShell->UpdateSocket(skt, startup != 0); + } + else + { + SecureShell->UpdatePortFwdSocket(skt, startup != 0); + } + + return nullptr; +} + +int from_backend(void * frontend, int is_stderr, const char * data, int datalen) +{ + DebugAssert(frontend); + if (is_stderr >= 0) + { + DebugAssert((is_stderr == 0) || (is_stderr == 1)); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->FromBackend((is_stderr == 1), reinterpret_cast(data), datalen); + } + else + { + DebugAssert(is_stderr == -1); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->CWrite(data, datalen); + } + return 0; +} + +int from_backend_untrusted(void * /*frontend*/, const char * /*data*/, int /*len*/) +{ + // currently used with authentication banner only, + // for which we have own interface display_banner + return 0; +} + +int from_backend_eof(void * /*frontend*/) +{ + return FALSE; +} +//--------------------------------------------------------------------------- +int GetUserpassInput(prompts_t * p, const uint8_t * /*in*/, int /*inlen*/); + +int get_userpass_input(prompts_t * p, const uint8_t * in, int inlen) +{ + return GetUserpassInput(p, in, inlen); +} + +int GetUserpassInput(prompts_t * p, const uint8_t * /*in*/, int /*inlen*/) +{ + DebugAssert(p != nullptr); + TSecureShell * SecureShell = NB_STATIC_DOWNCAST(TSecureShell, p->frontend); + DebugAssert(SecureShell != nullptr); + + int Result; + std::unique_ptr Prompts(new TStringList()); + std::unique_ptr Results(new TStringList()); + { + UnicodeString Name = UTF8ToString(p->name); + UnicodeString AName = Name; + TPromptKind PromptKind = SecureShell->IdentifyPromptKind(AName); + bool UTF8Prompt = (PromptKind != pkPassphrase); + + for (size_t Index = 0; Index < p->n_prompts; ++Index) + { + prompt_t * Prompt = p->prompts[Index]; + UnicodeString S; + if (UTF8Prompt) + { + S = UTF8ToString(Prompt->prompt); + } + else + { + S = UnicodeString(AnsiString(Prompt->prompt)); + } + Prompts->AddObject(S, reinterpret_cast(static_cast(FLAGMASK(Prompt->echo, pupEcho)))); + // this fails, when new passwords do not match on change password prompt, + // and putty retries the prompt + DebugAssert(Prompt->resultsize == 0); + Results->Add(L""); + } + + UnicodeString Instructions = UTF8ToString(p->instruction); + if (SecureShell->PromptUser(p->to_server != 0, Name, p->name_reqd != 0, + Instructions, p->instr_reqd != 0, Prompts.get(), Results.get())) + { + for (size_t Index = 0; Index < p->n_prompts; ++Index) + { + prompt_t * Prompt = p->prompts[Index]; + RawByteString S; + if (UTF8Prompt) + { + S = RawByteString(UTF8String(Results->GetString(Index))); + } + else + { + S = RawByteString(AnsiString(Results->GetString(Index))); + } + prompt_set_result(Prompt, S.c_str()); + } + Result = 1; + } + else + { + Result = 0; + } + } + __finally + { +// delete Prompts; +// delete Results; + }; + + return Result; +} + +char * get_ttymode(void * /*frontend*/, const char * /*mode*/) +{ + // should never happen when Config.nopty == TRUE + DebugFail(); + return nullptr; +} + +void logevent(void * frontend, const char * str) +{ + // Frontend maybe NULL here + if (frontend != nullptr) + { + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->PuttyLogEvent(str); + } +} + +void connection_fatal(void * frontend, const char * fmt, ...) +{ + va_list Param; + std::string Buf; + Buf.resize(32 * 1024); + va_start(Param, fmt); + vsnprintf((char *)Buf.c_str(), Buf.size(), fmt, Param); + Buf[Buf.size() - 1] = '\0'; + va_end(Param); + + DebugAssert(frontend != nullptr); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->PuttyFatalError(UnicodeString(Buf.c_str())); +} + +int verify_ssh_host_key(void * frontend, char * host, int port, char * keytype, + char * keystr, char * fingerprint, void (* /*callback*/)(void * ctx, int result), + void * /*ctx*/) +{ + DebugAssert(frontend != nullptr); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->VerifyHostKey(UnicodeString(host), port, keytype, keystr, fingerprint); + + // We should return 0 when key was not confirmed, we throw exception instead. + return 1; +} + +int have_ssh_host_key(void * frontend, const char * hostname, int port, + const char * keytype) +{ + DebugAssert(frontend != nullptr); + return static_cast(frontend)->HaveHostKey(hostname, port, keytype) ? 1 : 0; +} + +int askalg(void * frontend, const char * algtype, const char * algname, + void (* /*callback*/)(void * ctx, int result), void * /*ctx*/) +{ + DebugAssert(frontend != nullptr); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->AskAlg(algtype, algname); + + // We should return 0 when alg was not confirmed, we throw exception instead. + return 1; +} + +int askhk(void * /*frontend*/, const char * /*algname*/, const char * /*betteralgs*/, + void (* /*callback*/)(void *ctx, int result), void * /*ctx*/) +{ + return 1; +} + +void old_keyfile_warning(void) +{ + // no reference to TSecureShell instance available +} + +void display_banner(void * frontend, const char * banner, int size) +{ + DebugAssert(frontend); + UnicodeString Banner(banner, size); + (NB_STATIC_DOWNCAST(TSecureShell, frontend))->DisplayBanner(Banner); +} + +static void SSHFatalError(const char * Format, va_list Param) +{ + std::string Buf; + Buf.resize(32 * 1024); + vsnprintf((char *)Buf.c_str(), Buf.size(), Format, Param); + Buf[Buf.size() - 1] = '\0'; + + // Only few calls from putty\winnet.c might be connected with specific + // TSecureShell. Otherwise called only for really fatal errors + // like 'out of memory' from putty\ssh.c. + throw ESshFatal(nullptr, Buf.c_str()); +} + +void fatalbox(const char * fmt, ...) +{ + va_list Param; + va_start(Param, fmt); + SSHFatalError(fmt, Param); + va_end(Param); +} + +void modalfatalbox(const char * fmt, ...) +{ + va_list Param; + va_start(Param, fmt); + SSHFatalError(fmt, Param); + va_end(Param); +} + +void nonfatal(const char * fmt, ...) +{ + va_list Param; + va_start(Param, fmt); + SSHFatalError(fmt, Param); + va_end(Param); +} +//--------------------------------------------------------------------------- +void CleanupExit(int /*code*/); +void cleanup_exit(int code) +{ + CleanupExit(code); +} + +void CleanupExit(int /*code*/) +{ + throw ESshFatal(nullptr, L""); +} +//--------------------------------------------------------------------------- +int askappend(void * /*frontend*/, Filename * /*filename*/, + void (* /*callback*/)(void * ctx, int result), void * /*ctx*/) +{ + // this is called from logging.c of putty, which is never used with WinSCP + DebugFail(); + return 0; +} + +void ldisc_echoedit_update(void * /*handle*/) +{ + DebugFail(); +} + +void agent_schedule_callback(void (* /*callback*/)(void *, void *, int), + void * /*callback_ctx*/, void * /*data*/, int /*len*/) +{ + DebugFail(); +} + +void notify_remote_exit(void * /*frontend*/) +{ + // nothing +} + +void update_specials_menu(void * /*frontend*/) +{ + // nothing +} + +unsigned long schedule_timer(int ticks, timer_fn_t /*fn*/, void * /*ctx*/) +{ + return ticks + ::GetTickCount(); +} + +void expire_timer_context(void * /*ctx*/) +{ + // nothing +} + +Pinger pinger_new(Conf * /*conf*/, Backend * /*back*/, void * /*backhandle*/) +{ + return nullptr; +} + +void pinger_reconfig(Pinger /*pinger*/, Conf * /*oldconf*/, Conf * /*newconf*/) +{ + // nothing +} + +void pinger_free(Pinger /*pinger*/) +{ + // nothing +} + +void set_busy_status(void * /*frontend*/, int /*status*/) +{ + // nothing +} + +void platform_get_x11_auth(struct X11Display * /*display*/, Conf * /*conf*/) +{ + // nothing, therefore no auth. +} + +// Based on PuTTY's settings.c +char * get_remote_username(Conf * conf) +{ + char * username = conf_get_str(conf, CONF_username); + char * result; + if (*username) + { + result = dupstr(username); + } + else + { + result = nullptr; + } + return result; +} + +static long OpenWinSCPKey(HKEY Key, const char * SubKey, HKEY * Result, bool CanCreate) +{ + long R; + DebugAssert(GetConfiguration() != nullptr); + + DebugAssert(Key == HKEY_CURRENT_USER); + DebugUsedParam(Key); + + UnicodeString RegKey = SubKey; + intptr_t PuttyKeyLen = OriginalPuttyRegistryStorageKey.Length(); + DebugAssert(RegKey.SubString(1, PuttyKeyLen) == OriginalPuttyRegistryStorageKey); + RegKey = RegKey.SubString(PuttyKeyLen + 1, RegKey.Length() - PuttyKeyLen); + if (!RegKey.IsEmpty()) + { + DebugAssert(RegKey[1] == L'\\'); + RegKey.Delete(1, 1); + } + + if (RegKey.IsEmpty()) + { + *Result = static_cast(nullptr); + R = ERROR_SUCCESS; + } + else + { + // we expect this to be called only from verify_host_key() or store_host_key() + DebugAssert(RegKey == L"SshHostKeys"); + + std::unique_ptr Storage(GetConfiguration()->CreateConfigStorage()); + Storage->SetAccessMode((CanCreate ? smReadWrite : smRead)); + if (Storage->OpenSubKey(RegKey, CanCreate)) + { + *Result = reinterpret_cast(Storage.release()); + R = ERROR_SUCCESS; + } + else + { + R = ERROR_OPEN_FAILED; + } + } + + return R; +} + +long reg_open_winscp_key(HKEY Key, const char * SubKey, HKEY * Result) +{ + return OpenWinSCPKey(Key, SubKey, Result, false); +} + +long reg_create_winscp_key(HKEY Key, const char * SubKey, HKEY * Result) +{ + return OpenWinSCPKey(Key, SubKey, Result, true); +} + +long reg_query_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long * /*Reserved*/, + unsigned long * Type, uint8_t * Data, unsigned long * DataSize) +{ + long R; + DebugAssert(GetConfiguration() != nullptr); + + THierarchicalStorage * Storage = reinterpret_cast(Key); + AnsiString Value; + if (Storage == nullptr) + { + if (UnicodeString(ValueName) == L"RandSeedFile") + { + Value = AnsiString(GetConfiguration()->GetRandomSeedFileName()); + R = ERROR_SUCCESS; + } + else + { + DebugFail(); + R = ERROR_READ_FAULT; + } + } + else + { + if (Storage->ValueExists(ValueName)) + { + Value = AnsiString(Storage->ReadStringRaw(ValueName, L"")); + R = ERROR_SUCCESS; + } + else + { + R = ERROR_READ_FAULT; + } + } + + if (R == ERROR_SUCCESS) + { + DebugAssert(Type != nullptr); + *Type = REG_SZ; + char * DataStr = reinterpret_cast(Data); + int sz = static_cast(*DataSize); + if (sz > 0) + { + strncpy(DataStr, Value.c_str(), sz); + DataStr[sz - 1] = '\0'; + } + *DataSize = static_cast(strlen(DataStr)); + } + + return R; +} + +long reg_set_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long /*Reserved*/, + unsigned long Type, const uint8_t * Data, unsigned long DataSize) +{ + DebugAssert(GetConfiguration() != nullptr); + + DebugAssert(Type == REG_SZ); + DebugUsedParam(Type); + THierarchicalStorage * Storage = reinterpret_cast(Key); + DebugAssert(Storage != nullptr); + if (Storage != nullptr) + { + UnicodeString Value(reinterpret_cast(Data), DataSize - 1); + Storage->WriteStringRaw(ValueName, Value); + } + + return ERROR_SUCCESS; +} + +long reg_close_winscp_key(HKEY Key) +{ + DebugAssert(GetConfiguration() != nullptr); + + THierarchicalStorage * Storage = reinterpret_cast(Key); + if (Storage != nullptr) + { + SAFE_DESTROY_EX(THierarchicalStorage, Storage); + } + + return ERROR_SUCCESS; +} + +TKeyType GetKeyType(const UnicodeString & AFileName) +{ + DebugAssert(ktUnopenable == SSH_KEYTYPE_UNOPENABLE); +#ifndef __linux__ + DebugAssert(ktSSH2PublicOpenSSH == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH); +#endif + UTF8String UtfFileName = UTF8String(::ExpandEnvironmentVariables(AFileName)); + Filename * KeyFile = filename_from_str(UtfFileName.c_str()); + TKeyType Result = static_cast(key_type(KeyFile)); + filename_free(KeyFile); + return Result; +} + +bool IsKeyEncrypted(TKeyType KeyType, const UnicodeString & FileName, UnicodeString & Comment) +{ + UTF8String UtfFileName = UTF8String(::ExpandEnvironmentVariables(FileName)); + Filename * KeyFile = filename_from_str(UtfFileName.c_str()); + bool Result; + char * CommentStr = nullptr; + switch (KeyType) + { + case ktSSH2: + Result = (ssh2_userkey_encrypted(KeyFile, &CommentStr) != 0); + break; + + case ktOpenSSHPEM: + case ktOpenSSHNew: + case ktSSHCom: + Result = (import_encrypted(KeyFile, KeyType, &CommentStr) != 0); + break; + + default: + DebugFail(); + Result = false; + break; + } + + if (CommentStr != nullptr) + { + Comment = UnicodeString(AnsiString(CommentStr)); + // ktOpenSSH has no comment, PuTTY defaults to file path + if (Comment == FileName) + { + Comment = base::ExtractFileName(FileName, false); + } + sfree(CommentStr); + } + + return Result; +} + +TPrivateKey * LoadKey(TKeyType KeyType, const UnicodeString & FileName, const UnicodeString & Passphrase) +{ + UTF8String UtfFileName = UTF8String(::ExpandEnvironmentVariables(FileName)); + Filename * KeyFile = filename_from_str(UtfFileName.c_str()); + AnsiString AnsiPassphrase = AnsiString(Passphrase); + struct ssh2_userkey * Ssh2Key = nullptr; + const char * ErrorStr = nullptr; + + switch (KeyType) + { + case ktSSH2: + Ssh2Key = ssh2_load_userkey(KeyFile, (char *)AnsiPassphrase.c_str(), &ErrorStr); + break; + + case ktOpenSSHPEM: + case ktOpenSSHNew: + case ktSSHCom: + Ssh2Key = import_ssh2(KeyFile, KeyType, (char *)AnsiPassphrase.c_str(), &ErrorStr); + break; + + default: + DebugFail(); + break; + } + + Shred(AnsiPassphrase); + + if (Ssh2Key == nullptr) + { + UnicodeString Error = UnicodeString(ErrorStr); + // While theoretically we may get "unable to open key file" and + // so we should check system error code, + // we actully never get here unless we call KeyType previously + // and handle ktUnopenable accordingly. + throw Exception(Error); + } + else if (Ssh2Key == SSH2_WRONG_PASSPHRASE) + { + throw Exception(LoadStr(AUTH_TRANSL_WRONG_PASSPHRASE)); + } + + return reinterpret_cast(Ssh2Key); +} + +void ChangeKeyComment(TPrivateKey * PrivateKey, const UnicodeString & Comment) +{ + AnsiString AnsiComment(Comment); + struct ssh2_userkey * Ssh2Key = reinterpret_cast(PrivateKey); + sfree(Ssh2Key->comment); + Ssh2Key->comment = dupstr(AnsiComment.c_str()); +} + +void SaveKey(TKeyType KeyType, const UnicodeString & FileName, + const UnicodeString & Passphrase, TPrivateKey * PrivateKey) +{ + UTF8String UtfFileName = UTF8String(::ExpandEnvironmentVariables(FileName)); + Filename * KeyFile = filename_from_str(UtfFileName.c_str()); + struct ssh2_userkey * Ssh2Key = reinterpret_cast(PrivateKey); + AnsiString AnsiPassphrase = AnsiString(Passphrase); + const char * PassphrasePtr = (AnsiPassphrase.IsEmpty() ? nullptr : AnsiPassphrase.c_str()); + switch (KeyType) + { + case ktSSH2: + if (!ssh2_save_userkey(KeyFile, Ssh2Key, (char *)PassphrasePtr)) + { + int Error = errno; + throw EOSExtException(FMTLOAD(KEY_SAVE_ERROR, FileName.c_str()), Error); + } + break; + + default: + DebugFail(); + break; + } +} + +void FreeKey(TPrivateKey * PrivateKey) +{ + struct ssh2_userkey * Ssh2Key = reinterpret_cast(PrivateKey); + Ssh2Key->alg->freekey(Ssh2Key->data); + sfree(Ssh2Key); +} + +int64_t ParseSize(const UnicodeString & SizeStr) +{ + AnsiString AnsiSizeStr = AnsiString(SizeStr); + return parse_blocksize(AnsiSizeStr.c_str()); +} + +bool HasGSSAPI(const UnicodeString & CustomPath) +{ + static int has = -1; + if (has < 0) + { + Conf * conf = conf_new(); + ssh_gss_liblist * List = nullptr; + try__finally + { + SCOPE_EXIT + { + ssh_gss_cleanup(List); + conf_free(conf); + }; + Filename * filename = filename_from_str(UTF8String(CustomPath).c_str()); + conf_set_filename(conf, CONF_ssh_gss_custom, filename); + filename_free(filename); + List = ssh_gss_setup(conf); + for (intptr_t Index = 0; (has <= 0) && (Index < List->nlibraries); ++Index) + { + ssh_gss_library * library = &List->libraries[Index]; + Ssh_gss_ctx ctx; + ::memset(&ctx, 0, sizeof(ctx)); + has = + ((library->acquire_cred(library, &ctx) == SSH_GSS_OK) && + (library->release_cred(library, &ctx) == SSH_GSS_OK)) ? 1 : 0; + } + } + __finally + { + ssh_gss_cleanup(List); + conf_free(conf); + }; + + if (has < 0) + { + has = 0; + } + } + return (has > 0); +} + +static void DoNormalizeFingerprint(UnicodeString & Fingerprint, UnicodeString & KeyType) +{ + const wchar_t NormalizedSeparator = L'-'; + const int MaxCount = 10; + const ssh_signkey * SignKeys[MaxCount]; + int Count = _countof(SignKeys); + // We may use find_pubkey_alg, but it gets complicated with normalized fingerprint + // as the names have different number of dashes + get_hostkey_algs(&Count, SignKeys); + + for (intptr_t Index = 0; Index < Count; Index++) + { + const ssh_signkey * SignKey = SignKeys[Index]; + UnicodeString Name = UnicodeString(SignKey->name); + if (StartsStr(Name + L" ", Fingerprint)) + { + intptr_t LenStart = Name.Length() + 1; + Fingerprint[LenStart] = NormalizedSeparator; + intptr_t Space = Fingerprint.Pos(L" "); + DebugAssert(IsNumber(Fingerprint.SubString(LenStart + 1, Space - LenStart - 1))); + Fingerprint.Delete(LenStart + 1, Space - LenStart); + Fingerprint = ReplaceChar(Fingerprint, L':', NormalizedSeparator); + KeyType = UnicodeString(SignKey->keytype); + return; + } + else if (StartsStr(Name + NormalizedSeparator, Fingerprint)) + { + KeyType = UnicodeString(SignKey->keytype); + return; + } + } +} + +UnicodeString NormalizeFingerprint(const UnicodeString & AFingerprint) +{ + UnicodeString Fingerprint = AFingerprint; + UnicodeString KeyType; // unused + DoNormalizeFingerprint(Fingerprint, KeyType); + return Fingerprint; +} + +UnicodeString GetKeyTypeFromFingerprint(const UnicodeString & AFingerprint) +{ + UnicodeString Fingerprint = AFingerprint; + UnicodeString KeyType; + DoNormalizeFingerprint(Fingerprint, KeyType); + return KeyType; +} + +UnicodeString GetPuTTYVersion() +{ + // "Release 0.64" + // "Pre-release 0.65:2015-07-20.95501a1" + // "Development snapshot 2015-12-22.51465fa" + UnicodeString Result = get_putty_version(); + // Skip "Release", "Pre-release", "Development snapshot" + intptr_t P = Result.LastDelimiter(L" "); + Result.Delete(1, P); + return Result; +} + +UnicodeString Sha256(const char * Data, size_t Size) +{ + unsigned char Digest[32]; + putty_SHA256_Simple(Data, static_cast(Size), Digest); + UnicodeString Result(BytesToHex(Digest, _countof(Digest))); + return Result; +} + diff --git a/netbox/src/core/PuttyIntf.h b/netbox/src/core/PuttyIntf.h new file mode 100644 index 000000000..62f1763f4 --- /dev/null +++ b/netbox/src/core/PuttyIntf.h @@ -0,0 +1,28 @@ + +#ifndef PuttyIntfH +#define PuttyIntfH + +#include "PuttyTools.h" + +void PuttyInitialize(); +void PuttyFinalize(); + +void DontSaveRandomSeed(); + +#ifndef MPEXT +#define MPEXT +#endif +extern "C" +{ +#include +#include +#include +#include +#include +// Defined in misc.h - Conflicts with std::min/max +#undef min +#undef max + +} + +#endif diff --git a/netbox/src/core/PuttyTools.h b/netbox/src/core/PuttyTools.h new file mode 100644 index 000000000..24f8c1c02 --- /dev/null +++ b/netbox/src/core/PuttyTools.h @@ -0,0 +1,40 @@ +#pragma once + +enum TKeyType +{ + ktUnopenable, + ktUnknown, + ktSSH1, + ktSSH2, + ktOpenSSHAuto, + ktOpenSSHPEM, + ktOpenSSHNew, + ktSSHCom, + ktSSH1Public, + ktSSH2PublicRFC4716, + ktSSH2PublicOpenSSH, +}; + +TKeyType GetKeyType(const UnicodeString & AFileName); +UnicodeString GetKeyTypeName(TKeyType KeyType); +bool IsKeyEncrypted(TKeyType KeyType, const UnicodeString & FileName, UnicodeString & Comment); +struct TPrivateKey; +TPrivateKey * LoadKey(TKeyType KeyType, const UnicodeString & FileName, const UnicodeString & Passphrase); +void ChangeKeyComment(TPrivateKey * PrivateKey, const UnicodeString & Comment); +void SaveKey(TKeyType KeyType, const UnicodeString & FileName, + const UnicodeString & Passphrase, TPrivateKey * PrivateKey); +void FreeKey(TPrivateKey * PrivateKey); + +int64_t ParseSize(const UnicodeString & SizeStr); + +bool HasGSSAPI(const UnicodeString & CustomPath); + +void AES256EncodeWithMAC(char * Data, size_t Len, const char * Password, + size_t PasswordLen, const char * Salt); + +UnicodeString NormalizeFingerprint(const UnicodeString & AFingerprint); +UnicodeString GetKeyTypeFromFingerprint(const UnicodeString & AFingerprint); + +UnicodeString GetPuTTYVersion(); + +UnicodeString Sha256(const char * Data, size_t Size); diff --git a/netbox/src/core/Queue.cpp b/netbox/src/core/Queue.cpp new file mode 100644 index 000000000..0bc77bc81 --- /dev/null +++ b/netbox/src/core/Queue.cpp @@ -0,0 +1,2715 @@ +#include +#pragma hdrstop + +#include + +#include "Terminal.h" +#include "Queue.h" + +class TBackgroundTerminal; + +class TUserAction : public TObject +{ +NB_DISABLE_COPY(TUserAction) +public: + explicit TUserAction() {} + virtual ~TUserAction() {} + virtual void Execute(void * Arg) = 0; + virtual bool Force() { return false; } +}; + +class TNotifyAction : public TUserAction +{ +NB_DISABLE_COPY(TNotifyAction) +public: + explicit TNotifyAction(TNotifyEvent AOnNotify) : + OnNotify(AOnNotify), + Sender(nullptr) + { + } + + virtual void Execute(void * /*Arg*/) + { + if (OnNotify != nullptr) + { + OnNotify(Sender); + } + } + + TNotifyEvent OnNotify; + TObject * Sender; +}; + +class TInformationUserAction : public TUserAction +{ +NB_DISABLE_COPY(TInformationUserAction) +public: + explicit TInformationUserAction(TInformationEvent AOnInformation) : + OnInformation(AOnInformation), + Terminal(nullptr), + Status(false), + Phase(0) + { + } + + virtual void Execute(void * /*Arg*/) + { + if (OnInformation != nullptr) + { + OnInformation(Terminal, Str, Status, Phase); + } + } + + virtual bool Force() + { + // we need to propagate mainly the end-phase event even, when user cancels + // the connection, so that authentication window is closed + return TUserAction::Force() || (Phase >= 0); + } + + TInformationEvent OnInformation; + TTerminal * Terminal; + UnicodeString Str; + bool Status; + intptr_t Phase; +}; + +class TQueryUserAction : public TUserAction +{ +NB_DISABLE_COPY(TQueryUserAction) +public: + explicit TQueryUserAction(TQueryUserEvent AOnQueryUser) : + OnQueryUser(AOnQueryUser), + Sender(nullptr), + MoreMessages(nullptr), + Answers(0), + Params(nullptr), + Answer(0), + Type(qtConfirmation) + { + } + + virtual void Execute(void * Arg) + { + if (OnQueryUser != nullptr) + { + OnQueryUser(Sender, Query, MoreMessages, Answers, Params, Answer, Type, Arg); + } + } + + TQueryUserEvent OnQueryUser; + TObject * Sender; + UnicodeString Query; + TStrings * MoreMessages; + uintptr_t Answers; + const TQueryParams * Params; + uintptr_t Answer; + TQueryType Type; +}; + +class TPromptUserAction : public TUserAction +{ +NB_DISABLE_COPY(TPromptUserAction) +public: + explicit TPromptUserAction(TPromptUserEvent AOnPromptUser) : + OnPromptUser(AOnPromptUser), + Terminal(nullptr), + Kind(pkPrompt), + Prompts(nullptr), + Results(new TStringList()), + Result(false) + { + } + + virtual ~TPromptUserAction() + { + SAFE_DESTROY(Results); + } + + virtual void Execute(void * Arg) + { + if (OnPromptUser != nullptr) + { + OnPromptUser(Terminal, Kind, Name, Instructions, Prompts, Results, Result, Arg); + } + } + + TPromptUserEvent OnPromptUser; + TTerminal * Terminal; + TPromptKind Kind; + UnicodeString Name; + UnicodeString Instructions; + TStrings * Prompts; + TStrings * Results; + bool Result; +}; + +class TShowExtendedExceptionAction : public TUserAction +{ +NB_DISABLE_COPY(TShowExtendedExceptionAction) +public: + explicit TShowExtendedExceptionAction(TExtendedExceptionEvent AOnShowExtendedException) : + OnShowExtendedException(AOnShowExtendedException), + Terminal(nullptr), + E(nullptr) + { + } + + virtual void Execute(void * Arg) + { + if (OnShowExtendedException != nullptr) + { + OnShowExtendedException(Terminal, E, Arg); + } + } + + TExtendedExceptionEvent OnShowExtendedException; + TTerminal * Terminal; + Exception * E; +}; + +class TDisplayBannerAction : public TUserAction +{ +NB_DISABLE_COPY(TDisplayBannerAction) +public: + explicit TDisplayBannerAction(TDisplayBannerEvent AOnDisplayBanner) : + OnDisplayBanner(AOnDisplayBanner), + Terminal(nullptr), + NeverShowAgain(false), + Options(0) + { + } + + virtual void Execute(void * /*Arg*/) + { + if (OnDisplayBanner != nullptr) + { + OnDisplayBanner(Terminal, SessionName, Banner, NeverShowAgain, Options); + } + } + + TDisplayBannerEvent OnDisplayBanner; + TTerminal * Terminal; + UnicodeString SessionName; + UnicodeString Banner; + bool NeverShowAgain; + intptr_t Options; +}; + +class TReadDirectoryAction : public TUserAction +{ +NB_DISABLE_COPY(TReadDirectoryAction) +public: + explicit TReadDirectoryAction(TReadDirectoryEvent AOnReadDirectory) : + OnReadDirectory(AOnReadDirectory), + Sender(nullptr), + ReloadOnly(false) + { + } + + virtual void Execute(void * /*Arg*/) + { + if (OnReadDirectory != nullptr) + { + OnReadDirectory(Sender, ReloadOnly); + } + } + + TReadDirectoryEvent OnReadDirectory; + TObject * Sender; + bool ReloadOnly; +}; + +class TReadDirectoryProgressAction : public TUserAction +{ +NB_DISABLE_COPY(TReadDirectoryProgressAction) +public: + explicit TReadDirectoryProgressAction(TReadDirectoryProgressEvent AOnReadDirectoryProgress) : + OnReadDirectoryProgress(AOnReadDirectoryProgress), + Sender(nullptr), + Progress(0), + ResolvedLinks(0), + Cancel(false) + { + } + + virtual void Execute(void * /*Arg*/) + { + if (OnReadDirectoryProgress != nullptr) + { + OnReadDirectoryProgress(Sender, Progress, ResolvedLinks, Cancel); + } + } + + TReadDirectoryProgressEvent OnReadDirectoryProgress; + TObject * Sender; + intptr_t Progress; + intptr_t ResolvedLinks; + bool Cancel; +}; + +class TTerminalItem : public TSignalThread +{ +friend class TQueueItem; +friend class TBackgroundTerminal; +NB_DISABLE_COPY(TTerminalItem) +NB_DECLARE_CLASS(TTerminalItem) +public: + explicit TTerminalItem(TTerminalQueue * Queue); + virtual ~TTerminalItem(); + virtual void Init(intptr_t Index); + + void Process(TQueueItem * Item); + bool ProcessUserAction(void * Arg); + void Cancel(); + void Idle(); + bool Pause(); + bool Resume(); + +protected: + TTerminalQueue * FQueue; + TBackgroundTerminal * FTerminal; + TQueueItem * FItem; + TCriticalSection FCriticalSection; + TUserAction * FUserAction; + bool FCancel; + bool FPause; + + virtual void ProcessEvent(); + virtual void Finished(); + bool WaitForUserAction(TQueueItem::TStatus ItemStatus, TUserAction * UserAction); + bool OverrideItemStatus(TQueueItem::TStatus & ItemStatus); + + void TerminalQueryUser(TObject * Sender, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * Params, uintptr_t & Answer, TQueryType Type, void * Arg); + void TerminalPromptUser(TTerminal * Terminal, TPromptKind Kind, + const UnicodeString & Name, const UnicodeString & Instructions, + TStrings * Prompts, TStrings * Results, bool & Result, void * Arg); + void TerminalShowExtendedException(TTerminal * Terminal, + Exception * E, void * Arg); + void OperationFinished(TFileOperation Operation, TOperationSide Side, + bool Temp, const UnicodeString & AFileName, bool Success, + TOnceDoneOperation & OnceDoneOperation); + void OperationProgress(TFileOperationProgressType & ProgressData); +}; + +// TSignalThread + +int TSimpleThread::ThreadProc(void * Thread) +{ + TSimpleThread * SimpleThread = NB_STATIC_DOWNCAST(TSimpleThread, Thread); + DebugAssert(SimpleThread != nullptr); + try + { + SimpleThread->Execute(); + } + catch (...) + { + // we do not expect thread to be terminated with exception + DebugFail(); + } + SimpleThread->FFinished = true; + SimpleThread->Finished(); + return 0; +} + +TSimpleThread::TSimpleThread() : + FThread(nullptr), + FThreadId(0), + FFinished(true) +{ +} + +void TSimpleThread::Init() +{ + FThread = StartThread(nullptr, 0, this, CREATE_SUSPENDED, FThreadId); +} + +TSimpleThread::~TSimpleThread() +{ + Close(); + + if (FThread != nullptr) + { + ::CloseHandle(FThread); + } +} + +bool TSimpleThread::IsFinished() +{ + return FFinished; +} + +void TSimpleThread::Start() +{ + if (ResumeThread(FThread) == 1) + { + FFinished = false; + } +} + +void TSimpleThread::Finished() +{ +} + +void TSimpleThread::Close() +{ + if (!FFinished) + { + Terminate(); + WaitFor(); + } +} + +void TSimpleThread::WaitFor(uint32_t Milliseconds) +{ + ::WaitForSingleObject(FThread, Milliseconds); +} +//--------------------------------------------------------------------------- +// TSignalThread +//--------------------------------------------------------------------------- +TSignalThread::TSignalThread() : + TSimpleThread(), + FEvent(nullptr), + FTerminated(true) +{ +} + +void TSignalThread::Init(bool LowPriority) +{ + TSimpleThread::Init(); + FEvent = ::CreateEvent(nullptr, false, false, nullptr); + DebugAssert(FEvent != nullptr); +#ifndef __linux__ + if (LowPriority) + { + ::SetThreadPriority(FThread, THREAD_PRIORITY_BELOW_NORMAL); + } +#endif +} + +TSignalThread::~TSignalThread() +{ + // cannot leave closing to TSimpleThread as we need to close it before + // destroying the event + Close(); + + if (FEvent) + { + ::CloseHandle(FEvent); + } +} + +void TSignalThread::Start() +{ + FTerminated = false; + TSimpleThread::Start(); +} + +void TSignalThread::TriggerEvent() +{ + if (FEvent && FEvent != INVALID_HANDLE_VALUE) + { + ::SetEvent(FEvent); + } +} + +bool TSignalThread::WaitForEvent() +{ + // should never return -1, so it is only about 0 or 1 + return WaitForEvent(INFINITE) > 0; +} + +int TSignalThread::WaitForEvent(uint32_t Timeout) +{ + uint32_t Result = ::WaitForSingleObject(FEvent, Timeout); + int Return; + if ((Result == WAIT_TIMEOUT) && !FTerminated) + { + Return = -1; + } + else + { + Return = ((Result == WAIT_OBJECT_0) && !FTerminated) ? 1 : 0; + } + return Return; +} + +void TSignalThread::Execute() +{ + while (!FTerminated) + { + if (WaitForEvent()) + { + ProcessEvent(); + } + } +} + +void TSignalThread::Terminate() +{ + FTerminated = true; + TriggerEvent(); +} +//--------------------------------------------------------------------------- +// TTerminalQueue +//--------------------------------------------------------------------------- +TTerminalQueue::TTerminalQueue(TTerminal * ATerminal, + TConfiguration * AConfiguration) : + TSignalThread(), + FOnQueryUser(nullptr), + FOnPromptUser(nullptr), + FOnShowExtendedException(nullptr), + FOnQueueItemUpdate(nullptr), + FOnListUpdate(nullptr), + FOnEvent(nullptr), + FTerminal(ATerminal), + FConfiguration(AConfiguration), + FSessionData(new TSessionData(L"")), + FItems(new TList()), + FDoneItems(new TList()), + FItemsInProcess(0), + FFreeTerminals(0), + FTerminals(new TList()), + FForcedItems(new TList()), + FTemporaryTerminals(0), + FOverallTerminals(0), + FTransfersLimit(2), + FKeepDoneItemsFor(0), + FEnabled(true) +{ +} + +void TTerminalQueue::Init() +{ + TSignalThread::Init(true); + FOnQueryUser = nullptr; + FOnPromptUser = nullptr; + FOnShowExtendedException = nullptr; + FOnQueueItemUpdate = nullptr; + FOnListUpdate = nullptr; + FOnEvent = nullptr; + FLastIdle = Now(); + FIdleInterval = EncodeTimeVerbose(0, 0, 2, 0); + + DebugAssert(FTerminal != nullptr); + FSessionData->Assign(FTerminal->GetSessionData()); + + /*FItems = new TList(); + FDoneItems = new TList(); + FTerminals = new TList(); + FForcedItems = new TList();*/ + DebugAssert(FItems); + DebugAssert(FDoneItems); + DebugAssert(FTerminals); + DebugAssert(FForcedItems); + + Start(); +} + +TTerminalQueue::~TTerminalQueue() +{ + Close(); + + { + TGuard Guard(FItemsSection); + + while (FTerminals->GetCount() > 0) + { + TTerminalItem * TerminalItem = NB_STATIC_DOWNCAST(TTerminalItem, FTerminals->GetItem(0)); + FTerminals->Delete(0); + TerminalItem->Terminate(); + TerminalItem->WaitFor(); + SAFE_DESTROY(TerminalItem); + } + SAFE_DESTROY(FTerminals); + SAFE_DESTROY(FForcedItems); + + FreeItemsList(FItems); + FreeItemsList(FDoneItems); + } + + SAFE_DESTROY_EX(TSessionData, FSessionData); +} + +void TTerminalQueue::FreeItemsList(TList *& List) +{ + for (intptr_t Index = 0; Index < List->GetCount(); ++Index) + { + TQueueItem * Item = GetItem(List, Index); + SAFE_DESTROY(Item); + } + SAFE_DESTROY(List); +} + +void TTerminalQueue::TerminalFinished(TTerminalItem * TerminalItem) +{ + if (!FTerminated) + { + { + TGuard Guard(FItemsSection); + + intptr_t Index = FTerminals->IndexOf(TerminalItem); + DebugAssert(Index >= 0); + + if (Index < FFreeTerminals) + { + FFreeTerminals--; + } + + // Index may be >= FTransfersLimit also when the transfer limit was + // recently decreased, then + // FTemporaryTerminals < FTerminals->Count - FTransfersLimit + if ((FTransfersLimit >= 0) && (Index >= FTransfersLimit) && (FTemporaryTerminals > 0)) + { + FTemporaryTerminals--; + } + + FTerminals->Extract(TerminalItem); + + SAFE_DESTROY(TerminalItem); + } + + TriggerEvent(); + } +} + +bool TTerminalQueue::TerminalFree(TTerminalItem * TerminalItem) +{ + bool Result = true; + + if (!FTerminated) + { + { + TGuard Guard(FItemsSection); + + intptr_t Index = FTerminals->IndexOf(TerminalItem); + DebugAssert(Index >= 0); + DebugAssert(Index >= FFreeTerminals); + + Result = (FTransfersLimit < 0) || (Index < FTransfersLimit); + if (Result) + { + FTerminals->Move(Index, 0); + FFreeTerminals++; + } + } + + TriggerEvent(); + } + + return Result; +} + +void TTerminalQueue::AddItem(TQueueItem * Item) +{ + DebugAssert(!FTerminated); + + Item->SetStatus(TQueueItem::qsPending); + + { + TGuard Guard(FItemsSection); + + FItems->Add(Item); + Item->FQueue = this; + } + + DoListUpdate(); + + TriggerEvent(); +} + +void TTerminalQueue::RetryItem(TQueueItem * Item) +{ + if (!FTerminated) + { + { + TGuard Guard(FItemsSection); + + intptr_t Index = FItems->Remove(Item); + DebugAssert(Index < FItemsInProcess); + DebugUsedParam(Index); + FItemsInProcess--; + FItems->Add(Item); + } + + DoListUpdate(); + + TriggerEvent(); + } +} + +void TTerminalQueue::DeleteItem(TQueueItem * Item, bool CanKeep) +{ + if (!FTerminated) + { + bool Empty; + bool EmptyButMonitored; + bool Monitored; + { + TGuard Guard(FItemsSection); + + // does this need to be within guard? + Monitored = (Item->GetCompleteEvent() != INVALID_HANDLE_VALUE); + intptr_t Index = FItems->Remove(Item); + DebugAssert(Index < FItemsInProcess); + DebugUsedParam(Index); + FItemsInProcess--; + FForcedItems->Remove(Item); + // =0 do not keep + // <0 infinity + if ((FKeepDoneItemsFor != 0) && CanKeep) + { + DebugAssert(Item->GetStatus() == TQueueItem::qsDone); + Item->Complete(); + FDoneItems->Add(Item); + } + else + { + SAFE_DESTROY(Item); + } + + EmptyButMonitored = true; + Index = 0; + while (EmptyButMonitored && (Index < FItems->GetCount())) + { + EmptyButMonitored = (GetItem(FItems, Index)->GetCompleteEvent() != INVALID_HANDLE_VALUE); + ++Index; + } + Empty = (FItems->GetCount() == 0); + } + + DoListUpdate(); + // report empty but/except for monitored, if queue is empty or only monitored items are pending. + // do not report if current item was the last, but was monitored. + if (!Monitored && EmptyButMonitored) + { + DoEvent(qeEmptyButMonitored); + } + if (Empty) + { + DoEvent(qeEmpty); + } + } +} +//--------------------------------------------------------------------------- +TQueueItem * TTerminalQueue::GetItem(TList * List, intptr_t Index) +{ + return NB_STATIC_DOWNCAST(TQueueItem, List->GetItem(Index)); +} + +TQueueItem * TTerminalQueue::GetItem(intptr_t Index) +{ + return NB_STATIC_DOWNCAST(TQueueItem, FItems->GetItem(Index)); +} +//--------------------------------------------------------------------------- +void TTerminalQueue::UpdateStatusForList( + TTerminalQueueStatus * Status, TList * List, TTerminalQueueStatus * Current) +{ + for (intptr_t Index = 0; Index < List->GetCount(); ++Index) + { + TQueueItem * Item = GetItem(List, Index); + TQueueItemProxy * ItemProxy; + if (Current) + { + ItemProxy = Current->FindByQueueItem(Item); + } + else + { + ItemProxy = nullptr; + } + + if (Current && ItemProxy) + { + Current->Delete(ItemProxy); + Status->Add(ItemProxy); + ItemProxy->Update(); + } + else + { + Status->Add(new TQueueItemProxy(this, Item)); + } + } +} + +TTerminalQueueStatus * TTerminalQueue::CreateStatus(TTerminalQueueStatus * Current) +{ + std::unique_ptr Status(new TTerminalQueueStatus()); + try__catch + { + SCOPE_EXIT + { + if (Current != nullptr) + { + SAFE_DESTROY(Current); + } + }; + + try__finally + { + TGuard Guard(FItemsSection); + + UpdateStatusForList(Status.get(), FDoneItems, Current); + Status->SetDoneCount(Status->GetCount()); + UpdateStatusForList(Status.get(), FItems, Current); + } + __finally + { + if (Current != nullptr) + { +// delete Current; + } + }; + } + /*catch (...) + { + delete Status; + throw; + }*/ + + return Status.release(); +} + +bool TTerminalQueue::ItemGetData(TQueueItem * Item, + TQueueItemProxy * Proxy) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + TGuard Guard(FItemsSection); + + Result = (FDoneItems->IndexOf(Item) >= 0) || (FItems->IndexOf(Item) >= 0); + if (Result) + { + Item->GetData(Proxy); + } + } + + return Result; +} + +bool TTerminalQueue::ItemProcessUserAction(TQueueItem * Item, void * Arg) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + TTerminalItem * TerminalItem = nullptr; + + { + TGuard Guard(FItemsSection); + + Result = (FItems->IndexOf(Item) >= 0) && + TQueueItem::IsUserActionStatus(Item->GetStatus()); + if (Result) + { + TerminalItem = Item->FTerminalItem; + } + } + + if (Result) + { + Result = TerminalItem->ProcessUserAction(Arg); + } + } + + return Result; +} + +bool TTerminalQueue::ItemMove(TQueueItem * Item, TQueueItem * BeforeItem) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + { + TGuard Guard(FItemsSection); + + intptr_t Index = FItems->IndexOf(Item); + intptr_t IndexDest = FItems->IndexOf(BeforeItem); + Result = (Index >= 0) && (IndexDest >= 0) && + (Item->GetStatus() == TQueueItem::qsPending) && + (BeforeItem->GetStatus() == TQueueItem::qsPending); + if (Result) + { + FItems->Move(Index, IndexDest); + } + } + + if (Result) + { + DoListUpdate(); + TriggerEvent(); + } + } + + return Result; +} + +bool TTerminalQueue::ItemExecuteNow(TQueueItem * Item) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + { + TGuard Guard(FItemsSection); + + intptr_t Index = FItems->IndexOf(Item); + Result = (Index >= 0) && (Item->GetStatus() == TQueueItem::qsPending) && + // prevent double-initiation when "execute" is clicked twice too fast + (Index >= FItemsInProcess); + if (Result) + { + if (Index > FItemsInProcess) + { + FItems->Move(Index, FItemsInProcess); + } + + if ((FTransfersLimit >= 0) && (FTerminals->GetCount() >= FTransfersLimit) && + // when queue is disabled, we may have idle terminals, + // even when there are pending queue items + (FFreeTerminals == 0)) + { + FTemporaryTerminals++; + } + + FForcedItems->Add(Item); + } + } + + if (Result) + { + DoListUpdate(); + TriggerEvent(); + } + } + + return Result; +} + +bool TTerminalQueue::ItemDelete(TQueueItem * Item) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + bool UpdateList = false; + + { + TGuard Guard(FItemsSection); + + intptr_t Index = FItems->IndexOf(Item); + Result = (Index >= 0); + if (Result) + { + if (Item->GetStatus() == TQueueItem::qsPending) + { + FItems->Delete(Index); + FForcedItems->Remove(Item); + SAFE_DESTROY(Item); + UpdateList = true; + } + else + { + Item->FTerminalItem->Cancel(); + } + } + else + { + Index = FDoneItems->IndexOf(Item); + Result = (Index >= 0); + if (Result) + { + FDoneItems->Delete(Index); + UpdateList = true; + } + } + } + + if (UpdateList) + { + DoListUpdate(); + TriggerEvent(); + } + } + + return Result; +} + +bool TTerminalQueue::ItemPause(TQueueItem * Item, bool Pause) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + TTerminalItem * TerminalItem = nullptr; + + { + TGuard Guard(FItemsSection); + + Result = (FItems->IndexOf(Item) >= 0) && + ((Pause && (Item->GetStatus() == TQueueItem::qsProcessing)) || + (!Pause && (Item->GetStatus() == TQueueItem::qsPaused))); + if (Result) + { + TerminalItem = Item->FTerminalItem; + } + } + + if (Result) + { + if (Pause) + { + Result = TerminalItem->Pause(); + } + else + { + Result = TerminalItem->Resume(); + } + } + } + + return Result; +} + +bool TTerminalQueue::ItemSetCPSLimit(TQueueItem * Item, uintptr_t CPSLimit) +{ + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + TGuard Guard(FItemsSection); + + Result = (FItems->IndexOf(Item) >= 0); + if (Result) + { + Item->SetCPSLimit(CPSLimit); + } + } + + return Result; +} + +bool TTerminalQueue::ItemGetCPSLimit(TQueueItem * Item, uintptr_t & CPSLimit) const +{ + CPSLimit = 0; + // to prevent deadlocks when closing queue from other thread + bool Result = !FFinished; + if (Result) + { + TGuard Guard(FItemsSection); + + Result = (FItems->IndexOf(Item) >= 0); + if (Result) + { + CPSLimit = Item->GetCPSLimit(); + } + } + + return Result; +} + +void TTerminalQueue::Idle() +{ + TDateTime N = Now(); + if (N - FLastIdle > FIdleInterval) + { + FLastIdle = N; + TTerminalItem * TerminalItem = nullptr; + + if (FFreeTerminals > 0) + { + TGuard Guard(FItemsSection); + + if (FFreeTerminals > 0) + { + // take the last free terminal, because TerminalFree() puts it to the + // front, this ensures we cycle thru all free terminals + TerminalItem = NB_STATIC_DOWNCAST(TTerminalItem, FTerminals->GetItem(FFreeTerminals - 1)); + FTerminals->Move(FFreeTerminals - 1, FTerminals->GetCount() - 1); + FFreeTerminals--; + } + } + + if (TerminalItem != nullptr) + { + TerminalItem->Idle(); + } + } +} + +bool TTerminalQueue::WaitForEvent() +{ + // terminate loop regularly, so that we can check for expired done items + bool Result = (TSignalThread::WaitForEvent(1000) != 0); + return Result; +} + +void TTerminalQueue::ProcessEvent() +{ + TTerminalItem * TerminalItem; + do + { + TerminalItem = nullptr; + TQueueItem * Item1 = nullptr; + + { + TGuard Guard(FItemsSection); + + // =0 do not keep + // <0 infinity + if (FKeepDoneItemsFor >= 0) + { + TDateTime RemoveDoneItemsBefore = Now(); + if (FKeepDoneItemsFor > 0) + { + RemoveDoneItemsBefore = ::IncSecond(RemoveDoneItemsBefore, -FKeepDoneItemsFor); + } + for (intptr_t Index = 0; Index < FDoneItems->GetCount(); ++Index) + { + TQueueItem * Item2 = GetItem(FDoneItems, Index); + if (Item2->FDoneAt <= RemoveDoneItemsBefore) + { + FDoneItems->Delete(Index); + SAFE_DESTROY(Item2); + Index--; + DoListUpdate(); + } + } + } + + if (FItems->GetCount() > FItemsInProcess) + { + Item1 = GetItem(FItemsInProcess); + intptr_t ForcedIndex = FForcedItems->IndexOf(Item1); + + if (FEnabled || (ForcedIndex >= 0)) + { + if ((FFreeTerminals == 0) && + ((FTransfersLimit <= 0) || + (FTerminals->GetCount() < FTransfersLimit + FTemporaryTerminals))) + { + FOverallTerminals++; + TerminalItem = new TTerminalItem(this); + TerminalItem->Init(FOverallTerminals); + FTerminals->Add(TerminalItem); + } + else if (FFreeTerminals > 0) + { + TerminalItem = NB_STATIC_DOWNCAST(TTerminalItem, FTerminals->GetItem(0)); + FTerminals->Move(0, FTerminals->GetCount() - 1); + FFreeTerminals--; + } + + if (TerminalItem != nullptr) + { + if (ForcedIndex >= 0) + { + FForcedItems->Delete(ForcedIndex); + } + FItemsInProcess++; + } + } + } + } + + if (TerminalItem != nullptr) + { + TerminalItem->Process(Item1); + } + } + while (!FTerminated && (TerminalItem != nullptr)); +} + +void TTerminalQueue::DoQueueItemUpdate(TQueueItem * Item) +{ + if (GetOnQueueItemUpdate() != nullptr) + { + GetOnQueueItemUpdate()(this, Item); + } +} + +void TTerminalQueue::DoListUpdate() +{ + if (GetOnListUpdate() != nullptr) + { + GetOnListUpdate()(this); + } +} + +void TTerminalQueue::DoEvent(TQueueEvent Event) +{ + if (GetOnEvent() != nullptr) + { + GetOnEvent()(this, Event); + } +} + +void TTerminalQueue::SetTransfersLimit(intptr_t Value) +{ + if (FTransfersLimit != Value) + { + { + TGuard Guard(FItemsSection); + + if ((Value >= 0) && (Value < FItemsInProcess)) + { + FTemporaryTerminals = (FItemsInProcess - Value); + } + else + { + FTemporaryTerminals = 0; + } + FTransfersLimit = Value; + } + + TriggerEvent(); + } +} + +void TTerminalQueue::SetKeepDoneItemsFor(intptr_t Value) +{ + if (FKeepDoneItemsFor != Value) + { + { + TGuard Guard(FItemsSection); + + FKeepDoneItemsFor = Value; + } + } +} + +void TTerminalQueue::SetEnabled(bool Value) +{ + if (FEnabled != Value) + { + { + TGuard Guard(FItemsSection); + + FEnabled = Value; + } + + TriggerEvent(); + } +} + +bool TTerminalQueue::GetIsEmpty() const +{ + TGuard Guard(FItemsSection); + return (FItems->GetCount() == 0); +} + +// TBackgroundItem + +class TBackgroundTerminal : public TSecondaryTerminal +{ + friend class TTerminalItem; +public: + explicit TBackgroundTerminal(TTerminal * MainTerminal); + virtual ~TBackgroundTerminal() {} + void Init( + TSessionData * SessionData, TConfiguration * Configuration, + TTerminalItem * Item, const UnicodeString & Name); + +protected: + virtual bool DoQueryReopen(Exception * E); + +private: + TTerminalItem * FItem; +}; + +TBackgroundTerminal::TBackgroundTerminal(TTerminal * MainTerminal) : + TSecondaryTerminal(MainTerminal), + FItem(nullptr) +{ +} + +void TBackgroundTerminal::Init(TSessionData * SessionData, TConfiguration * Configuration, TTerminalItem * Item, + const UnicodeString & Name) +{ + TSecondaryTerminal::Init(SessionData, Configuration, Name); + FItem = Item; +} + +bool TBackgroundTerminal::DoQueryReopen(Exception * /*E*/) +{ + bool Result; + if (FItem->FTerminated || FItem->FCancel) + { + // avoid reconnection if we are closing + Result = false; + } + else + { + ::Sleep(static_cast(GetConfiguration()->GetSessionReopenBackground())); + Result = true; + } + return Result; +} + +// TTerminalItem + +TTerminalItem::TTerminalItem(TTerminalQueue * Queue) : + TSignalThread(), FQueue(Queue), FTerminal(nullptr), FItem(nullptr), + FUserAction(nullptr), FCancel(false), FPause(false) +{ +} + +void TTerminalItem::Init(intptr_t Index) +{ + TSignalThread::Init(true); + + std::unique_ptr Terminal(new TBackgroundTerminal(FQueue->FTerminal)); + try__catch + { + Terminal->Init(FQueue->FSessionData, FQueue->FConfiguration, this, FORMAT(L"Background %d", Index)); + Terminal->SetUseBusyCursor(false); + + Terminal->SetOnQueryUser(MAKE_CALLBACK(TTerminalItem::TerminalQueryUser, this)); + Terminal->SetOnPromptUser(MAKE_CALLBACK(TTerminalItem::TerminalPromptUser, this)); + Terminal->SetOnShowExtendedException(MAKE_CALLBACK(TTerminalItem::TerminalShowExtendedException, this)); + Terminal->SetOnProgress(MAKE_CALLBACK(TTerminalItem::OperationProgress, this)); + Terminal->SetOnFinished(MAKE_CALLBACK(TTerminalItem::OperationFinished, this)); + FTerminal = Terminal.release(); + } + /*catch(...) + { + delete FTerminal; + throw; + }*/ + + Start(); +} + +TTerminalItem::~TTerminalItem() +{ + Close(); + + DebugAssert(FItem == nullptr); + SAFE_DESTROY(FTerminal); +} + +void TTerminalItem::Process(TQueueItem * Item) +{ + { + TGuard Guard(FCriticalSection); + + DebugAssert(FItem == nullptr); + FItem = Item; + } + + TriggerEvent(); +} + +void TTerminalItem::ProcessEvent() +{ + TGuard Guard(FCriticalSection); + + bool Retry = true; + + FCancel = false; + FPause = false; + + try + { + DebugAssert(FItem != nullptr); + + FItem->FTerminalItem = this; + + if (!FTerminal->GetActive()) + { + FItem->SetStatus(TQueueItem::qsConnecting); + + FTerminal->GetSessionData()->SetRemoteDirectory(FItem->GetStartupDirectory()); + FTerminal->Open(); + } + + Retry = false; + + if (!FCancel) + { + FTerminal->UpdateFromMain(); + + FItem->SetStatus(TQueueItem::qsProcessing); + + FItem->Execute(this); + } + } + catch (Exception & E) + { + UnicodeString Message; + if (ExceptionMessageFormatted(&E, Message)) + { + // do not show error messages, if task was canceled anyway + // (for example if transfer is canceled during reconnection attempts) + if (!FCancel && + (FTerminal->QueryUserException(L"", &E, qaOK | qaCancel, nullptr, qtError) == qaCancel)) + { + FCancel = true; + } + } + } + + FItem->SetStatus(TQueueItem::qsDone); + + FItem->FTerminalItem = nullptr; + + TQueueItem * Item = FItem; + FItem = nullptr; + + if (Retry && !FCancel) + { + FQueue->RetryItem(Item); + } + else + { + FQueue->DeleteItem(Item, !FCancel); + } + + if (!FTerminal->GetActive() || + !FQueue->TerminalFree(this)) + { + Terminate(); + } +} + +void TTerminalItem::Idle() +{ + TGuard Guard(FCriticalSection); + + DebugAssert(FTerminal->GetActive()); + + try + { + FTerminal->Idle(); + } + catch (...) + { + } + + if (!FTerminal->GetActive() || + !FQueue->TerminalFree(this)) + { + Terminate(); + } +} + +void TTerminalItem::Cancel() +{ + FCancel = true; + if ((FItem->GetStatus() == TQueueItem::qsPaused) || + TQueueItem::IsUserActionStatus(FItem->GetStatus())) + { + TriggerEvent(); + } +} + +bool TTerminalItem::Pause() +{ + DebugAssert(FItem != nullptr); + bool Result = (FItem->GetStatus() == TQueueItem::qsProcessing) && !FPause; + if (Result) + { + FPause = true; + } + return Result; +} + +bool TTerminalItem::Resume() +{ + DebugAssert(FItem != nullptr); + bool Result = (FItem->GetStatus() == TQueueItem::qsPaused); + if (Result) + { + TriggerEvent(); + } + return Result; +} + +bool TTerminalItem::ProcessUserAction(void * Arg) +{ + // When status is changed twice quickly, the controller when responding + // to the first change (non-user-action) can be so slow to check only after + // the second (user-action) change occurs. Thus it responds it. + // Then as reaction to the second (user-action) change there will not be + // any outstanding user-action. + bool Result = (FUserAction != nullptr); + if (Result) + { + DebugAssert(FItem != nullptr); + + FUserAction->Execute(Arg); + FUserAction = nullptr; + + TriggerEvent(); + } + return Result; +} + +bool TTerminalItem::WaitForUserAction( + TQueueItem::TStatus ItemStatus, TUserAction * UserAction) +{ + DebugAssert(FItem != nullptr); + DebugAssert((FItem->GetStatus() == TQueueItem::qsProcessing) || + (FItem->GetStatus() == TQueueItem::qsConnecting)); + + bool Result; + + TQueueItem::TStatus PrevStatus = FItem->GetStatus(); + + try__finally + { + SCOPE_EXIT + { + FUserAction = nullptr; + FItem->SetStatus(PrevStatus); + }; + FUserAction = UserAction; + + FItem->SetStatus(ItemStatus); + FQueue->DoEvent(qePendingUserAction); + + Result = !FTerminated && WaitForEvent() && !FCancel; + } + __finally + { + FUserAction = nullptr; + FItem->SetStatus(PrevStatus); + }; + + return Result; +} + +void TTerminalItem::Finished() +{ + TSignalThread::Finished(); + + FQueue->TerminalFinished(this); +} + +void TTerminalItem::TerminalQueryUser(TObject * Sender, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * Params, uintptr_t & Answer, TQueryType Type, void * Arg) +{ + // so far query without queue item can occur only for key confirmation + // on re-key with non-cached host key. make it fail. + if (FItem != nullptr) + { + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + TQueryUserAction Action(FQueue->GetOnQueryUser()); + Action.Sender = Sender; + Action.Query = AQuery; + Action.MoreMessages = MoreMessages; + Action.Answers = Answers; + Action.Params = Params; + Action.Answer = Answer; + Action.Type = Type; + + // if the query is "error", present it as an "error" state in UI, + // however it is still handled as query by the action. + + TQueueItem::TStatus ItemStatus = + (Action.Type == qtError ? TQueueItem::qsError : TQueueItem::qsQuery); + + if (WaitForUserAction(ItemStatus, &Action)) + { + Answer = Action.Answer; + } + } +} + +void TTerminalItem::TerminalPromptUser(TTerminal * Terminal, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Results, bool & Result, void * Arg) +{ + if (FItem == nullptr) + { + // sanity, should not occur + DebugFail(); + Result = false; + } + else + { + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + TPromptUserAction Action(FQueue->GetOnPromptUser()); + Action.Terminal = Terminal; + Action.Kind = Kind; + Action.Name = Name; + Action.Instructions = Instructions; + Action.Prompts = Prompts; + Action.Results->AddStrings(Results); + + if (WaitForUserAction(TQueueItem::qsPrompt, &Action)) + { + Results->Clear(); + Results->AddStrings(Action.Results); + Result = Action.Result; + } + } +} + +void TTerminalItem::TerminalShowExtendedException( + TTerminal * Terminal, Exception * E, void * Arg) +{ + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + if ((FItem != nullptr) && + ShouldDisplayException(E)) + { + TShowExtendedExceptionAction Action(FQueue->GetOnShowExtendedException()); + Action.Terminal = Terminal; + Action.E = E; + + WaitForUserAction(TQueueItem::qsError, &Action); + } +} + +void TTerminalItem::OperationFinished(TFileOperation /*Operation*/, + TOperationSide /*Side*/, bool /*Temp*/, const UnicodeString & /*AFileName*/, + bool /*Success*/, TOnceDoneOperation & /*OnceDoneOperation*/) +{ + // nothing +} + +void TTerminalItem::OperationProgress( + TFileOperationProgressType & ProgressData) +{ + if (FPause && !FTerminated && !FCancel) + { + DebugAssert(FItem != nullptr); + TQueueItem::TStatus PrevStatus = FItem->GetStatus(); + DebugAssert(PrevStatus == TQueueItem::qsProcessing); + // must be set before TFileOperationProgressType::Suspend(), because + // it invokes this method back + FPause = false; + ProgressData.Suspend(); + + try__finally + { + SCOPE_EXIT + { + FItem->SetStatus(PrevStatus); + ProgressData.Resume(); + }; + FItem->SetStatus(TQueueItem::qsPaused); + + WaitForEvent(); + } + __finally + { + FItem->SetStatus(PrevStatus); + ProgressData.Resume(); + }; + } + + if (FTerminated || FCancel) + { + if (ProgressData.TransferingFile) + { + ProgressData.Cancel = csCancelTransfer; + } + else + { + ProgressData.Cancel = csCancel; + } + } + + DebugAssert(FItem != nullptr); + FItem->SetProgress(ProgressData); +} + +bool TTerminalItem::OverrideItemStatus(TQueueItem::TStatus & ItemStatus) +{ + DebugAssert(FTerminal != nullptr); + bool Result = (FTerminal->GetStatus() < ssOpened) && (ItemStatus == TQueueItem::qsProcessing); + if (Result) + { + ItemStatus = TQueueItem::qsConnecting; + } + return Result; +} + +// TQueueItem + +TQueueItem::TQueueItem() : + FStatus(qsPending), FTerminalItem(nullptr), FProgressData(nullptr), + FInfo(new TInfo()), + FQueue(nullptr), FCompleteEvent(INVALID_HANDLE_VALUE), + FCPSLimit((uintptr_t)-1) +{ + FInfo->SingleFile = false; +} + +TQueueItem::~TQueueItem() +{ + // we need to keep the total transfer size even after transfer completes + SAFE_DESTROY(FProgressData); + + Complete(); + + SAFE_DESTROY(FInfo); +} + +void TQueueItem::Complete() +{ + TGuard Guard(FSection); + + if (FCompleteEvent != INVALID_HANDLE_VALUE) + { + ::SetEvent(FCompleteEvent); + FCompleteEvent = INVALID_HANDLE_VALUE; + } +} + +bool TQueueItem::IsUserActionStatus(TStatus Status) +{ + return (Status == qsQuery) || (Status == qsError) || (Status == qsPrompt); +} + +TQueueItem::TStatus TQueueItem::GetStatus() const +{ + TGuard Guard(FSection); + + return FStatus; +} + +void TQueueItem::SetStatus(TStatus Status) +{ + { + TGuard Guard(FSection); + + FStatus = Status; + if (FStatus == qsDone) + { + FDoneAt = Now(); + } + } + + DebugAssert((FQueue != nullptr) || (Status == qsPending)); + if (FQueue != nullptr) + { + FQueue->DoQueueItemUpdate(this); + } +} + +void TQueueItem::SetProgress( + TFileOperationProgressType & ProgressData) +{ + { + TGuard Guard(FSection); + + // do not lose CPS limit override on "calculate size" operation, + // wait until the real transfer operation starts + if ((FCPSLimit != static_cast(-1)) && ((ProgressData.Operation == foMove) || (ProgressData.Operation == foCopy))) + { + ProgressData.CPSLimit = FCPSLimit; + FCPSLimit = static_cast(-1); + } + + DebugAssert(FProgressData != nullptr); + *FProgressData = ProgressData; + FProgressData->Reset(); + + if (FCPSLimit != static_cast(-1)) + { + ProgressData.CPSLimit = FCPSLimit; + FCPSLimit = static_cast(-1); + } + } + FQueue->DoQueueItemUpdate(this); +} + +void TQueueItem::GetData(TQueueItemProxy * Proxy) const +{ + TGuard Guard(FSection); + + DebugAssert(Proxy->FProgressData != nullptr); + if (FProgressData != nullptr) + { + *Proxy->FProgressData = *FProgressData; + } + else + { + Proxy->FProgressData->Clear(); + } + *Proxy->FInfo = *FInfo; + Proxy->FStatus = FStatus; + if (FTerminalItem != nullptr) + { + FTerminalItem->OverrideItemStatus(Proxy->FStatus); + } +} + +void TQueueItem::Execute(TTerminalItem * TerminalItem) +{ + { + DebugAssert(FProgressData == nullptr); + TGuard Guard(FSection); + FProgressData = new TFileOperationProgressType(); + } + DoExecute(TerminalItem->FTerminal); +} + +void TQueueItem::SetCPSLimit(uintptr_t CPSLimit) +{ + FCPSLimit = CPSLimit; +} + +uintptr_t TQueueItem::DefaultCPSLimit() const +{ + return 0; +} + +uintptr_t TQueueItem::GetCPSLimit() const +{ + uintptr_t Result; + if (FCPSLimit != static_cast(-1)) + { + Result = FCPSLimit; + } + else if (FProgressData != nullptr) + { + Result = FProgressData->CPSLimit; + } + else + { + Result = DefaultCPSLimit(); + } + return Result; +} + +// TQueueItemProxy + +TQueueItemProxy::TQueueItemProxy(TTerminalQueue * Queue, + TQueueItem * QueueItem) : + FProgressData(new TFileOperationProgressType()), FStatus(TQueueItem::qsPending), FQueue(Queue), FQueueItem(QueueItem), + FQueueStatus(nullptr), FInfo(new TQueueItem::TInfo()), + FProcessingUserAction(false), FUserData(nullptr) +{ + Update(); +} + +TQueueItemProxy::~TQueueItemProxy() +{ + SAFE_DESTROY(FProgressData); + SAFE_DESTROY(FInfo); +} + +TFileOperationProgressType * TQueueItemProxy::GetProgressData() +{ + return (FProgressData->Operation == foNone) ? nullptr : FProgressData; +} + +int64_t TQueueItemProxy::GetTotalTransferred() +{ + // want to show total transferred also for "completed" items, + // for which GetProgressData() is NULL + return + (FProgressData->Operation == GetInfo()->Operation) || (GetStatus() == TQueueItem::qsDone) ? + FProgressData->TotalTransfered : -1; +} + +bool TQueueItemProxy::Update() +{ + DebugAssert(FQueueItem != nullptr); + + TQueueItem::TStatus PrevStatus = GetStatus(); + + bool Result = FQueue->ItemGetData(FQueueItem, this); + + if ((FQueueStatus != nullptr) && (PrevStatus != GetStatus())) + { + FQueueStatus->ResetStats(); + } + + return Result; +} + +bool TQueueItemProxy::ExecuteNow() +{ + return FQueue->ItemExecuteNow(FQueueItem); +} + +bool TQueueItemProxy::Move(bool Sooner) +{ + bool Result = false; + intptr_t Index = GetIndex(); + if (Sooner) + { + if (Index > 0) + { + Result = Move(FQueueStatus->GetItem(Index - 1)); + } + } + else + { + if (Index < FQueueStatus->GetCount() - 1) + { + Result = FQueueStatus->GetItem(Index + 1)->Move(this); + } + } + return Result; +} + +bool TQueueItemProxy::Move(TQueueItemProxy * BeforeItem) +{ + return FQueue->ItemMove(FQueueItem, BeforeItem->FQueueItem); +} + +bool TQueueItemProxy::Delete() +{ + return FQueue->ItemDelete(FQueueItem); +} + +bool TQueueItemProxy::Pause() +{ + return FQueue->ItemPause(FQueueItem, true); +} + +bool TQueueItemProxy::Resume() +{ + return FQueue->ItemPause(FQueueItem, false); +} + +bool TQueueItemProxy::ProcessUserAction() +{ + DebugAssert(FQueueItem != nullptr); + + bool Result = false; + FProcessingUserAction = true; + try__finally + { + SCOPE_EXIT + { + FProcessingUserAction = false; + }; + Result = FQueue->ItemProcessUserAction(FQueueItem, nullptr); + } + __finally + { + FProcessingUserAction = false; + }; + return Result; +} + +bool TQueueItemProxy::GetCPSLimit(uintptr_t & CPSLimit) const +{ + return FQueue->ItemGetCPSLimit(FQueueItem, CPSLimit); +} + +bool TQueueItemProxy::SetCPSLimit(uintptr_t CPSLimit) +{ + return FQueue->ItemSetCPSLimit(FQueueItem, CPSLimit); +} + +intptr_t TQueueItemProxy::GetIndex() const +{ + DebugAssert(FQueueStatus != nullptr); + intptr_t Index = FQueueStatus->FList->IndexOf(this); + DebugAssert(Index >= 0); + return Index; +} + +// TTerminalQueueStatus + +TTerminalQueueStatus::TTerminalQueueStatus() : + FList(new TList()), + FDoneCount(0), + FActiveCount(0) +{ + ResetStats(); +} + +TTerminalQueueStatus::~TTerminalQueueStatus() +{ + for (intptr_t Index = 0; Index < FList->GetCount(); ++Index) + { + TQueueItemProxy * Item = GetItem(Index); + SAFE_DESTROY(Item); + } + SAFE_DESTROY(FList); +} + +void TTerminalQueueStatus::ResetStats() +{ + FActiveCount = -1; +} + +void TTerminalQueueStatus::SetDoneCount(intptr_t Value) +{ + FDoneCount = Value; + ResetStats(); +} + +intptr_t TTerminalQueueStatus::GetActiveCount() const +{ + if (FActiveCount < 0) + { + FActiveCount = 0; + + intptr_t Index = FDoneCount; + while ((Index < FList->GetCount()) && + (GetItem(Index)->GetStatus() != TQueueItem::qsPending)) + { + FActiveCount++; + ++Index; + } + } + + return FActiveCount; +} + +intptr_t TTerminalQueueStatus::GetDoneAndActiveCount() const +{ + return GetDoneCount() + GetActiveCount(); +} + +intptr_t TTerminalQueueStatus::GetActiveAndPendingCount() const +{ + return GetCount() - GetDoneCount(); +} + +void TTerminalQueueStatus::Add(TQueueItemProxy * ItemProxy) +{ + ItemProxy->FQueueStatus = this; + FList->Add(ItemProxy); + ResetStats(); +} + +void TTerminalQueueStatus::Delete(TQueueItemProxy * ItemProxy) +{ + FList->Extract(ItemProxy); + ItemProxy->FQueueStatus = nullptr; + ResetStats(); +} + +intptr_t TTerminalQueueStatus::GetCount() const +{ + return FList->GetCount(); +} + +TQueueItemProxy * TTerminalQueueStatus::GetItem(intptr_t Index) const +{ + return const_cast(this)->GetItem(Index); +} + +TQueueItemProxy * TTerminalQueueStatus::GetItem(intptr_t Index) +{ + return NB_STATIC_DOWNCAST(TQueueItemProxy, FList->GetItem(Index)); +} + +TQueueItemProxy * TTerminalQueueStatus::FindByQueueItem( + TQueueItem * QueueItem) +{ + for (intptr_t Index = 0; Index < FList->GetCount(); ++Index) + { + TQueueItemProxy * Item = GetItem(Index); + if (Item->FQueueItem == QueueItem) + { + return Item; + } + } + return nullptr; +} + +// TLocatedQueueItem + +TLocatedQueueItem::TLocatedQueueItem(TTerminal * Terminal) : + TQueueItem() +{ + DebugAssert(Terminal != nullptr); + FCurrentDir = Terminal->GetCurrDirectory(); +} + +UnicodeString TLocatedQueueItem::GetStartupDirectory() const +{ + return FCurrentDir; +} + +void TLocatedQueueItem::DoExecute(TTerminal * Terminal) +{ + DebugAssert(Terminal != nullptr); + if (Terminal) + Terminal->TerminalSetCurrentDirectory(FCurrentDir); +} + +// TTransferQueueItem + +TTransferQueueItem::TTransferQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, TOperationSide Side, + bool SingleFile) : + TLocatedQueueItem(Terminal), FFilesToCopy(new TStringList()), FCopyParam(nullptr) +{ + FInfo->Operation = (Params & cpDelete) ? foMove : foCopy; + FInfo->Side = Side; + FInfo->SingleFile = SingleFile; + + DebugAssert(AFilesToCopy != nullptr); + for (intptr_t Index = 0; Index < AFilesToCopy->GetCount(); ++Index) + { + FFilesToCopy->AddObject(AFilesToCopy->GetString(Index), + ((AFilesToCopy->GetObj(Index) == nullptr) || (Side == osLocal)) ? nullptr : + NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(Index))->Duplicate()); + } + + FTargetDir = TargetDir; + + DebugAssert(CopyParam != nullptr); + FCopyParam = new TCopyParamType(*CopyParam); + + FParams = Params; +} + +TTransferQueueItem::~TTransferQueueItem() +{ + for (intptr_t Index = 0; Index < FFilesToCopy->GetCount(); ++Index) + { + TObject * Object = FFilesToCopy->GetObj(Index); + SAFE_DESTROY(Object); + } + SAFE_DESTROY(FFilesToCopy); + SAFE_DESTROY(FCopyParam); +} + +uintptr_t TTransferQueueItem::DefaultCPSLimit() const +{ + return FCopyParam->GetCPSLimit(); +} + +// TUploadQueueItem + +TUploadQueueItem::TUploadQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, bool SingleFile) : + TTransferQueueItem(Terminal, AFilesToCopy, TargetDir, CopyParam, Params, osLocal, SingleFile) +{ + if (AFilesToCopy->GetCount() > 1) + { + if (FLAGSET(Params, cpTemporary)) + { + FInfo->Source.Clear(); + FInfo->ModifiedLocal.Clear(); + } + else + { + core::ExtractCommonPath(AFilesToCopy, FInfo->Source); + // this way the trailing backslash is preserved for root directories like "D:\\" + FInfo->Source = ::ExtractFileDir(::IncludeTrailingBackslash(FInfo->Source)); + FInfo->ModifiedLocal = FLAGCLEAR(Params, cpDelete) ? UnicodeString() : + ::IncludeTrailingBackslash(FInfo->Source); + } + } + else + { + if (FLAGSET(Params, cpTemporary)) + { + FInfo->Source = base::ExtractFileName(AFilesToCopy->GetString(0), true); + FInfo->ModifiedLocal.Clear(); + } + else + { + DebugAssert(AFilesToCopy->GetCount() > 0); + FInfo->Source = AFilesToCopy->GetString(0); + FInfo->ModifiedLocal = FLAGCLEAR(Params, cpDelete) ? UnicodeString() : + ::IncludeTrailingBackslash(::ExtractFilePath(FInfo->Source)); + } + } + + FInfo->Destination = + core::UnixIncludeTrailingBackslash(TargetDir) + CopyParam->GetFileMask(); + FInfo->ModifiedRemote = core::UnixIncludeTrailingBackslash(TargetDir); +} + +void TUploadQueueItem::DoExecute(TTerminal * Terminal) +{ + TTransferQueueItem::DoExecute(Terminal); + + DebugAssert(Terminal != nullptr); + if (Terminal) + Terminal->CopyToRemote(FFilesToCopy, FTargetDir, FCopyParam, FParams); +} + +// TDownloadQueueItem + +TDownloadQueueItem::TDownloadQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, bool SingleFile) : + TTransferQueueItem(Terminal, AFilesToCopy, TargetDir, CopyParam, Params, osRemote, SingleFile) +{ + if (AFilesToCopy->GetCount() > 1) + { + if (!core::UnixExtractCommonPath(AFilesToCopy, FInfo->Source)) + { + FInfo->Source = Terminal->GetCurrDirectory(); + } + FInfo->Source = core::UnixExcludeTrailingBackslash(FInfo->Source); + FInfo->ModifiedRemote = FLAGCLEAR(Params, cpDelete) ? UnicodeString() : + core::UnixIncludeTrailingBackslash(FInfo->Source); + } + else + { + DebugAssert(AFilesToCopy->GetCount() > 0); + FInfo->Source = AFilesToCopy->GetString(0); + if (core::UnixExtractFilePath(FInfo->Source).IsEmpty()) + { + FInfo->Source = core::UnixIncludeTrailingBackslash(Terminal->GetCurrDirectory()) + + FInfo->Source; + FInfo->ModifiedRemote = FLAGCLEAR(Params, cpDelete) ? UnicodeString() : + core::UnixIncludeTrailingBackslash(Terminal->GetCurrDirectory()); + } + else + { + FInfo->ModifiedRemote = FLAGCLEAR(Params, cpDelete) ? UnicodeString() : + core::UnixExtractFilePath(FInfo->Source); + } + } + + if (FLAGSET(Params, cpTemporary)) + { + FInfo->Destination.Clear(); + } + else + { + FInfo->Destination = + ::IncludeTrailingBackslash(TargetDir) + CopyParam->GetFileMask(); + } + FInfo->ModifiedLocal = ::IncludeTrailingBackslash(TargetDir); +} + +void TDownloadQueueItem::DoExecute(TTerminal * Terminal) +{ + TTransferQueueItem::DoExecute(Terminal); + + DebugAssert(Terminal != nullptr); + Terminal->CopyToLocal(FFilesToCopy, FTargetDir, FCopyParam, FParams); +} + +// TTerminalThread + +TTerminalThread::TTerminalThread(TTerminal * Terminal) : + TSignalThread(), FTerminal(Terminal), + FOnInformation(nullptr), + FOnQueryUser(nullptr), + FOnPromptUser(nullptr), + FOnShowExtendedException(nullptr), + FOnDisplayBanner(nullptr), + FOnChangeDirectory(nullptr), + FOnReadDirectory(nullptr), + FOnStartReadDirectory(nullptr), + FOnReadDirectoryProgress(nullptr), + FOnInitializeLog(nullptr), + FOnIdle(nullptr), + FAction(nullptr) +{ + FAction = nullptr; + FActionEvent = ::CreateEvent(nullptr, false, false, nullptr); + FException = nullptr; + FIdleException = nullptr; + FOnIdle = nullptr; + FUserAction = nullptr; + FCancel = false; + FCancelled = false; + FPendingIdle = false; + FMainThread = GetCurrentThreadId(); +} + +void TTerminalThread::Init() +{ + TSignalThread::Init(false); + + FOnInformation = FTerminal->GetOnInformation(); + FOnQueryUser = FTerminal->GetOnQueryUser(); + FOnPromptUser = FTerminal->GetOnPromptUser(); + FOnShowExtendedException = FTerminal->GetOnShowExtendedException(); + FOnDisplayBanner = FTerminal->GetOnDisplayBanner(); + FOnChangeDirectory = FTerminal->GetOnChangeDirectory(); + FOnReadDirectory = FTerminal->GetOnReadDirectory(); + FOnStartReadDirectory = FTerminal->GetOnStartReadDirectory(); + FOnReadDirectoryProgress = FTerminal->GetOnReadDirectoryProgress(); + FOnInitializeLog = FTerminal->GetOnInitializeLog(); + + FTerminal->SetOnInformation(MAKE_CALLBACK(TTerminalThread::TerminalInformation, this)); + FTerminal->SetOnQueryUser(MAKE_CALLBACK(TTerminalThread::TerminalQueryUser, this)); + FTerminal->SetOnPromptUser(MAKE_CALLBACK(TTerminalThread::TerminalPromptUser, this)); + FTerminal->SetOnShowExtendedException(MAKE_CALLBACK(TTerminalThread::TerminalShowExtendedException, this)); + FTerminal->SetOnDisplayBanner(MAKE_CALLBACK(TTerminalThread::TerminalDisplayBanner, this)); + FTerminal->SetOnChangeDirectory(MAKE_CALLBACK(TTerminalThread::TerminalChangeDirectory, this)); + FTerminal->SetOnReadDirectory(MAKE_CALLBACK(TTerminalThread::TerminalReadDirectory, this)); + FTerminal->SetOnStartReadDirectory(MAKE_CALLBACK(TTerminalThread::TerminalStartReadDirectory, this)); + FTerminal->SetOnReadDirectoryProgress(MAKE_CALLBACK(TTerminalThread::TerminalReadDirectoryProgress, this)); + FTerminal->SetOnInitializeLog(MAKE_CALLBACK(TTerminalThread::TerminalInitializeLog, this)); + + Start(); +} + +TTerminalThread::~TTerminalThread() +{ + Close(); + + ::CloseHandle(FActionEvent); + + DebugAssert(FTerminal->GetOnInformation() == MAKE_CALLBACK(TTerminalThread::TerminalInformation, this)); + DebugAssert(FTerminal->GetOnQueryUser() == MAKE_CALLBACK(TTerminalThread::TerminalQueryUser, this)); + DebugAssert(FTerminal->GetOnPromptUser() == MAKE_CALLBACK(TTerminalThread::TerminalPromptUser, this)); + DebugAssert(FTerminal->GetOnShowExtendedException() == MAKE_CALLBACK(TTerminalThread::TerminalShowExtendedException, this)); + DebugAssert(FTerminal->GetOnDisplayBanner() == MAKE_CALLBACK(TTerminalThread::TerminalDisplayBanner, this)); + DebugAssert(FTerminal->GetOnChangeDirectory() == MAKE_CALLBACK(TTerminalThread::TerminalChangeDirectory, this)); + DebugAssert(FTerminal->GetOnReadDirectory() == MAKE_CALLBACK(TTerminalThread::TerminalReadDirectory, this)); + DebugAssert(FTerminal->GetOnStartReadDirectory() == MAKE_CALLBACK(TTerminalThread::TerminalStartReadDirectory, this)); + DebugAssert(FTerminal->GetOnReadDirectoryProgress() == MAKE_CALLBACK(TTerminalThread::TerminalReadDirectoryProgress, this)); + DebugAssert(FTerminal->GetOnInitializeLog() == MAKE_CALLBACK(TTerminalThread::TerminalInitializeLog, this)); + + FTerminal->SetOnInformation(FOnInformation); + FTerminal->SetOnQueryUser(FOnQueryUser); + FTerminal->SetOnPromptUser(FOnPromptUser); + FTerminal->SetOnShowExtendedException(FOnShowExtendedException); + FTerminal->SetOnDisplayBanner(FOnDisplayBanner); + FTerminal->SetOnChangeDirectory(FOnChangeDirectory); + FTerminal->SetOnReadDirectory(FOnReadDirectory); + FTerminal->SetOnStartReadDirectory(FOnStartReadDirectory); + FTerminal->SetOnReadDirectoryProgress(FOnReadDirectoryProgress); + FTerminal->SetOnInitializeLog(FOnInitializeLog); +} + +void TTerminalThread::Cancel() +{ + FCancel = true; +} + +void TTerminalThread::Idle() +{ + TGuard Guard(FSection); + // only when running user action already, + // so that the exception is caught, saved and actually + // passed back into the terminal thread, saved again + // and passed back to us + if ((FUserAction != nullptr) && (FIdleException != nullptr)) + { + Rethrow(FIdleException); + } + FPendingIdle = true; +} + +void TTerminalThread::TerminalOpen() +{ + RunAction(MAKE_CALLBACK(TTerminalThread::TerminalOpenEvent, this)); +} + +void TTerminalThread::TerminalReopen() +{ + RunAction(MAKE_CALLBACK(TTerminalThread::TerminalReopenEvent, this)); +} + +void TTerminalThread::RunAction(TNotifyEvent Action) +{ + DebugAssert(FAction == nullptr); + DebugAssert(FException == nullptr); + DebugAssert(FIdleException == nullptr); + DebugAssert(FOnIdle != nullptr); + + FCancelled = false; + FAction = Action; + try + { + try__finally + { + SCOPE_EXIT + { + FAction = nullptr; + SAFE_DESTROY(FException); + }; + TriggerEvent(); + + bool Done = false; + + do + { + switch (::WaitForSingleObject(FActionEvent, 50)) + { + case WAIT_OBJECT_0: + Done = true; + break; + + case WAIT_TIMEOUT: + if (FUserAction != nullptr) + { + try + { + FUserAction->Execute(nullptr); + } + catch (Exception & E) + { + SaveException(E, FException); + } + + FUserAction = nullptr; + TriggerEvent(); + } + else + { + if (FOnIdle != nullptr) + { + FOnIdle(nullptr); + } + } + break; + + default: + throw Exception(L"Error waiting for background session task to complete"); + } + } + while (!Done); + + + Rethrow(FException); + } + __finally + { + FAction = nullptr; + SAFE_DESTROY(FException); + }; + } + catch (...) + { + if (FCancelled) + { + // even if the abort thrown as result of Cancel() was wrapped into + // some higher-level exception, normalize back to message-less fatal + // exception here + FatalAbort(); + } + else + { + throw; + } + } +} + +void TTerminalThread::TerminalOpenEvent(TObject * /*Sender*/) +{ + FTerminal->Open(); +} + +void TTerminalThread::TerminalReopenEvent(TObject * /*Sender*/) +{ + FTerminal->Reopen(0); +} + +void TTerminalThread::ProcessEvent() +{ + DebugAssert(FEvent != nullptr); + DebugAssert(FException == nullptr); + + try + { + FAction(nullptr); + } + catch (Exception & E) + { + SaveException(E, FException); + } + + ::SetEvent(FActionEvent); +} + +void TTerminalThread::Rethrow(Exception *& Exception) +{ + if (Exception != nullptr) + { + try__finally + { + SCOPE_EXIT + { + SAFE_DESTROY(Exception); + }; + RethrowException(Exception); + } + __finally + { + SAFE_DESTROY(Exception); + }; + } +} + +void TTerminalThread::SaveException(Exception & E, Exception *& Exception) +{ + DebugAssert(Exception == nullptr); + + Exception = CloneException(&E); +} + +void TTerminalThread::FatalAbort() +{ + FTerminal->FatalAbort(); +} + +void TTerminalThread::CheckCancel() +{ + if (FCancel && !FCancelled) + { + FCancelled = true; + FatalAbort(); + } +} + +void TTerminalThread::WaitForUserAction(TUserAction * UserAction) +{ + DWORD Thread = GetCurrentThreadId(); + // we can get called from the main thread from within Idle, + // should be only to call HandleExtendedException + if (Thread == FMainThread) + { + if (UserAction != nullptr) + { + UserAction->Execute(nullptr); + } + } + else + { + // we should be called from our thread only, + // with exception noted above + DebugAssert(Thread == FThreadId); + + bool DoCheckCancel = + DebugAlwaysFalse(UserAction == nullptr) || !UserAction->Force(); + if (DoCheckCancel) + { + CheckCancel(); + } + + // have to save it as we can go recursive via TQueryParams::TimerEvent, + // see TTerminalThread::TerminalQueryUser + TUserAction * PrevUserAction = FUserAction; + try__finally + { + SCOPE_EXIT + { + FUserAction = PrevUserAction; + SAFE_DESTROY(FException); + }; + FUserAction = UserAction; + + while (true) + { + + { + TGuard Guard(FSection); + // If idle exception is already set, we are only waiting + // for the main thread to pick it up + // (or at least to finish handling the user action, so + // that we rethrow the idle exception below) + // Also if idle exception is set, it is probable that terminal + // is not active anyway. + if (FTerminal->GetActive() && FPendingIdle && (FIdleException == nullptr)) + { + FPendingIdle = false; + try + { + FTerminal->Idle(); + } + catch (Exception & E) + { + SaveException(E, FIdleException); + } + } + } + + int WaitResult = WaitForEvent(1000); + if (WaitResult == 0) + { + SAFE_DESTROY(FIdleException); + FatalAbort(); + } + else if (WaitResult > 0) + { + break; + } + } + + + Rethrow(FException); + + if (FIdleException != nullptr) + { + // idle exception was not used to cancel the user action + // (if it where it would be already cloned into the FException above + // and rethrown) + Rethrow(FIdleException); + } + } + __finally + { + FUserAction = PrevUserAction; + SAFE_DESTROY(FException); + }; + + // Contrary to a call before, this is unconditional, + // otherwise cancelling authentication won't work, + // if it is tried only after the last user action + // (what is common, when cancelling while waiting for + // resolving of unresolvable host name, where the last user action is + // "resolving hostname" information action) + CheckCancel(); + } +} + +void TTerminalThread::TerminalInformation( + TTerminal * Terminal, const UnicodeString & Str, bool Status, intptr_t Phase) +{ + TInformationUserAction Action(FOnInformation); + Action.Terminal = Terminal; + Action.Str = Str; + Action.Status = Status; + Action.Phase = Phase; + + WaitForUserAction(&Action); +} + +void TTerminalThread::TerminalQueryUser(TObject * Sender, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * Params, uintptr_t & Answer, TQueryType Type, void * Arg) +{ + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + // note about TQueryParams::TimerEvent + // So far there is only one use for this, the TSecureShell::SendBuffer, + // which should be thread-safe, as long as the terminal thread, + // is stopped waiting for OnQueryUser to finish. + + // note about TQueryButtonAlias::OnClick + // So far there is only one use for this, the TClipboardHandler, + // which is thread-safe. + + TQueryUserAction Action(FOnQueryUser); + Action.Sender = Sender; + Action.Query = AQuery; + Action.MoreMessages = MoreMessages; + Action.Answers = Answers; + Action.Params = Params; + Action.Answer = Answer; + Action.Type = Type; + + WaitForUserAction(&Action); + + Answer = Action.Answer; +} + +void TTerminalThread::TerminalInitializeLog(TObject * Sender) +{ + if (FOnInitializeLog != nullptr) + { + // never used, so not tested either + DebugFail(); + TNotifyAction Action(FOnInitializeLog); + Action.Sender = Sender; + + WaitForUserAction(&Action); + } +} + +void TTerminalThread::TerminalPromptUser(TTerminal * Terminal, + TPromptKind Kind, const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Results, bool & Result, void * Arg) +{ + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + TPromptUserAction Action(FOnPromptUser); + Action.Terminal = Terminal; + Action.Kind = Kind; + Action.Name = Name; + Action.Instructions = Instructions; + Action.Prompts = Prompts; + Action.Results->AddStrings(Results); + + WaitForUserAction(&Action); + + Results->Clear(); + Results->AddStrings(Action.Results); + Result = Action.Result; +} + +void TTerminalThread::TerminalShowExtendedException( + TTerminal * Terminal, Exception * E, void * Arg) +{ + DebugUsedParam(Arg); + DebugAssert(Arg == nullptr); + + TShowExtendedExceptionAction Action(FOnShowExtendedException); + Action.Terminal = Terminal; + Action.E = E; + + WaitForUserAction(&Action); +} + +void TTerminalThread::TerminalDisplayBanner(TTerminal * Terminal, + const UnicodeString & SessionName, const UnicodeString & Banner, + bool & NeverShowAgain, intptr_t Options) +{ + TDisplayBannerAction Action(FOnDisplayBanner); + Action.Terminal = Terminal; + Action.SessionName = SessionName; + Action.Banner = Banner; + Action.NeverShowAgain = NeverShowAgain; + Action.Options = Options; + + WaitForUserAction(&Action); + + NeverShowAgain = Action.NeverShowAgain; +} + +void TTerminalThread::TerminalChangeDirectory(TObject * Sender) +{ + TNotifyAction Action(FOnChangeDirectory); + Action.Sender = Sender; + + WaitForUserAction(&Action); +} + +void TTerminalThread::TerminalReadDirectory(TObject * Sender, Boolean ReloadOnly) +{ + TReadDirectoryAction Action(FOnReadDirectory); + Action.Sender = Sender; + Action.ReloadOnly = ReloadOnly; + + WaitForUserAction(&Action); +} + +void TTerminalThread::TerminalStartReadDirectory(TObject * Sender) +{ + TNotifyAction Action(FOnStartReadDirectory); + Action.Sender = Sender; + + WaitForUserAction(&Action); +} + +void TTerminalThread::TerminalReadDirectoryProgress( + TObject * Sender, intptr_t Progress, intptr_t ResolvedLinks, bool & Cancel) +{ + TReadDirectoryProgressAction Action(FOnReadDirectoryProgress); + Action.Sender = Sender; + Action.Progress = Progress; + Action.ResolvedLinks = ResolvedLinks; + Action.Cancel = Cancel; + + WaitForUserAction(&Action); + + Cancel = Action.Cancel; +} + +NB_IMPLEMENT_CLASS(TSimpleThread, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TQueueItem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TQueueItemProxy, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TSignalThread, NB_GET_CLASS_INFO(TSimpleThread), nullptr) +NB_IMPLEMENT_CLASS(TTerminalItem, NB_GET_CLASS_INFO(TSignalThread), nullptr) + diff --git a/netbox/src/core/Queue.h b/netbox/src/core/Queue.h new file mode 100644 index 000000000..c349f7bbb --- /dev/null +++ b/netbox/src/core/Queue.h @@ -0,0 +1,498 @@ +#pragma once + +#include "Terminal.h" +#include "FileOperationProgress.h" + +class TTerminalItem; +class TSimpleThread : public TObject +{ +NB_DISABLE_COPY(TSimpleThread) +NB_DECLARE_CLASS(TSimpleThread) +public: + explicit TSimpleThread(); + virtual ~TSimpleThread(); + void Init(); + + virtual void Start(); + void WaitFor(uint32_t Milliseconds = INFINITE); + virtual void Terminate() {} + void Close(); + bool IsFinished(); + +protected: + HANDLE FThread; + TThreadID FThreadId; + bool FFinished; + + virtual void Execute() = 0; + virtual void Finished(); + +public: + static int ThreadProc(void * Thread); +}; + +class TSignalThread : public TSimpleThread +{ +NB_DISABLE_COPY(TSignalThread) +NB_DECLARE_CLASS(TSignalThread) +public: + void Init(bool LowPriority); + virtual void Start(); + virtual void Terminate(); + void TriggerEvent(); + +protected: + HANDLE FEvent; + bool FTerminated; + + explicit TSignalThread(); + virtual ~TSignalThread(); + + virtual bool WaitForEvent(); + int WaitForEvent(uint32_t Timeout); + virtual void Execute(); + virtual void ProcessEvent() = 0; +}; + +class TTerminal; +class TQueueItem; +class TTerminalQueue; +class TQueueItemProxy; +class TTerminalQueueStatus; + +//typedef void __fastcall (__closure * TQueueListUpdate) +// (TTerminalQueue * Queue); +DEFINE_CALLBACK_TYPE1(TQueueListUpdateEvent, void, + TTerminalQueue * /*Queue*/); +//typedef void __fastcall (__closure * TQueueItemUpdateEvent) +// (TTerminalQueue * Queue, TQueueItem * Item); +DEFINE_CALLBACK_TYPE2(TQueueItemUpdateEvent, void, + TTerminalQueue * /*Queue*/, TQueueItem * /*Item*/); + +enum TQueueEvent +{ + qeEmpty, + qeEmptyButMonitored, + qePendingUserAction, +}; + +//typedef void __fastcall (__closure * TQueueEventEvent) +// (TTerminalQueue * Queue, TQueueEvent Event); +DEFINE_CALLBACK_TYPE2(TQueueEventEvent, void, + TTerminalQueue * /*Queue*/, TQueueEvent /*Event*/); +//--------------------------------------------------------------------------- +class TTerminalQueue : public TSignalThread +{ +friend class TQueueItem; +friend class TQueueItemProxy; +NB_DISABLE_COPY(TTerminalQueue) +public: + explicit TTerminalQueue(TTerminal * ATerminal, TConfiguration * AConfiguration); + virtual ~TTerminalQueue(); + + void Init(); + void AddItem(TQueueItem * Item); + TTerminalQueueStatus * CreateStatus(TTerminalQueueStatus * Current); + void Idle(); + + /*__property bool IsEmpty = { read = GetIsEmpty }; + __property int TransfersLimit = { read = FTransfersLimit, write = SetTransfersLimit }; + __property int KeepDoneItemsFor = { read = FKeepDoneItemsFor, write = SetKeepDoneItemsFor }; + __property bool Enabled = { read = FEnabled, write = SetEnabled }; + __property TQueryUserEvent OnQueryUser = { read = FOnQueryUser, write = FOnQueryUser }; + __property TPromptUserEvent OnPromptUser = { read = FOnPromptUser, write = FOnPromptUser }; + __property TExtendedExceptionEvent OnShowExtendedException = { read = FOnShowExtendedException, write = FOnShowExtendedException }; + __property TQueueListUpdate OnListUpdate = { read = FOnListUpdate, write = FOnListUpdate }; + __property TQueueItemUpdateEvent OnQueueItemUpdate = { read = FOnQueueItemUpdate, write = FOnQueueItemUpdate }; + __property TQueueEventEvent OnEvent = { read = FOnEvent, write = FOnEvent };*/ + + bool GetIsEmpty() const; + intptr_t GetTransfersLimit() const { return FTransfersLimit; } + intptr_t GetKeepDoneItemsFor() const { return FKeepDoneItemsFor; } + bool GetEnabled() const { return FEnabled; } + TQueryUserEvent & GetOnQueryUser() { return FOnQueryUser; } + void SetOnQueryUser(TQueryUserEvent Value) { FOnQueryUser = Value; } + TPromptUserEvent & GetOnPromptUser() { return FOnPromptUser; } + void SetOnPromptUser(TPromptUserEvent Value) { FOnPromptUser = Value; } + TExtendedExceptionEvent & GetOnShowExtendedException() { return FOnShowExtendedException; } + void SetOnShowExtendedException(TExtendedExceptionEvent Value) { FOnShowExtendedException = Value; } + TQueueListUpdateEvent & GetOnListUpdate() { return FOnListUpdate; } + void SetOnListUpdate(TQueueListUpdateEvent Value) { FOnListUpdate = Value; } + TQueueItemUpdateEvent & GetOnQueueItemUpdate() { return FOnQueueItemUpdate; } + void SetOnQueueItemUpdate(TQueueItemUpdateEvent Value) { FOnQueueItemUpdate = Value; } + TQueueEventEvent & GetOnEvent() { return FOnEvent; } + void SetOnEvent(TQueueEventEvent Value) { FOnEvent = Value; } + +protected: + friend class TTerminalItem; + friend class TQueryUserAction; + friend class TPromptUserAction; + friend class TShowExtendedExceptionAction; + + TQueryUserEvent FOnQueryUser; + TPromptUserEvent FOnPromptUser; + TExtendedExceptionEvent FOnShowExtendedException; + TQueueItemUpdateEvent FOnQueueItemUpdate; + TQueueListUpdateEvent FOnListUpdate; + TQueueEventEvent FOnEvent; + TTerminal * FTerminal; + TConfiguration * FConfiguration; + TSessionData * FSessionData; + TList * FItems; + TList * FDoneItems; + intptr_t FItemsInProcess; + TCriticalSection FItemsSection; + intptr_t FFreeTerminals; + TList * FTerminals; + TList * FForcedItems; + intptr_t FTemporaryTerminals; + intptr_t FOverallTerminals; + intptr_t FTransfersLimit; + intptr_t FKeepDoneItemsFor; + bool FEnabled; + TDateTime FIdleInterval; + TDateTime FLastIdle; + +public: + inline static TQueueItem * GetItem(TList * List, intptr_t Index); + inline TQueueItem * GetItem(intptr_t Index); + void FreeItemsList(TList *& List); + void UpdateStatusForList( + TTerminalQueueStatus * Status, TList * List, TTerminalQueueStatus * Current); + bool ItemGetData(TQueueItem * Item, TQueueItemProxy * Proxy); + bool ItemProcessUserAction(TQueueItem * Item, void * Arg); + bool ItemMove(TQueueItem * Item, TQueueItem * BeforeItem); + bool ItemExecuteNow(TQueueItem * Item); + bool ItemDelete(TQueueItem * Item); + bool ItemPause(TQueueItem * Item, bool Pause); + bool ItemSetCPSLimit(TQueueItem * Item, uintptr_t CPSLimit); + bool ItemGetCPSLimit(TQueueItem * Item, uintptr_t & CPSLimit) const; + + void RetryItem(TQueueItem * Item); + void DeleteItem(TQueueItem * Item, bool CanKeep); + + virtual bool WaitForEvent(); + virtual void ProcessEvent(); + void TerminalFinished(TTerminalItem * TerminalItem); + bool TerminalFree(TTerminalItem * TerminalItem); + + void DoQueueItemUpdate(TQueueItem * Item); + void DoListUpdate(); + void DoEvent(TQueueEvent Event); + +public: + void SetMasks(const UnicodeString & Value); + void SetTransfersLimit(intptr_t Value); + void SetKeepDoneItemsFor(intptr_t Value); + void SetEnabled(bool Value); + void SetIsEmpty(bool Value); +}; + +//--------------------------------------------------------------------------- +class TQueueItem : public TObject +{ +friend class TTerminalQueue; +friend class TTerminalItem; +NB_DISABLE_COPY(TQueueItem) +NB_DECLARE_CLASS(TQueueItem) +public: + enum TStatus + { + qsPending, qsConnecting, qsProcessing, qsPrompt, qsQuery, qsError, + qsPaused, qsDone, + }; + struct TInfo : public TObject + { + TInfo() : + Operation(foNone), + Side(osLocal), + SingleFile(false) + {} + TFileOperation Operation; + TOperationSide Side; + UnicodeString Source; + UnicodeString Destination; + UnicodeString ModifiedLocal; + UnicodeString ModifiedRemote; + bool SingleFile; + }; + + static bool IsUserActionStatus(TStatus Status); + +/* + __property TStatus Status = { read = GetStatus }; + __property HANDLE CompleteEvent = { read = FCompleteEvent, write = FCompleteEvent }; +*/ + HANDLE GetCompleteEvent() const { return FCompleteEvent; } + void SetCompleteEvent(HANDLE Value) { FCompleteEvent = Value; } + +protected: + TStatus FStatus; + TCriticalSection FSection; + TTerminalItem * FTerminalItem; + TFileOperationProgressType * FProgressData; + TQueueItem::TInfo * FInfo; + TTerminalQueue * FQueue; + HANDLE FCompleteEvent; + uintptr_t FCPSLimit; + TDateTime FDoneAt; + + explicit TQueueItem(); + virtual ~TQueueItem(); + +public: + void SetMasks(const UnicodeString & Value); + void SetStatus(TStatus Status); + TStatus GetStatus() const; + void SetProgress(TFileOperationProgressType & ProgressData); + void GetData(TQueueItemProxy * Proxy) const; + void SetCPSLimit(uintptr_t CPSLimit); + bool GetCPSLimit(uintptr_t & CPSLimit) const; + +private: + void Execute(TTerminalItem * TerminalItem); + virtual void DoExecute(TTerminal * Terminal) = 0; + uintptr_t GetCPSLimit() const; + virtual uintptr_t DefaultCPSLimit() const; + virtual UnicodeString GetStartupDirectory() const = 0; + void Complete(); +}; + +class TQueueItemProxy : public TObject +{ +friend class TQueueItem; +friend class TTerminalQueueStatus; +friend class TTerminalQueue; +NB_DISABLE_COPY(TQueueItemProxy) +NB_DECLARE_CLASS(TQueueItemProxy) +public: + bool Update(); + bool ProcessUserAction(); + bool Move(bool Sooner); + bool Move(TQueueItemProxy * BeforeItem); + bool ExecuteNow(); + bool Delete(); + bool Pause(); + bool Resume(); + bool SetCPSLimit(uintptr_t CPSLimit); + bool GetCPSLimit(uintptr_t & CPSLimit) const; + +/* + __property TFileOperationProgressType * ProgressData = { read = GetProgressData }; + __property __int64 TotalTransferred = { read = GetTotalTransferred }; + __property TQueueItem::TInfo * Info = { read = FInfo }; + __property TQueueItem::TStatus Status = { read = FStatus }; + __property bool ProcessingUserAction = { read = FProcessingUserAction }; + __property int Index = { read = GetIndex }; + __property void * UserData = { read = FUserData, write = FUserData }; +*/ + TQueueItem::TInfo * GetInfo() const { return FInfo; } + TQueueItem::TStatus GetStatus() const { return FStatus; } + bool GetProcessingUserAction() const { return FProcessingUserAction; } + void * GetUserData() const { return FUserData; } + void * GetUserData() { return FUserData; } + void SetUserData(void * Value) { FUserData = Value; } + void SetMasks(const UnicodeString & Value); + +private: + TFileOperationProgressType * FProgressData; + TQueueItem::TStatus FStatus; + TTerminalQueue * FQueue; + TQueueItem * FQueueItem; + TTerminalQueueStatus * FQueueStatus; + TQueueItem::TInfo * FInfo; + bool FProcessingUserAction; + void * FUserData; + + explicit TQueueItemProxy(TTerminalQueue * Queue, TQueueItem * QueueItem); + virtual ~TQueueItemProxy(); + +public: + intptr_t GetIndex() const; + TFileOperationProgressType * GetProgressData(); + int64_t GetTotalTransferred(); +}; + +class TTerminalQueueStatus : public TObject +{ +friend class TTerminalQueue; +friend class TQueueItemProxy; +NB_DISABLE_COPY(TTerminalQueueStatus) +public: + virtual ~TTerminalQueueStatus(); + + TQueueItemProxy * FindByQueueItem(TQueueItem * QueueItem); + + /*__property int Count = { read = GetCount }; + __property int DoneCount = { read = FDoneCount }; + __property int ActiveCount = { read = GetActiveCount }; + __property int DoneAndActiveCount = { read = GetDoneAndActiveCount }; + __property int ActiveAndPendingCount = { read = GetActiveAndPendingCount }; + __property TQueueItemProxy * Items[int Index] = { read = GetItem };*/ + +protected: + TTerminalQueueStatus(); + + void Add(TQueueItemProxy * ItemProxy); + void Delete(TQueueItemProxy * ItemProxy); + void ResetStats(); + +private: + TList * FList; + intptr_t FDoneCount; + mutable intptr_t FActiveCount; + +public: + intptr_t GetCount() const; + intptr_t GetDoneCount() const { return FDoneCount; } + intptr_t GetActiveCount() const; + intptr_t GetDoneAndActiveCount() const; + intptr_t GetActiveAndPendingCount() const; + TQueueItemProxy * GetItem(intptr_t Index) const; + TQueueItemProxy * GetItem(intptr_t Index); + void SetMasks(const UnicodeString & Value); + void SetDoneCount(intptr_t Value); +}; + +class TLocatedQueueItem : public TQueueItem +{ +protected: + explicit TLocatedQueueItem(TTerminal * Terminal); + virtual ~TLocatedQueueItem() {} + + virtual void DoExecute(TTerminal * Terminal); + virtual UnicodeString GetStartupDirectory() const; + +private: + UnicodeString FCurrentDir; +}; + +class TTransferQueueItem : public TLocatedQueueItem +{ +NB_DISABLE_COPY(TTransferQueueItem) +public: + explicit TTransferQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, TOperationSide Side, + bool SingleFile); + virtual ~TTransferQueueItem(); + +protected: + TStrings * FFilesToCopy; + UnicodeString FTargetDir; + TCopyParamType * FCopyParam; + intptr_t FParams; + + virtual uintptr_t DefaultCPSLimit() const; +}; + +class TUploadQueueItem : public TTransferQueueItem +{ +public: + explicit TUploadQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, bool SingleFile); + virtual ~TUploadQueueItem() {} + +protected: + virtual void DoExecute(TTerminal * Terminal); +}; + +class TDownloadQueueItem : public TTransferQueueItem +{ +public: + explicit TDownloadQueueItem(TTerminal * Terminal, + const TStrings * AFilesToCopy, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, bool SingleFile); + virtual ~TDownloadQueueItem() {} + +protected: + virtual void DoExecute(TTerminal * Terminal); +}; + +class TUserAction; +class TTerminalThread : public TSignalThread +{ +NB_DISABLE_COPY(TTerminalThread) +public: + explicit TTerminalThread(TTerminal * Terminal); + void Init(); + virtual ~TTerminalThread(); + + void TerminalOpen(); + void TerminalReopen(); + + void Cancel(); + void Idle(); + +/* + __property TNotifyEvent OnIdle = { read = FOnIdle, write = FOnIdle }; + __property bool Cancelling = { read = FCancel }; +*/ + TNotifyEvent & GetOnIdle() { return FOnIdle; } + void SetOnIdle(TNotifyEvent Value) { FOnIdle = Value; } + bool GetCancelling() const { return FCancel; } + +protected: + virtual void ProcessEvent(); + +private: + TTerminal * FTerminal; + + TInformationEvent FOnInformation; + TQueryUserEvent FOnQueryUser; + TPromptUserEvent FOnPromptUser; + TExtendedExceptionEvent FOnShowExtendedException; + TDisplayBannerEvent FOnDisplayBanner; + TNotifyEvent FOnChangeDirectory; + TReadDirectoryEvent FOnReadDirectory; + TNotifyEvent FOnStartReadDirectory; + TReadDirectoryProgressEvent FOnReadDirectoryProgress; + TNotifyEvent FOnInitializeLog; + + TNotifyEvent FOnIdle; + + TNotifyEvent FAction; + HANDLE FActionEvent; + TUserAction * FUserAction; + + Exception * FException; + Exception * FIdleException; + bool FCancel; + bool FCancelled; + bool FPendingIdle; + + DWORD FMainThread; + TCriticalSection FSection; + + void WaitForUserAction(TUserAction * UserAction); + void RunAction(TNotifyEvent Action); + + static void SaveException(Exception & E, Exception *& Exception); + static void Rethrow(Exception *& Exception); + void FatalAbort(); + void CheckCancel(); + + void TerminalOpenEvent(TObject * Sender); + void TerminalReopenEvent(TObject * Sender); + + void TerminalInformation( + TTerminal * Terminal, const UnicodeString & Str, bool Status, intptr_t Phase); + void TerminalQueryUser(TObject * Sender, + const UnicodeString & AQuery, TStrings * MoreMessages, uintptr_t Answers, + const TQueryParams * Params, uintptr_t & Answer, TQueryType Type, void * Arg); + void TerminalPromptUser(TTerminal * Terminal, TPromptKind Kind, + const UnicodeString & Name, const UnicodeString & Instructions, + TStrings * Prompts, TStrings * Results, bool & Result, void * Arg); + void TerminalShowExtendedException(TTerminal * Terminal, + Exception * E, void * Arg); + void TerminalDisplayBanner(TTerminal * Terminal, + const UnicodeString & SessionName, const UnicodeString & Banner, + bool & NeverShowAgain, intptr_t Options); + void TerminalChangeDirectory(TObject * Sender); + void TerminalReadDirectory(TObject * Sender, Boolean ReloadOnly); + void TerminalStartReadDirectory(TObject * Sender); + void TerminalReadDirectoryProgress(TObject * Sender, intptr_t Progress, intptr_t ResolvedLinks, bool & Cancel); + void TerminalInitializeLog(TObject * Sender); +}; + diff --git a/netbox/src/core/RemoteFiles.cpp b/netbox/src/core/RemoteFiles.cpp new file mode 100644 index 000000000..749ec3e63 --- /dev/null +++ b/netbox/src/core/RemoteFiles.cpp @@ -0,0 +1,2917 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#include "RemoteFiles.h" +#include "Interface.h" +#include "Terminal.h" +#include "TextsCore.h" +#include "HelpCore.h" +/* TODO 1 : Path class instead of UnicodeString (handle relativity...) */ +//--------------------------------------------------------------------------- + +namespace core { + +bool IsUnixStyleWindowsPath(const UnicodeString & APath) +{ + return (APath.Length() >= 3) && IsLetter(APath[1]) && (APath[2] == L':') && (APath[3] == L'/'); +} + +bool UnixIsAbsolutePath(const UnicodeString & APath) +{ + return + ((APath.Length() >= 1) && (APath[1] == L'/')) || + // we need this for FTP only, but this is unfortunately used in a static context + core::IsUnixStyleWindowsPath(APath); +} + +UnicodeString UnixIncludeTrailingBackslash(const UnicodeString & APath) +{ + // it used to return "/" when input path was empty + if (!APath.IsEmpty() && !APath.IsDelimiter(SLASH, APath.Length())) + { + return APath + SLASH; + } + else + { + return APath; + } +} + +// Keeps "/" for root path +UnicodeString UnixExcludeTrailingBackslash(const UnicodeString & APath, bool Simple) +{ + if (APath.IsEmpty() || + (APath == ROOTDIRECTORY) || + !APath.IsDelimiter(SLASH, APath.Length()) || + (!Simple && ((APath.Length() == 3) && core::IsUnixStyleWindowsPath(APath)))) + { + return APath; + } + else + { + return APath.SubString(1, APath.Length() - 1); + } +} + +UnicodeString SimpleUnixExcludeTrailingBackslash(const UnicodeString & APath) +{ + return core::UnixExcludeTrailingBackslash(APath, true); +} + +Boolean UnixSamePath(const UnicodeString & APath1, const UnicodeString & APath2) +{ + return (core::UnixIncludeTrailingBackslash(APath1) == core::UnixIncludeTrailingBackslash(APath2)); +} + +bool UnixIsChildPath(const UnicodeString & AParent, const UnicodeString & AChild) +{ + UnicodeString Parent = core::UnixIncludeTrailingBackslash(AParent); + UnicodeString Child = core::UnixIncludeTrailingBackslash(AChild); + return (Child.SubString(1, Parent.Length()) == Parent); +} + +UnicodeString UnixExtractFileDir(const UnicodeString & APath) +{ + intptr_t Pos = APath.LastDelimiter(L'/'); + // it used to return Path when no slash was found + if (Pos > 1) + { + return APath.SubString(1, Pos - 1); + } + else + { + return (Pos == 1) ? UnicodeString(L"/") : UnicodeString(); + } +} + +// must return trailing backslash +UnicodeString UnixExtractFilePath(const UnicodeString & APath) +{ + intptr_t Pos = APath.LastDelimiter(L'/'); + // it used to return Path when no slash was found + if (Pos > 0) + { + return APath.SubString(1, Pos); + } + else + { + return UnicodeString(); + } +} + +UnicodeString UnixExtractFileName(const UnicodeString & APath) +{ + intptr_t Pos = APath.LastDelimiter(L'/'); + UnicodeString Result; + if (Pos > 0) + { + Result = APath.SubString(Pos + 1, APath.Length() - Pos); + } + else + { + Result = APath; + } + return Result; +} + +UnicodeString UnixExtractFileExt(const UnicodeString & APath) +{ + UnicodeString FileName = UnixExtractFileName(APath); + intptr_t Pos = FileName.LastDelimiter(L"."); + if (Pos > 0) + return APath.SubString(Pos, APath.Length() - Pos + 1); + else + return UnicodeString(); +} + +UnicodeString ExtractFileName(const UnicodeString & Path, bool Unix) +{ + if (Unix) + { + return UnixExtractFileName(Path); + } + else + { + return base::ExtractFileName(Path, Unix); + } +} + +bool ExtractCommonPath(const TStrings * AFiles, OUT UnicodeString & APath) +{ + DebugAssert(AFiles->GetCount() > 0); + + APath = ::ExtractFilePath(AFiles->GetString(0)); + bool Result = !APath.IsEmpty(); + if (Result) + { + for (intptr_t Index = 1; Index < AFiles->GetCount(); ++Index) + { + while (!APath.IsEmpty() && + (AFiles->GetString(Index).SubString(1, APath.Length()) != APath)) + { + intptr_t PrevLen = APath.Length(); + APath = ::ExtractFilePath(::ExcludeTrailingBackslash(APath)); + if (APath.Length() == PrevLen) + { + APath.Clear(); + Result = false; + } + } + } + } + + return Result; +} + +bool UnixExtractCommonPath(const TStrings * const AFiles, OUT UnicodeString & APath) +{ + DebugAssert(AFiles->GetCount() > 0); + + APath = core::UnixExtractFilePath(AFiles->GetString(0)); + bool Result = !APath.IsEmpty(); + if (Result) + { + for (intptr_t Index = 1; Index < AFiles->GetCount(); ++Index) + { + while (!APath.IsEmpty() && + (AFiles->GetString(Index).SubString(1, APath.Length()) != APath)) + { + intptr_t PrevLen = APath.Length(); + APath = core::UnixExtractFilePath(core::UnixExcludeTrailingBackslash(APath)); + if (APath.Length() == PrevLen) + { + APath.Clear(); + Result = false; + } + } + } + } + + return Result; +} + +bool IsUnixRootPath(const UnicodeString & APath) +{ + return APath.IsEmpty() || (APath == ROOTDIRECTORY); +} + +bool IsUnixHiddenFile(const UnicodeString & AFileName) +{ + return (AFileName != THISDIRECTORY) && (AFileName != PARENTDIRECTORY) && + !AFileName.IsEmpty() && (AFileName[1] == L'.'); +} + +UnicodeString AbsolutePath(const UnicodeString & Base, const UnicodeString & APath) +{ + // There's a duplicate implementation in TTerminal::ExpandFileName() + UnicodeString Result; + if (APath.IsEmpty()) + { + Result = Base; + } + else if (APath[1] == L'/') + { + Result = core::UnixExcludeTrailingBackslash(APath); + } + else + { + Result = core::UnixIncludeTrailingBackslash( + core::UnixIncludeTrailingBackslash(Base) + APath); + intptr_t P; + while ((P = Result.Pos(L"/../")) > 0) + { + // special case, "/../" => "/" + if (P == 1) + { + Result = ROOTDIRECTORY; + } + else + { + intptr_t P2 = Result.SubString(1, P-1).LastDelimiter(L"/"); + DebugAssert(P2 > 0); + Result.Delete(P2, P - P2 + 3); + } + } + while ((P = Result.Pos(L"/./")) > 0) + { + Result.Delete(P, 2); + } + Result = core::UnixExcludeTrailingBackslash(Result); + } + return Result; +} + +UnicodeString FromUnixPath(const UnicodeString & APath) +{ + return ReplaceStr(APath, SLASH, BACKSLASH); +} + +UnicodeString ToUnixPath(const UnicodeString & APath) +{ + return ReplaceStr(APath, BACKSLASH, SLASH); +} + +static void CutFirstDirectory(UnicodeString & S, bool Unix) +{ + UnicodeString Sep = Unix ? SLASH : BACKSLASH; + if (S == Sep) + { + S.Clear(); + } + else + { + bool Root = false; + intptr_t P = 0; + if (S[1] == Sep[1]) + { + Root = true; + S.Delete(1, 1); + } + else + { + Root = false; + } + if (S[1] == L'.') + { + S.Delete(1, 4); + } + P = S.Pos(Sep[1]); + if (P) + { + S.Delete(1, P); + S = L"..." + Sep + S; + } + else + { + S.Clear(); + } + if (Root) + { + S = Sep + S; + } + } +} + +UnicodeString MinimizeName(const UnicodeString & AFileName, intptr_t MaxLen, bool Unix) +{ + UnicodeString Drive, Dir, Name, Result; + UnicodeString Sep = Unix ? SLASH : BACKSLASH; + + Result = AFileName; + if (Unix) + { + intptr_t P = Result.LastDelimiter(SLASH); + if (P) + { + Dir = Result.SubString(1, P); + Name = Result.SubString(P + 1, Result.Length() - P); + } + else + { + Dir.Clear(); + Name = Result; + } + } + else + { + Dir = ::ExtractFilePath(Result); + Name = base::ExtractFileName(Result, false); + + if (Dir.Length() >= 2 && Dir[2] == L':') + { + Drive = Dir.SubString(1, 2); + Dir.Delete(1, 2); + } + } + + while ((!Dir.IsEmpty() || !Drive.IsEmpty()) && (Result.Length() > MaxLen)) + { + if (Dir == Sep + L"..." + Sep) + { + Dir = L"..." + Sep; + } + else if (Dir.IsEmpty()) + { + Drive.Clear(); + } + else + { + CutFirstDirectory(Dir, Unix); + } + Result = Drive + Dir + Name; + } + + if (Result.Length() > MaxLen) + { + Result = Result.SubString(1, MaxLen); + } + return Result; +} + +UnicodeString MakeFileList(const TStrings * AFileList) +{ + UnicodeString Result; + for (intptr_t Index = 0; Index < AFileList->GetCount(); ++Index) + { + UnicodeString FileName = AFileList->GetString(Index); + // currently this is used for local file only, so no delimiting is done + AddToList(Result, AddQuotes(FileName), L" "); + } + return Result; +} + +// copy from BaseUtils.pas +TDateTime ReduceDateTimePrecision(const TDateTime & ADateTime, + TModificationFmt Precision) +{ + TDateTime DateTime = ADateTime; + if (Precision == mfNone) + { + DateTime = double(0.0); + } + else if (Precision != mfFull) + { + uint16_t Y, M, D, H, N, S, MS; + + ::DecodeDate(DateTime, Y, M, D); + ::DecodeTime(DateTime, H, N, S, MS); + switch (Precision) + { + case mfMDHM: + S = 0; + MS = 0; + break; + + case mfMDY: + H = 0; + N = 0; + S = 0; + MS = 0; + break; + + default: + DebugFail(); + } + + DateTime = EncodeDateVerbose(Y, M, D) + EncodeTimeVerbose(H, N, S, MS); + } + return DateTime; +} + +TModificationFmt LessDateTimePrecision( + TModificationFmt Precision1, TModificationFmt Precision2) +{ + return (Precision1 < Precision2) ? Precision1 : Precision2; +} + +UnicodeString UserModificationStr(const TDateTime & DateTime, + TModificationFmt Precision) +{ + Word Year, Month, Day, Hour, Min, Sec, MSec; + DateTime.DecodeDate(Year, Month, Day); + DateTime.DecodeTime(Hour, Min, Sec, MSec); + switch (Precision) + { + case mfNone: + return L""; + case mfMDY: + return FORMAT(L"%3s %2d %2d", EngShortMonthNames[Month-1], Day, Year); + case mfMDHM: + return FORMAT(L"%3s %2d %2d:%2.2d", + EngShortMonthNames[Month-1], Day, Hour, Min); + case mfFull: + return FORMAT(L"%3s %2d %2d:%2.2d:%2.2d %4d", + EngShortMonthNames[Month-1], Day, Hour, Min, Sec, Year); + default: + DebugAssert(false); + } + return UnicodeString(); +} + +UnicodeString ModificationStr(const TDateTime & DateTime, + TModificationFmt Precision) +{ + uint16_t Year, Month, Day, Hour, Min, Sec, MSec; + DateTime.DecodeDate(Year, Month, Day); + DateTime.DecodeTime(Hour, Min, Sec, MSec); + switch (Precision) + { + case mfNone: + return L""; + + case mfMDY: + return FORMAT(L"%3s %2d %2d", EngShortMonthNames[Month-1], Day, Year); + + case mfMDHM: + return FORMAT(L"%3s %2d %2d:%2.2d", + EngShortMonthNames[Month-1], Day, Hour, Min); + + default: + DebugFail(); + // fall thru + + case mfFull: + return FORMAT(L"%3s %2d %2d:%2.2d:%2.2d %4d", + EngShortMonthNames[Month-1], Day, Hour, Min, Sec, Year); + } +} + +int FakeFileImageIndex(const UnicodeString & /*AFileName*/, uint32_t /*Attrs*/, + UnicodeString * /*TypeName*/) +{ + /*Attrs |= FILE_ATTRIBUTE_NORMAL; + + TSHFileInfoW SHFileInfo = {0}; + // On Win2k we get icon of "ZIP drive" for ".." (parent directory) + if ((FileName == L"..") || + ((FileName.Length() == 2) && (FileName[2] == L':') && IsLetter(FileName[1])) || + IsReservedName(FileName)) + { + FileName = L"dumb"; + } + // this should be somewhere else, probably in TUnixDirView, + // as the "partial" overlay is added there too + if (AnsiSameText(base::UnixExtractFileExt(FileName), PARTIAL_EXT)) + { + static const size_t PartialExtLen = _countof(PARTIAL_EXT) - 1; + FileName.SetLength(FileName.Length() - PartialExtLen); + } + + int Icon; + if (SHGetFileInfo(FileName.c_str(), + Attrs, &SHFileInfo, sizeof(SHFileInfo), + SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME) != 0) + { + + if (TypeName != nullptr) + { + *TypeName = SHFileInfo.szTypeName; + } + Icon = SHFileInfo.iIcon; + } + else + { + if (TypeName != nullptr) + { + *TypeName = L""; + } + Icon = -1; + } + + return Icon;*/ + return -1; +} + +bool SameUserName(const UnicodeString & UserName1, const UnicodeString & UserName2) +{ + // Bitvise reports file owner as "user@host", but we login with "user" only. + UnicodeString AUserName1 = CopyToChar(UserName1, L'@', true); + UnicodeString AUserName2 = CopyToChar(UserName2, L'@', true); + return SameText(AUserName1, AUserName2); +} + +UnicodeString FormatMultiFilesToOneConfirmation(const UnicodeString & ATarget, bool Unix) +{ + UnicodeString Dir; + UnicodeString Name; + UnicodeString Path; + if (Unix) + { + Dir = UnixExtractFileDir(ATarget); + Name = UnixExtractFileName(ATarget); + Path = UnixIncludeTrailingBackslash(ATarget); + } + else + { + Dir = ExtractFilePath(ATarget); + Name = ExtractFileName(ATarget, Unix); + Path = IncludeTrailingBackslash(ATarget); + } + return FMTLOAD(MULTI_FILES_TO_ONE, Name.c_str(), Dir.c_str(), Path.c_str()); +} + +} // namespace core + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +TRemoteToken::TRemoteToken() : + FID(0), + FIDValid(false) +{ +} + +TRemoteToken::TRemoteToken(const UnicodeString & Name) : + FName(Name), + FID(0), + FIDValid(false) +{ +} + +TRemoteToken::TRemoteToken(const TRemoteToken & rhp) : + FName(rhp.FName), + FID(rhp.FID), + FIDValid(rhp.FIDValid) +{ +} + +void TRemoteToken::Clear() +{ + FID = 0; + FIDValid = false; +} + +bool TRemoteToken::operator ==(const TRemoteToken & rht) const +{ + return + (FName == rht.FName) && + (FIDValid == rht.FIDValid) && + (!FIDValid || (FID == rht.FID)); +} + +bool TRemoteToken::operator !=(const TRemoteToken & rht) const +{ + return !(*this == rht); +} + +TRemoteToken & TRemoteToken::operator =(const TRemoteToken & rht) +{ + if (this != &rht) + { + FName = rht.FName; + FIDValid = rht.FIDValid; + FID = rht.FID; + } + return *this; +} + +intptr_t TRemoteToken::Compare(const TRemoteToken & rht) const +{ + intptr_t Result; + if (!FName.IsEmpty()) + { + if (!rht.FName.IsEmpty()) + { + Result = ::AnsiCompareText(FName, rht.FName); + } + else + { + Result = -1; + } + } + else + { + if (!rht.FName.IsEmpty()) + { + Result = 1; + } + else + { + if (FIDValid) + { + if (rht.FIDValid) + { + Result = (FID < rht.FID) ? -1 : ((FID > rht.FID) ? 1 : 0); + } + else + { + Result = -1; + } + } + else + { + if (rht.FIDValid) + { + Result = 1; + } + else + { + Result = 0; + } + } + } + } + return Result; +} + +void TRemoteToken::SetID(intptr_t Value) +{ + FID = Value; + FIDValid = Value != 0; +} + +bool TRemoteToken::GetNameValid() const +{ + return !FName.IsEmpty(); +} + +bool TRemoteToken::GetIsSet() const +{ + return !FName.IsEmpty() || FIDValid; +} + +UnicodeString TRemoteToken::GetDisplayText() const +{ + if (!FName.IsEmpty()) + { + return FName; + } + else if (FIDValid) + { + return ::IntToStr(FID); + } + else + { + return UnicodeString(); + } +} + +UnicodeString TRemoteToken::GetLogText() const +{ + return FORMAT(L"\"%s\" [%d]", FName.c_str(), static_cast(FID)); +} +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +TRemoteTokenList * TRemoteTokenList::Duplicate() const +{ + std::unique_ptr Result(new TRemoteTokenList()); + try__catch + { + TTokens::const_iterator it = FTokens.begin(); + while (it != FTokens.end()) + { + Result->Add(*it); + ++it; + } + } + /*catch (...) + { + delete Result; + throw; + }*/ + return Result.release(); +} + +void TRemoteTokenList::Clear() +{ + FTokens.clear(); + FNameMap.clear(); + FIDMap.clear(); +} + +void TRemoteTokenList::Add(const TRemoteToken & Token) +{ + FTokens.push_back(Token); + if (Token.GetIDValid()) + { + // std::pair Position = + FIDMap.insert(TIDMap::value_type(Token.GetID(), FTokens.size() - 1)); + } + if (Token.GetNameValid()) + { + // std::pair Position = + FNameMap.insert(TNameMap::value_type(Token.GetName(), FTokens.size() - 1)); + } +} + +void TRemoteTokenList::AddUnique(const TRemoteToken & Token) +{ + if (Token.GetIDValid()) + { + TIDMap::const_iterator it = FIDMap.find(Token.GetID()); + if (it != FIDMap.end()) + { + // is present already. + // may have different name (should not), + // but what can we do about it anyway? + } + else + { + Add(Token); + } + } + else if (Token.GetNameValid()) + { + TNameMap::const_iterator it = FNameMap.find(Token.GetName()); + if (it != FNameMap.end()) + { + // is present already. + } + else + { + Add(Token); + } + } + else + { + // can happen, e.g. with winsshd/SFTP + } +} + +bool TRemoteTokenList::Exists(const UnicodeString & Name) const +{ + // We should make use of SameUserName + return (FNameMap.find(Name) != FNameMap.end()); +} + +const TRemoteToken * TRemoteTokenList::Find(uintptr_t ID) const +{ + TIDMap::const_iterator it = FIDMap.find(ID); + const TRemoteToken * Result; + if (it != FIDMap.end()) + { + Result = &FTokens[(*it).second]; + } + else + { + Result = nullptr; + } + return Result; +} + +const TRemoteToken * TRemoteTokenList::Find(const UnicodeString & Name) const +{ + TNameMap::const_iterator it = FNameMap.find(Name); + const TRemoteToken * Result; + if (it != FNameMap.end()) + { + Result = &FTokens[(*it).second]; + } + else + { + Result = nullptr; + } + return Result; +} + +void TRemoteTokenList::Log(TTerminal * Terminal, const wchar_t * Title) +{ + if (!FTokens.empty()) + { + Terminal->LogEvent(FORMAT(L"Following %s found:", Title)); + for (intptr_t Index = 0; Index < static_cast(FTokens.size()); ++Index) + { + Terminal->LogEvent(UnicodeString(L" ") + FTokens[Index].GetLogText()); + } + } + else + { + Terminal->LogEvent(FORMAT(L"No %s found.", Title)); + } +} + +intptr_t TRemoteTokenList::GetCount() const +{ + return static_cast(FTokens.size()); +} + +const TRemoteToken * TRemoteTokenList::Token(intptr_t Index) const +{ + return &FTokens[Index]; +} +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +TRemoteFile::TRemoteFile(TRemoteFile * ALinkedByFile) : + TPersistent(), + FDirectory(nullptr), + FSize(0), + FINodeBlocks(0), + FIconIndex(0), + FIsSymLink(false), + FLinkedFile(nullptr), + FLinkedByFile(nullptr), + FRights(nullptr), + FTerminal(nullptr), + FType(0), + FSelected(false), + FCyclicLink(false), + FIsHidden(0) +{ + FLinkedFile = nullptr; + FRights = new TRights(); + FIconIndex = -1; + FCyclicLink = false; + FModificationFmt = mfFull; + FLinkedByFile = ALinkedByFile; + FTerminal = nullptr; + FDirectory = nullptr; + FIsHidden = -1; +} + +TRemoteFile::~TRemoteFile() +{ + SAFE_DESTROY(FRights); + SAFE_DESTROY(FLinkedFile); +} + +TRemoteFile * TRemoteFile::Duplicate(bool Standalone) const +{ + std::unique_ptr Result(new TRemoteFile()); + try__catch + { + if (FLinkedFile) + { + Result->FLinkedFile = FLinkedFile->Duplicate(true); + Result->FLinkedFile->FLinkedByFile = Result.get(); + } + Result->SetRights(FRights); +#define COPY_FP(PROP) Result->F ## PROP = F ## PROP; + COPY_FP(Terminal); + COPY_FP(Owner); + COPY_FP(ModificationFmt); + COPY_FP(Size); + COPY_FP(FileName); + COPY_FP(INodeBlocks); + COPY_FP(Modification); + COPY_FP(LastAccess); + COPY_FP(Group); + COPY_FP(IconIndex); + COPY_FP(TypeName); + COPY_FP(IsSymLink); + COPY_FP(LinkTo); + COPY_FP(Type); + COPY_FP(Selected); + COPY_FP(CyclicLink); + COPY_FP(HumanRights); +#undef COPY_FP + if (Standalone && (!FFullFileName.IsEmpty() || (GetDirectory() != nullptr))) + { + Result->FFullFileName = GetFullFileName(); + } + } + /*catch (...) + { + delete Result; + throw; + }*/ + return Result.release(); +} + +void TRemoteFile::LoadTypeInfo() const +{ + /* TODO : If file is link: Should be attributes taken from linked file? */ + /*uint32_t Attrs = INVALID_FILE_ATTRIBUTES; + if (GetIsDirectory()) + { + Attrs |= FILE_ATTRIBUTE_DIRECTORY; + } + if (GetIsHidden()) + { + Attrs |= FILE_ATTRIBUTE_HIDDEN; + } + + UnicodeString DumbFileName = (GetIsSymLink() && !GetLinkTo().IsEmpty() ? GetLinkTo() : GetFileName()); + + FIconIndex = FakeFileImageIndex(DumbFileName, Attrs, &FTypeName);*/ +} + +int64_t TRemoteFile::GetSize() const +{ + return GetIsDirectory() ? 0 : FSize; +} + +intptr_t TRemoteFile::GetIconIndex() const +{ + if (FIconIndex == -1) + { + const_cast(this)->LoadTypeInfo(); + } + return FIconIndex; +} + +UnicodeString TRemoteFile::GetTypeName() const +{ + // check availability of type info by icon index, because type name can be empty + if (FIconIndex < 0) + { + LoadTypeInfo(); + } + return FTypeName; +} + +Boolean TRemoteFile::GetIsHidden() const +{ + bool Result; + switch (FIsHidden) + { + case 0: + Result = false; + break; + + case 1: + Result = true; + break; + + default: + Result = core::IsUnixHiddenFile(GetFileName()); + break; + } + + return Result; +} + +void TRemoteFile::SetIsHidden(bool Value) +{ + FIsHidden = Value ? 1 : 0; +} + +Boolean TRemoteFile::GetIsDirectory() const +{ + return (::UpCase(GetType()) == FILETYPE_DIRECTORY); +} + +Boolean TRemoteFile::GetIsParentDirectory() const +{ + return wcscmp(FFileName.c_str(), PARENTDIRECTORY) == 0; +} + +Boolean TRemoteFile::GetIsThisDirectory() const +{ + return wcscmp(FFileName.c_str(), THISDIRECTORY) == 0; +} + +Boolean TRemoteFile::GetIsInaccesibleDirectory() const +{ + Boolean Result; + if (GetIsDirectory()) + { + DebugAssert(GetTerminal()); + Result = ! + (core::SameUserName(GetTerminal()->TerminalGetUserName(), L"root")) || + (((GetRights()->GetRightUndef(TRights::rrOtherExec) != TRights::rsNo)) || + ((GetRights()->GetRight(TRights::rrGroupExec) != TRights::rsNo) && + GetTerminal()->GetMembership()->Exists(GetFileGroup().GetName())) || + ((GetRights()->GetRight(TRights::rrUserExec) != TRights::rsNo) && + (core::SameUserName(GetTerminal()->TerminalGetUserName(), GetFileOwner().GetName())))); + } + else + { + Result = False; + } + return Result; +} + +wchar_t TRemoteFile::GetType() const +{ + if (GetIsSymLink() && FLinkedFile) + { + return FLinkedFile->GetType(); + } + else + { + return FType; + } +} + +void TRemoteFile::SetType(wchar_t AType) +{ + FType = AType; + FIsSymLink = (static_cast(towupper(FType)) == FILETYPE_SYMLINK); +} + +TRemoteFile * TRemoteFile::GetLinkedFile() const +{ + // do not call FindLinkedFile as it would be called repeatedly for broken symlinks + return FLinkedFile; +} + +void TRemoteFile::SetLinkedFile(TRemoteFile * Value) +{ + if (FLinkedFile != Value) + { + if (FLinkedFile) + { + SAFE_DESTROY(FLinkedFile); + } + FLinkedFile = Value; + } +} + +bool TRemoteFile::GetBrokenLink() const +{ + DebugAssert(GetTerminal()); + // If file is symlink but we couldn't find linked file we assume broken link + return (GetIsSymLink() && (FCyclicLink || !FLinkedFile) && + GetTerminal()->GetResolvingSymlinks()); + // "!FLinkTo.IsEmpty()" removed because it does not work with SFTP +} + +bool TRemoteFile::GetIsTimeShiftingApplicable() const +{ + return GetIsTimeShiftingApplicable(GetModificationFmt()); +} + +bool TRemoteFile::GetIsTimeShiftingApplicable(TModificationFmt ModificationFmt) +{ + return (ModificationFmt == mfMDHM) || (ModificationFmt == mfFull); +} + +void TRemoteFile::ShiftTimeInSeconds(int64_t Seconds) +{ + ShiftTimeInSeconds(FModification, GetModificationFmt(), Seconds); + ShiftTimeInSeconds(FLastAccess, GetModificationFmt(), Seconds); +} + +void TRemoteFile::ShiftTimeInSeconds(TDateTime & DateTime, TModificationFmt ModificationFmt, int64_t Seconds) +{ + if ((Seconds != 0) && GetIsTimeShiftingApplicable(ModificationFmt)) + { + /*DebugAssert(int(FModification) != 0); + FModification = IncSecond(FModification, Seconds); + DebugAssert(int(FLastAccess) != 0); + FLastAccess = IncSecond(FLastAccess, Seconds);*/ + DebugAssert(int(DateTime) != 0); + DateTime = IncSecond(DateTime, Seconds); + } +} + +void TRemoteFile::SetModification(const TDateTime & Value) +{ + if (FModification != Value) + { + FModificationFmt = mfFull; + FModification = Value; + } +} + +UnicodeString TRemoteFile::GetUserModificationStr() const +{ + return core::UserModificationStr(GetModification(), FModificationFmt); +} + +UnicodeString TRemoteFile::GetModificationStr() const +{ + return core::ModificationStr(GetModification(), FModificationFmt); +} + +UnicodeString TRemoteFile::GetExtension() const +{ + return base::UnixExtractFileExt(FFileName); +} + +void TRemoteFile::SetRights(TRights * Value) +{ + FRights->Assign(Value); +} + +UnicodeString TRemoteFile::GetRightsStr() const +{ + // note that HumanRights is typically an empty string + // (with an exception of Perm-fact-only MLSD FTP listing) + return FRights->GetUnknown() ? GetHumanRights() : FRights->GetText(); +} + +void TRemoteFile::SetListingStr(const UnicodeString & Value) +{ + // Value stored in 'Value' can be used for error message + UnicodeString ListingStr = Value; + FIconIndex = -1; + try + { + UnicodeString Col; + + // Do we need to do this (is ever TAB is LS output)? + ListingStr = ReplaceChar(ListingStr, L'\t', L' '); + + SetType(ListingStr[1]); + ListingStr.Delete(1, 1); + + auto GetNCol = [&]() + { + if (ListingStr.IsEmpty()) + throw Exception(L""); + intptr_t P = ListingStr.Pos(L' '); + if (P) + { + Col = ListingStr; + Col.SetLength(P - 1); + ListingStr.Delete(1, P); + } + else + { + Col = ListingStr; + ListingStr.Clear(); + } + }; + auto GetCol = [&]() + { + GetNCol(); + ListingStr = ::TrimLeft(ListingStr); + }; + + // Rights string may contain special permission attributes (S,t, ...) + TODO("maybe no longer necessary, once we can handle the special permissions"); + GetRights()->SetAllowUndef(True); + // On some system there is no space between permissions and node blocks count columns + // so we get only first 9 characters and trim all following spaces (if any) + GetRights()->SetText(ListingStr.SubString(1, 9)); + ListingStr.Delete(1, 9); + // Rights column maybe followed by '+', '@' or '.' signs, we ignore them + // (On MacOS, there may be a space in between) + if (!ListingStr.IsEmpty() && ((ListingStr[1] == L'+') || (ListingStr[1] == L'@') || (ListingStr[1] == L'.'))) + { + ListingStr.Delete(1, 1); + } + else if ((ListingStr.Length() >= 2) && (ListingStr[1] == L' ') && + ((ListingStr[2] == L'+') || (ListingStr[2] == L'@') || (ListingStr[2] == L'.'))) + { + ListingStr.Delete(1, 2); + } + ListingStr = ListingStr.TrimLeft(); + + GetCol(); + if (!::TryStrToInt(Col, FINodeBlocks)) + { + // if the column is not an integer, suppose it's owner + // (Android BusyBox) + FINodeBlocks = 0; + } + else + { + GetCol(); + } + + FOwner.SetName(Col); + + // #60 17.10.01: group name can contain space + FGroup.SetName(L""); + GetCol(); + int64_t ASize; + do + { + FGroup.SetName(FGroup.GetName() + Col); + GetCol(); + DebugAssert(!Col.IsEmpty()); + // for devices etc.. there is additional column ending by comma, we ignore it + if (Col[Col.Length()] == L',') + GetCol(); + ASize = ::StrToInt64Def(Col, -1); + // if it's not a number (file size) we take it as part of group name + // (at least on CygWin, there can be group with space in its name) + if (ASize < 0) + Col = L" " + Col; + } + while (ASize < 0); + + // do not read modification time and filename if it is already set + if (::IsZero(FModification.GetValue()) && GetFileName().IsEmpty()) + { + FSize = ASize; + + bool DayMonthFormat = false; + Word Year = 0, Month = 0, Day = 0, Hour = 0, Min = 0, Sec = 0; + Word CurrYear = 0, CurrMonth = 0, CurrDay = 0; + ::DecodeDate(::Date(), CurrYear, CurrMonth, CurrDay); + + GetCol(); + // format dd mmm or mmm dd ? + Day = ::ToWord(::StrToIntDef(Col, 0)); + if (Day > 0) + { + DayMonthFormat = true; + GetCol(); + } + Month = 0; + auto Col2Month = [&]() + { + for (Word IMonth = 0; IMonth < 12; IMonth++) + if (!Col.CompareIC(EngShortMonthNames[IMonth])) + { + Month = IMonth; + Month++; + break; + } + }; + + Col2Month(); + // if the column is not known month name, it may have been "yyyy-mm-dd" + // for --full-time format + if ((Month == 0) && (Col.Length() == 10) && (Col[5] == L'-') && (Col[8] == L'-')) + { + Year = ToWord(Col.SubString(1, 4).ToInt()); + Month = ToWord(Col.SubString(6, 2).ToInt()); + Day = ToWord(Col.SubString(9, 2).ToInt()); + GetCol(); + Hour = ToWord(Col.SubString(1, 2).ToInt()); + Min = ToWord(Col.SubString(4, 2).ToInt()); + if (Col.Length() >= 8) + { + Sec = ToWord(::StrToInt64(Col.SubString(7, 2))); + } + else + { + Sec = 0; + } + FModificationFmt = mfFull; + // skip TZ (TODO) + // do not trim leading space of filename + GetNCol(); + } + else if ((Month == 0) && (Col.Length() == 3)) + { + // drwxr-xr-x 4 root wheel 512 2 mmm 13:00 .'. + Month = CurrMonth; + GetCol(); + Hour = ToWord(Col.SubString(1, 2).ToInt()); + Min = ToWord(Col.SubString(4, 2).ToInt()); + if (Col.Length() >= 8) + { + Sec = ToWord(::StrToInt64(Col.SubString(7, 2))); + } + else + { + Sec = 0; + } + } + else + { + bool FullTime = false; + // or it may have been day name for another format of --full-time + if (Month == 0) + { + GetCol(); + Col2Month(); + // neither standard, not --full-time format + if (Month == 0) + { + Abort(); + } + else + { + FullTime = true; + } + } + + if (Day == 0) + { + GetNCol(); + Day = ToWord(::StrToInt64(Col)); + } + if ((Day < 1) || (Day > 31)) + { + Abort(); + } + + // second full-time format + // ddd mmm dd hh:nn:ss yyyy + if (FullTime) + { + GetCol(); + if (Col.Length() != 8) + { + Abort(); + } + Hour = ToWord(::StrToInt64(Col.SubString(1, 2))); + Min = ToWord(::StrToInt64(Col.SubString(4, 2))); + Sec = ToWord(::StrToInt64(Col.SubString(7, 2))); + FModificationFmt = mfFull; + // do not trim leading space of filename + GetNCol(); + Year = ToWord(::StrToInt64(Col)); + } + else + { + // for format dd mmm the below description seems not to be true, + // the year is not aligned to 5 characters + if (DayMonthFormat) + { + GetCol(); + } + else + { + // Time/Year indicator is always 5 characters long (???), on most + // systems year is aligned to right (_YYYY), but on some to left (YYYY_), + // we must ensure that trailing space is also deleted, so real + // separator space is not treated as part of file name + Col = ListingStr.SubString(1, 6).Trim(); + ListingStr.Delete(1, 6); + } + // GetNCol(); // We don't want to trim input strings (name with space at beginning???) + // Check if we got time (contains :) or year + intptr_t P; + if ((P = ToWord(Col.Pos(L':'))) > 0) + { + Hour = ToWord(::StrToInt64(Col.SubString(1, P - 1))); + Min = ToWord(::StrToInt64(Col.SubString(P + 1, Col.Length() - P))); + if ((Hour > 23) || (Min > 59)) + Abort(); + // When we don't got year, we assume current year + // with exception that the date would be in future + // in this case we assume last year. + ::DecodeDate(::Date(), Year, CurrMonth, CurrDay); + if ((Month > CurrMonth) || + (Month == CurrMonth && Day > CurrDay)) + { + Year--; + } + Sec = 0; + FModificationFmt = mfMDHM; + } + else + { + Year = ToWord(::StrToInt64(Col)); + if (Year > 10000) + Abort(); + // When we don't got time we assume midnight + Hour = 0; + Min = 0; + Sec = 0; + FModificationFmt = mfMDY; + } + } + } + + if (Year == 0) + Year = CurrYear; + if (Month == 0) + Month = CurrMonth; + if (Day == 0) + Day = CurrDay; + FModification = EncodeDateVerbose(Year, Month, Day) + EncodeTimeVerbose(Hour, Min, Sec, 0); + // adjust only when time is known, + // adjusting default "midnight" time makes no sense + if ((FModificationFmt == mfMDHM) || (FModificationFmt == mfFull)) + { + DebugAssert(GetTerminal() != nullptr); + FModification = ::AdjustDateTimeFromUnix(FModification, + GetTerminal()->GetSessionData()->GetDSTMode()); + } + + if (::IsZero(FLastAccess.GetValue())) + { + FLastAccess = FModification; + } + + // separating space is already deleted, other spaces are treated as part of name + + { + FLinkTo.Clear(); + if (GetIsSymLink()) + { + intptr_t P = ListingStr.Pos(SYMLINKSTR); + if (P) + { + FLinkTo = ListingStr.SubString( + P + wcslen(SYMLINKSTR), ListingStr.Length() - P + wcslen(SYMLINKSTR) + 1); + ListingStr.SetLength(P - 1); + } + else + { + Abort(); + } + } + FFileName = base::UnixExtractFileName(::Trim(ListingStr)); + } + } + } + catch (Exception & E) + { + throw ETerminal(&E, FMTLOAD(LIST_LINE_ERROR, Value.c_str()), HELP_LIST_LINE_ERROR); + } +} + +void TRemoteFile::Complete() +{ + DebugAssert(GetTerminal() != nullptr); + if (GetIsSymLink() && GetTerminal()->GetResolvingSymlinks()) + { + FindLinkedFile(); + } +} + +void TRemoteFile::FindLinkedFile() +{ + DebugAssert(GetTerminal() && GetIsSymLink()); + + if (FLinkedFile) + { + SAFE_DESTROY(FLinkedFile); + } + FLinkedFile = nullptr; + + FCyclicLink = false; + if (!GetLinkTo().IsEmpty()) + { + // check for cyclic link + TRemoteFile * LinkedBy = FLinkedByFile; + while (LinkedBy) + { + if (LinkedBy->GetLinkTo() == GetLinkTo()) + { + // this is currently redundant information, because it is used only to + // detect broken symlink, which would be otherwise detected + // by FLinkedFile == nullptr + FCyclicLink = true; + break; + } + LinkedBy = LinkedBy->FLinkedByFile; + } + } + + if (FCyclicLink) + { + TRemoteFile * LinkedBy = FLinkedByFile; + while (LinkedBy) + { + LinkedBy->FCyclicLink = true; + LinkedBy = LinkedBy->FLinkedByFile; + } + } + else + { + DebugAssert(GetTerminal()->GetResolvingSymlinks()); + GetTerminal()->SetExceptionOnFail(true); + try + { + try__finally + { + SCOPE_EXIT + { + GetTerminal()->SetExceptionOnFail(false); + }; + GetTerminal()->ReadSymlink(this, FLinkedFile); + } + __finally + { + GetTerminal()->SetExceptionOnFail(false); + }; + } + catch (Exception & E) + { + if (NB_STATIC_DOWNCAST(EFatal, &E) != nullptr) + { + throw; + } + else + { + GetTerminal()->GetLog()->AddException(&E); + } + } + } +} + +UnicodeString TRemoteFile::GetListingStr() const +{ + // note that ModificationStr is longer than 12 for mfFull + UnicodeString LinkPart; + // expanded from ?: to avoid memory leaks + if (GetIsSymLink()) + { + LinkPart = UnicodeString(SYMLINKSTR) + GetLinkTo(); + } + return FORMAT(L"%s%s %3s %-8s %-8s %9s %-12s %s%s", + GetType(), GetRights()->GetText().c_str(), ::Int64ToStr(FINodeBlocks).c_str(), GetFileOwner().GetName().c_str(), GetFileGroup().GetName().c_str(), + ::Int64ToStr(GetSize()).c_str(), // explicitly using size even for directories + GetModificationStr().c_str(), GetFileName().c_str(), + LinkPart.c_str()); +} + +UnicodeString TRemoteFile::GetFullFileName() const +{ + if (FFullFileName.IsEmpty()) + { + DebugAssert(GetTerminal()); + DebugAssert(GetDirectory() != nullptr); + UnicodeString Path; + if (GetIsParentDirectory()) + { + Path = GetDirectory()->GetParentPath(); + } + else if (GetIsDirectory()) + { + Path = core::UnixIncludeTrailingBackslash(GetDirectory()->GetFullDirectory() + GetFileName()); + } + else + { + Path = GetDirectory()->GetFullDirectory() + GetFileName(); + } + return GetTerminal()->TranslateLockedPath(Path, true); + } + else + { + return FFullFileName; + } +} + +bool TRemoteFile::GetHaveFullFileName() const +{ + return !FFullFileName.IsEmpty() || (GetDirectory() != nullptr); +} + +intptr_t TRemoteFile::GetAttr() const +{ + intptr_t Result = 0; + if (GetRights()->GetReadOnly()) + { + Result |= faReadOnly; + } + if (GetIsHidden()) + { + Result |= faHidden; + } + return Result; +} + +void TRemoteFile::SetTerminal(TTerminal * Value) +{ + FTerminal = Value; + if (FLinkedFile) + { + FLinkedFile->SetTerminal(Value); + } +} + +const TRemoteToken & TRemoteFile::GetFileOwner() const +{ + return FOwner; +} + +TRemoteToken & TRemoteFile::GetFileOwner() +{ + return FOwner; +} + +void TRemoteFile::SetFileOwner(const TRemoteToken & Value) +{ + FOwner = Value; +} + +const TRemoteToken & TRemoteFile::GetFileGroup() const +{ + return FGroup; +} + +TRemoteToken & TRemoteFile::GetFileGroup() +{ + return FGroup; +} + +void TRemoteFile::SetFileGroup(const TRemoteToken & Value) +{ + FGroup = Value; +} + +void TRemoteFile::SetFileName(const UnicodeString & Value) +{ + FFileName = Value; +} + +UnicodeString TRemoteFile::GetLinkTo() const +{ + return FLinkTo; +} + +void TRemoteFile::SetLinkTo(const UnicodeString & Value) +{ + FLinkTo = Value; +} + +void TRemoteFile::SetFullFileName(const UnicodeString & Value) +{ + FFullFileName = Value; +} +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +TRemoteDirectoryFile::TRemoteDirectoryFile() : TRemoteFile() +{ + SetModification(TDateTime(0.0)); + SetModificationFmt(mfNone); + SetLastAccess(GetModification()); + SetType(L'D'); + SetSize(0); +} +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +TRemoteParentDirectory::TRemoteParentDirectory(TTerminal * ATerminal) + : TRemoteDirectoryFile() +{ + SetFileName(PARENTDIRECTORY); + SetTerminal(ATerminal); +} +//=== TRemoteFileList ------------------------------------------------------ +TRemoteFileList::TRemoteFileList() : + TObjectList(), + FTimestamp(Now()) +{ + SetOwnsObjects(true); +} + +void TRemoteFileList::AddFile(TRemoteFile * AFile) +{ + if (AFile) + { + Add(AFile); + AFile->SetDirectory(this); + } +} + +void TRemoteFileList::AddFiles(const TRemoteFileList * AFileList) +{ + if (!AFileList) + return; + for (intptr_t Index = 0; Index < AFileList->GetCount(); ++Index) + { + AddFile(AFileList->GetFile(Index)); + } +} + +void TRemoteFileList::DuplicateTo(TRemoteFileList * Copy) const +{ + Copy->Reset(); + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TRemoteFile * File = GetFile(Index); + Copy->AddFile(File->Duplicate(false)); + } + Copy->FDirectory = GetDirectory(); + Copy->FTimestamp = FTimestamp; +} + +void TRemoteFileList::Reset() +{ + FTimestamp = Now(); + TObjectList::Clear(); +} + +void TRemoteFileList::SetDirectory(const UnicodeString & Value) +{ + FDirectory = core::UnixExcludeTrailingBackslash(Value); +} + +UnicodeString TRemoteFileList::GetFullDirectory() const +{ + return core::UnixIncludeTrailingBackslash(GetDirectory()); +} + +TRemoteFile * TRemoteFileList::GetFile(Integer Index) const +{ + return NB_STATIC_DOWNCAST(TRemoteFile, GetObj(Index)); +} + +Boolean TRemoteFileList::GetIsRoot() const +{ + return (GetDirectory() == ROOTDIRECTORY); +} + +UnicodeString TRemoteFileList::GetParentPath() const +{ + return core::UnixExtractFilePath(GetDirectory()); +} + +int64_t TRemoteFileList::GetTotalSize() const +{ + int64_t Result = 0; + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + // if (!GetFile(Index)->GetIsDirectory()) + { + Result += GetFile(Index)->GetSize(); + } + } + return Result; +} + +TRemoteFile * TRemoteFileList::FindFile(const UnicodeString & AFileName) const +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (GetFile(Index)->GetFileName() == AFileName) + { + return GetFile(Index); + } + } + return nullptr; +} +//=== TRemoteDirectory ------------------------------------------------------ +TRemoteDirectory::TRemoteDirectory(TTerminal * aTerminal, TRemoteDirectory * Template) : + TRemoteFileList(), + FIncludeParentDirectory(false), + FIncludeThisDirectory(false), + FTerminal(aTerminal), + FSelectedFiles(nullptr), + FParentDirectory(nullptr), + FThisDirectory(nullptr) +{ + if (Template == nullptr) + { + FIncludeThisDirectory = false; + FIncludeParentDirectory = true; + } + else + { + FIncludeThisDirectory = Template->FIncludeThisDirectory; + FIncludeParentDirectory = Template->FIncludeParentDirectory; + } +} + +TRemoteDirectory::~TRemoteDirectory() +{ + ReleaseRelativeDirectories(); +} + +void TRemoteDirectory::ReleaseRelativeDirectories() +{ + if ((GetThisDirectory() != nullptr) && !GetIncludeThisDirectory()) + { + SAFE_DESTROY(FThisDirectory); + } + if ((GetParentDirectory() != nullptr) && !GetIncludeParentDirectory()) + { + SAFE_DESTROY(FParentDirectory); + } +} + +void TRemoteDirectory::Reset() +{ + ReleaseRelativeDirectories(); + TRemoteFileList::Reset(); +} + +void TRemoteDirectory::SetDirectory(const UnicodeString & Value) +{ + TRemoteFileList::SetDirectory(Value); +} + +void TRemoteDirectory::AddFile(TRemoteFile * AFile) +{ + if (AFile->GetIsThisDirectory()) + { + FThisDirectory = AFile; + } + if (AFile->GetIsParentDirectory()) + { + FParentDirectory = AFile; + } + + if ((!AFile->GetIsThisDirectory() || GetIncludeThisDirectory()) && + (!AFile->GetIsParentDirectory() || GetIncludeParentDirectory())) + { + TRemoteFileList::AddFile(AFile); + } + AFile->SetTerminal(GetTerminal()); +} + +void TRemoteDirectory::DuplicateTo(TRemoteFileList * Copy) const +{ + TRemoteFileList::DuplicateTo(Copy); + if (GetThisDirectory() && !GetIncludeThisDirectory()) + { + Copy->AddFile(GetThisDirectory()->Duplicate(false)); + } + if (GetParentDirectory() && !GetIncludeParentDirectory()) + { + Copy->AddFile(GetParentDirectory()->Duplicate(false)); + } +} + +bool TRemoteDirectory::GetLoaded() const +{ + return ((GetTerminal() != nullptr) && GetTerminal()->GetActive() && !GetDirectory().IsEmpty()); +} + +TStrings * TRemoteDirectory::GetSelectedFiles() const +{ + if (!FSelectedFiles) + { + FSelectedFiles = new TStringList(); + } + else + { + FSelectedFiles->Clear(); + } + + for (intptr_t Index = 0; Index < GetCount(); Index ++) + { + if (GetFile(Index)->GetSelected()) + { + FSelectedFiles->Add(GetFile(Index)->GetFullFileName()); + } + } + + return FSelectedFiles; +} + +void TRemoteDirectory::SetIncludeParentDirectory(Boolean Value) +{ + if (GetIncludeParentDirectory() != Value) + { + FIncludeParentDirectory = Value; + if (Value && GetParentDirectory()) + { + DebugAssert(IndexOf(GetParentDirectory()) < 0); + Add(GetParentDirectory()); + } + else if (!Value && GetParentDirectory()) + { + DebugAssert(IndexOf(GetParentDirectory()) >= 0); + Extract(GetParentDirectory()); + } + } +} + +void TRemoteDirectory::SetIncludeThisDirectory(Boolean Value) +{ + if (GetIncludeThisDirectory() != Value) + { + FIncludeThisDirectory = Value; + if (Value && GetThisDirectory()) + { + DebugAssert(IndexOf(GetThisDirectory()) < 0); + Add(GetThisDirectory()); + } + else if (!Value && GetThisDirectory()) + { + DebugAssert(IndexOf(GetThisDirectory()) >= 0); + Extract(GetThisDirectory()); + } + } +} + +TRemoteDirectoryCache::TRemoteDirectoryCache() : TStringList() +{ + SetSorted(true); + SetDuplicates(dupError); + SetCaseSensitive(true); +} + +TRemoteDirectoryCache::~TRemoteDirectoryCache() +{ + Clear(); +} + +void TRemoteDirectoryCache::Clear() +{ + TGuard Guard(FSection); + + try__finally + { + SCOPE_EXIT + { + TStringList::Clear(); + }; + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TRemoteFileList * List = NB_STATIC_DOWNCAST(TRemoteFileList, GetObj(Index)); + SAFE_DESTROY(List); + SetObj(Index, nullptr); + } + } + __finally + { + TStringList::Clear(); + }; +} + +bool TRemoteDirectoryCache::GetIsEmpty() const +{ + TGuard Guard(FSection); + + return (const_cast(this)->GetCount() == 0); +} + +bool TRemoteDirectoryCache::HasFileList(const UnicodeString & Directory) const +{ + TGuard Guard(FSection); + + intptr_t Index = IndexOf(core::UnixExcludeTrailingBackslash(Directory)); + return (Index >= 0); +} + +bool TRemoteDirectoryCache::HasNewerFileList(const UnicodeString & Directory, + const TDateTime & Timestamp) const +{ + TGuard Guard(FSection); + + intptr_t Index = IndexOf(core::UnixExcludeTrailingBackslash(Directory)); + if (Index >= 0) + { + TRemoteFileList * FileList = NB_STATIC_DOWNCAST(TRemoteFileList, GetObj(Index)); + if (FileList->GetTimestamp() <= Timestamp) + { + Index = -1; + } + } + return (Index >= 0); +} + +bool TRemoteDirectoryCache::GetFileList(const UnicodeString & Directory, + TRemoteFileList * FileList) const +{ + TGuard Guard(FSection); + + intptr_t Index = IndexOf(core::UnixExcludeTrailingBackslash(Directory)); + bool Result = (Index >= 0); + if (Result) + { + DebugAssert(GetObj(Index) != nullptr); + NB_STATIC_DOWNCAST(TRemoteFileList, GetObj(Index))->DuplicateTo(FileList); + } + return Result; +} + +void TRemoteDirectoryCache::AddFileList(TRemoteFileList * FileList) +{ + DebugAssert(FileList); + if (FileList) + { + TRemoteFileList * Copy = new TRemoteFileList(); + + FileList->DuplicateTo(Copy); + + TGuard Guard(FSection); + + // file list cannot be cached already with only one thread, but it can be + // when directory is loaded by secondary terminal + DoClearFileList(FileList->GetDirectory(), false); + AddObject(Copy->GetDirectory(), Copy); + } +} + +void TRemoteDirectoryCache::ClearFileList(const UnicodeString & Directory, bool SubDirs) +{ + TGuard Guard(FSection); + DoClearFileList(Directory, SubDirs); +} + +void TRemoteDirectoryCache::DoClearFileList(const UnicodeString & Directory, bool SubDirs) +{ + UnicodeString Directory2 = core::UnixExcludeTrailingBackslash(Directory); + intptr_t Index = IndexOf(Directory2); + if (Index >= 0) + { + Delete(Index); + } + if (SubDirs) + { + Directory2 = core::UnixIncludeTrailingBackslash(Directory2); + Index = GetCount() - 1; + while (Index >= 0) + { + if (GetString(Index).SubString(1, Directory2.Length()) == Directory2) + { + Delete(Index); + } + Index--; + } + } +} + +void TRemoteDirectoryCache::Delete(intptr_t Index) +{ + TRemoteFileList * List = NB_STATIC_DOWNCAST(TRemoteFileList, GetObj(Index)); + SAFE_DESTROY(List); + TStringList::Delete(Index); +} + +TRemoteDirectoryChangesCache::TRemoteDirectoryChangesCache(intptr_t MaxSize) : + TStringList(), + FMaxSize(MaxSize) +{ +} + +void TRemoteDirectoryChangesCache::Clear() +{ + TStringList::Clear(); +} + +bool TRemoteDirectoryChangesCache::GetIsEmpty() const +{ + return (const_cast(this)->GetCount() == 0); +} + +void TRemoteDirectoryChangesCache::SetValue(const UnicodeString & Name, + const UnicodeString & Value) +{ + intptr_t Index = IndexOfName(Name); + if (Index >= 0) + { + Delete(Index); + } + TStringList::SetValue(Name, Value); +} + +UnicodeString TRemoteDirectoryChangesCache::GetValue(const UnicodeString & Name) const +{ + return TStringList::GetValue(Name); +} + +UnicodeString TRemoteDirectoryChangesCache::GetValue(const UnicodeString & Name) +{ + UnicodeString Value = TStringList::GetValue(Name); + TStringList::SetValue(Name, Value); + return Value; +} + +void TRemoteDirectoryChangesCache::AddDirectoryChange( + const UnicodeString & SourceDir, const UnicodeString & Change, + const UnicodeString & TargetDir) +{ + DebugAssert(!TargetDir.IsEmpty()); + SetValue(TargetDir, L"//"); + if (TTerminal::ExpandFileName(Change, SourceDir) != TargetDir) + { + UnicodeString Key; + if (DirectoryChangeKey(SourceDir, Change, Key)) + { + SetValue(Key, TargetDir); + } + } +} + +void TRemoteDirectoryChangesCache::ClearDirectoryChange( + const UnicodeString & SourceDir) +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (GetName(Index).SubString(1, SourceDir.Length()) == SourceDir) + { + Delete(Index); + Index--; + } + } +} + +void TRemoteDirectoryChangesCache::ClearDirectoryChangeTarget( + const UnicodeString & TargetDir) +{ + UnicodeString Key; + // hack to clear at least local sym-link change in case symlink is deleted + DirectoryChangeKey(core::UnixExcludeTrailingBackslash(core::UnixExtractFilePath(TargetDir)), + base::UnixExtractFileName(TargetDir), Key); + + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + UnicodeString Name = GetName(Index); + if ((Name.SubString(1, TargetDir.Length()) == TargetDir) || + (GetValue(Name).SubString(1, TargetDir.Length()) == TargetDir) || + (!Key.IsEmpty() && (Name == Key))) + { + Delete(Index); + Index--; + } + } +} + +bool TRemoteDirectoryChangesCache::GetDirectoryChange( + const UnicodeString & SourceDir, const UnicodeString & Change, UnicodeString & TargetDir) const +{ + UnicodeString Key; + Key = TTerminal::ExpandFileName(Change, SourceDir); + if (Key.IsEmpty()) + { + Key = ROOTDIRECTORY; + } + bool Result = (IndexOfName(Key.c_str()) >= 0); + if (Result) + { + TargetDir = GetValue(Key); + // TargetDir is not "//" here only when Change is full path to symbolic link + if (TargetDir == L"//") + { + TargetDir = Key; + } + } + else + { + Result = DirectoryChangeKey(SourceDir, Change, Key); + if (Result) + { + UnicodeString Directory = GetValue(Key); + Result = !Directory.IsEmpty(); + if (Result) + { + TargetDir = Directory; + } + } + } + return Result; +} + +void TRemoteDirectoryChangesCache::Serialize(UnicodeString & Data) const +{ + Data = L"A"; + intptr_t ACount = GetCount(); + if (ACount > FMaxSize) + { + std::unique_ptr Limited(new TStringList()); + try__finally + { + intptr_t Index = ACount - FMaxSize; + while (Index < ACount) + { + Limited->Add(GetString(Index)); + ++Index; + } + Data += Limited->GetText(); + } + __finally + { +// delete Limited; + }; + } + else + { + Data += GetText(); + } +} + +void TRemoteDirectoryChangesCache::Deserialize(const UnicodeString & Data) +{ + if (Data.IsEmpty()) + { + SetText(L""); + } + else + { + SetText(Data.c_str() + 1); + } +} + +bool TRemoteDirectoryChangesCache::DirectoryChangeKey( + const UnicodeString & SourceDir, const UnicodeString & Change, UnicodeString & Key) +{ + bool Result = !Change.IsEmpty(); + if (Result) + { + bool Absolute = core::UnixIsAbsolutePath(Change); + Result = !SourceDir.IsEmpty() || Absolute; + if (Result) + { + // expanded from ?: to avoid memory leaks + if (Absolute) + { + Key = Change; + } + else + { + Key = SourceDir + L"," + Change; + } + } + } + return Result; +} + +const wchar_t TRights::BasicSymbols[] = L"rwxrwxrwx"; +const wchar_t TRights::CombinedSymbols[] = L"--s--s--t"; +const wchar_t TRights::ExtendedSymbols[] = L"--S--S--T"; +const wchar_t TRights::ModeGroups[] = L"ugo"; + +TRights::TRights() : + FAllowUndef(false), + FSet(0), + FUnset(0), + FUnknown(true) +{ + SetNumber(0); +} + +TRights::TRights(uint16_t ANumber) : + FAllowUndef(false), + FSet(0), + FUnset(0), + FUnknown(true) +{ + SetNumber(ANumber); +} + +TRights::TRights(const TRights & Source) +{ + Assign(&Source); +} + +void TRights::Assign(const TRights * Source) +{ + FAllowUndef = Source->GetAllowUndef(); + FSet = Source->FSet; + FUnset = Source->FUnset; + FText = Source->FText; + FUnknown = Source->FUnknown; +} + +TRights::TFlag TRights::RightToFlag(TRights::TRight Right) +{ + return static_cast(1 << (rrLast - Right)); +} + +bool TRights::operator ==(const TRights & rhr) const +{ + if (GetAllowUndef() || rhr.GetAllowUndef()) + { + for (int Right = rrFirst; Right <= rrLast; Right++) + { + if (GetRightUndef(static_cast(Right)) != + rhr.GetRightUndef(static_cast(Right))) + { + return false; + } + } + return true; + } + else + { + return (GetNumber() == rhr.GetNumber()); + } +} + +bool TRights::operator ==(uint16_t rhr) const +{ + return (GetNumber() == rhr); +} + +bool TRights::operator !=(const TRights & rhr) const +{ + return !(*this == rhr); +} + +TRights & TRights::operator =(uint16_t rhr) +{ + SetNumber(rhr); + return *this; +} + +TRights & TRights::operator =(const TRights & rhr) +{ + Assign(&rhr); + return *this; +} + +TRights TRights::operator ~() const +{ + TRights Result(static_cast(~GetNumber())); + return Result; +} + +TRights TRights::operator &(const TRights & rhr) const +{ + TRights Result(*this); + Result &= rhr; + return Result; +} + +TRights TRights::operator &(uint16_t rhr) const +{ + TRights Result(*this); + Result &= rhr; + return Result; +} + +TRights & TRights::operator &=(const TRights & rhr) +{ + if (GetAllowUndef() || rhr.GetAllowUndef()) + { + for (int Right = rrFirst; Right <= rrLast; Right++) + { + if (GetRightUndef(static_cast(Right)) != + rhr.GetRightUndef(static_cast(Right))) + { + SetRightUndef(static_cast(Right), rsUndef); + } + } + } + else + { + SetNumber(GetNumber() & rhr.GetNumber()); + } + return *this; +} + +TRights & TRights::operator &=(uint16_t rhr) +{ + SetNumber(GetNumber() & rhr); + return *this; +} + +TRights TRights::operator |(const TRights & rhr) const +{ + TRights Result(*this); + Result |= rhr; + return Result; +} + +TRights TRights::operator |(uint16_t rhr) const +{ + TRights Result(*this); + Result |= rhr; + return Result; +} + +TRights & TRights::operator |=(const TRights & rhr) +{ + SetNumber(GetNumber() | rhr.GetNumber()); + return *this; +} + +TRights & TRights::operator |=(uint16_t rhr) +{ + SetNumber(GetNumber() | rhr); + return *this; +} + +void TRights::SetAllowUndef(bool Value) +{ + if (FAllowUndef != Value) + { + DebugAssert(!Value || ((FSet | FUnset) == rfAllSpecials)); + FAllowUndef = Value; + } +} + +void TRights::SetText(const UnicodeString & Value) +{ + if (Value != GetText()) + { + if ((Value.Length() != TextLen) || + (!GetAllowUndef() && (Value.Pos(UndefSymbol) > 0)) || + (Value.Pos(L" ") > 0)) + { + throw Exception(FMTLOAD(RIGHTS_ERROR, Value.c_str())); + } + + FSet = 0; + FUnset = 0; + intptr_t Flag = 00001; + int ExtendedFlag = 01000; //-V536 + bool KeepText = false; + for (intptr_t Index = TextLen; Index >= 1; Index--) + { + if (Value[Index] == UnsetSymbol) + { + FUnset |= static_cast(Flag | ExtendedFlag); + } + else if (Value[Index] == UndefSymbol) + { + // do nothing + } + else if (Value[Index] == CombinedSymbols[Index - 1]) + { + FSet |= static_cast(Flag | ExtendedFlag); + } + else if (Value[Index] == ExtendedSymbols[Index - 1]) + { + FSet |= static_cast(ExtendedFlag); + FUnset |= static_cast(Flag); + } + else + { + if (Value[Index] != BasicSymbols[Index - 1]) + { + KeepText = true; + } + FSet |= static_cast(Flag); + if (Index % 3 == 0) + { + FUnset |= static_cast(ExtendedFlag); + } + } + + Flag <<= 1; + if (Index % 3 == 1) + { + ExtendedFlag <<= 1; + } + } + + FText = KeepText ? Value : UnicodeString(); + } + FUnknown = false; +} + +UnicodeString TRights::GetText() const +{ + if (!FText.IsEmpty()) + { + return FText; + } + else + { + UnicodeString Result(TextLen, 0); + + intptr_t Flag = 00001; + int ExtendedFlag = 01000; //-V536 + bool ExtendedPos = true; + wchar_t Symbol; + intptr_t Index = TextLen; + while (Index >= 1) + { + if (ExtendedPos && + ((FSet & (Flag | ExtendedFlag)) == (Flag | ExtendedFlag))) + { + Symbol = CombinedSymbols[Index - 1]; + } + else if ((FSet & Flag) != 0) + { + Symbol = BasicSymbols[Index - 1]; + } + else if (ExtendedPos && ((FSet & ExtendedFlag) != 0)) + { + Symbol = ExtendedSymbols[Index - 1]; + } + else if ((!ExtendedPos && ((FUnset & Flag) == Flag)) || + (ExtendedPos && ((FUnset & (Flag | ExtendedFlag)) == (Flag | ExtendedFlag)))) + { + Symbol = UnsetSymbol; + } + else + { + Symbol = UndefSymbol; + } + + Result[Index] = Symbol; + + Flag <<= 1; + Index--; + ExtendedPos = ((Index % 3) == 0); + if (ExtendedPos) + { + ExtendedFlag <<= 1; + } + } + return Result; + } +} + +void TRights::SetOctal(const UnicodeString & AValue) +{ + UnicodeString Value(AValue); + if (Value.Length() == 3) + { + Value = L"0" + Value; + } + + if (GetOctal() != Value.c_str()) + { + bool Correct = (Value.Length() == 4); + if (Correct) + { + for (intptr_t Index = 1; (Index <= Value.Length()) && Correct; ++Index) + { + Correct = (Value[Index] >= L'0') && (Value[Index] <= L'7'); + } + } + + if (!Correct) + { + throw Exception(FMTLOAD(INVALID_OCTAL_PERMISSIONS, AValue.c_str())); + } + + SetNumber(static_cast( + ((Value[1] - L'0') << 9) + + ((Value[2] - L'0') << 6) + + ((Value[3] - L'0') << 3) + + ((Value[4] - L'0') << 0))); + } + FUnknown = false; +} + +uint32_t TRights::GetNumberDecadic() const +{ + uint32_t N = GetNumberSet(); // used to be "Number" + uint32_t Result = + ((N & 07000) / 01000 * 1000) + + ((N & 00700) / 0100 * 100) + + ((N & 00070) / 010 * 10) + + ((N & 00007) / 01 * 1); + + return Result; +} + +UnicodeString TRights::GetOctal() const +{ + UnicodeString Result; + uint16_t N = GetNumberSet(); // used to be "Number" + Result.SetLength(4); + Result[1] = static_cast(L'0' + ((N & 07000) >> 9)); + Result[2] = static_cast(L'0' + ((N & 00700) >> 6)); + Result[3] = static_cast(L'0' + ((N & 00070) >> 3)); + Result[4] = static_cast(L'0' + ((N & 00007) >> 0)); + + return Result; +} + +void TRights::SetNumber(uint16_t Value) +{ + if ((FSet != Value) || ((FSet | FUnset) != rfAllSpecials)) + { + FSet = Value; + FUnset = static_cast(rfAllSpecials & ~FSet); + FText.Clear(); + } + FUnknown = false; +} + +uint16_t TRights::GetNumber() const +{ + DebugAssert(!GetIsUndef()); + return FSet; +} + +void TRights::SetRight(TRight Right, bool Value) +{ + SetRightUndef(Right, (Value ? rsYes : rsNo)); +} + +bool TRights::GetRight(TRight Right) const +{ + TState State = GetRightUndef(Right); + DebugAssert(State != rsUndef); + return (State == rsYes); +} + +void TRights::SetRightUndef(TRight Right, TState Value) +{ + if (Value != GetRightUndef(Right)) + { + DebugAssert((Value != rsUndef) || GetAllowUndef()); + + TFlag Flag = RightToFlag(Right); + + switch (Value) + { + case rsYes: + FSet |= static_cast(Flag); + FUnset &= static_cast(~Flag); + break; + + case rsNo: + FSet &= static_cast(~Flag); + FUnset |= static_cast(Flag); + break; + + case rsUndef: + default: + FSet &= static_cast(~Flag); + FUnset &= static_cast(~Flag); + break; + } + + FText.Clear(); + } + FUnknown = false; +} + +TRights::TState TRights::GetRightUndef(TRight Right) const +{ + TFlag Flag = RightToFlag(Right); + TState Result; + + if ((FSet & Flag) != 0) + { + Result = rsYes; + } + else if ((FUnset & Flag) != 0) + { + Result = rsNo; + } + else + { + Result = rsUndef; + } + return Result; +} + +void TRights::SetReadOnly(bool Value) +{ + SetRight(rrUserWrite, !Value); + SetRight(rrGroupWrite, !Value); + SetRight(rrOtherWrite, !Value); +} + +bool TRights::GetReadOnly() const +{ + return GetRight(rrUserWrite) && GetRight(rrGroupWrite) && GetRight(rrOtherWrite); +} + +UnicodeString TRights::GetSimplestStr() const +{ + if (GetIsUndef()) + { + return GetModeStr(); + } + else + { + return GetOctal(); + } +} + +UnicodeString TRights::GetModeStr() const +{ + UnicodeString Result; + UnicodeString SetModeStr, UnsetModeStr; + TRight Right; + intptr_t Index; + + for (intptr_t Group = 0; Group < 3; Group++) + { + SetModeStr.Clear(); + UnsetModeStr.Clear(); + for (intptr_t Mode = 0; Mode < 3; Mode++) + { + Index = (Group * 3) + Mode; + Right = static_cast(rrUserRead + Index); + switch (GetRightUndef(Right)) + { + case rsYes: + SetModeStr += BasicSymbols[Index]; + break; + + case rsNo: + UnsetModeStr += BasicSymbols[Index]; + break; + + case rsUndef: + break; + } + } + + Right = static_cast(rrUserIDExec + Group); + Index = (Group * 3) + 2; + switch (GetRightUndef(Right)) + { + case rsYes: + SetModeStr += CombinedSymbols[Index]; + break; + + case rsNo: + UnsetModeStr += CombinedSymbols[Index]; + break; + + case rsUndef: + break; + } + + if (!SetModeStr.IsEmpty() || !UnsetModeStr.IsEmpty()) + { + if (!Result.IsEmpty()) + { + Result += L','; + } + Result += ModeGroups[Group]; + if (!SetModeStr.IsEmpty()) + { + Result += L"+" + SetModeStr; + } + if (!UnsetModeStr.IsEmpty()) + { + Result += L"-" + UnsetModeStr; + } + } + } + return Result; +} + +void TRights::AddExecute() +{ + for (int Group = 0; Group < 3; Group++) + { + if ((GetRightUndef(static_cast(rrUserRead + (Group * 3))) == rsYes) || + (GetRightUndef(static_cast(rrUserWrite + (Group * 3))) == rsYes)) + { + SetRight(static_cast(rrUserExec + (Group * 3)), true); + } + } + FUnknown = false; +} + +void TRights::AllUndef() +{ + if ((FSet != 0) || (FUnset != 0)) + { + FSet = 0; + FUnset = 0; + FText.Clear(); + } + FUnknown = false; +} + +bool TRights::GetIsUndef() const +{ + return ((FSet | FUnset) != rfAllSpecials); +} + +TRights::operator uint16_t() const +{ + return GetNumber(); +} + +TRights::operator uint32_t() const +{ + return GetNumber(); +} + +TRemoteProperties::TRemoteProperties() +{ + Default(); +} + +TRemoteProperties::TRemoteProperties(const TRemoteProperties & rhp) : + Valid(rhp.Valid), + Recursive(rhp.Recursive), + Rights(rhp.Rights), + AddXToDirectories(rhp.AddXToDirectories), + Group(rhp.Group), + Owner(rhp.Owner), + Modification(rhp.Modification), + LastAccess(rhp.Modification) +{ +} + +void TRemoteProperties::Default() +{ + Valid.Clear(); + AddXToDirectories = false; + Recursive = false; + Rights.SetAllowUndef(false); + Rights.SetNumber(0); + Group.Clear(); + Owner.Clear(); + Modification = 0; + LastAccess = 0; +} + +bool TRemoteProperties::operator ==(const TRemoteProperties & rhp) const +{ + bool Result = (Valid == rhp.Valid && Recursive == rhp.Recursive); + + if (Result) + { + if ((Valid.Contains(vpRights) && + (Rights != rhp.Rights || AddXToDirectories != rhp.AddXToDirectories)) || + (Valid.Contains(vpOwner) && (Owner != rhp.Owner)) || + (Valid.Contains(vpGroup) && (Group != rhp.Group)) || + (Valid.Contains(vpModification) && (Modification != rhp.Modification)) || + (Valid.Contains(vpLastAccess) && (LastAccess != rhp.LastAccess))) + { + Result = false; + } + } + return Result; +} + +bool TRemoteProperties::operator !=(const TRemoteProperties & rhp) const +{ + return !(*this == rhp); +} + +TRemoteProperties TRemoteProperties::CommonProperties(TStrings * AFileList) +{ + TODO("Modification and LastAccess"); + TRemoteProperties CommonProperties; + for (intptr_t Index = 0; Index < AFileList->GetCount(); ++Index) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(Index)); + DebugAssert(File); + if (!Index) + { + CommonProperties.Rights.Assign(File->GetRights()); + // previously we allowed undef implicitly for directories, + // now we do it explicitly in properties dialog and only in combination + // with "recursive" option + CommonProperties.Rights.SetAllowUndef(File->GetRights()->GetIsUndef()); + CommonProperties.Valid << vpRights; + if (File->GetFileOwner().GetIsSet()) + { + CommonProperties.Owner = File->GetFileOwner(); + CommonProperties.Valid << vpOwner; + } + if (File->GetFileGroup().GetIsSet()) + { + CommonProperties.Group = File->GetFileGroup(); + CommonProperties.Valid << vpGroup; + } + } + else + { + CommonProperties.Rights.SetAllowUndef(True); + CommonProperties.Rights &= *File->GetRights(); + if (CommonProperties.Owner != File->GetFileOwner()) + { + CommonProperties.Owner.Clear(); + CommonProperties.Valid >> vpOwner; + } + if (CommonProperties.Group != File->GetFileGroup()) + { + CommonProperties.Group.Clear(); + CommonProperties.Valid >> vpGroup; + } + } + } + return CommonProperties; +} + +TRemoteProperties TRemoteProperties::ChangedProperties( + const TRemoteProperties & OriginalProperties, TRemoteProperties & NewProperties) +{ + TODO("Modification and LastAccess"); + if (!NewProperties.Recursive) + { + if (NewProperties.Rights == OriginalProperties.Rights && + !NewProperties.AddXToDirectories) + { + NewProperties.Valid >> vpRights; + } + + if (NewProperties.Group == OriginalProperties.Group) + { + NewProperties.Valid >> vpGroup; + } + + if (NewProperties.Owner == OriginalProperties.Owner) + { + NewProperties.Valid >> vpOwner; + } + + NewProperties.Group.SetID(OriginalProperties.Group.GetID()); + NewProperties.Owner.SetID(OriginalProperties.Owner.GetID()); + } + return NewProperties; +} + +TRemoteProperties & TRemoteProperties::operator=(const TRemoteProperties & other) +{ + Valid = other.Valid; + Recursive = other.Recursive; + Rights = other.Rights; + AddXToDirectories = other.AddXToDirectories; + Group = other.Group; + Owner = other.Owner; + Modification = other.Modification; + LastAccess = other.Modification; + return *this; +} +//--------------------------------------------------------------------------- +void TRemoteProperties::Load(THierarchicalStorage * Storage) +{ + uint8_t Buf[sizeof(Valid)]; + if (static_cast(Storage->ReadBinaryData("Valid", &Buf, sizeof(Buf))) == sizeof(Buf)) + { + memmove(&Valid, Buf, sizeof(Valid)); + } + + if (Valid.Contains(vpRights)) + { + Rights.SetText(Storage->ReadString("Rights", Rights.GetText())); + } + + // TODO +} + +void TRemoteProperties::Save(THierarchicalStorage * Storage) const +{ + Storage->WriteBinaryData(UnicodeString(L"Valid"), + static_cast(&Valid), sizeof(Valid)); + + if (Valid.Contains(vpRights)) + { + Storage->WriteString("Rights", Rights.GetText()); + } + + // TODO +} + +NB_IMPLEMENT_CLASS(TRemoteFile, NB_GET_CLASS_INFO(TPersistent), nullptr) +NB_IMPLEMENT_CLASS(TRemoteFileList, NB_GET_CLASS_INFO(TObjectList), nullptr) +NB_IMPLEMENT_CLASS(TRemoteProperties, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/RemoteFiles.h b/netbox/src/core/RemoteFiles.h new file mode 100644 index 000000000..5b7618ebf --- /dev/null +++ b/netbox/src/core/RemoteFiles.h @@ -0,0 +1,616 @@ +#pragma once + +#include +#include +#include + +enum TModificationFmt +{ + mfNone, + mfMDHM, + mfMDY, + mfFull, +}; + +#define SYMLINKSTR L" -> " +#define FILETYPE_DEFAULT L'-' +#define FILETYPE_SYMLINK L'L' +#define FILETYPE_DIRECTORY L'D' +#define PARTIAL_EXT L".filepart" + +class TTerminal; +class TRights; +class TRemoteFileList; +class THierarchicalStorage; + +class TRemoteToken : public TObject +{ +public: + TRemoteToken(); + explicit TRemoteToken(const UnicodeString & Name); + explicit TRemoteToken(const TRemoteToken & rht); + + void Clear(); + + bool operator ==(const TRemoteToken & rht) const; + bool operator !=(const TRemoteToken & rht) const; + TRemoteToken & operator =(const TRemoteToken & rht); + + intptr_t Compare(const TRemoteToken & rht) const; + + /*__property UnicodeString Name = { read = FName, write = FName }; + __property bool NameValid = { read = GetNameValid }; + __property unsigned int ID = { read = FID, write = SetID }; + __property bool IDValid = { read = FIDValid }; + __property bool IsSet = { read = GetIsSet }; + __property UnicodeString LogText = { read = GetLogText }; + __property UnicodeString DisplayText = { read = GetDisplayText };*/ + + UnicodeString GetName() const { return FName; } + void SetName(const UnicodeString & Value) { FName = Value; } + intptr_t GetID() const { return FID; } + bool GetIDValid() const { return FIDValid; } + +private: + UnicodeString FName; + intptr_t FID; + bool FIDValid; + +public: + void SetID(intptr_t Value); + bool GetNameValid() const; + bool GetIsSet() const; + UnicodeString GetDisplayText() const; + UnicodeString GetLogText() const; +}; + +class TRemoteTokenList : public TObject +{ +public: + TRemoteTokenList * Duplicate() const; + void Clear(); + void Add(const TRemoteToken & Token); + void AddUnique(const TRemoteToken & Token); + bool Exists(const UnicodeString & Name) const; + const TRemoteToken * Find(uintptr_t ID) const; + const TRemoteToken * Find(const UnicodeString & Name) const; + void Log(TTerminal * Terminal, const wchar_t * Title); + + intptr_t GetCount() const; + const TRemoteToken * Token(intptr_t Index) const; + +private: + typedef std::vector TTokens; + typedef std::map TNameMap; + typedef std::map TIDMap; + TTokens FTokens; + mutable TNameMap FNameMap; + mutable TIDMap FIDMap; +}; + +class TRemoteFile : public TPersistent +{ +NB_DISABLE_COPY(TRemoteFile) +NB_DECLARE_CLASS(TRemoteFile) +private: + TRemoteFileList * FDirectory; + TRemoteToken FOwner; + TModificationFmt FModificationFmt; + UnicodeString FFileName; + TDateTime FModification; + TDateTime FLastAccess; + TRemoteToken FGroup; + TRemoteFile * FLinkedFile; + TRemoteFile * FLinkedByFile; + TRights * FRights; + TTerminal * FTerminal; + UnicodeString FLinkTo; + UnicodeString FHumanRights; + UnicodeString FFullFileName; + UnicodeString FTypeName; + int64_t FSize; + int64_t FINodeBlocks; + intptr_t FIconIndex; + int FIsHidden; + wchar_t FType; + Boolean FIsSymLink; + bool FSelected; + bool FCyclicLink; + +public: + intptr_t GetAttr() const; + bool GetBrokenLink() const; + bool GetIsDirectory() const; + TRemoteFile * GetLinkedFile() const; + void SetLinkedFile(TRemoteFile * Value); + UnicodeString GetModificationStr() const; + void SetModification(const TDateTime & Value); + void SetListingStr(const UnicodeString & Value); + UnicodeString GetListingStr() const; + UnicodeString GetRightsStr() const; + wchar_t GetType() const; + void SetType(wchar_t AType); + void SetTerminal(TTerminal * Value); + void SetRights(TRights * Value); + UnicodeString GetFullFileName() const; + bool GetHaveFullFileName() const; + intptr_t GetIconIndex() const; + UnicodeString GetTypeName() const; + bool GetIsHidden() const; + void SetIsHidden(bool Value); + bool GetIsParentDirectory() const; + bool GetIsThisDirectory() const; + bool GetIsInaccesibleDirectory() const; + UnicodeString GetExtension() const; + UnicodeString GetUserModificationStr() const; + +protected: + void FindLinkedFile(); + +public: + explicit TRemoteFile(TRemoteFile * ALinkedByFile = nullptr); + virtual ~TRemoteFile(); + TRemoteFile * Duplicate(bool Standalone = true) const; + + void ShiftTimeInSeconds(int64_t Seconds); + bool GetIsTimeShiftingApplicable() const; + void Complete(); + + static bool GetIsTimeShiftingApplicable(TModificationFmt ModificationFmt); + static void ShiftTimeInSeconds(TDateTime & DateTime, TModificationFmt ModificationFmt, int64_t Seconds); +/* + __property int Attr = { read = GetAttr }; + __property bool BrokenLink = { read = GetBrokenLink }; + __property TRemoteFileList * Directory = { read = FDirectory, write = FDirectory }; + __property UnicodeString RightsStr = { read = GetRightsStr }; + __property __int64 Size = { read = GetSize, write = FSize }; + __property TRemoteToken Owner = { read = FOwner, write = FOwner }; + __property TRemoteToken Group = { read = FGroup, write = FGroup }; + __property UnicodeString FileName = { read = FFileName, write = FFileName }; + __property int INodeBlocks = { read = FINodeBlocks }; + __property TDateTime Modification = { read = FModification, write = SetModification }; + __property UnicodeString ModificationStr = { read = GetModificationStr }; + __property UnicodeString UserModificationStr = { read = GetUserModificationStr }; + __property TModificationFmt ModificationFmt = { read = FModificationFmt, write = FModificationFmt }; + __property TDateTime LastAccess = { read = FLastAccess, write = FLastAccess }; + __property bool IsSymLink = { read = FIsSymLink }; + __property bool IsDirectory = { read = GetIsDirectory }; + __property TRemoteFile * LinkedFile = { read = GetLinkedFile, write = SetLinkedFile }; + __property UnicodeString LinkTo = { read = FLinkTo, write = FLinkTo }; + __property UnicodeString ListingStr = { read = GetListingStr, write = SetListingStr }; + __property TRights * Rights = { read = FRights, write = SetRights }; + __property UnicodeString HumanRights = { read = FHumanRights, write = FHumanRights }; + __property TTerminal * Terminal = { read = FTerminal, write = SetTerminal }; + __property wchar_t Type = { read = GetType, write = SetType }; + __property bool Selected = { read=FSelected, write=FSelected }; + __property UnicodeString FullFileName = { read = GetFullFileName, write = FFullFileName }; + __property bool HaveFullFileName = { read = GetHaveFullFileName }; + __property int IconIndex = { read = GetIconIndex }; + __property UnicodeString TypeName = { read = GetTypeName }; + __property bool IsHidden = { read = GetIsHidden, write = SetIsHidden }; + __property bool IsParentDirectory = { read = GetIsParentDirectory }; + __property bool IsThisDirectory = { read = GetIsThisDirectory }; + __property bool IsInaccesibleDirectory = { read=GetIsInaccesibleDirectory }; + __property UnicodeString Extension = { read=GetExtension }; +*/ + + TRemoteFileList * GetDirectory() const { return FDirectory; } + void SetDirectory(TRemoteFileList * Value) { FDirectory = Value; } + int64_t GetSize() const; + void SetSize(int64_t Value) { FSize = Value; } + const TRemoteToken & GetFileOwner() const; + TRemoteToken & GetFileOwner(); + void SetFileOwner(const TRemoteToken & Value); + const TRemoteToken & GetFileGroup() const; + TRemoteToken & GetFileGroup(); + void SetFileGroup(const TRemoteToken & Value); + UnicodeString GetFileName() const { return FFileName; } + void SetFileName(const UnicodeString & Value); + TDateTime GetModification() const { return FModification; } + TModificationFmt GetModificationFmt() const { return FModificationFmt; } + void SetModificationFmt(TModificationFmt Value) { FModificationFmt = Value; } + TDateTime GetLastAccess() const { return FLastAccess; } + void SetLastAccess(const TDateTime & Value) { FLastAccess = Value; } + bool GetIsSymLink() const { return FIsSymLink; } + UnicodeString GetLinkTo() const; + void SetLinkTo(const UnicodeString & Value); + TRights * GetRights() const { return FRights; } + UnicodeString GetHumanRights() const { return FHumanRights; } + void SetHumanRights(const UnicodeString & Value) { FHumanRights = Value; } + TTerminal * GetTerminal() const { return FTerminal; } + bool GetSelected() const { return FSelected; } + void SetSelected(bool Value) { FSelected = Value; } + void SetFullFileName(const UnicodeString & Value); + +private: + void LoadTypeInfo() const; +}; + +class TRemoteDirectoryFile : public TRemoteFile +{ +public: + TRemoteDirectoryFile(); + virtual ~TRemoteDirectoryFile() {} +}; + +class TRemoteParentDirectory : public TRemoteDirectoryFile +{ +public: + explicit TRemoteParentDirectory(TTerminal * Terminal); + virtual ~TRemoteParentDirectory() {} +}; + +class TRemoteFileList : public TObjectList +{ +friend class TSCPFileSystem; +friend class TSFTPFileSystem; +friend class TFTPFileSystem; +friend class TWebDAVFileSystem; +NB_DECLARE_CLASS(TRemoteFileList) +protected: + UnicodeString FDirectory; + TDateTime FTimestamp; +public: + TRemoteFile * GetFile(Integer Index) const; + virtual void SetDirectory(const UnicodeString & Value); + virtual void AddFiles(const TRemoteFileList * AFileList); + UnicodeString GetFullDirectory() const; + Boolean GetIsRoot() const; + TRemoteFile * GetParentDirectory(); + UnicodeString GetParentPath() const; + int64_t GetTotalSize() const; + +public: + TRemoteFileList(); + virtual ~TRemoteFileList() { Reset(); } + virtual void Reset(); + TRemoteFile * FindFile(const UnicodeString & AFileName) const; + virtual void DuplicateTo(TRemoteFileList * Copy) const; + virtual void AddFile(TRemoteFile * AFile); + /*__property UnicodeString Directory = { read = FDirectory, write = SetDirectory }; + __property TRemoteFile * Files[Integer Index] = { read = GetFiles }; + __property UnicodeString FullDirectory = { read=GetFullDirectory }; + __property Boolean IsRoot = { read = GetIsRoot }; + __property UnicodeString ParentPath = { read = GetParentPath }; + __property __int64 TotalSize = { read = GetTotalSize }; + __property TDateTime Timestamp = { read = FTimestamp };*/ + + UnicodeString GetDirectory() const { return FDirectory; } + TDateTime GetTimestamp() const { return FTimestamp; } +}; + +class TRemoteDirectory : public TRemoteFileList +{ +friend class TSCPFileSystem; +friend class TSFTPFileSystem; +friend class TWebDAVFileSystem; +NB_DISABLE_COPY(TRemoteDirectory) +private: + TTerminal * FTerminal; + mutable TStrings * FSelectedFiles; + TRemoteFile * FParentDirectory; + TRemoteFile * FThisDirectory; + Boolean FIncludeParentDirectory; + Boolean FIncludeThisDirectory; +public: + virtual void SetDirectory(const UnicodeString & Value); + TStrings * GetSelectedFiles() const; + Boolean GetLoaded() const; + void SetIncludeParentDirectory(Boolean Value); + void SetIncludeThisDirectory(Boolean Value); + void ReleaseRelativeDirectories(); + +public: + explicit TRemoteDirectory(TTerminal * ATerminal, TRemoteDirectory * Template = nullptr); + virtual ~TRemoteDirectory(); + virtual void AddFile(TRemoteFile * AFile); + virtual void DuplicateTo(TRemoteFileList * Copy) const; + virtual void Reset(); + /*__property TTerminal * Terminal = { read = FTerminal, write = FTerminal }; + __property TStrings * SelectedFiles = { read=GetSelectedFiles }; + __property Boolean IncludeParentDirectory = { read = FIncludeParentDirectory, write = SetIncludeParentDirectory }; + __property Boolean IncludeThisDirectory = { read = FIncludeThisDirectory, write = SetIncludeThisDirectory }; + __property Boolean Loaded = { read = GetLoaded }; + __property TRemoteFile * ParentDirectory = { read = FParentDirectory }; + __property TRemoteFile * ThisDirectory = { read = FThisDirectory };*/ + + TTerminal * GetTerminal() const { return FTerminal; } + void SetTerminal(TTerminal * Value) { FTerminal = Value; } + Boolean GetIncludeParentDirectory() const { return FIncludeParentDirectory; } + Boolean GetIncludeThisDirectory() const { return FIncludeThisDirectory; } + TRemoteFile * GetParentDirectory() const { return FParentDirectory; } + TRemoteFile * GetThisDirectory() const { return FThisDirectory; } +}; + +class TRemoteDirectoryCache : private TStringList +{ +CUSTOM_MEM_ALLOCATION_IMPL +NB_DISABLE_COPY(TRemoteDirectoryCache) +public: + TRemoteDirectoryCache(); + virtual ~TRemoteDirectoryCache(); + bool HasFileList(const UnicodeString & Directory) const; + bool HasNewerFileList(const UnicodeString & Directory, const TDateTime & Timestamp) const; + bool GetFileList(const UnicodeString & Directory, + TRemoteFileList * FileList) const; + void AddFileList(TRemoteFileList * FileList); + void ClearFileList(const UnicodeString & Directory, bool SubDirs); + void Clear(); + + // __property bool IsEmpty = { read = GetIsEmpty }; + bool GetIsEmpty() const; + +protected: + virtual void Delete(intptr_t Index); + +private: + TCriticalSection FSection; + void DoClearFileList(const UnicodeString & Directory, bool SubDirs); +}; + +class TRemoteDirectoryChangesCache : private TStringList +{ + CUSTOM_MEM_ALLOCATION_IMPL +public: + explicit TRemoteDirectoryChangesCache(intptr_t MaxSize); + virtual ~TRemoteDirectoryChangesCache(){} + + void AddDirectoryChange(const UnicodeString & SourceDir, + const UnicodeString & Change, const UnicodeString & TargetDir); + void ClearDirectoryChange(const UnicodeString & SourceDir); + void ClearDirectoryChangeTarget(const UnicodeString & TargetDir); + bool GetDirectoryChange(const UnicodeString & SourceDir, + const UnicodeString & Change, UnicodeString & TargetDir) const; + void Clear(); + + void Serialize(UnicodeString & Data) const; + void Deserialize(const UnicodeString & Data); + + // __property bool IsEmpty = { read = GetIsEmpty }; + bool GetIsEmpty() const; + +private: + static bool DirectoryChangeKey(const UnicodeString & SourceDir, + const UnicodeString & Change, UnicodeString & Key); + void SetValue(const UnicodeString & Name, const UnicodeString & Value); + UnicodeString GetValue(const UnicodeString & Name) const; + UnicodeString GetValue(const UnicodeString & Name); + + intptr_t FMaxSize; +}; + +class TRights : public TObject +{ +public: + static const intptr_t TextLen = 9; + static const wchar_t UndefSymbol = L'$'; + static const wchar_t UnsetSymbol = L'-'; + static const wchar_t BasicSymbols[]; + static const wchar_t CombinedSymbols[]; + static const wchar_t ExtendedSymbols[]; + static const wchar_t ModeGroups[]; + + enum TRight + { + rrUserIDExec, rrGroupIDExec, rrStickyBit, + rrUserRead, rrUserWrite, rrUserExec, + rrGroupRead, rrGroupWrite, rrGroupExec, + rrOtherRead, rrOtherWrite, rrOtherExec, + rrFirst = rrUserIDExec, rrLast = rrOtherExec + }; + + enum TFlag + { + rfSetUID = 04000, rfSetGID = 02000, rfStickyBit = 01000, + rfUserRead = 00400, rfUserWrite = 00200, rfUserExec = 00100, + rfGroupRead = 00040, rfGroupWrite = 00020, rfGroupExec = 00010, + rfOtherRead = 00004, rfOtherWrite = 00002, rfOtherExec = 00001, + rfRead = 00444, rfWrite = 00222, rfExec = 00111, + rfNo = 00000, rfDefault = 00644, rfAll = 00777, + rfSpecials = 07000, rfAllSpecials = 07777, + }; + + enum TUnsupportedFlag + { + rfDirectory = 040000, + }; + + enum TState + { + rsNo, + rsYes, + rsUndef, + }; + +public: + static TFlag RightToFlag(TRight Right); + + TRights(); + TRights(const TRights & Source); + explicit TRights(uint16_t Number); + void Assign(const TRights * Source); + void AddExecute(); + void AllUndef(); + + bool operator ==(const TRights & rhr) const; + bool operator ==(uint16_t rhr) const; + bool operator !=(const TRights & rhr) const; + TRights & operator =(const TRights & rhr); + TRights & operator =(uint16_t rhr); + TRights operator ~() const; + TRights operator &(uint16_t rhr) const; + TRights operator &(const TRights & rhr) const; + TRights & operator &=(uint16_t rhr); + TRights & operator &=(const TRights & rhr); + TRights operator |(uint16_t rhr) const; + TRights operator |(const TRights & rhr) const; + TRights & operator |=(uint16_t rhr); + TRights & operator |=(const TRights & rhr); + operator uint16_t() const; + operator uint32_t() const; + + /*__property bool AllowUndef = { read = FAllowUndef, write = SetAllowUndef }; + __property bool IsUndef = { read = GetIsUndef }; + __property UnicodeString ModeStr = { read = GetModeStr }; + __property UnicodeString SimplestStr = { read = GetSimplestStr }; + __property UnicodeString Octal = { read = GetOctal, write = SetOctal }; + __property unsigned short Number = { read = GetNumber, write = SetNumber }; + __property unsigned short NumberSet = { read = FSet }; + __property unsigned short NumberUnset = { read = FUnset }; + __property unsigned long NumberDecadic = { read = GetNumberDecadic }; + __property bool ReadOnly = { read = GetReadOnly, write = SetReadOnly }; + __property bool Right[TRight Right] = { read = GetRight, write = SetRight }; + __property TState RightUndef[TRight Right] = { read = GetRightUndef, write = SetRightUndef }; + __property UnicodeString Text = { read = GetText, write = SetText }; + __property bool Unknown = { read = FUnknown };*/ + +private: + UnicodeString FText; + uint16_t FSet; + uint16_t FUnset; + bool FAllowUndef; + bool FUnknown; + +public: + bool GetIsUndef() const; + UnicodeString GetModeStr() const; + UnicodeString GetSimplestStr() const; + void SetNumber(uint16_t Value); + UnicodeString GetText() const; + void SetText(const UnicodeString & Value); + void SetOctal(const UnicodeString & AValue); + uint16_t GetNumber() const; + uint16_t GetNumberSet() const { return FSet; } + uint16_t GetNumberUnset() const { return FUnset; } + uint32_t GetNumberDecadic() const; + UnicodeString GetOctal() const; + bool GetReadOnly() const; + bool GetRight(TRight Right) const; + TState GetRightUndef(TRight Right) const; + void SetAllowUndef(bool Value); + void SetReadOnly(bool Value); + void SetRight(TRight Right, bool Value); + void SetRightUndef(TRight Right, TState Value); + bool GetAllowUndef() const { return FAllowUndef; } + bool GetUnknown() const { return FUnknown; } +}; + +enum TValidProperty +{ + vpRights = 0x1, + vpGroup = 0x2, + vpOwner = 0x4, + vpModification = 0x8, + vpLastAccess = 0x10, +}; +// FIXME +// typedef Set TValidProperties; +class TValidProperties // : public TObject +{ +public: + TValidProperties() : + FValue(0) + { + } + void Clear() + { + FValue = 0; + } + bool Contains(TValidProperty Value) const + { + return (FValue & Value) != 0; + } + bool operator == (const TValidProperties & rhs) const + { + return FValue == rhs.FValue; + } + bool operator != (const TValidProperties & rhs) const + { + return !(operator == (rhs)); + } + TValidProperties & operator << (const TValidProperty Value) + { + FValue |= Value; + return *this; + } + TValidProperties & operator >> (const TValidProperty Value) + { + FValue &= ~(static_cast(Value)); + return *this; + } + bool Empty() const + { + return FValue == 0; + } + +private: + int64_t FValue; +}; + +class TRemoteProperties : public TObject +{ +NB_DECLARE_CLASS(TRemoteProperties) +public: + TValidProperties Valid; + TRights Rights; + TRemoteToken Group; + TRemoteToken Owner; + int64_t Modification; // unix time + int64_t LastAccess; // unix time + bool Recursive; + bool AddXToDirectories; + + TRemoteProperties(); + TRemoteProperties(const TRemoteProperties & rhp); + bool operator ==(const TRemoteProperties & rhp) const; + bool operator !=(const TRemoteProperties & rhp) const; + void Default(); + void Load(THierarchicalStorage * Storage); + void Save(THierarchicalStorage * Storage) const; + + static TRemoteProperties CommonProperties(TStrings * AFileList); + static TRemoteProperties ChangedProperties( + const TRemoteProperties & OriginalProperties, TRemoteProperties & NewProperties); + +public: + TRemoteProperties & operator=(const TRemoteProperties & other); +}; + +namespace core { + +bool IsUnixStyleWindowsPath(const UnicodeString & APath); +bool UnixIsAbsolutePath(const UnicodeString & APath); +UnicodeString UnixIncludeTrailingBackslash(const UnicodeString & APath); +UnicodeString UnixExcludeTrailingBackslash(const UnicodeString & APath, bool Simple = false); +UnicodeString SimpleUnixExcludeTrailingBackslash(const UnicodeString & APath); +UnicodeString UnixExtractFileDir(const UnicodeString & APath); +UnicodeString UnixExtractFilePath(const UnicodeString & APath); +UnicodeString UnixExtractFileName(const UnicodeString & APath); +UnicodeString UnixExtractFileExt(const UnicodeString & APath); +Boolean UnixSamePath(const UnicodeString & APath1, const UnicodeString & APath2); +bool UnixIsChildPath(const UnicodeString & AParent, const UnicodeString & AChild); +bool ExtractCommonPath(const TStrings * AFiles, OUT UnicodeString & APath); +bool UnixExtractCommonPath(const TStrings * AFiles, OUT UnicodeString & APath); +UnicodeString ExtractFileName(const UnicodeString & APath, bool Unix); +bool IsUnixRootPath(const UnicodeString & APath); +bool IsUnixHiddenFile(const UnicodeString & APath); +UnicodeString AbsolutePath(const UnicodeString & Base, const UnicodeString & APath); +UnicodeString FromUnixPath(const UnicodeString & APath); +UnicodeString ToUnixPath(const UnicodeString & APath); +UnicodeString MinimizeName(const UnicodeString & AFileName, intptr_t MaxLen, bool Unix); +UnicodeString MakeFileList(const TStrings * AFileList); +TDateTime ReduceDateTimePrecision(const TDateTime & ADateTime, + TModificationFmt Precision); +TModificationFmt LessDateTimePrecision( + TModificationFmt Precision1, TModificationFmt Precision2); +UnicodeString UserModificationStr(const TDateTime & DateTime, + TModificationFmt Precision); +UnicodeString ModificationStr(const TDateTime & DateTime, + TModificationFmt Precision); +int FakeFileImageIndex(const UnicodeString & AFileName, uint32_t Attrs = INVALID_FILE_ATTRIBUTES, + UnicodeString * TypeName = nullptr); +bool SameUserName(const UnicodeString & UserName1, const UnicodeString & UserName2); +UnicodeString FormatMultiFilesToOneConfirmation(const UnicodeString & ATarget, bool Unix); + +} // namespace core + diff --git a/netbox/src/core/ScpFileSystem.cpp b/netbox/src/core/ScpFileSystem.cpp new file mode 100644 index 000000000..8c5725e32 --- /dev/null +++ b/netbox/src/core/ScpFileSystem.cpp @@ -0,0 +1,2967 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#include "ScpFileSystem.h" +#include "Terminal.h" +#include "Interface.h" +#include "TextsCore.h" +#include "HelpCore.h" +#include "SecureShell.h" + +#include + +const int coRaiseExcept = 1; +const int coExpectNoOutput = 2; +const int coWaitForLastLine = 4; +const int coOnlyReturnCode = 8; +const int coIgnoreWarnings = 16; +const int coReadProgress = 32; + +const int ecRaiseExcept = 1; +const int ecIgnoreWarnings = 2; +const int ecReadProgress = 4; +const int ecDefault = ecRaiseExcept; + +inline void ThrowFileSkipped(Exception * Exception, const UnicodeString & Message) +{ + throw EFileSkipped(Exception, Message); +} + +inline void ThrowScpEror(Exception * Exception, const UnicodeString & Message) +{ + throw EScp(Exception, Message); +} + +#define MaxShellCommand fsLang +#define ShellCommandCount MaxShellCommand + 1 +#define MaxCommandLen 40 +struct TCommandType +{ + int MinLines; + int MaxLines; + bool ModifiesFiles; + bool ChangesDirectory; + bool InteractiveCommand; + char Command[MaxCommandLen]; +}; + +// Only one character! See TSCPFileSystem::ReadCommandOutput() +#define LastLineSeparator ":" +#define LAST_LINE "NetBox: this is end-of-file" +#define FIRST_LINE "NetBox: this is begin-of-file" +extern const TCommandType DefaultCommandSet[]; + +#define NationalVarCount 10 +extern const char NationalVars[NationalVarCount][15]; + +#define CHECK_CMD DebugAssert((Cmd >=0) && (Cmd <= MaxShellCommand)) + +class TSessionData; + +class TCommandSet : public TObject +{ +NB_DISABLE_COPY(TCommandSet) +private: + TCommandType CommandSet[ShellCommandCount]; + TSessionData * FSessionData; + UnicodeString FReturnVar; + +public: + void SetMasks(const UnicodeString & Value); + int GetMaxLines(TFSCommand Cmd) const; + int GetMinLines(TFSCommand Cmd) const; + bool GetModifiesFiles(TFSCommand Cmd) const; + bool GetChangesDirectory(TFSCommand Cmd) const; + bool GetOneLineCommand(TFSCommand Cmd) const; + void SetCommands(TFSCommand Cmd, const UnicodeString & Value); + UnicodeString GetCommands(TFSCommand Cmd) const; + UnicodeString GetFirstLine() const; + bool GetInteractiveCommand(TFSCommand Cmd) const; + UnicodeString GetLastLine() const; + UnicodeString GetReturnVar() const; + +public: + explicit TCommandSet(TSessionData * aSessionData); + void Default(); + void CopyFrom(TCommandSet * Source); + UnicodeString Command(TFSCommand Cmd, ...) const; + UnicodeString Command(TFSCommand Cmd, va_list args) const; + TStrings * CreateCommandList(); + UnicodeString FullCommand(TFSCommand Cmd, ...) const; + UnicodeString FullCommand(TFSCommand Cmd, va_list args) const; + static UnicodeString ExtractCommand(const UnicodeString & ACommand); + /*__property int MaxLines[TFSCommand Cmd] = { read=GetMaxLines}; + __property int MinLines[TFSCommand Cmd] = { read=GetMinLines }; + __property bool ModifiesFiles[TFSCommand Cmd] = { read=GetModifiesFiles }; + __property bool ChangesDirectory[TFSCommand Cmd] = { read=GetChangesDirectory }; + __property bool OneLineCommand[TFSCommand Cmd] = { read=GetOneLineCommand }; + __property UnicodeString Commands[TFSCommand Cmd] = { read=GetCommands, write=SetCommands }; + __property UnicodeString FirstLine = { read = GetFirstLine }; + __property bool InteractiveCommand[TFSCommand Cmd] = { read = GetInteractiveCommand }; + __property UnicodeString LastLine = { read=GetLastLine }; + __property TSessionData * SessionData = { read=FSessionData, write=FSessionData }; + __property UnicodeString ReturnVar = { read=GetReturnVar, write=FReturnVar };*/ + TSessionData * GetSessionData() const { return FSessionData; } + void SetSessionData(TSessionData * Value) { FSessionData = Value; } + void SetReturnVar(const UnicodeString & Value) { FReturnVar = Value; } +}; + +const char NationalVars[NationalVarCount][15] = +{ + "LANG", "LANGUAGE", "LC_CTYPE", "LC_COLLATE", "LC_MONETARY", "LC_NUMERIC", + "LC_TIME", "LC_MESSAGES", "LC_ALL", "HUMAN_BLOCKS" +}; +const char FullTimeOption[] = "--full-time"; + +#define F false +#define T true +TODO("remove 'mf' and 'cd, it is implemented in TTerminal already"); +const TCommandType DefaultCommandSet[ShellCommandCount] = +{ + // min max mf cd ia command + /*Null*/ { -1, -1, F, F, F, "" }, + /*VarValue*/ { -1, -1, F, F, F, "echo \"$%s\"" /* variable */ }, + /*LastLine*/ { -1, -1, F, F, F, "echo \"%s" LastLineSeparator "%s\"" /* last line, return var */ }, + /*FirstLine*/ { -1, -1, F, F, F, "echo \"%s\"" /* first line */ }, + /*CurrentDirectory*/ { 1, 1, F, F, F, "pwd" }, + /*ChangeDirectory*/ { 0, 0, F, T, F, "cd %s" /* directory */ }, + // list directory can be empty on permission denied, this is handled in ReadDirectory + /*ListDirectory*/ { -1, -1, F, F, F, "%s %s \"%s\"" /* listing command, options, directory */ }, + /*ListCurrentDirectory*/{ -1, -1, F, F, F, "%s %s" /* listing command, options */ }, + /*ListFile*/ { 1, 1, F, F, F, "%s -d %s \"%s\"" /* listing command, options, file/directory */ }, + /*LookupUserGroups*/ { 0, 1, F, F, F, "groups" }, + /*CopyToRemote*/ { -1, -1, T, F, T, "scp -r %s -d -t \"%s\"" /* options, directory */ }, + /*CopyToLocal*/ { -1, -1, F, F, T, "scp -r %s -d -f \"%s\"" /* options, file */ }, + /*DeleteFile*/ { 0, 0, T, F, F, "rm -f -r \"%s\"" /* file/directory */}, + /*RenameFile*/ { 0, 0, T, F, F, "mv -f \"%s\" \"%s\"" /* file/directory, new name*/}, + /*CreateDirectory*/ { 0, 0, T, F, F, "mkdir \"%s\"" /* new directory */}, + /*ChangeMode*/ { 0, 0, T, F, F, "chmod %s %s \"%s\"" /* options, mode, filename */}, + /*ChangeGroup*/ { 0, 0, T, F, F, "chgrp %s \"%s\" \"%s\"" /* options, group, filename */}, + /*ChangeOwner*/ { 0, 0, T, F, F, "chown %s \"%s\" \"%s\"" /* options, owner, filename */}, + /*HomeDirectory*/ { 0, 0, F, T, F, "cd" }, + /*Unset*/ { 0, 0, F, F, F, "unset \"%s\"" /* variable */ }, + /*Unalias*/ { 0, 0, F, F, F, "unalias \"%s\"" /* alias */ }, + /*CreateLink*/ { 0, 0, T, F, F, "ln %s \"%s\" \"%s\"" /*symbolic (-s), filename, point to*/}, + /*CopyFile*/ { 0, 0, T, F, F, "cp -p -r -f %s \"%s\" \"%s\"" /* file/directory, target name*/}, + /*AnyCommand*/ { 0, -1, T, T, F, "%s" }, + /*Lang*/ { 0, 1, F, F, F, "printenv LANG"}, +}; +#undef F +#undef T + +TCommandSet::TCommandSet(TSessionData * ASessionData) : + FSessionData(ASessionData), FReturnVar(L"") +{ + DebugAssert(FSessionData); + Default(); +} + +void TCommandSet::CopyFrom(TCommandSet * Source) +{ + memmove(&CommandSet, Source->CommandSet, sizeof(CommandSet)); +} + +void TCommandSet::Default() +{ + DebugAssert(sizeof(CommandSet) == sizeof(DefaultCommandSet)); + memmove(&CommandSet, &DefaultCommandSet, sizeof(CommandSet)); +} + +int TCommandSet::GetMaxLines(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].MaxLines; +} + +int TCommandSet::GetMinLines(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].MinLines; +} + +bool TCommandSet::GetModifiesFiles(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].ModifiesFiles; +} + +bool TCommandSet::GetChangesDirectory(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].ChangesDirectory; +} + +bool TCommandSet::GetInteractiveCommand(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].InteractiveCommand; +} + +bool TCommandSet::GetOneLineCommand(TFSCommand /*Cmd*/) const +{ + //CHECK_CMD; + // #56: we send "echo last line" from all commands on same line + // just as it was in 1.0 + return True; //CommandSet[Cmd].OneLineCommand; +} + +void TCommandSet::SetCommands(TFSCommand Cmd, const UnicodeString & Value) +{ + CHECK_CMD; + AnsiString AnsiValue(Value); + strncpy(const_cast(CommandSet[Cmd].Command), AnsiValue.SubString(1, MaxCommandLen - 1).c_str(), MaxCommandLen); +} + +UnicodeString TCommandSet::GetCommands(TFSCommand Cmd) const +{ + CHECK_CMD; + return CommandSet[Cmd].Command; +} + +UnicodeString TCommandSet::Command(TFSCommand Cmd, ...) const +{ + UnicodeString Result; + va_list args; + va_start(args, Cmd); + Result = Command(Cmd, args); + va_end(args); + return Result; +} + +UnicodeString TCommandSet::Command(TFSCommand Cmd, va_list args) const +{ + UnicodeString Result; + Result = ::FormatV(GetCommands(Cmd).c_str(), args); + return Result.c_str(); +} + +UnicodeString TCommandSet::FullCommand(TFSCommand Cmd, ...) const +{ + UnicodeString Result; + va_list args; + va_start(args, Cmd); + Result = FullCommand(Cmd, args); + va_end(args); + return Result.c_str(); +} + +UnicodeString TCommandSet::FullCommand(TFSCommand Cmd, va_list args) const +{ + UnicodeString Separator; + if (GetOneLineCommand(Cmd)) + { + Separator = L" ; "; + } + else + { + Separator = L"\n"; + } + UnicodeString Line = Command(Cmd, args); + UnicodeString LastLineCmd = + Command(fsLastLine, GetLastLine().c_str(), GetReturnVar().c_str()); + UnicodeString FirstLineCmd; + if (GetInteractiveCommand(Cmd)) + { + FirstLineCmd = Command(fsFirstLine, GetFirstLine().c_str()) + Separator; + } + + UnicodeString Result; + if (!Line.IsEmpty()) + { + Result = FORMAT(L"%s%s%s%s", FirstLineCmd.c_str(), Line.c_str(), Separator.c_str(), LastLineCmd.c_str()); + } + else + { + Result = FORMAT(L"%s%s", FirstLineCmd.c_str(), LastLineCmd.c_str()); + } + return Result; +} + +UnicodeString TCommandSet::GetFirstLine() const +{ + return FIRST_LINE; +} + +UnicodeString TCommandSet::GetLastLine() const +{ + return LAST_LINE; +} + +UnicodeString TCommandSet::GetReturnVar() const +{ + DebugAssert(GetSessionData()); + if (!FReturnVar.IsEmpty()) + { + return UnicodeString(L'$') + FReturnVar; + } + else if (GetSessionData()->GetDetectReturnVar()) + { + return L'0'; + } + else + { + return UnicodeString(L'$') + GetSessionData()->GetReturnVar(); + } +} + +UnicodeString TCommandSet::ExtractCommand(const UnicodeString & ACommand) +{ + UnicodeString Command = ACommand; + intptr_t P = Command.Pos(L" "); + if (P > 0) + { + Command.SetLength(P - 1); + } + return Command; +} + +TStrings * TCommandSet::CreateCommandList() +{ + TStrings * CommandList = new TStringList(); + for (intptr_t Index = 0; Index < ShellCommandCount; ++Index) + { + UnicodeString Cmd = GetCommands(static_cast(Index)); + if (!Cmd.IsEmpty()) + { + Cmd = ExtractCommand(Cmd); + if ((Cmd != L"%s") && (CommandList->IndexOf(Cmd.c_str()) < 0)) + CommandList->Add(Cmd); + } + } + return CommandList; +} +//=========================================================================== +TSCPFileSystem::TSCPFileSystem(TTerminal * ATerminal) : + TCustomFileSystem(ATerminal), + FSecureShell(nullptr), + FCommandSet(nullptr), + FOutput(nullptr), + FReturnCode(0), + FProcessingCommand(false), + FLsFullTime(asAuto), + FOnCaptureOutput(nullptr) +{ +} + +void TSCPFileSystem::Init(void * Data) +{ + FSecureShell = NB_STATIC_DOWNCAST(TSecureShell, Data); + DebugAssert(FSecureShell); + FCommandSet = new TCommandSet(FTerminal->GetSessionData()); + FLsFullTime = FTerminal->GetSessionData()->GetSCPLsFullTime(); + FOutput = new TStringList(); + FProcessingCommand = false; + FOnCaptureOutput = nullptr; + + FFileSystemInfo.ProtocolBaseName = L"SCP"; + FFileSystemInfo.ProtocolName = FFileSystemInfo.ProtocolBaseName; +} + +TSCPFileSystem::~TSCPFileSystem() +{ + SAFE_DESTROY(FCommandSet); + SAFE_DESTROY(FOutput); + SAFE_DESTROY(FSecureShell); +} + +void TSCPFileSystem::Open() +{ + // this is used for reconnects only + FSecureShell->Open(); +} + +void TSCPFileSystem::Close() +{ + FSecureShell->Close(); +} + +bool TSCPFileSystem::GetActive() const +{ + return FSecureShell->GetActive(); +} + +void TSCPFileSystem::CollectUsage() +{ + FSecureShell->CollectUsage(); +} + +const TSessionInfo & TSCPFileSystem::GetSessionInfo() const +{ + return FSecureShell->GetSessionInfo(); +} + +const TFileSystemInfo & TSCPFileSystem::GetFileSystemInfo(bool Retrieve) +{ + if (FFileSystemInfo.AdditionalInfo.IsEmpty() && Retrieve) + { + UnicodeString UName; + FTerminal->SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + FTerminal->SetExceptionOnFail(false); + }; + try + { + AnyCommand(L"uname -a", nullptr); + for (intptr_t Index = 0; Index < GetOutput()->GetCount(); ++Index) + { + if (Index > 0) + { + UName += L"; "; + } + UName += GetOutput()->GetString(Index); + } + } + catch (...) + { + if (!FTerminal->GetActive()) + { + throw; + } + } + } + __finally + { + FTerminal->SetExceptionOnFail(false); + }; + + FFileSystemInfo.RemoteSystem = UName; + } + + return FFileSystemInfo; +} + +bool TSCPFileSystem::TemporaryTransferFile(const UnicodeString & /*AFileName*/) +{ + return false; +} + +bool TSCPFileSystem::GetStoredCredentialsTried() const +{ + return FSecureShell->GetStoredCredentialsTried(); +} + +UnicodeString TSCPFileSystem::FSGetUserName() const +{ + return FSecureShell->ShellGetUserName(); +} + +void TSCPFileSystem::Idle() +{ + // Keep session alive + const TSessionData * Data = FTerminal->GetSessionData(); + if ((Data->GetPingType() != ptOff) && + (Now() - FSecureShell->GetLastDataSent() > Data->GetPingIntervalDT())) + { + if ((Data->GetPingType() == ptDummyCommand) && + FSecureShell->GetReady()) + { + if (!FProcessingCommand) + { + ExecCommand(fsNull, 0); + } + else + { + FTerminal->LogEvent("Cannot send keepalive, command is being executed"); + // send at least SSH-level keepalive, if nothing else, it at least updates + // LastDataSent, no the next keepalive attempt is postponed + FSecureShell->KeepAlive(); + } + } + else + { + FSecureShell->KeepAlive(); + } + } + + FSecureShell->Idle(); +} +//--------------------------------------------------------------------------- +UnicodeString TSCPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool Local) +{ + return static_cast(this)->GetAbsolutePath(APath, Local); +} + +UnicodeString TSCPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool /*Local*/) const +{ + return core::AbsolutePath(GetCurrDirectory(), APath); +} +//--------------------------------------------------------------------------- +bool TSCPFileSystem::IsCapable(intptr_t Capability) const +{ + DebugAssert(FTerminal); + switch (Capability) + { + case fcUserGroupListing: + case fcModeChanging: + case fcModeChangingUpload: + case fcPreservingTimestampUpload: + case fcGroupChanging: + case fcOwnerChanging: + case fcAnyCommand: + case fcShellAnyCommand: + case fcHardLink: + case fcSymbolicLink: + case fcResolveSymlink: + case fcRename: + case fcRemoteMove: + case fcRemoteCopy: + case fcRemoveCtrlZUpload: + case fcRemoveBOMUpload: + return true; + + case fcTextMode: + return FTerminal->GetSessionData()->GetEOLType() != FTerminal->GetConfiguration()->GetLocalEOLType(); + + case fcNativeTextMode: + case fcNewerOnlyUpload: + case fcTimestampChanging: + case fcLoadingAdditionalProperties: + case fcCheckingSpaceAvailable: + case fcIgnorePermErrors: + case fcCalculatingChecksum: + case fcSecondaryShell: // has fcShellAnyCommand + case fcGroupOwnerChangingByID: // by name + case fcMoveToQueue: + case fcLocking: + case fcPreservingTimestampDirs: + case fcResumeSupport: + return false; + + default: + DebugFail(); + return false; + } +} + +UnicodeString TSCPFileSystem::DelimitStr(const UnicodeString & AStr) +{ + UnicodeString Str = AStr; + if (!Str.IsEmpty()) + { + Str = ::DelimitStr(Str, L"\\`$\""); + if (Str[1] == L'-') + Str = L"./" + Str; + } + return Str; +} + +void TSCPFileSystem::EnsureLocation() +{ + if (!FCachedDirectoryChange.IsEmpty()) + { + FTerminal->LogEvent(FORMAT(L"Locating to cached directory \"%s\".", + FCachedDirectoryChange.c_str())); + UnicodeString Directory = FCachedDirectoryChange; + FCachedDirectoryChange.Clear(); + try + { + ChangeDirectory(Directory); + } + catch (...) + { + // when location to cached directory fails, pretend again + // location in cached directory + // here used to be check (CurrentDirectory != Directory), but it is + // false always (current directory is already set to cached directory), + // making the condition below useless. check removed. + if (FTerminal->GetActive()) + { + FCachedDirectoryChange = Directory; + } + throw; + } + } +} + +void TSCPFileSystem::SendCommand(const UnicodeString & Cmd) +{ + EnsureLocation(); + + UnicodeString Line; + FSecureShell->ClearStdError(); + FReturnCode = 0; + FOutput->Clear(); + // We suppose, that 'Cmd' already contains command that ensures, + // that 'LastLine' will be printed + FSecureShell->SendLine(Cmd); + FProcessingCommand = true; +} + +bool TSCPFileSystem::IsTotalListingLine(const UnicodeString & Line) +{ + // On some hosts there is not "total" but "totalt". What's the reason?? + // see mail from "Jan Wiklund (SysOp)" + return !Line.SubString(1, 5).CompareIC(L"total"); +} + +bool TSCPFileSystem::RemoveLastLine(UnicodeString & Line, + intptr_t & ReturnCode, const UnicodeString & ALastLine) +{ + UnicodeString LastLine = ALastLine; + bool IsLastLine = false; + if (LastLine.IsEmpty()) + { + LastLine = LAST_LINE; + } + // #55: fixed so, even when last line of command output does not + // contain CR/LF, we can recognize last line + intptr_t Pos = Line.Pos(LastLine); + if (Pos) + { + // 2003-07-14: There must be nothing after return code number to + // consider string as last line. This fixes bug with 'set' command + // in console window + UnicodeString ReturnCodeStr = Line.SubString(Pos + LastLine.Length() + 1, + Line.Length() - Pos + LastLine.Length()); + int64_t Code = 0; + if (::TryStrToInt(ReturnCodeStr, Code)) + { + IsLastLine = true; + Line.SetLength(Pos - 1); + } + ReturnCode = static_cast(Code); + } + return IsLastLine; +} + +bool TSCPFileSystem::IsLastLine(UnicodeString & Line) +{ + bool Result = false; + try + { + Result = RemoveLastLine(Line, FReturnCode, FCommandSet->GetLastLine()); + } + catch (Exception & E) + { + FTerminal->TerminalError(&E, LoadStr(CANT_DETECT_RETURN_CODE)); + } + return Result; +} + +void TSCPFileSystem::SkipFirstLine() +{ + UnicodeString Line = FSecureShell->ReceiveLine(); + if (Line != FCommandSet->GetFirstLine()) + { + FTerminal->TerminalError(nullptr, FMTLOAD(FIRST_LINE_EXPECTED, Line.c_str())); + } +} + +void TSCPFileSystem::ReadCommandOutput(intptr_t Params, const UnicodeString * Cmd) +{ + try__finally + { + SCOPE_EXIT + { + FProcessingCommand = false; + }; + if (Params & coWaitForLastLine) + { + UnicodeString Line; + bool IsLast = true; + intptr_t Total = 0; + // #55: fixed so, even when last line of command output does not + // contain CR/LF, we can recognize last line + do + { + Line = FSecureShell->ReceiveLine(); + IsLast = IsLastLine(Line); + if (!IsLast || !Line.IsEmpty()) + { + FOutput->Add(Line); + if (FLAGSET(Params, coReadProgress)) + { + Total++; + + if (Total % 10 == 0) + { + bool Cancel; //dummy + FTerminal->DoReadDirectoryProgress(Total, 0, Cancel); + } + } + } + } + while (!IsLast); + } + if (Params & coRaiseExcept) + { + UnicodeString Message = FSecureShell->GetStdError(); + if ((Params & coExpectNoOutput) && FOutput->GetCount()) + { + if (!Message.IsEmpty()) + { + Message += L"\n"; + } + Message += FOutput->GetText(); + } + while (!Message.IsEmpty() && (Message.LastDelimiter(L"\n\r") == Message.Length())) + { + Message.SetLength(Message.Length() - 1); + } + + bool WrongReturnCode = + (GetReturnCode() > 1) || (GetReturnCode() == 1 && !(Params & coIgnoreWarnings)); + + if (FOnCaptureOutput != nullptr) + { + FOnCaptureOutput(::Int64ToStr(GetReturnCode()), cotExitCode); + } + + if ((Params & coOnlyReturnCode) && WrongReturnCode) + { + FTerminal->TerminalError(FMTLOAD(COMMAND_FAILED_CODEONLY, GetReturnCode())); + } + else if (!(Params & coOnlyReturnCode) && + ((!Message.IsEmpty() && ((FOutput->GetCount() == 0) || !(Params & coIgnoreWarnings))) || + WrongReturnCode)) + { + DebugAssert(Cmd != nullptr); + FTerminal->TerminalError(FMTLOAD(COMMAND_FAILED, Cmd->c_str(), GetReturnCode(), Message.c_str())); + } + } + } + __finally + { + FProcessingCommand = false; + }; +} + +void TSCPFileSystem::ExecCommand2(const UnicodeString & Cmd, intptr_t Params, + const UnicodeString & CmdString) +{ + if (Params < 0) + { + Params = ecDefault; + } + + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + + SendCommand(Cmd); + + intptr_t COParams = coWaitForLastLine; + if (Params & ecRaiseExcept) + { + COParams |= coRaiseExcept; + } + if (Params & ecIgnoreWarnings) + { + COParams |= coIgnoreWarnings; + } + if (Params & ecReadProgress) + { + COParams |= coReadProgress; + } + ReadCommandOutput(COParams, &CmdString); +} + +#if defined(__BORLANDC__) +void TSCPFileSystem::ExecCommand(TFSCommand Cmd, const TVarRec * args, + int size, intptr_t Params) +{ + if (Params < 0) + Params = ecDefault; + UnicodeString FullCommand = FCommandSet->FullCommand(Cmd, args, size); + UnicodeString Command = FCommandSet->Command(Cmd, args, size); + ExecCommand(FullCommand, Params, Command); + if (Params & ecRaiseExcept) + { + Integer MinL = FCommandSet->GetMinLines(Cmd); + Integer MaxL = FCommandSet->GetMaxLines(Cmd); + if (((MinL >= 0) && (MinL > FOutput->GetCount())) || + ((MaxL >= 0) && (MaxL > FOutput->GetCount()))) + { + FTerminal->TerminalError(FMTLOAD(INVALID_OUTPUT_ERROR, + ARRAYOFCONST((FullCommand, GetOutput()->GetText())))); + } + } +} +#endif + +void TSCPFileSystem::ExecCommand(TFSCommand Cmd, intptr_t Params, ...) +{ + va_list args; + va_start(args, Params); + UnicodeString FullCommand = FCommandSet->FullCommand(Cmd, args); + UnicodeString Command = FCommandSet->Command(Cmd, args); + ExecCommand2(FullCommand, Params, Command); + va_end(args); + if (Params & ecRaiseExcept) + { + int MinL = FCommandSet->GetMinLines(Cmd); + int MaxL = FCommandSet->GetMaxLines(Cmd); + if (((MinL >= 0) && (MinL > static_cast(FOutput->GetCount()))) || + ((MaxL >= 0) && (MaxL > static_cast(FOutput->GetCount())))) + { + FTerminal->TerminalError(FMTLOAD(INVALID_OUTPUT_ERROR, + FullCommand.c_str(), GetOutput()->GetText().c_str())); + } + } +} + +UnicodeString TSCPFileSystem::GetCurrDirectory() const +{ + return FCurrentDirectory; +} + +void TSCPFileSystem::DoStartup() +{ + // capabilities of SCP protocol are fixed + FTerminal->SaveCapabilities(FFileSystemInfo); + + const TSessionData * Data = FTerminal->GetSessionData(); + // SkipStartupMessage and DetectReturnVar must succeed, + // otherwise session is to be closed. + try + { + FTerminal->SetExceptionOnFail(true); + SkipStartupMessage(); + if (Data->GetDetectReturnVar()) + { + DetectReturnVar(); + } + FTerminal->SetExceptionOnFail(false); + } + catch (Exception & E) + { + FTerminal->FatalError(&E, L""); + } + + // Needs to be done before UnsetNationalVars() + DetectUtf(); + + #define COND_OPER(OPER) if (Data->Get##OPER()) OPER() + COND_OPER(ClearAliases); + COND_OPER(UnsetNationalVars); +#undef COND_OPER +} + +void TSCPFileSystem::DetectUtf() +{ + const TSessionData * Data = FTerminal->GetSessionData(); + switch (Data->GetNotUtf()) + { + case asOn: + FSecureShell->SetUtfStrings(false); // noop + break; + + case asOff: + FSecureShell->SetUtfStrings(true); + break; + + case asAuto: + FSecureShell->SetUtfStrings(false); // noop + try + { + ExecCommand(fsLang, 0, false); + + if ((FOutput->GetCount() >= 1) && + ::AnsiContainsText(FOutput->GetString(0), L"UTF-8")) + { + FSecureShell->SetUtfStrings(true); + } + } + catch (Exception &) + { + // ignore non-fatal errors + if (!FTerminal->GetActive()) + { + throw; + } + } + break; + + default: + DebugFail(); + } + + if (FSecureShell->GetUtfStrings()) + { + FTerminal->LogEvent("We will use UTF-8"); + } + else + { + FTerminal->LogEvent("We will not use UTF-8"); + } +} + +void TSCPFileSystem::SkipStartupMessage() +{ + try + { + FTerminal->LogEvent("Skipping host startup message (if any)."); + ExecCommand(fsNull, 0); + } + catch (Exception & E) + { + FTerminal->CommandError(&E, LoadStr(SKIP_STARTUP_MESSAGE_ERROR), 0, HELP_SKIP_STARTUP_MESSAGE_ERROR); + } +} + +void TSCPFileSystem::LookupUsersGroups() +{ + ExecCommand(fsLookupUsersGroups, 0); + FTerminal->GetUsers()->Clear(); + FTerminal->GetGroups()->Clear(); + if (FOutput->GetCount() > 0) + { + UnicodeString Groups = FOutput->GetString(0); + while (!Groups.IsEmpty()) + { + UnicodeString NewGroup = CutToChar(Groups, L' ', false); + FTerminal->GetGroups()->Add(TRemoteToken(NewGroup)); + FTerminal->GetMembership()->Add(TRemoteToken(NewGroup)); + } + } +} + +void TSCPFileSystem::DetectReturnVar() +{ + // This suppose that something was already executed (probably SkipStartupMessage()) + // or return code variable is already set on start up. + + try + { + // #60 17.10.01: "status" and "?" switched + UnicodeString ReturnVars[2] = { L"status", L"?" }; + UnicodeString NewReturnVar; + FTerminal->LogEvent("Detecting variable containing return code of last command."); + for (intptr_t Index = 0; Index < 2; ++Index) + { + bool Success = true; + + try + { + FTerminal->LogEvent(FORMAT(L"Trying \"$%s\".", ReturnVars[Index].c_str())); + ExecCommand(fsVarValue, 0, ReturnVars[Index].c_str()); + UnicodeString Str = GetOutput()->GetCount() > 0 ? GetOutput()->GetString(0) : L""; + intptr_t Val = ::StrToIntDef(Str, 256); + if ((GetOutput()->GetCount() != 1) || Str.IsEmpty() || (Val > 255)) + { + FTerminal->LogEvent("The response is not numerical exit code"); + Abort(); + } + } + catch (EFatal &) + { + // if fatal error occurs, we need to exit ... + throw; + } + catch (Exception &) + { + // ...otherwise, we will try next variable (if any) + Success = false; + } + + if (Success) + { + NewReturnVar = ReturnVars[Index]; + break; + } + } + + if (NewReturnVar.IsEmpty()) + { + ThrowExtException(); + } + else + { + FCommandSet->SetReturnVar(NewReturnVar); + FTerminal->LogEvent(FORMAT(L"Return code variable \"%s\" selected.", + FCommandSet->GetReturnVar().c_str())); + } + } + catch (Exception & E) + { + FTerminal->CommandError(&E, LoadStr(DETECT_RETURNVAR_ERROR)); + } +} + +void TSCPFileSystem::ClearAlias(const UnicodeString & Alias) +{ + if (!Alias.IsEmpty()) + { + // this command usually fails, because there will never be + // aliases on all commands -> see last False parameter + ExecCommand(fsUnalias, 0, Alias.c_str(), false); + } +} + +void TSCPFileSystem::ClearAliases() +{ + try + { + FTerminal->LogEvent("Clearing all aliases."); + ClearAlias(TCommandSet::ExtractCommand(FTerminal->GetSessionData()->GetListingCommand())); + std::unique_ptr CommandList(FCommandSet->CreateCommandList()); + try__finally + { + for (intptr_t Index = 0; Index < CommandList->GetCount(); ++Index) + { + ClearAlias(CommandList->GetString(Index)); + } + } + __finally + { +// delete CommandList; + }; + } + catch (Exception & E) + { + FTerminal->CommandError(&E, LoadStr(UNALIAS_ALL_ERROR)); + } +} + +void TSCPFileSystem::UnsetNationalVars() +{ + try + { + FTerminal->LogEvent("Clearing national user variables."); + for (intptr_t Index = 0; Index < NationalVarCount; ++Index) + { + ExecCommand(fsUnset, 0, UnicodeString(NationalVars[Index]).c_str(), false); + } + } + catch (Exception & E) + { + FTerminal->CommandError(&E, LoadStr(UNSET_NATIONAL_ERROR)); + } +} + +void TSCPFileSystem::ReadCurrentDirectory() +{ + if (FCachedDirectoryChange.IsEmpty()) + { + ExecCommand(fsCurrentDirectory, 0); + FCurrentDirectory = core::UnixExcludeTrailingBackslash(FOutput->GetString(0)); + } + else + { + FCurrentDirectory = FCachedDirectoryChange; + } +} + +void TSCPFileSystem::HomeDirectory() +{ + ExecCommand(fsHomeDirectory, 0); +} + +void TSCPFileSystem::AnnounceFileListOperation() +{ + // noop +} + +void TSCPFileSystem::ChangeDirectory(const UnicodeString & Directory) +{ + UnicodeString ToDir; + if (!Directory.IsEmpty() && + ((Directory[1] != L'~') || (Directory.SubString(1, 2) == L"~ "))) + { + ToDir = L"\"" + DelimitStr(Directory) + L"\""; + } + else + { + ToDir = DelimitStr(Directory); + } + ExecCommand(fsChangeDirectory, 0, ToDir.c_str()); + FCachedDirectoryChange.Clear(); +} + +void TSCPFileSystem::CachedChangeDirectory(const UnicodeString & Directory) +{ + FCachedDirectoryChange = core::UnixExcludeTrailingBackslash(Directory); +} + +void TSCPFileSystem::ReadDirectory(TRemoteFileList * FileList) +{ + DebugAssert(FileList); + // emptying file list moved before command execution + FileList->Reset(); + + bool Again; + + do + { + Again = false; + try + { + intptr_t Params = ecDefault | ecReadProgress | + FLAGMASK(FTerminal->GetSessionData()->GetIgnoreLsWarnings(), ecIgnoreWarnings); + UnicodeString Options = + ((FLsFullTime == asAuto) || (FLsFullTime == asOn)) ? FullTimeOption : ""; + bool ListCurrentDirectory = (FileList->GetDirectory() == FTerminal->GetCurrDirectory()); + if (ListCurrentDirectory) + { + FTerminal->LogEvent("Listing current directory."); + ExecCommand(fsListCurrentDirectory, Params, + FTerminal->GetSessionData()->GetListingCommand().c_str(), Options.c_str()); + } + else + { + FTerminal->LogEvent(FORMAT(L"Listing directory \"%s\".", + FileList->GetDirectory().c_str())); + ExecCommand(fsListDirectory, Params, + FTerminal->GetSessionData()->GetListingCommand().c_str(), Options.c_str(), + DelimitStr(FileList->GetDirectory().c_str()).c_str()); + } + + // If output is not empty, we have successfully got file listing, + // otherwise there was an error, in case it was "permission denied" + // we try to get at least parent directory (see "else" statement below) + if (FOutput->GetCount() > 0) + { + // Copy LS command output, because eventual symlink analysis would + // modify FTerminal->Output + std::unique_ptr OutputCopy(new TStringList()); + try__finally + { + OutputCopy->Assign(FOutput); + + // delete leading "total xxx" line + // On some hosts there is not "total" but "totalt". What's the reason?? + // see mail from "Jan Wiklund (SysOp)" + if (IsTotalListingLine(OutputCopy->GetString(0))) + { + OutputCopy->Delete(0); + } + + for (intptr_t Index = 0; Index < OutputCopy->GetCount(); ++Index) + { + UnicodeString OutputLine = OutputCopy->GetString(Index); + if (!OutputLine.IsEmpty()) + { + TRemoteFile * File = CreateRemoteFile(OutputLine); + FileList->AddFile(File); + } + } + } + __finally + { +// delete OutputCopy; + }; + } + else + { + bool Empty; + if (ListCurrentDirectory) + { + TRemoteFile * File = nullptr; + // Empty file list -> probably "permission denied", we + // at least get link to parent directory ("..") + FTerminal->ReadFile( + core::UnixIncludeTrailingBackslash(FTerminal->GetFiles()->GetDirectory()) + + PARENTDIRECTORY, File); + Empty = (File == nullptr); + if (!Empty) + { + DebugAssert(File->GetIsParentDirectory()); + FileList->AddFile(File); + } + } + else + { + Empty = true; + } + + if (Empty) + { + throw ExtException( + nullptr, FMTLOAD(EMPTY_DIRECTORY, FileList->GetDirectory().c_str()), + HELP_EMPTY_DIRECTORY); + } + } + + if (FLsFullTime == asAuto) + { + FTerminal->LogEvent( + FORMAT(L"Directory listing with %s succeed, next time all errors during " + L"directory listing will be displayed immediately.", + UnicodeString(FullTimeOption).c_str())); + FLsFullTime = asOn; + } + } + catch (Exception & E) + { + if (FTerminal->GetActive()) + { + if (FLsFullTime == asAuto) + { + FTerminal->GetLog()->AddException(&E); + FLsFullTime = asOff; + Again = true; + FTerminal->LogEvent( + FORMAT(L"Directory listing with %s failed, try again regular listing.", + UnicodeString(FullTimeOption).c_str())); + } + else + { + throw; + } + } + else + { + throw; + } + } + } + while (Again); +} + +void TSCPFileSystem::ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& File) +{ + CustomReadFile(SymlinkFile->GetLinkTo(), File, SymlinkFile); +} + +void TSCPFileSystem::ReadFile(const UnicodeString & AFileName, + TRemoteFile *& File) +{ + CustomReadFile(AFileName, File, nullptr); +} + +TRemoteFile * TSCPFileSystem::CreateRemoteFile( + const UnicodeString & ListingStr, TRemoteFile * LinkedByFile) +{ + std::unique_ptr File(new TRemoteFile(LinkedByFile)); + try__catch + { + File->SetTerminal(FTerminal); + File->SetListingStr(ListingStr); + File->ShiftTimeInSeconds(TimeToSeconds(FTerminal->GetSessionData()->GetTimeDifference())); + File->Complete(); + } + /*catch (...) + { + delete File; + throw; + }*/ + + return File.release(); +} + +void TSCPFileSystem::CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& File, TRemoteFile * ALinkedByFile) +{ + File = nullptr; + intptr_t Params = ecDefault | + FLAGMASK(FTerminal->GetSessionData()->GetIgnoreLsWarnings(), ecIgnoreWarnings); + // the auto-detection of --full-time support is not implemented for fsListFile, + // so we use it only if we already know that it is supported (asOn). + UnicodeString Options = (FLsFullTime == asOn) ? FullTimeOption : ""; + ExecCommand(fsListFile, Params, + FTerminal->GetSessionData()->GetListingCommand().c_str(), Options.c_str(), DelimitStr(AFileName).c_str()); + if (FOutput->GetCount()) + { + intptr_t LineIndex = 0; + if (IsTotalListingLine(FOutput->GetString(LineIndex)) && FOutput->GetCount() > 1) + { + ++LineIndex; + } + + File = CreateRemoteFile(FOutput->GetString(LineIndex), ALinkedByFile); + } +} + +void TSCPFileSystem::RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action) +{ + DebugUsedParam(AFile); + DebugUsedParam(Params); + Action.Recursive(); + DebugAssert(FLAGCLEAR(Params, dfNoRecursive) || (AFile && AFile->GetIsSymLink())); + ExecCommand(fsDeleteFile, Params, DelimitStr(AFileName).c_str()); +} + +void TSCPFileSystem::RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + ExecCommand(fsRenameFile, 0, DelimitStr(AFileName).c_str(), DelimitStr(ANewName).c_str()); +} + +void TSCPFileSystem::RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + // ExecCommand2(fsCopyFile, 0, DelimitStr(AFileName).c_str(), DelimitStr(NewName).c_str()); + UnicodeString DelimitedFileName = DelimitStr(AFileName); + UnicodeString DelimitedNewName = DelimitStr(ANewName); + const UnicodeString AdditionalSwitches = L"-T"; + try + { + ExecCommand(fsCopyFile, 0, AdditionalSwitches.c_str(), DelimitedFileName.c_str(), DelimitedNewName.c_str()); + } + catch (Exception &) + { + if (FTerminal->GetActive()) + { + // The -T is GNU switch and may not be available on all platforms. + // http://lists.gnu.org/archive/html/bug-coreutils/2004-07/msg00000.html + FTerminal->LogEvent(FORMAT(L"Attempt with %s failed, trying without", AdditionalSwitches.c_str())); + ExecCommand(fsCopyFile, 0, L"", DelimitedFileName.c_str(), DelimitedNewName.c_str()); + } + else + { + throw; + } + } +} + +void TSCPFileSystem::RemoteCreateDirectory(const UnicodeString & ADirName) +{ + ExecCommand(fsCreateDirectory, 0, DelimitStr(ADirName).c_str()); +} + +void TSCPFileSystem::CreateLink(const UnicodeString & AFileName, + const UnicodeString & PointTo, bool Symbolic) +{ + ExecCommand(fsCreateLink, 0, + Symbolic ? L"-s" : L"", DelimitStr(PointTo).c_str(), DelimitStr(AFileName).c_str()); +} + +void TSCPFileSystem::ChangeFileToken(const UnicodeString & DelimitedName, + const TRemoteToken & Token, TFSCommand Cmd, const UnicodeString & RecursiveStr) +{ + UnicodeString Str; + if (Token.GetIDValid()) + { + Str = ::IntToStr(Token.GetID()); + } + else if (Token.GetNameValid()) + { + Str = Token.GetName(); + } + + if (!Str.IsEmpty()) + { + ExecCommand(Cmd, 0, RecursiveStr.c_str(), Str.c_str(), DelimitedName.c_str()); + } +} + +void TSCPFileSystem::ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action) +{ + DebugAssert(Properties); + bool IsDirectory = AFile && AFile->GetIsDirectory(); + bool Recursive = Properties->Recursive && IsDirectory; + UnicodeString RecursiveStr = Recursive ? L"-R" : L""; + + UnicodeString DelimitedName = DelimitStr(AFileName); + // change group before permissions as chgrp change permissions + if (Properties->Valid.Contains(vpGroup)) + { + ChangeFileToken(DelimitedName, Properties->Group, fsChangeGroup, RecursiveStr); + } + if (Properties->Valid.Contains(vpOwner)) + { + ChangeFileToken(DelimitedName, Properties->Owner, fsChangeOwner, RecursiveStr); + } + if (Properties->Valid.Contains(vpRights)) + { + TRights Rights = Properties->Rights; + + // if we don't set modes recursively, we may add X at once with other + // options. Otherwise we have to add X after recursive command + if (!Recursive && IsDirectory && Properties->AddXToDirectories) + Rights.AddExecute(); + + Action.Rights(Rights); + if (Recursive) + { + Action.Recursive(); + } + + if ((Rights.GetNumberSet() | Rights.GetNumberUnset()) != TRights::rfNo) + { + ExecCommand(fsChangeMode, 0, + RecursiveStr.c_str(), Rights.GetSimplestStr().c_str(), DelimitedName.c_str()); + } + + // if file is directory and we do recursive mode settings with + // add-x-to-directories option on, add those X + if (Recursive && IsDirectory && Properties->AddXToDirectories) + { + Rights.AddExecute(); + ExecCommand(fsChangeMode, 0, + L"", Rights.GetSimplestStr().c_str(), DelimitedName.c_str()); + } + } + else + { + Action.Cancel(); + } + DebugAssert(!Properties->Valid.Contains(vpLastAccess)); + DebugAssert(!Properties->Valid.Contains(vpModification)); +} + +bool TSCPFileSystem::LoadFilesProperties(TStrings * /*FileList*/ ) +{ + DebugFail(); + return false; +} + +void TSCPFileSystem::CalculateFilesChecksum(const UnicodeString & /*Alg*/, + TStrings * /*FileList*/, TStrings * /*Checksums*/, + TCalculatedChecksumEvent /*OnCalculatedChecksum*/) +{ + DebugFail(); +} + +void TSCPFileSystem::CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, + TCaptureOutputEvent OutputEvent) +{ + DebugAssert(AFile); + bool Dir = AFile->GetIsDirectory() && FTerminal->CanRecurseToDirectory(AFile); + if (Dir && (Params & ccRecursive)) + { + TCustomCommandParams AParams; + AParams.Command = Command; + AParams.Params = Params; + AParams.OutputEvent = OutputEvent; + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TTerminal::CustomCommandOnFile, FTerminal), + &AParams); + } + + if (!Dir || (Params & ccApplyToDirectories)) + { + TCustomCommandData Data(FTerminal); + UnicodeString Cmd = TRemoteCustomCommand( + Data, FTerminal->GetCurrDirectory(), AFileName, L""). + Complete(Command, true); + + AnyCommand(Cmd, OutputEvent); + } +} + +void TSCPFileSystem::CaptureOutput(const UnicodeString & AddedLine, TCaptureOutputType OutputType) +{ + intptr_t ReturnCode; + UnicodeString Line = AddedLine; + // TSecureShell never uses cotExitCode + DebugAssert((OutputType == cotOutput) || (OutputType == cotError)); + if ((OutputType == cotError) || DebugAlwaysFalse(OutputType == cotExitCode) || + !RemoveLastLine(Line, ReturnCode) || + !Line.IsEmpty()) + { + DebugAssert(FOnCaptureOutput != nullptr); + FOnCaptureOutput(Line, OutputType); + } +} + +void TSCPFileSystem::AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent) +{ + DebugAssert(!FSecureShell->GetOnCaptureOutput()); + if (OutputEvent) + { + FSecureShell->SetOnCaptureOutput(MAKE_CALLBACK(TSCPFileSystem::CaptureOutput, this)); + FOnCaptureOutput = OutputEvent; + } + + try__finally + { + SCOPE_EXIT + { + FOnCaptureOutput = nullptr; + FSecureShell->SetOnCaptureOutput(nullptr); + }; + ExecCommand(fsAnyCommand, + ecDefault | ecIgnoreWarnings, Command.c_str()); + } + __finally + { + FOnCaptureOutput = nullptr; + FSecureShell->SetOnCaptureOutput(nullptr); + }; +} + +TStrings * TSCPFileSystem::GetFixedPaths() +{ + return nullptr; +} + +void TSCPFileSystem::SpaceAvailable(const UnicodeString & /*APath*/, + TSpaceAvailable & /*ASpaceAvailable*/) +{ + DebugFail(); +} + +// transfer protocol + +uintptr_t TSCPFileSystem::ConfirmOverwrite( + const UnicodeString & ASourceFullFileName, + const UnicodeString & ATargetFileName, TOperationSide Side, + const TOverwriteFileParams * FileParams, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress) +{ + TSuspendFileOperationProgress Suspend(OperationProgress); + + TQueryButtonAlias Aliases[3]; + Aliases[0].Button = qaAll; + Aliases[0].Alias = LoadStr(YES_TO_NEWER_BUTTON); + Aliases[0].GroupWith = qaYes; + Aliases[0].GrouppedShiftState = ssCtrl; + Aliases[1].Button = qaYesToAll; + Aliases[1].GroupWith = qaYes; + Aliases[1].GrouppedShiftState = ssShift; + Aliases[2].Button = qaNoToAll; + Aliases[2].GroupWith = qaNo; + Aliases[2].GrouppedShiftState = ssShift; + TQueryParams QueryParams(qpNeverAskAgainCheck); + QueryParams.Aliases = Aliases; + QueryParams.AliasesCount = _countof(Aliases); + uintptr_t Answer = + FTerminal->ConfirmFileOverwrite( + ASourceFullFileName, ATargetFileName, FileParams, + qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll | qaAll, + &QueryParams, Side, CopyParam, Params, OperationProgress); + return Answer; +} + +void TSCPFileSystem::SCPResponse(bool * GotLastLine) +{ + // Taken from scp.c response() and modified + + uint8_t Resp; + FSecureShell->Receive(&Resp, 1); + + switch (Resp) + { + case 0: /* ok */ + FTerminal->LogEvent("SCP remote side confirmation (0)"); + return; + + default: + case 1: /* error */ + case 2: /* fatal error */ + // pscp adds 'Resp' to 'Msg', why? + UnicodeString Msg = FSecureShell->ReceiveLine(); + UnicodeString Line = UnicodeString(reinterpret_cast(&Resp), 1) + Msg; + if (IsLastLine(Line)) + { + if (GotLastLine != nullptr) + { + *GotLastLine = true; + } + + /* TODO 1 : Show stderror to user? */ + FSecureShell->ClearStdError(); + + try + { + ReadCommandOutput(coExpectNoOutput | coRaiseExcept | coOnlyReturnCode); + } + catch (...) + { + // when ReadCommandOutput() fails than remote SCP is terminated already + if (GotLastLine != nullptr) + { + *GotLastLine = true; + } + throw; + } + } + else if (Resp == 1) + { + FTerminal->LogEvent("SCP remote side error (1):"); + } + else + { + FTerminal->LogEvent("SCP remote side fatal error (2):"); + } + + if (Resp == 1) + { + ThrowFileSkipped(nullptr, Msg); + } + else + { + ThrowScpEror(nullptr, Msg); + } + } +} + +void TSCPFileSystem::CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + // scp.c: source(), toremote() + DebugAssert(AFilesToCopy && OperationProgress); + + Params &= ~(cpAppend | cpResume); + UnicodeString Options; + bool CheckExistence = core::UnixSamePath(TargetDir, FTerminal->GetCurrDirectory()) && + (FTerminal->GetFiles() != nullptr) && FTerminal->GetFiles()->GetLoaded(); + bool CopyBatchStarted = false; + bool Failed = true; + bool GotLastLine = false; + + UnicodeString TargetDirFull = core::UnixIncludeTrailingBackslash(TargetDir); + + if (CopyParam->GetPreserveRights()) + { + Options = L"-p"; + } + if (FTerminal->GetSessionData()->GetScp1Compatibility()) + { + Options += L" -1"; + } + + SendCommand(FCommandSet->FullCommand(fsCopyToRemote, + Options.c_str(), DelimitStr(core::UnixExcludeTrailingBackslash(TargetDir)).c_str())); + SkipFirstLine(); + + try__finally + { + SCOPE_EXIT + { + // Tell remote side, that we're done. + if (FTerminal->GetActive()) + { + try + { + if (!GotLastLine) + { + if (CopyBatchStarted) + { + // What about case, remote side sends fatal error ??? + // (Not sure, if it causes remote side to terminate scp) + FSecureShell->SendLine(L"E"); + SCPResponse(); + } + /* TODO 1 : Show stderror to user? */ + FSecureShell->ClearStdError(); + + ReadCommandOutput(coExpectNoOutput | coWaitForLastLine | coOnlyReturnCode | + (Failed ? 0 : coRaiseExcept)); + } + } + catch (Exception & E) + { + // Only log error message (it should always succeed, but + // some pending error maybe in queue) } + FTerminal->GetLog()->AddException(&E); + } + } + }; + try + { + SCPResponse(&GotLastLine); + + // This can happen only if SCP command is not executed and return code is 0 + // It has never happened to me (return code is usually 127) + if (GotLastLine) + { + throw Exception(L""); + } + } + catch (Exception & E) + { + if (GotLastLine && FTerminal->GetActive()) + { + FTerminal->TerminalError(&E, LoadStr(SCP_INIT_ERROR)); + } + else + { + throw; + } + } + CopyBatchStarted = true; + + for (intptr_t IFile = 0; (IFile < AFilesToCopy->GetCount()) && + !OperationProgress->Cancel; ++IFile) + { + UnicodeString FileName = AFilesToCopy->GetString(IFile); + TRemoteFile * File1 = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(IFile)); + UnicodeString RealFileName = File1 ? File1->GetFileName() : FileName; + bool CanProceed = false; + + UnicodeString FileNameOnly = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(RealFileName, false), osLocal, true); + + if (CheckExistence) + { + // previously there was assertion on FTerminal->FFiles->Loaded, but it + // fails for scripting, if 'ls' is not issued before. + // formally we should call CheckRemoteFile here but as checking is for + // free here (almost) ... + TRemoteFile * File2 = FTerminal->FFiles->FindFile(FileNameOnly); + if (File2 != nullptr) + { + uintptr_t Answer; + if (File2->GetIsDirectory()) + { + UnicodeString Message = FMTLOAD(DIRECTORY_OVERWRITE, FileNameOnly.c_str()); + TQueryParams QueryParams(qpNeverAskAgainCheck); + + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = FTerminal->ConfirmFileOverwrite( + FileName, FileNameOnly, nullptr, + qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll, + &QueryParams, osRemote, CopyParam, Params, OperationProgress, Message); + } + else + { + int64_t MTime = 0; + TOverwriteFileParams FileParams; + FTerminal->TerminalOpenLocalFile(FileName, GENERIC_READ, + nullptr, nullptr, nullptr, &MTime, nullptr, + &FileParams.SourceSize); + FileParams.SourceTimestamp = ::UnixToDateTime(MTime, + FTerminal->GetSessionData()->GetDSTMode()); + FileParams.DestSize = File2->GetSize(); + FileParams.DestTimestamp = File2->GetModification(); + Answer = ConfirmOverwrite( + FileName, FileNameOnly, osRemote, + &FileParams, CopyParam, Params, OperationProgress); + } + + switch (Answer) + { + case qaYes: + CanProceed = true; + break; + + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + CanProceed = false; + break; + case qaNo: + CanProceed = false; + break; + + default: + DebugFail(); + break; + } + } + else + { + CanProceed = true; + } + } + else + { + CanProceed = true; + } + + if (CanProceed) + { + if (FTerminal->GetSessionData()->GetCacheDirectories()) + { + FTerminal->DirectoryModified(TargetDir, false); + + if (::DirectoryExists(ApiPath(::ExtractFilePath(FileName)))) + { + FTerminal->DirectoryModified(core::UnixIncludeTrailingBackslash(TargetDir)+ + FileNameOnly, true); + } + } + + try + { + SCPSource(FileName, File1, TargetDirFull, + CopyParam, Params, OperationProgress, 0); + OperationProgress->Finish(RealFileName, true, OnceDoneOperation); + } + catch (EFileSkipped & E) + { + TQueryParams QueryParams(qpAllowContinueOnError); + + TSuspendFileOperationProgress Suspend1(OperationProgress); + + if (FTerminal->QueryUserException(FMTLOAD(COPY_ERROR, FileName.c_str()), &E, + qaOK | qaAbort, &QueryParams, qtError) == qaAbort) + { + OperationProgress->Cancel = csCancel; + } + OperationProgress->Finish(FileName, false, OnceDoneOperation); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + catch (ESkipFile & E) + { + OperationProgress->Finish(FileName, false, OnceDoneOperation); + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + // If ESkipFile occurs, just log it and continue with next file + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + catch (...) + { + OperationProgress->Finish(FileName, false, OnceDoneOperation); + throw; + } + } + } + Failed = false; + } + __finally + { + // Tell remote side, that we're done. + if (FTerminal->GetActive()) + { + try + { + if (!GotLastLine) + { + if (CopyBatchStarted) + { + // What about case, remote side sends fatal error ??? + // (Not sure, if it causes remote side to terminate scp) + FSecureShell->SendLine(L"E"); + SCPResponse(); + } + /* TODO 1 : Show stderror to user? */ + FSecureShell->ClearStdError(); + + ReadCommandOutput(coExpectNoOutput | coWaitForLastLine | coOnlyReturnCode | + (Failed ? 0 : coRaiseExcept)); + } + } + catch (Exception & E) + { + // Only log error message (it should always succeed, but + // some pending error maybe in queue) } + FTerminal->GetLog()->AddException(&E); + } + } + }; +} + +void TSCPFileSystem::SCPSource(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, intptr_t Level) +{ + UnicodeString RealFileName = AFile ? AFile->GetFileName() : AFileName; + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(RealFileName, false), osLocal, Level == 0); + + FTerminal->LogEvent(FORMAT(L"File: \"%s\"", RealFileName.c_str())); + + OperationProgress->SetFile(RealFileName, false); + + if (!FTerminal->AllowLocalFileTransfer(AFileName, CopyParam, OperationProgress)) + { + ThrowSkipFileNull(); + } + + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + uintptr_t LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + int64_t MTime, ATime; + int64_t Size; + + FTerminal->TerminalOpenLocalFile(AFileName, GENERIC_READ, + &LocalFileHandle, &LocalFileAttrs, nullptr, &MTime, &ATime, &Size); + + bool Dir = FLAGSET(LocalFileAttrs, faDirectory); + std::unique_ptr Stream(new TSafeHandleStream(LocalFileHandle)); + try__finally + { + SCOPE_EXIT + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + }; + OperationProgress->SetFileInProgress(); + + if (Dir) + { + SCPDirectorySource(AFileName, TargetDir, CopyParam, Params, OperationProgress, Level); + } + else + { + UnicodeString AbsoluteFileName = FTerminal->GetAbsolutePath(/* TargetDir + */DestFileName, false); + DebugAssert(LocalFileHandle != INVALID_HANDLE_VALUE); + + // File is regular file (not directory) + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", RealFileName.c_str())); + + OperationProgress->SetLocalSize(Size); + + // Suppose same data size to transfer as to read + // (not true with ASCII transfer) + OperationProgress->SetTransferSize(OperationProgress->LocalSize); + OperationProgress->TransferingFile = false; + + TDateTime Modification = ::UnixToDateTime(MTime, FTerminal->GetSessionData()->GetDSTMode()); + + // Will we use ASCII of BINARY file transfer? + TFileMasks::TParams MaskParams; + MaskParams.Size = Size; + MaskParams.Modification = Modification; + UnicodeString BaseFileName = FTerminal->GetBaseFileName(RealFileName); + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osLocal, MaskParams)); + FTerminal->LogEvent( + UnicodeString((OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary")) + + L" transfer mode selected."); + + TUploadSessionAction Action(FTerminal->GetActionLog()); + Action.SetFileName(::ExpandUNCFileName(AFileName)); + Action.Destination(AbsoluteFileName); + + TRights Rights = CopyParam->RemoteFileRights(LocalFileAttrs); + + try + { + // During ASCII transfer we will load whole file to this buffer + // than convert EOL and send it at once, because before converting EOL + // we can't know its size + TFileBuffer AsciiBuf; + bool ConvertToken = false; + do + { + // Buffer for one block of data + TFileBuffer BlockBuf; + + // This is crucial, if it fails during file transfer, it's fatal error + FileOperationLoopCustom(FTerminal, OperationProgress, !OperationProgress->TransferingFile, + FMTLOAD(READ_ERROR, AFileName.c_str()), "", + [&]() + { + BlockBuf.LoadStream(Stream.get(), OperationProgress->LocalBlockSize(), true); + }); + + OperationProgress->AddLocallyUsed(BlockBuf.GetSize()); + + // We do ASCII transfer: convert EOL of current block + // (we don't convert whole buffer, cause it would produce + // huge memory-transfers while inserting/deleting EOL characters) + // Than we add current block to file buffer + if (OperationProgress->AsciiTransfer) + { + int ConvertParams = + FLAGMASK(CopyParam->GetRemoveCtrlZ(), cpRemoveCtrlZ) | + FLAGMASK(CopyParam->GetRemoveBOM(), cpRemoveBOM); + BlockBuf.Convert(FTerminal->GetConfiguration()->GetLocalEOLType(), + FTerminal->GetSessionData()->GetEOLType(), + ConvertParams, ConvertToken); + BlockBuf.GetMemory()->Seek(0, soFromBeginning); + AsciiBuf.ReadStream(BlockBuf.GetMemory(), BlockBuf.GetSize(), true); + // We don't need it any more + BlockBuf.GetMemory()->Clear(); + // Calculate total size to sent (assume that ratio between + // size of source and size of EOL-transformed data would remain same) + // First check if file contains anything (div by zero!) + if (OperationProgress->LocallyUsed) + { + int64_t X = OperationProgress->LocalSize; + X *= AsciiBuf.GetSize(); + X /= OperationProgress->LocallyUsed; + OperationProgress->ChangeTransferSize(X); + } + else + { + OperationProgress->ChangeTransferSize(0); + } + } + + // We send file information on first pass during BINARY transfer + // and on last pass during ASCII transfer + // BINARY: We succeeded reading first buffer from file, hopefully + // we will be able to read whole, so we send file info to remote side + // This is done, because when reading fails we can't interrupt sending + // (don't know how to tell other side that it failed) + if (!OperationProgress->TransferingFile && + (!OperationProgress->AsciiTransfer || OperationProgress->IsLocallyDone())) + { + UnicodeString Buf; + + if (CopyParam->GetPreserveTime()) + { + // Send last file access and modification time + // TVarRec don't understand 'uint32_t' -> we use sprintf() + Buf.sprintf(L"T%lu 0 %lu 0", static_cast(MTime), + static_cast(ATime)); + FSecureShell->SendLine(Buf.c_str()); + SCPResponse(); + } + + // Send file modes (rights), filesize and file name + // TVarRec don't understand 'uint32_t' -> we use sprintf() + int64_t sz = OperationProgress->AsciiTransfer ? AsciiBuf.GetSize() : + OperationProgress->LocalSize; + Buf.sprintf(L"C%s %lld %s", + Rights.GetOctal().data(), + sz, + DestFileName.data()); + FSecureShell->SendLine(Buf.c_str()); + SCPResponse(); + // Indicate we started transferring file, we need to finish it + // If not, it's fatal error + OperationProgress->TransferingFile = true; + + // If we're doing ASCII transfer, this is last pass + // so we send whole file + /* TODO : We can't send file above 32bit size in ASCII mode! */ + if (OperationProgress->AsciiTransfer) + { + FTerminal->LogEvent(FORMAT(L"Sending ASCII data (%u bytes)", + AsciiBuf.GetSize())); + // Should be equal, just in case it's rounded (see above) + OperationProgress->ChangeTransferSize(AsciiBuf.GetSize()); + while (!OperationProgress->IsTransferDone()) + { + uintptr_t BlockSize = OperationProgress->TransferBlockSize(); + FSecureShell->Send( + reinterpret_cast(AsciiBuf.GetData() + static_cast(OperationProgress->TransferedSize)), + BlockSize); + OperationProgress->AddTransfered(BlockSize); + if (OperationProgress->Cancel == csCancelTransfer) + { + throw Exception(MainInstructions(LoadStr(USER_TERMINATED))); + } + } + } + } + + // At end of BINARY transfer pass, send current block + if (!OperationProgress->AsciiTransfer) + { + if (!OperationProgress->TransferedSize) + { + FTerminal->LogEvent(FORMAT(L"Sending BINARY data (first block, %u bytes)", + BlockBuf.GetSize())); + } + else if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1) + { + FTerminal->LogEvent(FORMAT(L"Sending BINARY data (%u bytes)", + BlockBuf.GetSize())); + } + FSecureShell->Send(reinterpret_cast(BlockBuf.GetData()), static_cast(BlockBuf.GetSize())); + OperationProgress->AddTransfered(BlockBuf.GetSize()); + } + + if ((OperationProgress->Cancel == csCancelTransfer) || + (OperationProgress->Cancel == csCancel && !OperationProgress->TransferingFile)) + { + throw Exception(MainInstructions(LoadStr(USER_TERMINATED))); + } + } + while (!OperationProgress->IsLocallyDone() || !OperationProgress->IsTransferDone()); + + FSecureShell->SendNull(); + try + { + SCPResponse(); + // If one of two following exceptions occurs, it means, that remote + // side already know, that file transfer finished, even if it failed + // so we don't have to throw EFatal + } + catch (EScp &) + { + // SCP protocol fatal error + OperationProgress->TransferingFile = false; + throw; + } + catch (EFileSkipped &) + { + // SCP protocol non-fatal error + OperationProgress->TransferingFile = false; + throw; + } + + // We succeeded transferring file, from now we can handle exceptions + // normally -> no fatal error + OperationProgress->TransferingFile = false; + } + catch (Exception & E) + { + // EScpFileSkipped is derived from ESkipFile, + // but is does not indicate file skipped by user here + if (NB_STATIC_DOWNCAST(EFileSkipped, &E) != nullptr) + { + Action.Rollback(&E); + } + else + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + + // Every exception during file transfer is fatal + if (OperationProgress->TransferingFile) + { + FTerminal->FatalError(&E, FMTLOAD(COPY_FATAL, AFileName.c_str())); + } + else + { + throw; + } + } + + // With SCP we are not able to distinguish reason for failure + // (upload itself, touch or chmod). + // So we always report error with upload action and + // log touch and chmod actions only if upload succeeds. + if (CopyParam->GetPreserveTime()) + { + TTouchSessionAction(FTerminal->GetActionLog(), AbsoluteFileName, Modification); + } + if (CopyParam->GetPreserveRights()) + { + TChmodSessionAction(FTerminal->GetActionLog(), AbsoluteFileName, + Rights); + } + + FTerminal->LogFileDone(OperationProgress); + } + } + __finally + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } +// delete Stream; + }; + + /* TODO : Delete also read-only files. */ + if (FLAGSET(Params, cpDelete)) + { + if (!Dir) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(AFileName)); + }); + } + } + else if (CopyParam->GetClearArchive() && FLAGSET(LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(AFileName, LocalFileAttrs & ~faArchive) == 0); + }); + } + + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to remote directory finished.", AFileName.c_str())); +} + +void TSCPFileSystem::SCPDirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, intptr_t Level) +{ + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + + FTerminal->LogEvent(FORMAT(L"Entering directory \"%s\".", DirectoryName.c_str())); + + OperationProgress->SetFile(DirectoryName); + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(DirectoryName, false), osLocal, Level == 0); + + // Get directory attributes + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_GET_ATTRS, DirectoryName.c_str()), "", + [&]() + { + LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DirectoryName)); + if (LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + ::RaiseLastOSError(); + } + }); + + UnicodeString TargetDirFull = core::UnixIncludeTrailingBackslash(TargetDir + DestFileName); + + UnicodeString Buf; + + /* TODO 1: maybe send filetime */ + + // Send directory modes (rights), filesize and file name + Buf = FORMAT(L"D%s 0 %s", + CopyParam->RemoteFileRights(LocalFileAttrs).GetOctal().c_str(), DestFileName.c_str()); + FSecureShell->SendLine(Buf); + SCPResponse(); + + try__finally + { + SCOPE_EXIT + { + if (FTerminal->GetActive()) + { + // Tell remote side, that we're done. + FTerminal->LogEvent(FORMAT(L"Leaving directory \"%s\".", DirectoryName.c_str())); + FSecureShell->SendLine(L"E"); + SCPResponse(); + } + }; + + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + TSearchRecChecked SearchRec; + bool FindOK = false; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + UnicodeString Path = ::IncludeTrailingBackslash(DirectoryName) + L"*.*"; + FindOK = ::FindFirstChecked(Path, + FindAttrs, SearchRec) == 0; + }); + + try__finally + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + while (FindOK && !OperationProgress->Cancel) + { + UnicodeString FileName = ::IncludeTrailingBackslash(DirectoryName) + SearchRec.Name; + try + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + { + SCPSource(FileName, nullptr, TargetDirFull, CopyParam, Params, OperationProgress, Level + 1); + } + } + // Previously we caught ESkipFile, making error being displayed + // even when file was excluded by mask. Now the ESkipFile is special + // case without error message. + catch (EFileSkipped & E) + { + TQueryParams QueryParams(qpAllowContinueOnError); + TSuspendFileOperationProgress Suspend(OperationProgress); + + if (FTerminal->QueryUserException(FMTLOAD(COPY_ERROR, FileName.c_str()), &E, + qaOK | qaAbort, &QueryParams, qtError) == qaAbort) + { + OperationProgress->Cancel = csCancel; + } + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + catch (ESkipFile & E) + { + // If ESkipFile occurs, just log it and continue with next file + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = (::FindNextChecked(SearchRec) == 0); + }); + } + } + __finally + { + FindClose(SearchRec); + }; + + /* TODO : Delete also read-only directories. */ + /* TODO : Show error message on failure. */ + if (!OperationProgress->Cancel) + { + if (FLAGSET(Params, cpDelete)) + { + FTerminal->RemoveLocalDirectory(ApiPath(DirectoryName)); + } + else if (CopyParam->GetClearArchive() && FLAGSET(LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DirectoryName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(DirectoryName, LocalFileAttrs & ~faArchive) == 0); + }); + } + } + } + __finally + { + if (FTerminal->GetActive()) + { + // Tell remote side, that we're done. + FTerminal->LogEvent(FORMAT(L"Leaving directory \"%s\".", DirectoryName.c_str())); + FSecureShell->SendLine(L"E"); + SCPResponse(); + } + }; +} + +void TSCPFileSystem::CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + bool CloseSCP = False; + Params &= ~(cpAppend | cpResume); + UnicodeString Options; + if (CopyParam->GetPreserveRights() || CopyParam->GetPreserveTime()) + { + Options = L"-p"; + } + if (FTerminal->GetSessionData()->GetScp1Compatibility()) + { + Options += L" -1"; + } + + FTerminal->LogEvent(FORMAT(L"Copying %d files/directories to local directory " + L"\"%s\"", AFilesToCopy->GetCount(), TargetDir.c_str())); + FTerminal->LogEvent(CopyParam->GetLogStr()); + + try__finally + { + SCOPE_EXIT + { + // In case that copying doesn't cause fatal error (ie. connection is + // still active) but wasn't successful (exception or user termination) + // we need to ensure, that SCP on remote side is closed + if (FTerminal->GetActive() && (CloseSCP || + (OperationProgress->Cancel == csCancel) || + (OperationProgress->Cancel == csCancelTransfer))) + { + // If we get LastLine, it means that remote side 'scp' is already + // terminated, so we need not to terminate it. There is also + // possibility that remote side waits for confirmation, so it will hang. + // This should not happen (hope) + UnicodeString Line = FSecureShell->ReceiveLine(); + bool LastLineRead = IsLastLine(Line); + if (!LastLineRead) + { + SCPSendError((OperationProgress->Cancel ? L"Terminated by user." : L"Exception"), true); + } + // Just in case, remote side already sent some more data (it's probable) + // but we don't want to raise exception (user asked to terminate, it's not error) + int ECParams = coOnlyReturnCode; + if (!LastLineRead) + { + ECParams |= coWaitForLastLine; + } + ReadCommandOutput(ECParams); + } + }; + for (intptr_t IFile = 0; (IFile < AFilesToCopy->GetCount()) && + !OperationProgress->Cancel; ++IFile) + { + UnicodeString FileName = AFilesToCopy->GetString(IFile); + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(IFile)); + DebugAssert(File); + + // Filename is used for error messaging and excluding files only + // Send in full path to allow path-based excluding + // operation succeeded (no exception), so it's ok that + // remote side closed SCP, but we continue with next file + UnicodeString FullFileName = core::UnixExcludeTrailingBackslash(File->GetFullFileName()); + UnicodeString TargetDirectory = CreateTargetDirectory(File->GetFileName(), TargetDir, CopyParam); + try + { + bool Success = true; // Have to be set to True (see ::SCPSink) + SendCommand(FCommandSet->FullCommand(fsCopyToLocal, + Options.c_str(), DelimitStr(FileName).c_str())); + SkipFirstLine(); + + // Filename is used for error messaging and excluding files only + // Send in full path to allow path-based excluding + SCPSink(FullFileName, File, TargetDirectory, core::UnixExtractFilePath(FullFileName), + CopyParam, Success, OperationProgress, Params, 0); + // operation succeeded (no exception), so it's ok that + // remote side closed SCP, but we continue with next file + if (OperationProgress->Cancel == csRemoteAbort) + { + OperationProgress->Cancel = csContinue; + } + + // Move operation -> delete file/directory afterwards + // but only if copying succeeded + if ((Params & cpDelete) && Success && !OperationProgress->Cancel) + { + try + { + FTerminal->SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + FTerminal->SetExceptionOnFail(false); + }; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(DELETE_FILE_ERROR, FileName.c_str()), "", + [&]() + { + // pass full file name in FileName, in case we are not moving + // from current directory + FTerminal->RemoteDeleteFile(FileName, File); + }); + } + __finally + { + FTerminal->SetExceptionOnFail(false); + }; + } + catch (EFatal &) + { + throw; + } + catch (...) + { + // If user selects skip (or abort), nothing special actually occurs + // we just run DoFinished with Success = False, so file won't + // be deselected in panel (depends on assigned event handler) + + // On csCancel we would later try to close remote SCP, but it + // is closed already + if (OperationProgress->Cancel == csCancel) + { + OperationProgress->Cancel = csRemoteAbort; + } + Success = false; + } + } + + OperationProgress->Finish(FileName, + (!OperationProgress->Cancel && Success), OnceDoneOperation); + } + catch (...) + { + OperationProgress->Finish(FileName, false, OnceDoneOperation); + CloseSCP = (OperationProgress->Cancel != csRemoteAbort); + throw; + } + } + } + __finally + { + // In case that copying doesn't cause fatal error (ie. connection is + // still active) but wasn't successful (exception or user termination) + // we need to ensure, that SCP on remote side is closed + if (FTerminal->GetActive() && (CloseSCP || + (OperationProgress->Cancel == csCancel) || + (OperationProgress->Cancel == csCancelTransfer))) + { + // If we get LastLine, it means that remote side 'scp' is already + // terminated, so we need not to terminate it. There is also + // possibility that remote side waits for confirmation, so it will hang. + // This should not happen (hope) + UnicodeString Line = FSecureShell->ReceiveLine(); + bool LastLineRead = IsLastLine(Line); + if (!LastLineRead) + { + SCPSendError((OperationProgress->Cancel ? L"Terminated by user." : L"Exception"), true); + } + // Just in case, remote side already sent some more data (it's probable) + // but we don't want to raise exception (user asked to terminate, it's not error) + int ECParams = coOnlyReturnCode; + if (!LastLineRead) + { + ECParams |= coWaitForLastLine; + } + ReadCommandOutput(ECParams); + } + }; +} + +void TSCPFileSystem::SCPError(const UnicodeString & Message, bool Fatal) +{ + SCPSendError(Message, Fatal); + ThrowFileSkipped(nullptr, Message); +} + +void TSCPFileSystem::SCPSendError(const UnicodeString & Message, bool Fatal) +{ + uint8_t ErrorLevel = static_cast(Fatal ? 2 : 1); + FTerminal->LogEvent(FORMAT(L"Sending SCP error (%d) to remote side:", + static_cast(ErrorLevel))); + FSecureShell->Send(&ErrorLevel, 1); + // We don't send exact error message, because some unspecified + // characters can terminate remote scp + FSecureShell->SendLine(FORMAT(L"scp: error: %s", Message.c_str())); +} + +void TSCPFileSystem::SCPSink( + const UnicodeString & AFileName, + const TRemoteFile * /*AFile*/, + const UnicodeString & TargetDir, + const UnicodeString & SourceDir, + const TCopyParamType * CopyParam, bool & Success, + TFileOperationProgressType * OperationProgress, intptr_t Params, + intptr_t Level) +{ + struct + { + int SetTime; + FILETIME AcTime; + FILETIME WrTime; + TRights RemoteRights; + DWORD LocalFileAttrs; + bool Exists; + } FileData; + TDateTime SourceTimestamp; + + bool SkipConfirmed = false; + bool Initialized = (Level > 0); + + FileData.SetTime = 0; + + FSecureShell->SendNull(); + + while (!OperationProgress->Cancel) + { + // See (switch ... case 'T':) + if (FileData.SetTime) + { + FileData.SetTime--; + } + + // In case of error occurred before control record arrived. + // We can finally use full path here, as we get current path in FileName param + // (we used to set the file into OperationProgress->FileName, but it collided + // with progress outputting, particularly for scripting) + UnicodeString AbsoluteFileName = AFileName; + + try + { + // Receive control record + UnicodeString Line = FSecureShell->ReceiveLine(); + + if (Line.Length() == 0) + { + FTerminal->FatalError(nullptr, LoadStr(SCP_EMPTY_LINE)); + } + + if (IsLastLine(Line)) + { + // Remote side finished copying, so remote SCP was closed + // and we don't need to terminate it manually, see CopyToLocal() + OperationProgress->Cancel = csRemoteAbort; + /* TODO 1 : Show stderror to user? */ + FSecureShell->ClearStdError(); + try + { + // coIgnoreWarnings should allow batch transfer to continue when + // download of one the files fails (user denies overwriting + // of target local file, no read permissions...) + ReadCommandOutput(coExpectNoOutput | coRaiseExcept | + coOnlyReturnCode | coIgnoreWarnings); + if (!Initialized) + { + throw Exception(L""); + } + } + catch (Exception & E) + { + if (!Initialized && FTerminal->GetActive()) + { + FTerminal->TerminalError(&E, LoadStr(SCP_INIT_ERROR)); + } + else + { + throw; + } + } + return; + } + else + { + Initialized = true; + + // First character distinguish type of control record + wchar_t Ctrl = Line[1]; + Line.Delete(1, 1); + + switch (Ctrl) + { + case 1: + // Error (already logged by ReceiveLine()) + ThrowFileSkipped(nullptr, FMTLOAD(REMOTE_ERROR, Line.c_str())); + + case 2: + // Fatal error, terminate copying + FTerminal->TerminalError(Line); + return; // Unreachable + + case L'E': // Exit + FSecureShell->SendNull(); + return; + + case L'T': + uint32_t MTime, ATime; + if (swscanf(Line.c_str(), L"%ld %*d %ld %*d", &MTime, &ATime) == 2) + { + const TSessionData * Data = FTerminal->GetSessionData(); + FileData.AcTime = ::DateTimeToFileTime(::UnixToDateTime(ATime, + Data->GetDSTMode()), Data->GetDSTMode()); + FileData.WrTime = ::DateTimeToFileTime(::UnixToDateTime(MTime, + Data->GetDSTMode()), Data->GetDSTMode()); + SourceTimestamp = ::UnixToDateTime(MTime, + Data->GetDSTMode()); + FSecureShell->SendNull(); + // File time is only valid until next pass + FileData.SetTime = 2; + continue; + } + else + { + SCPError(LoadStr(SCP_ILLEGAL_TIME_FORMAT), False); + } + + case L'C': + case L'D': + break; // continue pass switch{} + + default: + FTerminal->FatalError(nullptr, FMTLOAD(SCP_INVALID_CONTROL_RECORD, Ctrl, Line.c_str())); + } + + TFileMasks::TParams MaskParams; + MaskParams.Modification = SourceTimestamp; + + // We reach this point only if control record was 'C' or 'D' + try + { + FileData.RemoteRights.SetOctal(CutToChar(Line, L' ', True)); + // do not trim leading spaces of the filename + int64_t TSize = ::StrToInt64(CutToChar(Line, L' ', False).TrimRight()); + MaskParams.Size = TSize; + // Security fix: ensure the file ends up where we asked for it. + // (accept only filename, not path) + UnicodeString OnlyFileName = base::UnixExtractFileName(Line); + if (Line != OnlyFileName) + { + FTerminal->LogEvent(FORMAT(L"Warning: Remote host set a compound pathname '%s'", Line.c_str())); + } + + AbsoluteFileName = SourceDir + OnlyFileName; + OperationProgress->SetFile(AbsoluteFileName); + OperationProgress->SetTransferSize(TSize); + } + catch (Exception & E) + { + { + TSuspendFileOperationProgress Suspend(OperationProgress); + FTerminal->GetLog()->AddException(&E); + } + SCPError(LoadStr(SCP_ILLEGAL_FILE_DESCRIPTOR), false); + } + + // last possibility to cancel transfer before it starts + if (OperationProgress->Cancel) + { + ThrowSkipFile(nullptr, MainInstructions(LoadStr(USER_TERMINATED))); + } + + bool Dir = (Ctrl == L'D'); + UnicodeString BaseFileName = FTerminal->GetBaseFileName(AbsoluteFileName); + if (!CopyParam->AllowTransfer(BaseFileName, osRemote, Dir, MaskParams)) + { + FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", + AbsoluteFileName.c_str())); + SkipConfirmed = true; + SCPError(L"", false); + } + + if (CopyParam->SkipTransfer(AbsoluteFileName, Dir)) + { + SkipConfirmed = true; + SCPError(L"", false); + OperationProgress->AddSkippedFileSize(MaskParams.Size); + } + + FTerminal->LogFileDetails(AFileName, SourceTimestamp, MaskParams.Size); + + UnicodeString DestFileNameOnly = + FTerminal->ChangeFileName( + CopyParam, OperationProgress->FileName, osRemote, + Level == 0); + UnicodeString DestFileName = + ::IncludeTrailingBackslash(TargetDir) + DestFileNameOnly; + + FileData.LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFileName)); + // If getting attrs fails, we suppose, that file/folder doesn't exists + FileData.Exists = (FileData.LocalFileAttrs != INVALID_FILE_ATTRIBUTES); + if (Dir) + { + if (FileData.Exists && !(FileData.LocalFileAttrs & faDirectory)) + { + SCPError(FMTLOAD(NOT_DIRECTORY_ERROR, DestFileName.c_str()), false); + } + + if (!FileData.Exists) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CREATE_DIR_ERROR, DestFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::ForceDirectories(ApiPath(DestFileName))); + }); + /* SCP: can we set the timestamp for directories ? */ + } + UnicodeString FullFileName = SourceDir + OperationProgress->FileName; + SCPSink(FullFileName, nullptr, DestFileName, core::UnixIncludeTrailingBackslash(FullFileName), + CopyParam, Success, OperationProgress, Params, Level + 1); + continue; + } + else if (Ctrl == L'C') + { + TDownloadSessionAction Action(FTerminal->GetActionLog()); + Action.SetFileName(AbsoluteFileName); + + try + { + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + std::unique_ptr FileStream; + + /* TODO 1 : Turn off read-only attr */ + + try__finally + { + SCOPE_EXIT + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + FileStream.reset(); + }; + try + { + if (::FileExists(ApiPath(DestFileName))) + { + int64_t MTime = 0; + TOverwriteFileParams FileParams; + FileParams.SourceSize = OperationProgress->TransferSize; + FileParams.SourceTimestamp = SourceTimestamp; + FTerminal->TerminalOpenLocalFile(DestFileName, GENERIC_READ, + nullptr, nullptr, nullptr, &MTime, nullptr, + &FileParams.DestSize); + FileParams.DestTimestamp = ::UnixToDateTime(MTime, + FTerminal->GetSessionData()->GetDSTMode()); + + uintptr_t Answer = + ConfirmOverwrite(OperationProgress->FileName, DestFileNameOnly, osLocal, + &FileParams, CopyParam, Params, OperationProgress); + + switch (Answer) + { + case qaCancel: + OperationProgress->Cancel = csCancel; // continue on next case + // FALLTHROUGH + case qaNo: + SkipConfirmed = true; + ThrowExtException(); + } + } + + Action.Destination(DestFileName); + + if (!FTerminal->TerminalCreateLocalFile(DestFileName, OperationProgress, + FLAGSET(Params, cpResume), FLAGSET(Params, cpNoConfirmation), + &LocalFileHandle)) + { + SkipConfirmed = true; + ThrowExtException(); + } + + FileStream.reset(new TSafeHandleStream(LocalFileHandle)); + } + catch (Exception & E) + { + // In this step we can still cancel transfer, so we do it + SCPError(E.Message, false); + throw; + } + + // We succeeded, so we confirm transfer to remote side + FSecureShell->SendNull(); + // From now we need to finish file transfer, if not it's fatal error + OperationProgress->TransferingFile = true; + + // Suppose same data size to transfer as to write + // (not true with ASCII transfer) + OperationProgress->SetLocalSize(OperationProgress->TransferSize); + + // Will we use ASCII of BINARY file transfer? + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osRemote, MaskParams)); + FTerminal->LogEvent(UnicodeString((OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary")) + + L" transfer mode selected."); + + try + { + // Buffer for one block of data + TFileBuffer BlockBuf; + bool ConvertToken = false; + + do + { + BlockBuf.SetSize(OperationProgress->TransferBlockSize()); + BlockBuf.SetPosition(0); + + FSecureShell->Receive(reinterpret_cast(BlockBuf.GetData()), static_cast(BlockBuf.GetSize())); + OperationProgress->AddTransfered(BlockBuf.GetSize()); + + if (OperationProgress->AsciiTransfer) + { + int64_t PrevBlockSize = BlockBuf.GetSize(); + BlockBuf.Convert(FTerminal->GetSessionData()->GetEOLType(), + FTerminal->GetConfiguration()->GetLocalEOLType(), 0, ConvertToken); + OperationProgress->SetLocalSize( + OperationProgress->LocalSize - PrevBlockSize + BlockBuf.GetSize()); + } + + // This is crucial, if it fails during file transfer, it's fatal error + FileOperationLoopCustom(FTerminal, OperationProgress, false, + FMTLOAD(WRITE_ERROR, DestFileName.c_str()), "", + [&]() + { + BlockBuf.WriteToStream(FileStream.get(), static_cast(BlockBuf.GetSize())); + }); + + OperationProgress->AddLocallyUsed(BlockBuf.GetSize()); + + if (OperationProgress->Cancel == csCancelTransfer) + { + throw Exception(MainInstructions(LoadStr(USER_TERMINATED))); + } + } + while (!OperationProgress->IsLocallyDone() || ! + OperationProgress->IsTransferDone()); + } + catch (Exception & E) + { + // Every exception during file transfer is fatal + FTerminal->FatalError(&E, + FMTLOAD(COPY_FATAL, OperationProgress->FileName.c_str())); + } + + OperationProgress->TransferingFile = false; + + try + { + SCPResponse(); + // If one of following exception occurs, we still need + // to send confirmation to other side + } + catch (EScp &) + { + FSecureShell->SendNull(); + throw; + } + catch (EFileSkipped &) + { + FSecureShell->SendNull(); + throw; + } + + FSecureShell->SendNull(); +#ifndef __linux__ + if (FileData.SetTime && CopyParam->GetPreserveTime()) + { + SetFileTime(LocalFileHandle, nullptr, &FileData.AcTime, &FileData.WrTime); + } +#endif + } + __finally + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + FileStream.reset(); + }; + } + catch (Exception & E) + { + if (SkipConfirmed) + { + Action.Cancel(); + } + else + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + throw; + } + + if (FileData.LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + FileData.LocalFileAttrs = faArchive; + } + DWORD NewAttrs = CopyParam->LocalFileAttrs(FileData.RemoteRights); + if ((NewAttrs & FileData.LocalFileAttrs) != NewAttrs) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DestFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(DestFileName), FileData.LocalFileAttrs | NewAttrs) == 0); + }); + } + + FTerminal->LogFileDone(OperationProgress); + } + } + } + catch (EFileSkipped & E) + { + if (!SkipConfirmed) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + TQueryParams QueryParams(qpAllowContinueOnError); + if (FTerminal->QueryUserException(FMTLOAD(COPY_ERROR, AbsoluteFileName.c_str()), + &E, qaOK | qaAbort, &QueryParams, qtError) == qaAbort) + { + OperationProgress->Cancel = csCancel; + } + FTerminal->GetLog()->AddException(&E); + } + // this was inside above condition, but then transfer was considered + // successful, even when for example user refused to overwrite file + Success = false; + } + catch (ESkipFile & E) + { + SCPSendError(E.Message, false); + Success = false; + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } +} + +void TSCPFileSystem::GetSupportedChecksumAlgs(TStrings * /*Algs*/) +{ + // NOOP +} + +void TSCPFileSystem::LockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TSCPFileSystem::UnlockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TSCPFileSystem::UpdateFromMain(TCustomFileSystem * /*MainFileSystem*/) +{ + // noop +} diff --git a/netbox/src/core/ScpFileSystem.h b/netbox/src/core/ScpFileSystem.h new file mode 100644 index 000000000..e65a1ef7f --- /dev/null +++ b/netbox/src/core/ScpFileSystem.h @@ -0,0 +1,144 @@ +#pragma once + +#include +#include + +class TCommandSet; +class TSecureShell; + +class TSCPFileSystem : public TCustomFileSystem +{ +NB_DISABLE_COPY(TSCPFileSystem) +public: + explicit TSCPFileSystem(TTerminal * ATerminal); + virtual ~TSCPFileSystem(); + + virtual void Init(void *); // TSecureShell * + virtual void FileTransferProgress(int64_t /*TransferSize*/, int64_t /*Bytes*/) {} + + virtual void Open(); + virtual void Close(); + virtual bool GetActive() const; + virtual void CollectUsage(); + virtual void Idle(); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const; + virtual void AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent); + virtual void ChangeDirectory(const UnicodeString & Directory); + virtual void CachedChangeDirectory(const UnicodeString & Directory); + virtual void AnnounceFileListOperation(); + virtual void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action); + virtual bool LoadFilesProperties(TStrings * AFileList); + virtual void CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum); + virtual void CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void RemoteCreateDirectory(const UnicodeString & ADirName); + virtual void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic); + virtual void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action); + virtual void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent); + virtual void DoStartup(); + virtual void HomeDirectory(); + virtual bool IsCapable(intptr_t Capability) const; + virtual void LookupUsersGroups(); + virtual void ReadCurrentDirectory(); + virtual void ReadDirectory(TRemoteFileList * FileList); + virtual void ReadFile(const UnicodeString & AFileName, + TRemoteFile *& File); + virtual void ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& File); + virtual void RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual void RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual TStrings * GetFixedPaths(); + virtual void SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable); + virtual const TSessionInfo & GetSessionInfo() const; + virtual const TFileSystemInfo & GetFileSystemInfo(bool Retrieve); + virtual bool TemporaryTransferFile(const UnicodeString & AFileName); + virtual bool GetStoredCredentialsTried() const; + virtual UnicodeString FSGetUserName() const; + virtual void GetSupportedChecksumAlgs(TStrings * Algs); + virtual void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UpdateFromMain(TCustomFileSystem * MainFileSystem); + +protected: + TStrings * GetOutput() const { return FOutput; } + intptr_t GetReturnCode() const { return FReturnCode; } + + virtual UnicodeString GetCurrDirectory() const; + +private: + TSecureShell * FSecureShell; + TCommandSet * FCommandSet; + TFileSystemInfo FFileSystemInfo; + UnicodeString FCurrentDirectory; + TStrings * FOutput; + intptr_t FReturnCode; + UnicodeString FCachedDirectoryChange; + bool FProcessingCommand; + int FLsFullTime; + TCaptureOutputEvent FOnCaptureOutput; + + void DetectUtf(); + void ClearAliases(); + void ClearAlias(const UnicodeString & Alias); + void CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& File, TRemoteFile * ALinkedByFile); + static UnicodeString DelimitStr(const UnicodeString & AStr); + void DetectReturnVar(); + bool IsLastLine(UnicodeString & Line); + static bool IsTotalListingLine(const UnicodeString & Line); + void EnsureLocation(); + void ExecCommand2(const UnicodeString & Cmd, intptr_t Params, + const UnicodeString & CmdString); + void ExecCommand(TFSCommand Cmd, intptr_t Params, ...); + void ReadCommandOutput(intptr_t Params, const UnicodeString * Cmd = nullptr); + void SCPResponse(bool * GotLastLine = nullptr); + void SCPDirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, intptr_t Level); + void SCPError(const UnicodeString & Message, bool Fatal); + void SCPSendError(const UnicodeString & Message, bool Fatal); + void SCPSink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const UnicodeString & SourceDir, + const TCopyParamType * CopyParam, bool & Success, + TFileOperationProgressType * OperationProgress, intptr_t Params, intptr_t Level); + void SCPSource(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, intptr_t Level); + void SendCommand(const UnicodeString & Cmd); + void SkipFirstLine(); + void SkipStartupMessage(); + void UnsetNationalVars(); + TRemoteFile * CreateRemoteFile(const UnicodeString & ListingStr, + TRemoteFile * LinkedByFile = nullptr); + void CaptureOutput(const UnicodeString & AddedLine, TCaptureOutputType OutputType); + void ChangeFileToken(const UnicodeString & DelimitedName, + const TRemoteToken & Token, TFSCommand Cmd, const UnicodeString & RecursiveStr); + uintptr_t ConfirmOverwrite( + const UnicodeString & ASourceFullFileName, const UnicodeString & ATargetFileName, + TOperationSide Side, + const TOverwriteFileParams * FileParams, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress); + + static bool RemoveLastLine(UnicodeString & Line, + intptr_t & ReturnCode, const UnicodeString & ALastLine = L""); +}; + diff --git a/netbox/src/core/SecureShell.cpp b/netbox/src/core/SecureShell.cpp new file mode 100644 index 000000000..292d40b9c --- /dev/null +++ b/netbox/src/core/SecureShell.cpp @@ -0,0 +1,2694 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#include "PuttyIntf.h" +#include "Interface.h" +#include "SecureShell.h" +#include "TextsCore.h" +#include "HelpCore.h" +#include "CoreMain.h" +#include + +#if !defined(AUTO_WINSOCK) && !defined(__linux__) +#include +#endif +//#include + +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY _IOR('t', 123, ULONG) +#define SIO_IDEAL_SEND_BACKLOG_CHANGE _IO('t', 122) +#endif + +#define MAX_BUFSIZE 128 * 1024 + +const wchar_t HostKeyDelimiter = L';'; + +struct TPuttyTranslation +{ + const wchar_t * Original; + int Translation; + UnicodeString HelpKeyword; +}; + +TSecureShell::TSecureShell(TSessionUI * UI, + TSessionData * SessionData, TSessionLog * Log, TConfiguration * Configuration) +{ + FUI = UI; + FSessionData = SessionData; + FLog = Log; + FConfiguration = Configuration; + FAuthenticating = false; + FAuthenticated = false; + FUtfStrings = false; + FActive = false; + FSessionInfoValid = false; + FBackend = nullptr; + FSshImplementation = sshiUnknown; + PendLen = 0; + PendSize = 0; + OutLen = 0; + OutPtr = nullptr; + Pending = nullptr; + FBackendHandle = nullptr; + ResetConnection(); + FOnCaptureOutput = nullptr; + FOnReceive = nullptr; + FSocket = INVALID_SOCKET; + FSocketEvent = ::CreateEvent(nullptr, false, false, nullptr); + FFrozen = false; + FDataWhileFrozen = false; + FSshVersion = 0; + FOpened = false; + FWaiting = 0; + FSimple = false; + FNoConnectionResponse = false; + FCollectPrivateKeyUsage = false; + FWaitingForData = 0; +} + +TSecureShell::~TSecureShell() +{ + DebugAssert(FWaiting == 0); + SetActive(false); + ResetConnection(); + ::CloseHandle(FSocketEvent); + FSocketEvent = nullptr; +} + +void TSecureShell::ResetConnection() +{ + FreeBackend(); + ClearStdError(); + PendLen = 0; + PendSize = 0; + sfree(Pending); + Pending = nullptr; + FCWriteTemp.Clear(); + ResetSessionInfo(); + FAuthenticating = false; + FAuthenticated = false; + FStoredPasswordTried = false; + FStoredPasswordTriedForKI = false; + FStoredPassphraseTried = false; +} + +void TSecureShell::ResetSessionInfo() +{ + FSessionInfoValid = false; + FMinPacketSize = nullptr; + FMaxPacketSize = nullptr; +} + +void TSecureShell::UpdateSessionInfo() const +{ + if (!FSessionInfoValid) + { + FSshVersion = get_ssh_version(FBackendHandle); + FSessionInfo.ProtocolBaseName = L"SSH"; + FSessionInfo.ProtocolName = + FORMAT(L"%s-%d", FSessionInfo.ProtocolBaseName.c_str(), get_ssh_version(FBackendHandle)); + FSessionInfo.SecurityProtocolName = FSessionInfo.ProtocolName; + + FSessionInfo.CSCompression = + FuncToCompression(FSshVersion, get_cscomp(FBackendHandle)); + FSessionInfo.SCCompression = + FuncToCompression(FSshVersion, get_sccomp(FBackendHandle)); + + if (FSshVersion == 1) + { + FSessionInfo.CSCipher = CipherNames[FuncToSsh1Cipher(get_cipher(FBackendHandle))]; + FSessionInfo.SCCipher = CipherNames[FuncToSsh1Cipher(get_cipher(FBackendHandle))]; + } + else + { + FSessionInfo.CSCipher = CipherNames[FuncToSsh2Cipher(get_cscipher(FBackendHandle))]; + FSessionInfo.SCCipher = CipherNames[FuncToSsh2Cipher(get_sccipher(FBackendHandle))]; + } + + FSessionInfoValid = true; + } +} + +const TSessionInfo & TSecureShell::GetSessionInfo() const +{ + if (!FSessionInfoValid) + { + UpdateSessionInfo(); + } + return FSessionInfo; +} + +UnicodeString TSecureShell::GetHostKeyFingerprint() const +{ + return FSessionInfo.HostKeyFingerprint; +} + +Conf * TSecureShell::StoreToConfig(TSessionData * Data, bool Simple) +{ + Conf * conf = conf_new(); + + DebugAssert((asOn == FORCE_ON) && (asOff == FORCE_OFF) && (asAuto == AUTO)); + +#define CONF_ssh_cipherlist_MAX CIPHER_MAX +#define CONF_DEF_INT_NONE(KEY) conf_set_int(conf, KEY, 0); +#define CONF_DEF_STR_NONE(KEY) conf_set_str(conf, KEY, ""); + // noop, used only for these and we set the first four explicitly below and latter two are not used in our code +#ifndef __linux__ +#define CONF_DEF_INT_INT(KEY) DebugAssert((KEY == CONF_ssh_cipherlist) || (KEY == CONF_ssh_kexlist) || (KEY == CONF_ssh_gsslist) || (KEY == CONF_ssh_hklist) || (KEY == CONF_colours) || (KEY == CONF_wordness)); +#else +#define CONF_DEF_INT_INT(KEY) DebugAssert((KEY == CONF_ssh_cipherlist) || (KEY == CONF_ssh_kexlist) || (KEY == CONF_ssh_gsslist) || (KEY == CONF_colours) || (KEY == CONF_wordness)); +#endif + // noop, used only for these three and they all can handle undef value +#define CONF_DEF_STR_STR(KEY) DebugAssert((KEY == CONF_ttymodes) || (KEY == CONF_portfwd) || (KEY == CONF_environmt) || (KEY == CONF_ssh_manual_hostkeys)); + // noop, not used in our code +#define CONF_DEF_FONT_NONE(KEY) DebugAssert((KEY == CONF_font) || (KEY == CONF_boldfont) || (KEY == CONF_widefont) || (KEY == CONF_wideboldfont)); +#define CONF_DEF_FILENAME_NONE(KEY) \ + { \ + Filename * filename = filename_from_str(""); \ + conf_set_filename(conf, KEY, filename); \ + filename_free(filename); \ + } +#define CONF_SET_DEFAULT(VALTYPE, KEYTYPE, KEYWORD) CONF_DEF_ ## VALTYPE ## _ ## KEYTYPE(CONF_ ## KEYWORD); + CONFIG_OPTIONS(CONF_SET_DEFAULT); //-V501 +#undef CONF_SET_DEFAULT +#undef CONF_DEF_FILENAME_NONE +#undef CONF_DEF_FONT_NONE +#undef CONF_DEF_STR_STR +#undef CONF_DEF_INT_INT +#undef CONF_DEF_STR_NONE +#undef CONF_DEF_INT_NONE + + // user-configurable settings + conf_set_str(conf, CONF_host, AnsiString(Data->GetHostNameExpanded()).c_str()); + conf_set_str(conf, CONF_username, UTF8String(Data->GetUserNameExpanded()).c_str()); + conf_set_int(conf, CONF_port, static_cast(Data->GetPortNumber())); + conf_set_int(conf, CONF_protocol, PROT_SSH); + // always set 0, as we will handle keepalives ourselves to avoid + // multi-threaded issues in putty timer list + conf_set_int(conf, CONF_ping_interval, 0); + conf_set_int(conf, CONF_compression, Data->GetCompression()); + conf_set_int(conf, CONF_tryagent, Data->GetTryAgent()); + conf_set_int(conf, CONF_agentfwd, Data->GetAgentFwd()); + conf_set_int(conf, CONF_addressfamily, Data->GetAddressFamily()); + conf_set_str(conf, CONF_ssh_rekey_data, AnsiString(Data->GetRekeyData()).c_str()); + conf_set_int(conf, CONF_ssh_rekey_time, static_cast(Data->GetRekeyTime())); + + DebugAssert(CIPHER_MAX == CIPHER_COUNT); + for (int c = 0; c < CIPHER_COUNT; c++) + { + int pcipher = 0; + switch (Data->GetCipher(c)) + { + case cipWarn: + pcipher = CIPHER_WARN; + break; + case cip3DES: + pcipher = CIPHER_3DES; + break; + case cipBlowfish: + pcipher = CIPHER_BLOWFISH; + break; + case cipAES: + pcipher = CIPHER_AES; + break; + case cipDES: + pcipher = CIPHER_DES; + break; + case cipArcfour: + pcipher = CIPHER_ARCFOUR; + break; +#ifndef __linux__ + case cipChaCha20: + pcipher = CIPHER_CHACHA20; + break; +#endif + default: + DebugFail(); + } + conf_set_int_int(conf, CONF_ssh_cipherlist, c, pcipher); + } + + DebugAssert(KEX_MAX == KEX_COUNT); + for (int k = 0; k < KEX_COUNT; k++) + { + int pkex = 0; + switch (Data->GetKex(k)) + { + case kexWarn: + pkex = KEX_WARN; + break; + case kexDHGroup1: + pkex = KEX_DHGROUP1; + break; + case kexDHGroup14: + pkex = KEX_DHGROUP14; + break; + case kexDHGEx: + pkex = KEX_DHGEX; + break; + case kexRSA: + pkex = KEX_RSA; + break; +#ifndef __linux__ + case kexECDH: + pkex = KEX_ECDH; + break; +#endif + default: + DebugFail(); + break; + } + conf_set_int_int(conf, CONF_ssh_kexlist, k, pkex); + } + + UnicodeString SPublicKeyFile = Data->GetPublicKeyFile(); + if (SPublicKeyFile.IsEmpty()) + { + SPublicKeyFile = GetConfiguration()->GetDefaultKeyFile(); + } + // StripPathQuotes should not be needed as we do not feed quotes anymore + SPublicKeyFile = StripPathQuotes(::ExpandEnvironmentVariables(SPublicKeyFile)); + Filename * KeyFileFileName = filename_from_str(UTF8String(SPublicKeyFile).c_str()); + conf_set_filename(conf, CONF_keyfile, KeyFileFileName); + filename_free(KeyFileFileName); + + conf_set_int(conf, CONF_sshprot, Data->GetSshProt()); + conf_set_int(conf, CONF_ssh2_des_cbc, Data->GetSsh2DES()); + conf_set_int(conf, CONF_ssh_no_userauth, Data->GetSshNoUserAuth()); + conf_set_int(conf, CONF_try_tis_auth, Data->GetAuthTIS()); + conf_set_int(conf, CONF_try_ki_auth, Data->GetAuthKI()); + conf_set_int(conf, CONF_try_gssapi_auth, Data->GetAuthGSSAPI()); + conf_set_int(conf, CONF_gssapifwd, Data->GetGSSAPIFwdTGT()); + conf_set_int(conf, CONF_change_username, Data->GetChangeUsername()); + + conf_set_int(conf, CONF_proxy_type, Data->GetActualProxyMethod()); + conf_set_str(conf, CONF_proxy_host, AnsiString(Data->GetProxyHost()).c_str()); + conf_set_int(conf, CONF_proxy_port, static_cast(Data->GetProxyPort())); + conf_set_str(conf, CONF_proxy_username, UTF8String(Data->GetProxyUsername()).c_str()); + conf_set_str(conf, CONF_proxy_password, UTF8String(Data->GetProxyPassword()).c_str()); + if (Data->GetProxyMethod() == pmCmd) + { + conf_set_str(conf, CONF_proxy_telnet_command, AnsiString(Data->GetProxyLocalCommand()).c_str()); + } + else + { + conf_set_str(conf, CONF_proxy_telnet_command, AnsiString(Data->GetProxyTelnetCommand()).c_str()); + } + conf_set_int(conf, CONF_proxy_dns, Data->GetProxyDNS()); + conf_set_int(conf, CONF_even_proxy_localhost, Data->GetProxyLocalhost()); + + conf_set_int(conf, CONF_sshbug_ignore1, Data->GetBug(sbIgnore1)); + conf_set_int(conf, CONF_sshbug_plainpw1, Data->GetBug(sbPlainPW1)); + conf_set_int(conf, CONF_sshbug_rsa1, Data->GetBug(sbRSA1)); + conf_set_int(conf, CONF_sshbug_hmac2, Data->GetBug(sbHMAC2)); + conf_set_int(conf, CONF_sshbug_derivekey2, Data->GetBug(sbDeriveKey2)); + conf_set_int(conf, CONF_sshbug_rsapad2, Data->GetBug(sbRSAPad2)); + conf_set_int(conf, CONF_sshbug_rekey2, Data->GetBug(sbRekey2)); + conf_set_int(conf, CONF_sshbug_pksessid2, Data->GetBug(sbPKSessID2)); + conf_set_int(conf, CONF_sshbug_maxpkt2, Data->GetBug(sbMaxPkt2)); + conf_set_int(conf, CONF_sshbug_ignore2, Data->GetBug(sbIgnore2)); + conf_set_int(conf, CONF_sshbug_winadj, Data->GetBug(sbWinAdj)); + conf_set_int(conf, CONF_sshbug_oldgex2, Data->GetBug(sbOldGex2)); + + if (!Data->GetTunnelPortFwd().IsEmpty()) + { + DebugAssert(!Simple); + UnicodeString TunnelPortFwd = Data->GetTunnelPortFwd(); + while (!TunnelPortFwd.IsEmpty()) + { + UnicodeString Buf = CutToChar(TunnelPortFwd, L',', true); + AnsiString Key = AnsiString(CutToChar(Buf, L'\t', true)); + AnsiString Value = AnsiString(Buf); + conf_set_str_str(conf, CONF_portfwd, Key.c_str(), Value.c_str()); + } + + // when setting up a tunnel, do not open shell/sftp + conf_set_int(conf, CONF_ssh_no_shell, TRUE); + } + else + { + DebugAssert(Simple); + conf_set_int(conf, CONF_ssh_simple, Data->GetSshSimple() && Simple); + + if (Data->GetFSProtocol() == fsSCPonly) + { + conf_set_int(conf, CONF_ssh_subsys, FALSE); + if (Data->GetShell().IsEmpty()) + { + // Following forces Putty to open default shell + // see ssh.c: do_ssh2_authconn() and ssh1_protocol() + conf_set_str(conf, CONF_remote_cmd, ""); + } + else + { + conf_set_str(conf, CONF_remote_cmd, AnsiString(Data->GetShell().c_str()).c_str()); + } + } + else + { + if (Data->GetSftpServer().IsEmpty()) + { + conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_str(conf, CONF_remote_cmd, "sftp"); + } + else + { + conf_set_int(conf, CONF_ssh_subsys, FALSE); + conf_set_str(conf, CONF_remote_cmd, AnsiString(Data->GetSftpServer().c_str()).c_str()); + } + + if (Data->GetFSProtocol() != fsSFTPonly) + { + conf_set_int(conf, CONF_ssh_subsys2, FALSE); + if (Data->GetShell().IsEmpty()) + { + // Following forces Putty to open default shell + // see ssh.c: do_ssh2_authconn() and ssh1_protocol() + conf_set_str(conf, CONF_remote_cmd2, ""); + // PuTTY ignores CONF_remote_cmd2 set to "", + // so we have to enforce it + // (CONF_force_remote_cmd2 is our config option) + conf_set_int(conf, CONF_force_remote_cmd2, 1); + } + else + { + conf_set_str(conf, CONF_remote_cmd2, AnsiString(Data->GetShell().c_str()).c_str()); + } + } + + if ((Data->GetFSProtocol() == fsSFTPonly) && Data->GetSftpServer().IsEmpty()) + { + // see psftp_connect() from psftp.c + conf_set_int(conf, CONF_ssh_subsys2, FALSE); + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + } + } + } + + conf_set_int(conf, CONF_connect_timeout, static_cast(Data->GetTimeout() * MSecsPerSec)); + conf_set_int(conf, CONF_sndbuf, static_cast(Data->GetSendBuf())); + + // permanent settings + conf_set_int(conf, CONF_nopty, TRUE); + conf_set_int(conf, CONF_tcp_keepalives, 1); + conf_set_int(conf, CONF_ssh_show_banner, TRUE); + for (int Index = 0; Index < ngsslibs; ++Index) + { + conf_set_int_int(conf, CONF_ssh_gsslist, Index, gsslibkeywords[Index].v); + } +#ifndef __linux__ + conf_set_int(conf, CONF_proxy_log_to_term, FORCE_OFF); + + conf_set_int_int(conf, CONF_ssh_hklist, 0, HK_ED25519); + conf_set_int_int(conf, CONF_ssh_hklist, 1, HK_ECDSA); + conf_set_int_int(conf, CONF_ssh_hklist, 2, HK_RSA); + conf_set_int_int(conf, CONF_ssh_hklist, 3, HK_DSA); + conf_set_int_int(conf, CONF_ssh_hklist, 4, HK_WARN); + DebugAssert(HK_MAX == 5); +#endif + + return conf; +} + +void TSecureShell::Open() +{ + FBackend = &ssh_backend; + ResetConnection(); + + FAuthenticating = false; + FAuthenticated = false; + FLastSendBufferUpdate = 0; + FSendBuf = 0; + + // do not use UTF-8 until decided otherwise (see TSCPFileSystem::DetectUtf()) + FUtfStrings = false; + + SetActive(false); + + FAuthenticationLog.Clear(); + FNoConnectionResponse = false; + FUI->Information(LoadStr(STATUS_LOOKUPHOST), true); + + try + { + char * RealHost = nullptr; + FreeBackend(); // in case we are reconnecting + const char * InitError = nullptr; + Conf * conf = StoreToConfig(FSessionData, GetSimple()); + FSendBuf = FSessionData->GetSendBuf(); + try__finally + { + SCOPE_EXIT + { + conf_free(conf); + }; + InitError = FBackend->init(this, &FBackendHandle, conf, + (char *)AnsiString(FSessionData->GetHostNameExpanded()).c_str(), + static_cast(FSessionData->GetPortNumber()), &RealHost, + FSessionData->GetTcpNoDelay() ? 1 : 0, + conf_get_int(conf, CONF_tcp_keepalives)); + } + __finally + { + conf_free(conf); + }; + + sfree(RealHost); + if (InitError) + { + PuttyFatalError(UnicodeString(InitError)); + } + FUI->Information(LoadStr(STATUS_CONNECT), true); + Init(); + + CheckConnection(CONNECTION_FAILED); + } + catch (Exception & E) + { + if (FNoConnectionResponse && TryFtp()) + { + // GetConfiguration()->Usage->Inc(L"ProtocolSuggestions"); + // HELP_FTP_SUGGESTION won't be used as all errors that set + // FNoConnectionResponse have already their own help keyword + FUI->FatalError(&E, LoadStr(FTP_SUGGESTION)); + } + else + { + throw; + } + } + FLastDataSent = Now(); + + FSessionInfo.LoginTime = Now(); + + FAuthenticating = false; + FAuthenticated = true; + FUI->Information(LoadStr(STATUS_AUTHENTICATED), true); + + ResetSessionInfo(); + + DebugAssert(!FSessionInfo.SshImplementation.IsEmpty()); + FOpened = true; + + UnicodeString SshImplementation = GetSessionInfo().SshImplementation; + if (// e.g. "OpenSSH_5.3" + (SshImplementation.Pos(L"OpenSSH") == 1) || + // Sun SSH is based on OpenSSH (suffers the same bugs) + (SshImplementation.Pos(L"Sun_SSH") == 1)) + { + FSshImplementation = sshiOpenSSH; + } + // e.g. "mod_sftp/0.9.8" + else if (SshImplementation.Pos(L"mod_sftp") == 1) + { + FSshImplementation = sshiProFTPD; + } + // e.g. "5.25 FlowSsh: Bitvise SSH Server (WinSSHD) 6.07: free only for personal non-commercial use" + else if (SshImplementation.Pos(L"FlowSsh") > 0) + { + FSshImplementation = sshiBitvise; + } + // e.g. "srtSSHServer_10.00" + else if (::ContainsText(SshImplementation, L"srtSSHServer")) + { + FSshImplementation = sshiTitan; + } + else if (::ContainsText(FSessionInfo.SshImplementation, L"OpenVMS")) + { + FSshImplementation = sshiOpenVMS; + } + else if (::ContainsText(FSessionInfo.SshImplementation, L"CerberusFTPServer")) + { + FSshImplementation = sshiCerberus; + } + else + { + FSshImplementation = sshiUnknown; + } +} + +bool TSecureShell::TryFtp() +{ + bool Result; + if (!FConfiguration->GetTryFtpWhenSshFails()) + { + Result = false; + } + else + { + if (((FSessionData->GetFSProtocol() != fsSFTP) && (FSessionData->GetFSProtocol() != fsSFTPonly)) || + (FSessionData->GetPortNumber() != SshPortNumber) || + FSessionData->GetTunnel() || (FSessionData->GetProxyMethod() != ::pmNone)) + { + LogEvent("Using non-standard protocol or port, tunnel or proxy, will not knock FTP port."); + Result = false; + } + else + { + LogEvent("Knocking FTP port."); +#ifndef __linux__ + SOCKET Socket = socket(AF_INET, SOCK_STREAM, 0); + Result = (Socket != INVALID_SOCKET); + if (Result) + { + LPHOSTENT HostEntry = gethostbyname(AnsiString(FSessionData->GetHostNameExpanded()).c_str()); + Result = (HostEntry != nullptr); + if (Result) + { + SOCKADDR_IN Address; + + ClearStruct(Address); + Address.sin_family = AF_INET; + intptr_t Port = FtpPortNumber; + Address.sin_port = htons(static_cast(Port)); + Address.sin_addr.s_addr = *(reinterpret_cast(*HostEntry->h_addr_list)); + + HANDLE Event = ::CreateEvent(nullptr, false, false, nullptr); + Result = (::WSAEventSelect(Socket, (WSAEVENT)Event, FD_CONNECT | FD_CLOSE) != SOCKET_ERROR); + + if (Result) + { + Result = + (connect(Socket, reinterpret_cast(&Address), sizeof(Address)) != SOCKET_ERROR) || + (::WSAGetLastError() == WSAEWOULDBLOCK); + if (Result) + { + Result = (::WaitForSingleObject(Event, 2000) == WAIT_OBJECT_0); + } + } + ::CloseHandle(Event); + } + closesocket(Socket); + } +#endif + if (Result) + { + LogEvent("FTP port opened, will suggest using FTP protocol."); + } + else + { + LogEvent("FTP port did not open."); + } + } + } + return Result; +} + +void TSecureShell::Init() +{ + try + { + try + { + // Recent pscp checks FBackend->exitcode(FBackendHandle) in the loop + // (see comment in putty revision 8110) + // It seems that we do not need to do it. + + while (!get_ssh_state_session(FBackendHandle)) + { + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent("Waiting for the server to continue with the initialization"); + } + WaitForData(); + } + + // unless this is tunnel session, it must be safe to send now + DebugAssert(FBackend->sendok(FBackendHandle) || !FSessionData->GetTunnelPortFwd().IsEmpty()); + } + catch (Exception & E) + { + if (FAuthenticating && !FAuthenticationLog.IsEmpty()) + { + FUI->FatalError(&E, FMTLOAD(AUTHENTICATION_LOG, FAuthenticationLog.c_str())); + } + else + { + throw; + } + } + } + catch (Exception & E) + { + if (FAuthenticating) + { + FUI->FatalError(&E, LoadStr(AUTHENTICATION_FAILED)); + } + else + { + throw; + } + } +} + +UnicodeString TSecureShell::ConvertFromPutty(const char * Str, size_t Length) const +{ + size_t BomLength = strlen(MPEXT_BOM); + if ((Length >= BomLength) && + (strncmp(Str, MPEXT_BOM, BomLength) == 0)) + { + return UTF8ToString(Str + BomLength, Length - BomLength); + } + else + { + return AnsiToString(Str, Length); + } +} + +void TSecureShell::PuttyLogEvent(const char * AStr) +{ + UnicodeString Str = ConvertFromPutty(AStr, strlen(AStr)); +#define SERVER_VERSION_MSG L"Server version: " + // Gross hack + if (Str.Pos(SERVER_VERSION_MSG) == 1) + { + FSessionInfo.SshVersionString = Str.SubString(wcslen(SERVER_VERSION_MSG) + 1, + Str.Length() - wcslen(SERVER_VERSION_MSG)); + + const wchar_t * Ptr = wcschr(FSessionInfo.SshVersionString.c_str(), L'-'); + if (Ptr != nullptr) + { + Ptr = wcschr(Ptr + 1, L'-'); + } + FSessionInfo.SshImplementation = (Ptr != nullptr) ? Ptr + 1 : L""; + } +#define FORWARDING_FAILURE_MSG L"Forwarded connection refused by server: " + else if (Str.Pos(FORWARDING_FAILURE_MSG) == 1) + { + FLastTunnelError = Str.SubString(wcslen(FORWARDING_FAILURE_MSG) + 1, + Str.Length() - wcslen(FORWARDING_FAILURE_MSG)); + + static const TPuttyTranslation Translation[] = + { + { L"Administratively prohibited [%]", PFWD_TRANSL_ADMIN }, + { L"Connect failed [%]", PFWD_TRANSL_CONNECT }, + }; + TranslatePuttyMessage(Translation, _countof(Translation), FLastTunnelError); + } + LogEvent(Str); +} + +TPromptKind TSecureShell::IdentifyPromptKind(UnicodeString & AName) +{ + // beware of changing order + static const TPuttyTranslation NameTranslation[] = + { + { L"SSH login name", USERNAME_TITLE }, + { L"SSH key passphrase", PASSPHRASE_TITLE }, + { L"SSH TIS authentication", SERVER_PROMPT_TITLE }, + { L"SSH CryptoCard authentication", SERVER_PROMPT_TITLE }, + { L"SSH server: %", SERVER_PROMPT_TITLE2 }, + { L"SSH server authentication", SERVER_PROMPT_TITLE }, + { L"SSH password", PASSWORD_TITLE }, + { L"New SSH password", NEW_PASSWORD_TITLE }, + }; + + int Index = TranslatePuttyMessage(NameTranslation, _countof(NameTranslation), AName); + + TPromptKind PromptKind; + if (Index == 0) // username + { + PromptKind = pkUserName; + } + else if (Index == 1) // passphrase + { + PromptKind = pkPassphrase; + } + else if (Index == 2) // TIS + { + PromptKind = pkTIS; + } + else if (Index == 3) // CryptoCard + { + PromptKind = pkCryptoCard; + } + else if ((Index == 4) || (Index == 5)) + { + PromptKind = pkKeybInteractive; + } + else if (Index == 6) + { + PromptKind = pkPassword; + } + else if (Index == 7) + { + PromptKind = pkNewPassword; + } + else + { + PromptKind = pkPrompt; + DebugFail(); + } + + return PromptKind; +} + +bool TSecureShell::PromptUser(bool /*ToServer*/, + const UnicodeString & AName, bool /*NameRequired*/, + const UnicodeString & AInstructions, bool InstructionsRequired, + TStrings * Prompts, TStrings * Results) +{ + // there can be zero prompts! + + DebugAssert(Results->GetCount() == Prompts->GetCount()); + + UnicodeString Name = AName; + TPromptKind PromptKind = IdentifyPromptKind(Name); + + const TPuttyTranslation * InstructionTranslation = nullptr; + const TPuttyTranslation * PromptTranslation = nullptr; + size_t PromptTranslationCount = 1; + UnicodeString PromptDesc; + + if (PromptKind == pkUserName) + { + static const TPuttyTranslation UsernamePromptTranslation[] = + { + { L"login as: ", USERNAME_PROMPT2 }, + }; + + PromptTranslation = UsernamePromptTranslation; + PromptDesc = L"username"; + } + else if (PromptKind == pkPassphrase) + { + static const TPuttyTranslation PassphrasePromptTranslation[] = + { + { L"Passphrase for key \"%\": ", PROMPT_KEY_PASSPHRASE }, + }; + + PromptTranslation = PassphrasePromptTranslation; + PromptDesc = L"passphrase"; + } + else if (PromptKind == pkTIS) + { + static const TPuttyTranslation TISInstructionTranslation[] = + { + { L"Using TIS authentication.%", TIS_INSTRUCTION }, + }; + + static const TPuttyTranslation TISPromptTranslation[] = + { + { L"Response: ", PROMPT_PROMPT }, + }; + + InstructionTranslation = TISInstructionTranslation; + PromptTranslation = TISPromptTranslation; + PromptDesc = L"tis"; + } + else if (PromptKind == pkCryptoCard) + { + static const TPuttyTranslation CryptoCardInstructionTranslation[] = + { + { L"Using CryptoCard authentication.%", CRYPTOCARD_INSTRUCTION }, + }; + + static const TPuttyTranslation CryptoCardPromptTranslation[] = + { + { L"Response: ", PROMPT_PROMPT }, + }; + + InstructionTranslation = CryptoCardInstructionTranslation; + PromptTranslation = CryptoCardPromptTranslation; + PromptDesc = L"cryptocard"; + } + else if (PromptKind == pkKeybInteractive) + { + static const TPuttyTranslation KeybInteractiveInstructionTranslation[] = + { + { L"Using keyboard-interactive authentication.%", KEYBINTER_INSTRUCTION }, + }; + + static const TPuttyTranslation KeybInteractivePromptTranslation[] = + { + // as used by Linux-PAM (pam_exec/pam_exec.c, libpam/pam_get_authtok.c, + // pam_unix/pam_unix_auth.c, pam_userdb/pam_userdb.c) + { L"Password: ", PASSWORD_PROMPT }, + }; + + InstructionTranslation = KeybInteractiveInstructionTranslation; + PromptTranslation = KeybInteractivePromptTranslation; + PromptDesc = L"keyboard interactive"; + } + else if (PromptKind == pkPassword) + { + DebugAssert(Prompts->GetCount() == 1); + Prompts->SetString(0, LoadStr(PASSWORD_PROMPT)); + PromptDesc = L"password"; + } + else if (PromptKind == pkNewPassword) + { + // Can be tested with WS_FTP server + static const TPuttyTranslation NewPasswordPromptTranslation[] = + { + { L"Current password (blank for previously entered password): ", NEW_PASSWORD_CURRENT_PROMPT }, + { L"Enter new password: ", NEW_PASSWORD_NEW_PROMPT }, + { L"Confirm new password: ", NEW_PASSWORD_CONFIRM_PROMPT }, + }; + + PromptTranslation = NewPasswordPromptTranslation; + PromptTranslationCount = _countof(NewPasswordPromptTranslation); + PromptDesc = L"new password"; + } + else + { + PromptDesc = L"unknown"; + DebugFail(); + } + + UnicodeString InstructionsLog = + (AInstructions.IsEmpty() ? UnicodeString(L"") : FORMAT(L"\"%s\"", AInstructions.c_str())); + UnicodeString PromptsLog = + (Prompts->GetCount() > 0 ? FORMAT(L"\"%s\"", Prompts->GetString(0).c_str()) : UnicodeString(L"")) + + (Prompts->GetCount() > 1 ? FORMAT(L"%d more", Prompts->GetCount() - 1) : UnicodeString()); + LogEvent(FORMAT(L"Prompt (%s, \"%s\", %s, %s)", PromptDesc.c_str(), AName.c_str(), InstructionsLog.c_str(), PromptsLog.c_str())); + + Name = Name.Trim(); + + UnicodeString Instructions = ::ReplaceStrAll(AInstructions, L"\x0D\x0A", L"\x01"); + Instructions = ::ReplaceStrAll(Instructions, L"\x0A\x0D", L"\x01"); + Instructions = ::ReplaceStrAll(Instructions, L"\x0A", L"\x01"); + Instructions = ::ReplaceStrAll(Instructions, L"\x0D", L"\x01"); + Instructions = ::ReplaceStrAll(Instructions, L"\x01", L"\x0D\x0A"); + if (InstructionTranslation != nullptr) + { + TranslatePuttyMessage(InstructionTranslation, 1, Instructions); + } + + // some servers add leading blank line to make the prompt look prettier + // on terminal console + Instructions = Instructions.Trim(); + + for (intptr_t Index2 = 0; Index2 < Prompts->GetCount(); ++Index2) + { + UnicodeString Prompt = Prompts->GetString(Index2); + if (PromptTranslation != nullptr) + { + TranslatePuttyMessage(PromptTranslation, PromptTranslationCount, Prompt); + } + // some servers add leading blank line to make the prompt look prettier + // on terminal console + Prompts->SetString(Index2, Prompt.Trim()); + } + + bool Result = false; + if ((PromptKind == pkTIS) || (PromptKind == pkCryptoCard) || + (PromptKind == pkKeybInteractive)) + { + if (FSessionData->GetAuthKIPassword() && !FSessionData->GetPassword().IsEmpty() && + !FStoredPasswordTriedForKI && (Prompts->GetCount() == 1) && + FLAGCLEAR(reinterpret_cast(Prompts->GetObj(0)), pupEcho)) + { + LogEvent("Using stored password."); + FUI->Information(LoadStr(AUTH_PASSWORD), false); + Result = true; + Results->SetString(0, FSessionData->GetPassword()); + FStoredPasswordTriedForKI = true; + } + else if (Instructions.IsEmpty() && !InstructionsRequired && (Prompts->GetCount() == 0)) + { + LogEvent("Ignoring empty SSH server authentication request"); + Result = true; + } + } + else if (PromptKind == pkPassword) + { + if (!FSessionData->GetPassword().IsEmpty() && !FStoredPasswordTried) + { + LogEvent("Using stored password."); + FUI->Information(LoadStr(AUTH_PASSWORD), false); + Result = true; + Results->SetString(0, FSessionData->GetPassword()); + FStoredPasswordTried = true; + } + } + else if (PromptKind == pkPassphrase) + { + if (!FSessionData->GetPassphrase().IsEmpty() && !FStoredPassphraseTried) + { + LogEvent("Using configured passphrase."); + Result = true; + Results->SetString(0, FSessionData->GetPassphrase()); + FStoredPassphraseTried = true; + } + } + + if (!Result) + { + Result = FUI->PromptUser(FSessionData, + PromptKind, Name, Instructions, Prompts, Results); + + if (Result) + { + if ((Prompts->GetCount() >= 1) && + (FLAGSET(reinterpret_cast(Prompts->GetObj(0)), pupEcho) || GetConfiguration()->GetLogSensitive())) + { + LogEvent(FORMAT(L"Response: \"%s\"", Results->GetString(0).c_str())); + } + + if ((PromptKind == pkUserName) && (Prompts->GetCount() == 1)) + { + FUserName = Results->GetString(0); + } + } + } + + return Result; +} + +void TSecureShell::GotHostKey() +{ + // due to re-key GotHostKey() may be called again later during session + if (!FAuthenticating && !FAuthenticated) + { + FAuthenticating = true; + FUI->Information(LoadStr(STATUS_AUTHENTICATE), true); + } +} + +void TSecureShell::CWrite(const char * Data, intptr_t Length) +{ + // some messages to stderr may indicate that something has changed with the + // session, so reset the session info + ResetSessionInfo(); + + // We send only whole line at once, so we have to cache incoming data + FCWriteTemp += DeleteChar(ConvertFromPutty(Data, Length), L'\r'); + + // Do we have at least one complete line in std error cache? + while (FCWriteTemp.Pos(L"\n") > 0) + { + UnicodeString Line = CutToChar(FCWriteTemp, L'\n', false); + + FLog->Add(llStdError, Line); + + if (FAuthenticating) + { + TranslateAuthenticationMessage(Line); + FAuthenticationLog += (FAuthenticationLog.IsEmpty() ? L"" : L"\n") + Line; + } + + FUI->Information(Line, false); + } +} + +void TSecureShell::RegisterReceiveHandler(TNotifyEvent Handler) +{ + DebugAssert(FOnReceive == nullptr); + FOnReceive = Handler; +} + +void TSecureShell::UnregisterReceiveHandler(TNotifyEvent Handler) +{ + DebugAssert(FOnReceive == Handler); + DebugUsedParam(Handler); + FOnReceive = nullptr; +} + +void TSecureShell::FromBackend(bool IsStdErr, const uint8_t * Data, intptr_t Length) +{ + // Note that we do not apply ConvertFromPutty to Data yet (as opposite to CWrite). + // as there's no use for this atm. + CheckConnection(); + + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"Received %u bytes (%d)", Length, static_cast(IsStdErr))); + } + + // Following is taken from scp.c from_backend() and modified + + if (IsStdErr) + { + AddStdError(ConvertInput(RawByteString(reinterpret_cast(Data), Length), FSessionData->GetCodePageAsNumber())); + } + else + { + const uint8_t * p = Data; + intptr_t Len = Length; + + // with event-select mechanism we can now receive data even before we + // actually expect them (OutPtr can be nullptr) + + if ((OutPtr != nullptr) && (OutLen > 0) && (Len > 0)) + { + intptr_t Used = OutLen; + if (Used > Len) + { + Used = Len; + } + memmove(OutPtr, p, Used); + OutPtr += Used; OutLen -= Used; + p += Used; Len -= Used; + } + + if (Len > 0) + { + if (PendSize < PendLen + Len) + { + PendSize = PendLen + Len + 4096; + Pending = static_cast + (Pending ? srealloc(Pending, PendSize) : smalloc(PendSize)); + if (!Pending) + { + FatalError(L"Out of memory"); + } + } + memmove(Pending + PendLen, p, Len); + PendLen += Len; + } + + if (FOnReceive != nullptr) + { + if (!FFrozen) + { + FFrozen = true; + try__finally + { + SCOPE_EXIT + { + FFrozen = false; + }; + do + { + FDataWhileFrozen = false; + FOnReceive(nullptr); + } + while (FDataWhileFrozen); + } + __finally + { + FFrozen = false; + }; + } + else + { + FDataWhileFrozen = true; + } + } + } +} + +bool TSecureShell::Peek(uint8_t *& Buf, intptr_t Length) const +{ + bool Result = (PendLen >= Length); + + if (Result) + { + Buf = Pending; + } + + return Result; +} + +intptr_t TSecureShell::Receive(uint8_t * Buf, intptr_t Length) +{ + CheckConnection(); + + if (Length > 0) + { + // Following is taken from scp.c ssh_scp_recv() and modified + + OutPtr = Buf; + OutLen = Length; + + try__finally + { + SCOPE_EXIT + { + OutPtr = nullptr; + }; + /* + * See if the pending-input block contains some of what we + * need. + */ + if (PendLen > 0) + { + intptr_t PendUsed = PendLen; + if (PendUsed > OutLen) + { + PendUsed = OutLen; + } + memmove(OutPtr, Pending, PendUsed); //-V575 + memmove(Pending, Pending + PendUsed, PendLen - PendUsed); + OutPtr += PendUsed; + OutLen -= PendUsed; + PendLen -= PendUsed; + if (PendLen == 0) + { + PendSize = 0; + sfree(Pending); + Pending = nullptr; + } + } + + while (OutLen > 0) + { + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"Waiting for another %u bytes", static_cast(OutLen))); + } + WaitForData(); + } + + // This seems ambiguous +// if (Length <= 0) +// { +// FatalError(LoadStr(LOST_CONNECTION)); +// } + } + __finally + { + OutPtr = nullptr; + }; + } + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"Read %d bytes (%d pending)", + static_cast(Length), static_cast(PendLen))); + } + return Length; +} + +UnicodeString TSecureShell::ReceiveLine() +{ + intptr_t Index = 0; + RawByteString Line; + Boolean EOL = False; + + do + { + // If there is any buffer of received chars + if (PendLen > 0) + { + Index = 0; + // Repeat until we walk thru whole buffer or reach end-of-line + while ((Index < PendLen) && (!Index || (Pending[Index - 1] != '\n'))) + { + ++Index; + } + EOL = static_cast(Index && (Pending[Index - 1] == '\n')); + intptr_t PrevLen = Line.Length(); + Line.SetLength(PrevLen + Index); + Receive(reinterpret_cast(const_cast(Line.c_str()) + PrevLen), Index); + } + + // If buffer don't contain end-of-line character + // we read one more which causes receiving new buffer of chars + if (!EOL) + { + uint8_t Ch; + Receive(&Ch, 1); + Line += Ch; + EOL = (Ch == '\n'); + } + } + while (!EOL); + + // We don't want end-of-line character + Line.SetLength(Line.Length() - 1); + +// UnicodeString Result = ::TrimRight(::MB2W(Line.c_str(), (UINT)FSessionData->GetCodePageAsNumber())); + UnicodeString Result = ConvertInput(Line, FSessionData->GetCodePageAsNumber()); + CaptureOutput(llOutput, Result); + + return Result; +} + +UnicodeString TSecureShell::ConvertInput(const RawByteString & Input, uintptr_t CodePage) const +{ + UnicodeString Result; + if (GetUtfStrings()) + { + Result = UnicodeString(UTF8String(Input.c_str())); + } + else + { +// Result = UnicodeString(AnsiString(Input.c_str())); + Result = ::MB2W(Input.c_str(), static_cast(CodePage)); + } + return Result; +} + +void TSecureShell::SendSpecial(int Code) +{ + LogEvent(FORMAT(L"Sending special code: %d", Code)); + CheckConnection(); + FBackend->special(FBackendHandle, static_cast(Code)); + CheckConnection(); + FLastDataSent = Now(); +} + +void TSecureShell::SendEOF() +{ + SendSpecial(TS_EOF); +} + +uintptr_t TSecureShell::TimeoutPrompt(TQueryParamsTimerEvent PoolEvent) +{ + FWaiting++; + + uintptr_t Answer = 0; + try__finally + { + SCOPE_EXIT + { + FWaiting--; + }; + TQueryParams Params(qpFatalAbort | qpAllowContinueOnError | qpIgnoreAbort); + Params.HelpKeyword = HELP_MESSAGE_HOST_IS_NOT_COMMUNICATING; + Params.Timer = 500; + Params.TimerEvent = PoolEvent; + Params.TimerMessage = MainInstructionsFirstParagraph(FMTLOAD(TIMEOUT_STILL_WAITING3, FSessionData->GetTimeout())); + Params.TimerAnswers = qaAbort; + Params.TimerQueryType = qtInformation; + if (FConfiguration->GetSessionReopenAutoStall() > 0) + { + Params.Timeout = FConfiguration->GetSessionReopenAutoStall(); + Params.TimeoutAnswer = qaAbort; + } + Answer = FUI->QueryUser(MainInstructions(FMTLOAD(CONFIRM_PROLONG_TIMEOUT3, FSessionData->GetTimeout(), FSessionData->GetTimeout())), + nullptr, qaRetry | qaAbort, &Params); + } + __finally + { + FWaiting--; + }; + return Answer; +} + +void TSecureShell::SendBuffer(intptr_t & Result) +{ + // for comments see PoolForData + if (!GetActive()) + { + Result = qaRetry; + } + else + { + try + { + if (FBackend->sendbuffer(FBackendHandle) <= MAX_BUFSIZE) + { + Result = qaOK; + } + } + catch (...) + { + Result = qaRetry; + } + } +} + +void TSecureShell::DispatchSendBuffer(intptr_t BufSize) +{ + TDateTime Start = Now(); + do + { + CheckConnection(); + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"There are %u bytes remaining in the send buffer, " + L"need to send at least another %u bytes", + BufSize, BufSize - MAX_BUFSIZE)); + } + EventSelectLoop(100, false, nullptr); + BufSize = FBackend->sendbuffer(FBackendHandle); + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"There are %u bytes remaining in the send buffer", BufSize)); + } + + if (Now() - Start > FSessionData->GetTimeoutDT()) + { + LogEvent("Waiting for dispatching send buffer timed out, asking user what to do."); + uintptr_t Answer = TimeoutPrompt(MAKE_CALLBACK(TSecureShell::SendBuffer, this)); + switch (Answer) + { + case qaRetry: + Start = Now(); + break; + + case qaOK: + BufSize = 0; + break; + + default: + DebugFail(); + // fallthru + + case qaAbort: + FatalError(MainInstructions(LoadStr(USER_TERMINATED))); + break; + } + } + } + while (BufSize > MAX_BUFSIZE); +} + +void TSecureShell::Send(const uint8_t * Buf, intptr_t Length) +{ + CheckConnection(); + int BufSize = FBackend->send(FBackendHandle, const_cast(reinterpret_cast(Buf)), static_cast(Length)); + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent(FORMAT(L"Sent %d bytes", static_cast(Length))); + LogEvent(FORMAT(L"There are %u bytes remaining in the send buffer", BufSize)); + } + FLastDataSent = Now(); + // among other forces receive of pending data to free the servers's send buffer + EventSelectLoop(0, false, nullptr); + + if (BufSize > MAX_BUFSIZE) + { + DispatchSendBuffer(BufSize); + } + CheckConnection(); +} + +void TSecureShell::SendNull() +{ + LogEvent("Sending nullptr."); + uint8_t Null = 0; + Send(&Null, 1); +} + +void TSecureShell::SendLine(const UnicodeString & Line) +{ + CheckConnection(); + RawByteString Buf; + if (GetUtfStrings()) + { + Buf = RawByteString(UTF8String(Line)); + } + else + { + Buf = RawByteString(AnsiString(::W2MB(Line.c_str(), static_cast(FSessionData->GetCodePageAsNumber())))); + } + Buf += "\n"; + + // FLog->Add(llInput, Line); + Send(reinterpret_cast(Buf.c_str()), Buf.Length()); +} + +int TSecureShell::TranslatePuttyMessage( + const TPuttyTranslation * Translation, intptr_t Count, UnicodeString & Message, + UnicodeString * HelpKeyword) const +{ + int Result = -1; + for (intptr_t Index = 0; Index < Count; ++Index) + { + const wchar_t * Original = Translation[Index].Original; + const wchar_t * Div = wcschr(Original, L'%'); + if (Div == nullptr) + { + if (wcscmp(Message.c_str(), Original) == 0) + { + Message = LoadStr(Translation[Index].Translation); + Result = static_cast(Index); + break; + } + } + else + { + size_t OriginalLen = wcslen(Original); + size_t PrefixLen = Div - Original; + size_t SuffixLen = OriginalLen - PrefixLen - 1; + if ((static_cast(Message.Length()) >= OriginalLen - 1) && + (wcsncmp(Message.c_str(), Original, PrefixLen) == 0) && + (wcsncmp(Message.c_str() + Message.Length() - SuffixLen, Div + 1, SuffixLen) == 0)) + { + Message = FMTLOAD(Translation[Index].Translation, + Message.SubString(PrefixLen + 1, Message.Length() - PrefixLen - SuffixLen).TrimRight().c_str()); + Result = static_cast(Index); + break; + } + } + } + + if ((HelpKeyword != nullptr) && (Result >= 0)) + { + *HelpKeyword = Translation[Result].HelpKeyword; + } + + return Result; +} + +int TSecureShell::TranslateAuthenticationMessage( + UnicodeString & Message, UnicodeString * HelpKeyword) +{ + static const TPuttyTranslation Translation[] = + { + { L"Using username \"%\".", AUTH_TRANSL_USERNAME }, + { L"Using keyboard-interactive authentication.", AUTH_TRANSL_KEYB_INTER }, // not used anymore + { L"Authenticating with public key \"%\" from agent", AUTH_TRANSL_PUBLIC_KEY_AGENT }, + { L"Authenticating with public key \"%\"", AUTH_TRANSL_PUBLIC_KEY }, + { L"Authenticated using RSA key \"%\" from agent", AUTH_TRANSL_PUBLIC_KEY_AGENT }, + { L"Wrong passphrase", AUTH_TRANSL_WRONG_PASSPHRASE }, + { L"Wrong passphrase.", AUTH_TRANSL_WRONG_PASSPHRASE }, + { L"Access denied", AUTH_TRANSL_ACCESS_DENIED }, + { L"Trying public key authentication.", AUTH_TRANSL_TRY_PUBLIC_KEY }, + { L"Server refused our public key.", AUTH_TRANSL_KEY_REFUSED }, + { L"Server refused our key", AUTH_TRANSL_KEY_REFUSED } + }; + + int Result = TranslatePuttyMessage(Translation, _countof(Translation), Message, HelpKeyword); + + if ((Result == 2) || (Result == 3) || (Result == 4)) + { + // GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsPrivateKey2"); + FCollectPrivateKeyUsage = true; + } + + return Result; +} + +void TSecureShell::AddStdError(const UnicodeString & AStr) +{ + UnicodeString Str; + FStdError += AStr; + + intptr_t P = 0; + Str = DeleteChar(AStr, L'\r'); + // We send only whole line at once to log, so we have to cache + // incoming std error data + FStdErrorTemp += Str; + UnicodeString Line; + // Do we have at least one complete line in std error cache? + while ((P = FStdErrorTemp.Pos(L"\n")) > 0) + { + Line = FStdErrorTemp.SubString(1, P - 1); + FStdErrorTemp.Delete(1, P); + AddStdErrorLine(Line); + } +} + +void TSecureShell::AddStdErrorLine(const UnicodeString & AStr) +{ + if (FAuthenticating) + { + FAuthenticationLog += (FAuthenticationLog.IsEmpty() ? L"" : L"\n") + AStr; + } + if (!AStr.Trim().IsEmpty()) + { + CaptureOutput(llStdError, AStr); + } +} + +const UnicodeString & TSecureShell::GetStdError() const +{ + return FStdError; +} + +void TSecureShell::ClearStdError() +{ + // Flush std error cache + if (!FStdErrorTemp.IsEmpty()) + { + if (FAuthenticating) + { + FAuthenticationLog += + (FAuthenticationLog.IsEmpty() ? L"" : L"\n") + FStdErrorTemp; + } + CaptureOutput(llStdError, FStdErrorTemp); + FStdErrorTemp.Clear(); + } + FStdError.Clear(); +} + +void TSecureShell::CaptureOutput(TLogLineType Type, + const UnicodeString & Line) +{ + if (FOnCaptureOutput != nullptr) + { + FOnCaptureOutput(Line, (Type == llStdError) ? cotError : cotOutput); + } + FLog->Add(Type, Line); +} + +int TSecureShell::TranslateErrorMessage( + UnicodeString & Message, UnicodeString * HelpKeyword) +{ + static const TPuttyTranslation Translation[] = + { + { L"Server unexpectedly closed network connection", UNEXPECTED_CLOSE_ERROR, HELP_UNEXPECTED_CLOSE_ERROR }, + { L"Network error: Connection refused", NET_TRANSL_REFUSED2, HELP_NET_TRANSL_REFUSED }, + { L"Network error: Connection reset by peer", NET_TRANSL_RESET, HELP_NET_TRANSL_RESET }, + { L"Network error: Connection timed out", NET_TRANSL_TIMEOUT2, HELP_NET_TRANSL_TIMEOUT }, + { L"Network error: No route to host", NET_TRANSL_NO_ROUTE2, HELP_NET_TRANSL_NO_ROUTE }, + { L"Network error: Software caused connection abort", NET_TRANSL_CONN_ABORTED, HELP_NET_TRANSL_CONN_ABORTED }, + { L"Host does not exist", NET_TRANSL_HOST_NOT_EXIST2, HELP_NET_TRANSL_HOST_NOT_EXIST }, + { L"Incoming packet was garbled on decryption", NET_TRANSL_PACKET_GARBLED, HELP_NET_TRANSL_PACKET_GARBLED }, + }; + + int Index = TranslatePuttyMessage(Translation, _countof(Translation), Message, HelpKeyword); + + if ((Index == 0) || (Index == 1) || (Index == 2) || (Index == 3)) + { + FNoConnectionResponse = true; + } + + Message = ReplaceStr(Message, L"%HOST%", FSessionData->GetHostNameExpanded()); + + return Index; +} + +void TSecureShell::PuttyFatalError(const UnicodeString & Error) +{ + UnicodeString Error2 = Error; + UnicodeString HelpKeyword; + TranslateErrorMessage(Error2, &HelpKeyword); + + FatalError(Error2, HelpKeyword); +} + +void TSecureShell::FatalError(const UnicodeString & Error, const UnicodeString & HelpKeyword) +{ + FUI->FatalError(nullptr, Error, HelpKeyword); +} + +void TSecureShell::LogEvent(const UnicodeString & AStr) +{ + if (FLog->GetLogging()) + { + FLog->Add(llMessage, AStr); + } +} + +void TSecureShell::SocketEventSelect(SOCKET Socket, HANDLE Event, bool Startup) +{ +#ifndef __linux__ + int Events; + + if (Startup) + { + Events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); + } + else + { + Events = 0; + } + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Selecting events %d for socket %d", static_cast(Events), static_cast(Socket))); + } + + if (::WSAEventSelect(Socket, (WSAEVENT)Event, Events) == SOCKET_ERROR) + { + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Error selecting events %d for socket %d", static_cast(Events), static_cast(Socket))); + } + + if (Startup) + { + FatalError(FMTLOAD(EVENT_SELECT_ERROR, ::WSAGetLastError())); + } + } +#endif +} + +void TSecureShell::UpdateSocket(SOCKET Value, bool Startup) +{ + if (!FActive && !Startup) + { + // no-op + // Remove the branch eventually: + // When TCP connection fails, PuTTY does not release the memory allocated for + // socket. As a simple hack we call sk_tcp_close() in ssh.c to release the memory, + // until they fix it better. Unfortunately sk_tcp_close calls do_select, + // so we must filter that out. + } + else + { + DebugAssert(Value); + DebugAssert((FActive && (FSocket == Value)) || (!FActive && Startup)); + + // filter our "local proxy" connection, which have no socket + if (Value != INVALID_SOCKET) + { + SocketEventSelect(Value, FSocketEvent, Startup); + } + else + { + DebugAssert(FSessionData->GetProxyMethod() == pmCmd); + } + + if (Startup) + { + FSocket = Value; + FActive = true; + } + else + { + FSocket = INVALID_SOCKET; + Discard(); + } + } +} + +void TSecureShell::UpdatePortFwdSocket(SOCKET Value, bool Startup) +{ + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Updating forwarding socket %d (%d)", static_cast(Value), static_cast(Startup))); + } + + SocketEventSelect(Value, FSocketEvent, Startup); + + if (Startup) + { + FPortFwdSockets.push_back(Value); + } + else + { + TSockets::iterator it = std::find(FPortFwdSockets.begin(), FPortFwdSockets.end(), Value); + if (it != FPortFwdSockets.end()) + FPortFwdSockets.erase(it); + } +} + +void TSecureShell::SetActive(bool Value) +{ + if (FActive != Value) + { + if (Value) + { + Open(); + } + else + { + Close(); + } + } +} + +void TSecureShell::FreeBackend() +{ + if (FBackendHandle != nullptr) + { + FBackend->free(FBackendHandle); + FBackendHandle = nullptr; + } +} + +void TSecureShell::Discard() +{ + bool WasActive = FActive; + FActive = false; + FOpened = false; + + if (WasActive) + { + FUI->Closed(); + } +} + +void TSecureShell::Close() +{ + LogEvent("Closing connection."); + DebugAssert(FActive); + + // this is particularly necessary when using local proxy command + // (e.g. plink), otherwise it hangs in sk_localproxy_close + SendEOF(); + + FreeBackend(); + + Discard(); +} + +void inline TSecureShell::CheckConnection(int Message) +{ + if (!FActive || get_ssh_state_closed(FBackendHandle)) + { + UnicodeString Str; + UnicodeString HelpKeyword; + + if (Message >= 0) + { + Str = LoadStr(Message); + } + else + { + Str = LoadStr(NOT_CONNECTED); + HelpKeyword = HELP_NOT_CONNECTED; + } + + Str = MainInstructions(Str); + + int ExitCode = get_ssh_exitcode(FBackendHandle); + if (ExitCode >= 0) + { + Str += L" " + FMTLOAD(SSH_EXITCODE, ExitCode); + } + FatalError(Str, HelpKeyword); + } +} + +void TSecureShell::PoolForData(WSANETWORKEVENTS & Events, intptr_t & Result) +{ + if (!GetActive()) + { + // see comment below + Result = qaRetry; + } + else + { + try + { + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent("Pooling for data in case they finally arrives"); + } + + // in extreme condition it may happen that send buffer is full, but there + // will be no data coming and we may not empty the send buffer because we + // do not process FD_WRITE until we receive any FD_READ + if (EventSelectLoop(0, false, &Events)) + { + LogEvent("Data has arrived, closing query to user."); + Result = qaOK; + } + } + catch (...) + { + // if we let the exception out, it may popup another message dialog + // in whole event loop, another call to PoolForData from original dialog + // would be invoked, leading to an infinite loop. + // by retrying we hope (that probably fatal) error would repeat in WaitForData. + // anyway now once no actual work is done in EventSelectLoop, + // hardly any exception can occur actually + Result = qaRetry; + } + } +} + +class TPoolForDataEvent : public TObject +{ +NB_DISABLE_COPY(TPoolForDataEvent) +public: + TPoolForDataEvent(TSecureShell * SecureShell, WSANETWORKEVENTS & Events) : + FSecureShell(SecureShell), + FEvents(Events) + { + } + + void PoolForData(intptr_t & Result) + { + FSecureShell->PoolForData(FEvents, Result); + } + +private: + TSecureShell * FSecureShell; + WSANETWORKEVENTS & FEvents; +}; + +void TSecureShell::WaitForData() +{ +#ifndef __linux__ + // see winsftp.c + bool IncomingData; + + do + { + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent("Looking for incoming data"); + } + + IncomingData = EventSelectLoop(FSessionData->GetTimeout() * MSecsPerSec, true, nullptr); + if (!IncomingData) + { + DebugAssert(FWaitingForData == 0); + TAutoNestingCounter NestingCounter(FWaitingForData); + + WSANETWORKEVENTS Events; + ClearStruct(Events); + TPoolForDataEvent Event(this, Events); + + LogEvent("Waiting for data timed out, asking user what to do."); + uintptr_t Answer = TimeoutPrompt(MAKE_CALLBACK(TPoolForDataEvent::PoolForData, &Event)); + switch (Answer) + { + case qaRetry: + // noop + break; + + case qaOK: + // read event was already captured in PoolForData(), + // make sure we do not try to select it again as it would timeout + // unless another read event occurs + IncomingData = true; + HandleNetworkEvents(FSocket, Events); + break; + + default: + DebugFail(); + // fallthru + + case qaAbort: + FatalError(MainInstructions(LoadStr(USER_TERMINATED))); + break; + } + } + } + while (!IncomingData); +#endif +} + +bool TSecureShell::SshFallbackCmd() const +{ + return ssh_fallback_cmd(FBackendHandle) != 0; +} + +bool TSecureShell::EnumNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events) +{ + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Enumerating network events for socket %d", static_cast(Socket))); + } +#ifndef __linux__ + // see winplink.c + WSANETWORKEVENTS AEvents; + if (::WSAEnumNetworkEvents(Socket, nullptr, &AEvents) == 0) + { + noise_ultralight(static_cast(Socket)); + noise_ultralight(AEvents.lNetworkEvents); + + Events.lNetworkEvents |= AEvents.lNetworkEvents; + for (intptr_t Index = 0; Index < FD_MAX_EVENTS; ++Index) + { + if (AEvents.iErrorCode[Index] != 0) + { + Events.iErrorCode[Index] = AEvents.iErrorCode[Index]; + } + } + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Enumerated %d network events making %d cumulative events for socket %d", + static_cast(AEvents.lNetworkEvents), static_cast(Events.lNetworkEvents), static_cast(Socket))); + } + } + else + { + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Error enumerating network events for socket %d", static_cast(Socket))); + } + } + + return + FLAGSET(Events.lNetworkEvents, FD_READ) || + FLAGSET(Events.lNetworkEvents, FD_CLOSE); +#else + return false; +#endif +} + +void TSecureShell::HandleNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events) +{ +#ifndef __linux__ + static const struct { int Bit, Mask; const wchar_t * Desc; } EventTypes[] = + { + { FD_READ_BIT, FD_READ, L"read" }, + { FD_WRITE_BIT, FD_WRITE, L"write" }, + { FD_OOB_BIT, FD_OOB, L"oob" }, + { FD_ACCEPT_BIT, FD_ACCEPT, L"accept" }, + { FD_CONNECT_BIT, FD_CONNECT, L"connect" }, + { FD_CLOSE_BIT, FD_CLOSE, L"close" }, + }; + + for (uintptr_t Event = 0; Event < _countof(EventTypes); Event++) + { + if (FLAGSET(Events.lNetworkEvents, EventTypes[Event].Mask)) + { + int Err = Events.iErrorCode[EventTypes[Event].Bit]; + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Handling network %s event on socket %d with error %d", + EventTypes[Event].Desc, int(Socket), Err)); + } + LPARAM SelectEvent = WSAMAKESELECTREPLY(EventTypes[Event].Mask, Err); + if (!select_result(static_cast(Socket), SelectEvent)) + { + // note that connection was closed definitely, + // so "check" is actually not required + CheckConnection(); + } + } + } +#endif +} + +bool TSecureShell::ProcessNetworkEvents(SOCKET Socket) +{ +#ifndef __linux__ + WSANETWORKEVENTS Events; + ClearStruct(Events); + bool Result = EnumNetworkEvents(Socket, Events); + HandleNetworkEvents(Socket, Events); + + return Result; +#else + return false; +#endif +} + +bool TSecureShell::EventSelectLoop(uintptr_t MSec, bool ReadEventRequired, + WSANETWORKEVENTS * Events) +{ + CheckConnection(); + + bool Result = false; +#ifndef __linux__ + do + { + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + // LogEvent("Looking for network events"); + } + uintptr_t TicksBefore = ::GetTickCount(); + int HandleCount; + // note that this returns all handles, not only the session-related handles + HANDLE * Handles = handle_get_events(&HandleCount); + try__finally + { + SCOPE_EXIT + { + sfree(Handles); + }; + Handles = sresize(Handles, static_cast(HandleCount + 1), HANDLE); + Handles[HandleCount] = FSocketEvent; + intptr_t Timeout = static_cast(MSec); + if (toplevel_callback_pending()) + { + Timeout = 0; + } + // uint32_t WaitResult = ::WaitForMultipleObjects(HandleCount + 1, Handles, FALSE, Timeout); + uint32_t WaitResult; + do + { + uint32_t TimeoutStep = Min(GUIUpdateInterval, (uint32_t)Timeout); + Timeout -= TimeoutStep; + WaitResult = ::WaitForMultipleObjects(HandleCount + 1, Handles, FALSE, TimeoutStep); + FUI->ProcessGUI(); + } + while ((WaitResult == WAIT_TIMEOUT) && (Timeout > 0)); + + if (WaitResult < WAIT_OBJECT_0 + HandleCount) + { + if (handle_got_event(Handles[WaitResult - WAIT_OBJECT_0])) + { + Result = true; + } + } + else if (WaitResult == WAIT_OBJECT_0 + HandleCount) + { + + if (GetConfiguration()->GetActualLogProtocol() >= 1) + { + LogEvent("Detected network event"); + } + + if (Events == nullptr) + { + if (ProcessNetworkEvents(FSocket)) + { + Result = true; + } + } + else + { + if (EnumNetworkEvents(FSocket, *Events)) + { + Result = true; + } + } + + { + TSockets::iterator it = FPortFwdSockets.begin(); + while (it != FPortFwdSockets.end()) + { + ProcessNetworkEvents(*it); + ++it; + } + } + } + else if (WaitResult == WAIT_TIMEOUT) + { + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + // LogEvent("Timeout waiting for network events"); + } + + MSec = 0; + } + else + { + + if (GetConfiguration()->GetActualLogProtocol() >= 2) + { + LogEvent(FORMAT(L"Unknown waiting result %d", static_cast(WaitResult))); + } + + MSec = 0; + } + } + __finally + { + sfree(Handles); + }; + + run_toplevel_callbacks(); + + uintptr_t TicksAfter = ::GetTickCount(); + // ticks wraps once in 49.7 days + if (TicksBefore < TicksAfter) + { + uintptr_t Ticks = TicksAfter - TicksBefore; + if (Ticks > MSec) + { + MSec = 0; + } + else + { + MSec -= Ticks; + } + } + + if ((FSendBuf > 0) && (TicksAfter - FLastSendBufferUpdate >= 1000)) + { + DWORD BufferLen = 0; + DWORD OutLen = 0; + if (WSAIoctl(FSocket, SIO_IDEAL_SEND_BACKLOG_QUERY, NULL, 0, &BufferLen, sizeof(BufferLen), &OutLen, 0, 0) == 0) + { + DebugAssert(OutLen == sizeof(BufferLen)); + if (FSendBuf < static_cast(BufferLen)) + { + LogEvent(FORMAT(L"Increasing send buffer from %d to %d", FSendBuf, static_cast(BufferLen))); + FSendBuf = BufferLen; + setsockopt(FSocket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&BufferLen), sizeof(BufferLen)); + } + } + FLastSendBufferUpdate = TicksAfter; + } + } + while (ReadEventRequired && (MSec > 0) && !Result); +#endif + return Result; +} + +void TSecureShell::Idle(uintptr_t MSec) +{ + noise_regular(); + + call_ssh_timer(FBackendHandle); + + // if we are actively waiting for data in WaitForData, + // do not read here, otherwise we swallow read event and never wake + if (FWaitingForData <= 0) + { + EventSelectLoop(MSec, false, nullptr); + } +} + +void TSecureShell::KeepAlive() +{ + if (FActive && (FWaiting == 0)) + { + LogEvent("Sending null packet to keep session alive."); + SendSpecial(TS_PING); + } + else + { + // defer next keepalive attempt + FLastDataSent = Now(); + } +} + +static uint32_t minPacketSize = 0; + +uint32_t TSecureShell::MinPacketSize() const +{ + if (!FSessionInfoValid) + { + UpdateSessionInfo(); + } + + if (FSshVersion == 1) + { + return 0; + } + else + { + if (FMinPacketSize == nullptr) + { + FMinPacketSize = &minPacketSize; + } + return *FMinPacketSize; + } +} + +uint32_t TSecureShell::MaxPacketSize() const +{ + if (!FSessionInfoValid) + { + UpdateSessionInfo(); + } + + if (FSshVersion == 1) + { + return 0; + } + else + { + if (FMaxPacketSize == nullptr) + { + FMaxPacketSize = ssh2_remmaxpkt(FBackendHandle); + } + return *FMaxPacketSize; + } +} + +UnicodeString TSecureShell::FuncToCompression( + int SshVersion, const void * Compress) const +{ + enum TCompressionType + { + ctNone, + ctZLib, + }; + if (SshVersion == 1) + { + return get_ssh1_compressing(FBackendHandle) ? L"ZLib" : L""; + } + else + { + return reinterpret_cast(const_cast(Compress)) == &ssh_zlib ? L"ZLib" : L""; + } +} + +TCipher TSecureShell::FuncToSsh1Cipher(const void * Cipher) +{ + const ssh_cipher *CipherFuncs[] = + {&ssh_3des, &ssh_des, &ssh_blowfish_ssh1}; + const TCipher TCiphers[] = {cip3DES, cipDES, cipBlowfish}; + DebugAssert(_countof(CipherFuncs) == _countof(TCiphers)); + TCipher Result = cipWarn; + + for (intptr_t Index = 0; Index < static_cast(_countof(TCiphers)); ++Index) + { + if (static_cast(const_cast(Cipher)) == CipherFuncs[Index]) + { + Result = TCiphers[Index]; + } + } + + DebugAssert(Result != cipWarn); + return Result; +} + +TCipher TSecureShell::FuncToSsh2Cipher(const void * Cipher) +{ + const ssh2_ciphers *CipherFuncs[] = + {&ssh2_3des, &ssh2_des, &ssh2_aes, &ssh2_blowfish, &ssh2_arcfour +#ifndef __linux__ + , &ssh2_ccp +#endif + }; + const TCipher TCiphers[] = {cip3DES, cipDES, cipAES, cipBlowfish, cipArcfour, cipChaCha20}; + DebugAssert(_countof(CipherFuncs) == _countof(TCiphers)); + TCipher Result = cipWarn; + + for (uintptr_t C = 0; C < _countof(TCiphers); C++) + { + for (int F = 0; F < CipherFuncs[C]->nciphers; F++) + { + if (reinterpret_cast(const_cast(Cipher)) == CipherFuncs[C]->list[F]) + { + Result = TCiphers[C]; + } + } + } + + DebugAssert(Result != cipWarn); + return Result; +} + +UnicodeString TSecureShell::FormatKeyStr(const UnicodeString & AKeyStr) const +{ + UnicodeString KeyStr = AKeyStr; + intptr_t Index = 1; + intptr_t Digits = 0; + while (Index <= KeyStr.Length()) + { + if (IsHex(KeyStr[Index])) + { + Digits++; + if (Digits >= 16) + { + KeyStr.Insert(L" ", Index + 1); + ++Index; + Digits = 0; + } + } + else + { + Digits = 0; + } + ++Index; + } + return KeyStr; +} + +void TSecureShell::GetRealHost(UnicodeString & Host, intptr_t & Port) +{ + if (FSessionData->GetTunnel()) + { + Host = FSessionData->GetOrigHostName(); + Port = FSessionData->GetOrigPortNumber(); + } +} + +UnicodeString TSecureShell::RetrieveHostKey(const UnicodeString & Host, intptr_t Port, const UnicodeString & KeyType) +{ + AnsiString AnsiStoredKeys; + AnsiStoredKeys.SetLength(10 * 1024); + UnicodeString Result; +#ifndef __linux__ + if (retrieve_host_key(AnsiString(Host).c_str(), static_cast(Port), AnsiString(KeyType).c_str(), + (char *)AnsiStoredKeys.c_str(), static_cast(AnsiStoredKeys.Length())) == 0) +#else + if (verify_host_key(AnsiString(Host).c_str(), static_cast(Port), AnsiString(KeyType).c_str(), + (char *)AnsiStoredKeys.c_str()) == 0) +#endif + { + PackStr(AnsiStoredKeys); + Result = UnicodeString(AnsiStoredKeys); + } + return Result; +} + +void TSecureShell::VerifyHostKey(const UnicodeString & AHost, intptr_t Port, + const UnicodeString & AKeyType, const UnicodeString & AKeyStr, const UnicodeString & Fingerprint) +{ + UnicodeString Host = AHost; + UnicodeString KeyStr = AKeyStr; + LogEvent(FORMAT(L"Verifying host key %s %s with fingerprint %s", AKeyType.c_str(), FormatKeyStr(KeyStr).c_str(), Fingerprint.c_str())); + + GotHostKey(); + + DebugAssert(AKeyStr.Pos(HostKeyDelimiter) == 0); + + GetRealHost(Host, Port); + + FSessionInfo.HostKeyFingerprint = Fingerprint; + + if (FSessionData->GetFingerprintScan()) + { + Abort(); + } + + UnicodeString NormalizedFingerprint = NormalizeFingerprint(Fingerprint); + + bool Result = false; + + UnicodeString StoredKeys = RetrieveHostKey(AHost, Port, AKeyType); + UnicodeString Buf = StoredKeys; + while (!Result && !Buf.IsEmpty()) + { + UnicodeString StoredKey = CutToChar(Buf, HostKeyDelimiter, false); + // skip leading ECDH subtype identification + intptr_t P = StoredKey.Pos(L","); + // start from beginning or after the comma, if there 's any + bool Fingerprint = (StoredKey.SubString(P + 1, 2) != L"0x"); + // it's probably a fingerprint (stored by TSessionData::CacheHostKey) + UnicodeString NormalizedExpectedKey; + if (Fingerprint) + { + NormalizedExpectedKey = NormalizeFingerprint(StoredKey); + } + if ((!Fingerprint && (StoredKey == AKeyStr)) || + (Fingerprint && (NormalizedExpectedKey == NormalizedFingerprint))) + { + LogEvent(L"Host key matches cached key"); + Result = true; + } + else + { + UnicodeString FormattedKey = Fingerprint ? StoredKey : FormatKeyStr(StoredKey); + LogEvent(FORMAT(L"Host key does not match cached key %s", FormattedKey.c_str())); + } + } + + bool ConfiguredKeyNotMatch = false; + + if (!Result && !FSessionData->GetHostKey().IsEmpty() && + (StoredKeys.IsEmpty() || FSessionData->GetOverrideCachedHostKey())) + { + UnicodeString Buf = FSessionData->GetHostKey(); + while (!Result && !Buf.IsEmpty()) + { + UnicodeString ExpectedKey = CutToChar(Buf, HostKeyDelimiter, false); + UnicodeString NormalizedExpectedKey = NormalizeFingerprint(ExpectedKey); + if (ExpectedKey == L"*") + { + UnicodeString Message = LoadStr(ANY_HOSTKEY); + FUI->Information(Message, true); + FLog->Add(llException, Message); + Result = true; + } + else if (NormalizedExpectedKey == NormalizedFingerprint) + { + LogEvent("Host key matches configured key"); + Result = true; + } + else + { + LogEvent(FORMAT(L"Host key does not match configured key %s", ExpectedKey.c_str())); + } + } + + if (!Result) + { + ConfiguredKeyNotMatch = true; + } + } + + if (!Result) + { + bool Verified; + if (ConfiguredKeyNotMatch || GetConfiguration()->GetDisableAcceptingHostKeys()) + { + Verified = false; + } + // no point offering manual verification, if we cannot persist the verified key + else if (!GetConfiguration()->GetPersistent() && GetConfiguration()->GetScripting()) + { + Verified = false; + } + else + { + // We should not offer caching if !Configuration->Persistent, + // but as scripting mode is handled earlier and in GUI it hardly happens, + // it's a small issue. + TClipboardHandler ClipboardHandler; + ClipboardHandler.Text = Fingerprint; + + bool Unknown = StoredKeys.IsEmpty(); + + uintptr_t Answers; + uintptr_t AliasesCount; + TQueryButtonAlias Aliases[3]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON); + Aliases[0].OnClick = MAKE_CALLBACK(TClipboardHandler::Copy, &ClipboardHandler); + Answers = qaYes | qaCancel | qaRetry; + AliasesCount = 1; + if (!Unknown) + { + Aliases[1].Button = qaYes; + Aliases[1].Alias = LoadStr(UPDATE_KEY_BUTTON); + Aliases[2].Button = qaOK; + Aliases[2].Alias = LoadStr(ADD_KEY_BUTTON); + AliasesCount += 2; + Answers |= qaSkip | qaOK; + } + else + { + Answers |= qaNo; + } + + TQueryParams Params(qpWaitInBatch); + Params.NoBatchAnswers = qaYes | qaRetry | qaSkip | qaOK; + Params.HelpKeyword = (Unknown ? HELP_UNKNOWN_KEY : HELP_DIFFERENT_KEY); + Params.Aliases = Aliases; + Params.AliasesCount = AliasesCount; + + UnicodeString Message = FMTLOAD((Unknown ? UNKNOWN_KEY3 : DIFFERENT_KEY4), AKeyType.c_str(), Fingerprint.c_str()); + if (GetConfiguration()->GetScripting()) + { + AddToList(Message, LoadStr(SCRIPTING_USE_HOSTKEY), L"\n"); + } + + uintptr_t R = + FUI->QueryUser(Message, nullptr, Answers, &Params, qtWarning); + + switch (R) + { + case qaOK: + DebugAssert(!Unknown); + KeyStr = (StoredKeys + HostKeyDelimiter + KeyStr); + // fall thru + case qaYes: + store_host_key(AnsiString(Host).c_str(), static_cast(Port), AnsiString(AKeyType).c_str(), + AnsiString(KeyStr).c_str()); + Verified = true; + break; + + case qaCancel: + Verified = false; + break; + + default: + Verified = true; + break; + } + } + + if (!Verified) + { + // Configuration->Usage->Inc(L"HostNotVerified"); + + UnicodeString Message; + if (ConfiguredKeyNotMatch) + { + Message = FMTLOAD(CONFIGURED_KEY_NOT_MATCH, FSessionData->GetHostKey().c_str()); + } + else if (!GetConfiguration()->GetPersistent() && GetConfiguration()->GetScripting()) + { + Message = LoadStr(HOSTKEY_NOT_CONFIGURED); + } + else + { + Message = LoadStr(KEY_NOT_VERIFIED); + } + + std::unique_ptr E(new Exception(MainInstructions(Message))); + try__finally + { + FUI->FatalError(E.get(), FMTLOAD(HOSTKEY, Fingerprint.c_str())); + } + __finally + { + // delete E; + }; + } + } + + GetConfiguration()->RememberLastFingerprint(FSessionData->GetSiteKey(), SshFingerprintType, Fingerprint); +} + +bool TSecureShell::HaveHostKey(const UnicodeString & AHost, intptr_t Port, const UnicodeString & KeyType) +{ + UnicodeString Host = AHost; + // Return true, if we have any host key fingerprint of a particular type + + GetRealHost(Host, Port); + + UnicodeString StoredKeys = RetrieveHostKey(Host, Port, KeyType); + bool Result = !StoredKeys.IsEmpty(); + + if (!FSessionData->GetHostKey().IsEmpty()) + { + UnicodeString Buf = FSessionData->GetHostKey(); + while (!Result && !Buf.IsEmpty()) + { + UnicodeString ExpectedKey = CutToChar(Buf, HostKeyDelimiter, false); + UnicodeString ExpectedKeyType = GetKeyTypeFromFingerprint(ExpectedKey); + Result = SameText(ExpectedKeyType, KeyType); + } + } + + if (Result) + { + LogEvent(FORMAT(L"Have a known host key of type %s", KeyType.c_str())); + } + + return Result; +} + +void TSecureShell::AskAlg(const UnicodeString & AlgType, + const UnicodeString & AlgName) +{ + UnicodeString Msg; + UnicodeString Error; + if (AlgType == L"key-exchange algorithm") + { + Msg = FMTLOAD(KEX_BELOW_TRESHOLD, AlgName.c_str()); + Error = FMTLOAD(KEX_NOT_VERIFIED, AlgName.c_str()); + } + else if (AlgType == L"hostkey type") + { + // noop as we do not allow host key algorithm configuration, + // so no algorithm can get below WARN level + } + else + { + int CipherType = 0; + if (AlgType == L"cipher") + { + CipherType = CIPHER_TYPE_BOTH; + } + else if (AlgType == L"client-to-server cipher") + { + CipherType = CIPHER_TYPE_CS; + } + else if (AlgType == L"server-to-client cipher") + { + CipherType = CIPHER_TYPE_SC; + } + else + { + DebugFail(); + CipherType = 0; + } + + if (CipherType != 0) + { + Msg = FMTLOAD(CIPHER_BELOW_TRESHOLD, LoadStr(CipherType).c_str(), AlgName.c_str()); + Error = FMTLOAD(CIPHER_NOT_VERIFIED, AlgName.c_str()); + } + } + + if (!Msg.IsEmpty()) + { + if (FUI->QueryUser(Msg, nullptr, qaYes | qaNo, nullptr, qtWarning) == qaNo) + { + FUI->FatalError(nullptr, Error); + } + } +} + +void TSecureShell::DisplayBanner(const UnicodeString & Banner) +{ + FUI->DisplayBanner(Banner); +} + +void TSecureShell::OldKeyfileWarning() +{ + // actually never called, see Net.cpp + FUI->QueryUser(LoadStr(OLD_KEY), nullptr, qaOK, nullptr, qtWarning); +} + +bool TSecureShell::GetStoredCredentialsTried() const +{ + return FStoredPasswordTried || FStoredPasswordTriedForKI || FStoredPassphraseTried; +} + +bool TSecureShell::GetReady() const +{ + return FOpened && (FWaiting == 0); +} + +void TSecureShell::CollectUsage() +{ + /*if (FCollectPrivateKeyUsage) + { + Configuration->Usage->Inc(L"OpenedSessionsPrivateKey2"); + } + + if (FSshVersion == 1) + { + Configuration->Usage->Inc(L"OpenedSessionsSSH1"); + } + else if (FSshVersion == 2) + { + Configuration->Usage->Inc(L"OpenedSessionsSSH2"); + } + + if (SshImplementation == sshiOpenSSH) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHOpenSSH"); + } + else if (SshImplementation == sshiProFTPD) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHProFTPD"); + } + else if (SshImplementation == sshiBitvise) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHBitvise"); + } + else if (SshImplementation == sshiTitan) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHTitan"); + } + else if (SshImplementation == sshiOpenVMS) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHOpenVMS"); + } + else if (ContainsText(FSessionInfo.SshImplementation, L"Serv-U")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHServU"); + } + else if (SshImplementation == sshiCerberus) + { + // Ntb, Cerberus can also be detected using vendor-id extension + // Cerberus FTP Server 7.0.5.3 (70005003) by Cerberus, LLC + Configuration->Usage->Inc(L"OpenedSessionsSSHCerberus"); + } + else if (ContainsText(FSessionInfo.SshImplementation, L"WS_FTP")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHWSFTP"); + } + // SSH-2.0-1.36_sshlib GlobalSCAPE + else if (ContainsText(FSessionInfo.SshImplementation, L"GlobalSCAPE")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHGlobalScape"); + } + // SSH-2.0-CompleteFTP-8.1.3 + else if (ContainsText(FSessionInfo.SshImplementation, L"CompleteFTP")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHComplete"); + } + // SSH-2.0-CoreFTP-0.3.3 + else if (ContainsText(FSessionInfo.SshImplementation, L"CoreFTP")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHCore"); + } + // SSH-2.0-SSHD-CORE-0.11.0 (value is configurable, this is a default) + // (Apache Mina SSHD, e.g. on brickftp.com) + else if (ContainsText(FSessionInfo.SshImplementation, L"SSHD-CORE")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHApache"); + } + // SSH-2.0-Syncplify_Me_Server + else if (ContainsText(FSessionInfo.SshImplementation, L"Syncplify")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHSyncplify"); + } + else if (ContainsText(FSessionInfo.SshImplementation, L"zFTPServer")) + { + Configuration->Usage->Inc(L"OpenedSessionsSSHzFTP"); + } + else + { + Configuration->Usage->Inc(L"OpenedSessionsSSHOther"); + }*/ +} + +NB_IMPLEMENT_CLASS(TSecureShell, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/SecureShell.h b/netbox/src/core/SecureShell.h new file mode 100644 index 000000000..1eaaa89ad --- /dev/null +++ b/netbox/src/core/SecureShell.h @@ -0,0 +1,206 @@ + +#pragma once + +#include "PuttyIntf.h" +#include "Configuration.h" +#include "SessionData.h" +#include "SessionInfo.h" + +#ifndef PuttyIntfH +struct Backend; +struct Conf; +#endif + +struct _WSANETWORKEVENTS; +typedef struct _WSANETWORKEVENTS WSANETWORKEVENTS; +#ifndef __linux__ +typedef UINT_PTR SOCKET; +#endif +typedef std::vector TSockets; +struct TPuttyTranslation; + +enum TSshImplementation +{ + sshiUnknown, + sshiOpenSSH, + sshiProFTPD, + sshiBitvise, + sshiTitan, + sshiOpenVMS, + sshiCerberus, +}; + +class TSecureShell : public TObject +{ +friend class TPoolForDataEvent; +NB_DISABLE_COPY(TSecureShell) +NB_DECLARE_CLASS(TSecureShell) +private: + SOCKET FSocket; + HANDLE FSocketEvent; + TSockets FPortFwdSockets; + TSessionUI * FUI; + TSessionData * FSessionData; + bool FActive; + mutable TSessionInfo FSessionInfo; + mutable bool FSessionInfoValid; + TDateTime FLastDataSent; + Backend * FBackend; + void * FBackendHandle; + mutable const uint32_t * FMinPacketSize; + mutable const uint32_t * FMaxPacketSize; + TNotifyEvent FOnReceive; + bool FFrozen; + bool FDataWhileFrozen; + bool FStoredPasswordTried; + bool FStoredPasswordTriedForKI; + bool FStoredPassphraseTried; + mutable int FSshVersion; + bool FOpened; + int FWaiting; + bool FSimple; + bool FNoConnectionResponse; + bool FCollectPrivateKeyUsage; + int FWaitingForData; + TSshImplementation FSshImplementation; + + intptr_t PendLen; + intptr_t PendSize; + intptr_t OutLen; + uint8_t * OutPtr; + uint8_t * Pending; + TSessionLog * FLog; + TConfiguration * FConfiguration; + bool FAuthenticating; + bool FAuthenticated; + UnicodeString FStdErrorTemp; + UnicodeString FStdError; + UnicodeString FCWriteTemp; + UnicodeString FAuthenticationLog; + UnicodeString FLastTunnelError; + UnicodeString FUserName; + bool FUtfStrings; + DWORD FLastSendBufferUpdate; + int FSendBuf; + +public: + static TCipher FuncToSsh1Cipher(const void * Cipher); + static TCipher FuncToSsh2Cipher(const void * Cipher); + UnicodeString FuncToCompression(int SshVersion, const void * Compress) const; + void Init(); + void SetActive(bool Value); + void inline CheckConnection(int Message = -1); + void WaitForData(); + void Discard(); + void FreeBackend(); + void PoolForData(WSANETWORKEVENTS & Events, intptr_t & Result); + inline void CaptureOutput(TLogLineType Type, + const UnicodeString & Line); + void ResetConnection(); + void ResetSessionInfo(); + void SocketEventSelect(SOCKET Socket, HANDLE Event, bool Startup); + bool EnumNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events); + void HandleNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events); + bool ProcessNetworkEvents(SOCKET Socket); + bool EventSelectLoop(uintptr_t MSec, bool ReadEventRequired, + WSANETWORKEVENTS * Events); + void UpdateSessionInfo() const; + bool GetReady() const; + void DispatchSendBuffer(intptr_t BufSize); + void SendBuffer(intptr_t & Result); + uintptr_t TimeoutPrompt(TQueryParamsTimerEvent PoolEvent); + bool TryFtp(); + UnicodeString ConvertInput(const RawByteString & Input, uintptr_t CodePage = CP_ACP) const; + void GetRealHost(UnicodeString & Host, intptr_t & Port); + UnicodeString RetrieveHostKey(const UnicodeString & Host, intptr_t Port, const UnicodeString & KeyType); + +protected: + TCaptureOutputEvent FOnCaptureOutput; + + void GotHostKey(); + int TranslatePuttyMessage(const TPuttyTranslation * Translation, + intptr_t Count, UnicodeString & Message, UnicodeString * HelpKeyword = nullptr) const; + int TranslateAuthenticationMessage(UnicodeString & Message, UnicodeString * HelpKeyword = nullptr); + int TranslateErrorMessage(UnicodeString & Message, UnicodeString * HelpKeyword = nullptr); + void AddStdError(const UnicodeString & AStr); + void AddStdErrorLine(const UnicodeString & AStr); + void LogEvent(const UnicodeString & AStr); + // void FatalError(Exception * E, const UnicodeString & Msg); + void FatalError(const UnicodeString & Error, const UnicodeString & HelpKeyword = L""); + UnicodeString FormatKeyStr(const UnicodeString & AKeyStr) const; + static Conf * StoreToConfig(TSessionData * Data, bool Simple); + +public: + explicit TSecureShell(TSessionUI * UI, TSessionData * SessionData, + TSessionLog * Log, TConfiguration * Configuration); + virtual ~TSecureShell(); + void Open(); + void Close(); + void KeepAlive(); + intptr_t Receive(uint8_t * Buf, intptr_t Length); + bool Peek(uint8_t *& Buf, intptr_t Length) const; + UnicodeString ReceiveLine(); + void Send(const uint8_t * Buf, intptr_t Length); + // void SendStr(const UnicodeString & Str); + void SendSpecial(int Code); + void Idle(uintptr_t MSec = 0); + void SendEOF(); + void SendLine(const UnicodeString & Line); + void SendNull(); + + const TSessionInfo & GetSessionInfo() const; + UnicodeString GetHostKeyFingerprint() const; + bool SshFallbackCmd() const; + uint32_t MinPacketSize() const; + uint32_t MaxPacketSize() const; + void ClearStdError(); + bool GetStoredCredentialsTried() const; + void CollectUsage(); + + void RegisterReceiveHandler(TNotifyEvent Handler); + void UnregisterReceiveHandler(TNotifyEvent Handler); + + // interface to PuTTY core + void UpdateSocket(SOCKET Value, bool Startup); + void UpdatePortFwdSocket(SOCKET Value, bool Startup); + void PuttyFatalError(const UnicodeString & Error); + TPromptKind IdentifyPromptKind(UnicodeString & AName); + bool PromptUser(bool ToServer, + const UnicodeString & AName, bool NameRequired, + const UnicodeString & AInstructions, bool InstructionsRequired, + TStrings * Prompts, TStrings * Results); + void FromBackend(bool IsStdErr, const uint8_t * Data, intptr_t Length); + void CWrite(const char * Data, intptr_t Length); + const UnicodeString & GetStdError() const; + void VerifyHostKey(const UnicodeString & AHost, intptr_t Port, + const UnicodeString & AKeyType, const UnicodeString & AKeyStr, const UnicodeString & Fingerprint); + bool HaveHostKey(const UnicodeString & AHost, intptr_t Port, const UnicodeString & KeyType); + void AskAlg(const UnicodeString & AlgType, const UnicodeString & AlgName); + void DisplayBanner(const UnicodeString & Banner); + void OldKeyfileWarning(); + void PuttyLogEvent(const char * AStr); + UnicodeString ConvertFromPutty(const char * Str, size_t Length) const; + + /*__property bool Active = { read = FActive, write = SetActive }; + __property bool Ready = { read = GetReady }; + __property TCaptureOutputEvent OnCaptureOutput = { read = FOnCaptureOutput, write = FOnCaptureOutput }; + __property TDateTime LastDataSent = { read = FLastDataSent }; + __property UnicodeString LastTunnelError = { read = FLastTunnelError }; + __property UnicodeString UserName = { read = FUserName }; + __property bool Simple = { read = FSimple, write = FSimple }; + __property TSshImplementation SshImplementation = { read = FSshImplementation }; + __property bool UtfStrings = { read = FUtfStrings, write = FUtfStrings };*/ + + bool GetActive() const { return FActive; } + const TCaptureOutputEvent & GetOnCaptureOutput() const { return FOnCaptureOutput; } + void SetOnCaptureOutput(TCaptureOutputEvent Value) { FOnCaptureOutput = Value; } + TDateTime GetLastDataSent() const { return FLastDataSent; } + UnicodeString GetLastTunnelError() const { return FLastTunnelError; } + UnicodeString ShellGetUserName() const { return FUserName; } + bool GetSimple() const { return FSimple; } + void SetSimple(bool Value) { FSimple = Value; } + TSshImplementation GetSshImplementation() const { return FSshImplementation; } + bool GetUtfStrings() const { return FUtfStrings; } + void SetUtfStrings(bool Value) { FUtfStrings = Value; } +}; + diff --git a/netbox/src/core/SessionData.cpp b/netbox/src/core/SessionData.cpp new file mode 100644 index 000000000..e2904364f --- /dev/null +++ b/netbox/src/core/SessionData.cpp @@ -0,0 +1,5166 @@ +#include +#pragma hdrstop + +#include +#include +#ifndef __linux__ +#include +#endif + +#include +#include +#include +#include + +#include "SessionData.h" +#include "CoreMain.h" +#include "TextsCore.h" +#include "PuttyIntf.h" +#include "RemoteFiles.h" +#include "SftpFileSystem.h" + +const wchar_t * PingTypeNames = L"Off;Null;Dummy"; +const wchar_t * ProxyMethodNames = L"None;SOCKS4;SOCKS5;HTTP;Telnet;Cmd"; +const wchar_t * DefaultName = L"Default Settings"; +const UnicodeString CipherNames[CIPHER_COUNT] = { L"WARN", L"3des", L"blowfish", L"aes", L"des", L"arcfour", L"chacha20" }; +const UnicodeString KexNames[KEX_COUNT] = { L"WARN", L"dh-group1-sha1", L"dh-group14-sha1", L"dh-gex-sha1", L"rsa", L"ecdh" }; +const wchar_t SshProtList[][10] = {L"1", L"1>2", L"2>1", L"2"}; +const TCipher DefaultCipherList[CIPHER_COUNT] = + { cipAES, cipChaCha20, cipBlowfish, cip3DES, cipWarn, cipArcfour, cipDES }; +const TKex DefaultKexList[KEX_COUNT] = + { kexECDH, kexDHGEx, kexDHGroup14, kexRSA, kexWarn, kexDHGroup1 }; +const wchar_t FSProtocolNames[FSPROTOCOL_COUNT][16] = { L"SCP", L"SFTP (SCP)", L"SFTP", L"", L"", L"FTP", L"WebDAV" }; +const intptr_t SshPortNumber = 22; +const intptr_t FtpPortNumber = 21; +const intptr_t FtpsImplicitPortNumber = 990; +const intptr_t HTTPPortNumber = 80; +const intptr_t HTTPSPortNumber = 443; +const intptr_t TelnetPortNumber = 23; +const intptr_t DefaultSendBuf = 256 * 1024; +const intptr_t ProxyPortNumber = 80; + +const UnicodeString AnonymousUserName(L"anonymous"); +const UnicodeString AnonymousPassword(L"anonymous@example.com"); +const UnicodeString PuttySshProtocol(L"ssh"); +const UnicodeString PuttyTelnetProtocol(L"telnet"); +const UnicodeString SftpProtocol(L"sftp"); +const UnicodeString ScpProtocol(L"scp"); +const UnicodeString FtpProtocol(L"ftp"); +const UnicodeString FtpsProtocol(L"ftps"); +const UnicodeString FtpesProtocol(L"ftpes"); +const UnicodeString SshProtocol(L"ssh"); +const UnicodeString WinSCPProtocolPrefix(L"winscp-"); +const wchar_t UrlParamSeparator = L';'; +const wchar_t UrlParamValueSeparator = L'='; +const UnicodeString UrlHostKeyParamName(L"fingerprint"); +const UnicodeString UrlSaveParamName(L"save"); +const UnicodeString PassphraseOption(L"passphrase"); + + +const uintptr_t CONST_DEFAULT_CODEPAGE = CP_UTF8; +const TFSProtocol CONST_DEFAULT_PROTOCOL = fsSFTP; + +const intptr_t SFTPMinVersion = 0; +const intptr_t SFTPMaxVersion = 6; + +static TDateTime SecToDateTime(intptr_t Sec) +{ + return TDateTime(double(Sec) / SecsPerDay); +} + +//--- TSessionData ---------------------------------------------------- +TSessionData::TSessionData(const UnicodeString & AName) : + TNamedObject(AName), + FIEProxyConfig(nullptr) +{ + Default(); + FModified = true; +} + +TSessionData::~TSessionData() +{ + if (nullptr != FIEProxyConfig) + { + SAFE_DESTROY(FIEProxyConfig); + FIEProxyConfig = nullptr; + } +} + +intptr_t TSessionData::Compare(const TNamedObject * Other) const +{ + intptr_t Result; + // To avoid using CompareLogicalText on hex names of sessions in workspace. + // The session 000A would be sorted before 0001. +// if (DebugNotNull(dynamic_cast(Other))->IsWorkspace) +// { +// Result = CompareText(Name, Other->GetName()); +// } +// else + { + Result = TNamedObject::Compare(Other); + } + return Result; +} + +TSessionData * TSessionData::Clone() +{ + std::unique_ptr Data(new TSessionData(L"")); + Data->Assign(this); + return Data.release(); +} + +void TSessionData::Default() +{ + SetHostName(L""); + SetPortNumber(SshPortNumber); + SetUserName(ANONYMOUS_USER_NAME); + SetPassword(ANONYMOUS_PASSWORD); + SetPingInterval(30); + SetPingType(ptOff); + SetTimeout(15); + SetTryAgent(true); + SetAgentFwd(false); + SetAuthTIS(false); + SetAuthKI(true); + SetAuthKIPassword(true); + SetAuthGSSAPI(false); + SetGSSAPIFwdTGT(false); + SetGSSAPIServerRealm(L""); + SetChangeUsername(false); + SetCompression(false); + SetSshProt(ssh2only); + SetSsh2DES(false); + SetSshNoUserAuth(false); + for (intptr_t Index = 0; Index < CIPHER_COUNT; ++Index) + { + SetCipher(Index, DefaultCipherList[Index]); + } + for (intptr_t Index = 0; Index < KEX_COUNT; ++Index) + { + SetKex(Index, DefaultKexList[Index]); + } + SetPublicKeyFile(L""); + SetPassphrase(L""); + SetPuttyProtocol(L""); + SetTcpNoDelay(true); + SetSendBuf(DefaultSendBuf); + SetSshSimple(true); + FNotUtf = asAuto; + FIsWorkspace = false; + SetHostKey(L""); + SetFingerprintScan(false); + FOverrideCachedHostKey = true; + SetNote(L""); + FOrigHostName.Clear(); + FOrigPortNumber = 0; + FOrigProxyMethod = pmNone; + FTunnelConfigured = false; + + SetProxyMethod(::pmNone); + SetProxyHost(L"proxy"); + SetProxyPort(ProxyPortNumber); + SetProxyUsername(L""); + SetProxyPassword(L""); + SetProxyTelnetCommand(L"connect %host %port\\n"); + SetProxyLocalCommand(L""); + SetProxyDNS(asAuto); + SetProxyLocalhost(false); + + for (intptr_t Index = 0; Index < static_cast(_countof(FBugs)); ++Index) + { + SetBug(static_cast(Index), asAuto); + } + + SetSpecial(false); + SetFSProtocol(CONST_DEFAULT_PROTOCOL); + SetAddressFamily(afAuto); + SetRekeyData(L"1G"); + SetRekeyTime(MinsPerHour); + + // FS common + SetLocalDirectory(L""); + SetRemoteDirectory(L""); + SetSynchronizeBrowsing(false); + SetUpdateDirectories(true); + SetCacheDirectories(true); + SetCacheDirectoryChanges(true); + SetPreserveDirectoryChanges(true); + SetLockInHome(false); + SetResolveSymlinks(true); + SetFollowDirectorySymlinks(true); + SetDSTMode(dstmUnix); + SetDeleteToRecycleBin(false); + SetOverwrittenToRecycleBin(false); + SetRecycleBinPath(L""); + SetColor(0); + SetPostLoginCommands(L""); + + // SCP + SetReturnVar(L""); + SetLookupUserGroups(asAuto); + SetEOLType(eolLF); + SetTrimVMSVersions(false), + SetShell(L""); //default shell + SetReturnVar(L""); + SetClearAliases(true); + SetUnsetNationalVars(true); + SetListingCommand(L"ls -la"); + SetIgnoreLsWarnings(true); + SetScp1Compatibility(false); + SetTimeDifference(TDateTime(0.0)); + SetTimeDifferenceAuto(true); + SetSCPLsFullTime(asAuto); + SetNotUtf(asOn); // asAuto + + // SFTP + SetSftpServer(L""); + SetSFTPDownloadQueue(32); + SetSFTPUploadQueue(32); + SetSFTPListingQueue(2); + SetSFTPMaxVersion(::SFTPMaxVersion); + SetSFTPMaxPacketSize(0); + SetSFTPMinPacketSize(0); + + for (intptr_t Index = 0; Index < static_cast(_countof(FSFTPBugs)); ++Index) + { + SetSFTPBug(static_cast(Index), asAuto); + } + + SetTunnel(false); + SetTunnelHostName(L""); + SetTunnelPortNumber(SshPortNumber); + SetTunnelUserName(L""); + SetTunnelPassword(L""); + SetTunnelPublicKeyFile(L""); + SetTunnelLocalPortNumber(0); + SetTunnelPortFwd(L""); + SetTunnelHostKey(L""); + + // FTP + SetFtpPasvMode(true); + SetFtpForcePasvIp(asAuto); + SetFtpUseMlsd(asAuto); + SetFtpAccount(L""); + SetFtpPingInterval(30); + SetFtpPingType(ptDummyCommand); + SetFtpTransferActiveImmediately(asAuto); + SetFtps(ftpsNone); + SetMinTlsVersion(tls10); + SetMaxTlsVersion(tls12); + SetFtpListAll(asAuto); + SetFtpHost(asAuto); + SetFtpDupFF(false); + SetFtpUndupFF(false); + SetSslSessionReuse(true); + SetTlsCertificateFile(L""); + + SetFtpProxyLogonType(0); // none + + SetCustomParam1(L""); + SetCustomParam2(L""); + + SetIsWorkspace(false); + // SetLink(L""); + + SetSelected(false); + FModified = false; + FSource = ::ssNone; + FSaveOnly = false; + + SetCodePage(::GetCodePageAsString(CONST_DEFAULT_CODEPAGE)); + SetLoginType(ltAnonymous); + SetFtpAllowEmptyPassword(false); + + FNumberOfRetries = 0; + FSessionVersion = ::StrToVersionNumber(GetGlobalFunctions()->GetStrVersionNumber()); + // add also to TSessionLog::AddStartupInfo() +} + +void TSessionData::NonPersistant() +{ + SetUpdateDirectories(false); + SetPreserveDirectoryChanges(false); +} + +#define BASE_PROPERTIES \ + PROPERTY(HostName); \ + PROPERTY(PortNumber); \ + PROPERTY(Password); \ + PROPERTY(PublicKeyFile); \ + PROPERTY(Passphrase); \ + PROPERTY(FSProtocol); \ + PROPERTY(Ftps); \ + PROPERTY(LocalDirectory); \ + PROPERTY(RemoteDirectory); \ + PROPERTY(Color); \ + PROPERTY(SynchronizeBrowsing); \ + PROPERTY(Note); + +#define ADVANCED_PROPERTIES \ + PROPERTY(PingInterval); \ + PROPERTY(PingType); \ + PROPERTY(Timeout); \ + PROPERTY(TryAgent); \ + PROPERTY(AgentFwd); \ + PROPERTY(AuthTIS); \ + PROPERTY(ChangeUsername); \ + PROPERTY(Compression); \ + PROPERTY(SshProt); \ + PROPERTY(Ssh2DES); \ + PROPERTY(SshNoUserAuth); \ + PROPERTY(CipherList); \ + PROPERTY(KexList); \ + PROPERTY(AddressFamily); \ + PROPERTY(RekeyData); \ + PROPERTY(RekeyTime); \ + PROPERTY(HostKey); \ + PROPERTY(FingerprintScan); \ + \ + PROPERTY(UpdateDirectories); \ + PROPERTY(CacheDirectories); \ + PROPERTY(CacheDirectoryChanges); \ + PROPERTY(PreserveDirectoryChanges); \ + \ + PROPERTY(ResolveSymlinks); \ + PROPERTY(FollowDirectorySymlinks); \ + PROPERTY(DSTMode); \ + PROPERTY(LockInHome); \ + PROPERTY(Special); \ + PROPERTY(Selected); \ + PROPERTY(ReturnVar); \ + PROPERTY(LookupUserGroups); \ + PROPERTY(EOLType); \ + PROPERTY(TrimVMSVersions); \ + PROPERTY(Shell); \ + PROPERTY(ClearAliases); \ + PROPERTY(Scp1Compatibility); \ + PROPERTY(UnsetNationalVars); \ + PROPERTY(ListingCommand); \ + PROPERTY(IgnoreLsWarnings); \ + PROPERTY(SCPLsFullTime); \ + \ + PROPERTY(TimeDifference); \ + PROPERTY(TimeDifferenceAuto); \ + PROPERTY(TcpNoDelay); \ + PROPERTY(SendBuf); \ + PROPERTY(SshSimple); \ + PROPERTY(AuthKI); \ + PROPERTY(AuthKIPassword); \ + PROPERTY(AuthGSSAPI); \ + PROPERTY(GSSAPIFwdTGT); \ + PROPERTY(GSSAPIServerRealm); \ + PROPERTY(DeleteToRecycleBin); \ + PROPERTY(OverwrittenToRecycleBin); \ + PROPERTY(RecycleBinPath); \ + PROPERTY(NotUtf); \ + PROPERTY(PostLoginCommands); \ + \ + PROPERTY(ProxyMethod); \ + PROPERTY(ProxyHost); \ + PROPERTY(ProxyPort); \ + PROPERTY(ProxyUsername); \ + PROPERTY(ProxyPassword); \ + PROPERTY(ProxyTelnetCommand); \ + PROPERTY(ProxyLocalCommand); \ + PROPERTY(ProxyDNS); \ + PROPERTY(ProxyLocalhost); \ + \ + PROPERTY(SftpServer); \ + PROPERTY(SFTPDownloadQueue); \ + PROPERTY(SFTPUploadQueue); \ + PROPERTY(SFTPListingQueue); \ + PROPERTY(SFTPMaxVersion); \ + PROPERTY(SFTPMaxPacketSize); \ + \ + PROPERTY(Tunnel); \ + PROPERTY(TunnelHostName); \ + PROPERTY(TunnelPortNumber); \ + PROPERTY(TunnelUserName); \ + PROPERTY(TunnelPassword); \ + PROPERTY(TunnelPublicKeyFile); \ + PROPERTY(TunnelLocalPortNumber); \ + PROPERTY(TunnelPortFwd); \ + PROPERTY(TunnelHostKey); \ + \ + PROPERTY(FtpPasvMode); \ + PROPERTY(FtpForcePasvIp); \ + PROPERTY(FtpUseMlsd); \ + PROPERTY(FtpAccount); \ + PROPERTY(FtpPingInterval); \ + PROPERTY(FtpPingType); \ + PROPERTY(FtpTransferActiveImmediately); \ + PROPERTY(FtpListAll); \ + PROPERTY(FtpHost); \ + PROPERTY(FtpDupFF); \ + PROPERTY(FtpUndupFF); \ + PROPERTY(SslSessionReuse); \ + PROPERTY(TlsCertificateFile); \ + \ + PROPERTY(FtpProxyLogonType); \ + \ + PROPERTY(MinTlsVersion); \ + PROPERTY(MaxTlsVersion); \ + \ + PROPERTY(CustomParam1); \ + PROPERTY(CustomParam2); \ + \ + PROPERTY(CodePage); \ + PROPERTY(LoginType); \ + PROPERTY(FtpAllowEmptyPassword); + + //PROPERTY(IsWorkspace); \ + //PROPERTY(Link); + +#define META_PROPERTIES \ + PROPERTY(IsWorkspace); \ + PROPERTY(Link); + +void TSessionData::Assign(const TPersistent * Source) +{ + if (Source && (NB_STATIC_DOWNCAST_CONST(TSessionData, Source) != nullptr)) + { + TSessionData * SourceData = NB_STATIC_DOWNCAST(TSessionData, const_cast(Source)); + CopyData(SourceData); + FSource = SourceData->FSource; + } + else + { + TNamedObject::Assign(Source); + } +} + +void TSessionData::CopyData(TSessionData * SourceData) +{ +#define PROPERTY(P) Set ## P(SourceData->Get ## P()) + PROPERTY(Name); + BASE_PROPERTIES; + ADVANCED_PROPERTIES; + //META_PROPERTIES; +#undef PROPERTY + + SetUserName(SourceData->SessionGetUserName()); + for (intptr_t Index = 0; Index < static_cast(_countof(FBugs)); ++Index) + { + // PROPERTY(Bug[(TSshBug)Index]); + SetBug(static_cast(Index), + SourceData->GetBug(static_cast(Index))); + } + for (intptr_t Index = 0; Index < static_cast(_countof(FSFTPBugs)); ++Index) + { + // PROPERTY(SFTPBug[(TSftpBug)Index]); + SetSFTPBug(static_cast(Index), + SourceData->GetSFTPBug(static_cast(Index))); + } + // Restore default kex list + for (intptr_t Index = 0; Index < KEX_COUNT; ++Index) + { + SetKex(Index, DefaultKexList[Index]); + } + + FOverrideCachedHostKey = SourceData->GetOverrideCachedHostKey(); + FModified = SourceData->GetModified(); + FSaveOnly = SourceData->GetSaveOnly(); + + FSource = SourceData->FSource; + FNumberOfRetries = SourceData->FNumberOfRetries; +} + +void TSessionData::CopyDirectoriesStateData(TSessionData * SourceData) +{ + SetRemoteDirectory(SourceData->GetRemoteDirectory()); + SetLocalDirectory(SourceData->GetLocalDirectory()); + SetSynchronizeBrowsing(SourceData->GetSynchronizeBrowsing()); +} + +bool TSessionData::HasStateData() const +{ + return + !GetRemoteDirectory().IsEmpty() || + !GetLocalDirectory().IsEmpty() || + (GetColor() != 0); +} + +void TSessionData::CopyStateData(TSessionData * SourceData) +{ + // Keep in sync with TCustomScpExplorerForm::UpdateSessionData. + CopyDirectoriesStateData(SourceData); + SetColor(SourceData->GetColor()); +} + +void TSessionData::CopyNonCoreData(TSessionData * SourceData) +{ + CopyStateData(SourceData); + SetUpdateDirectories(SourceData->GetUpdateDirectories()); + SetNote(SourceData->GetNote()); +} + +bool TSessionData::IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties) const +{ + bool Result = true; +#define PROPERTY(P) \ + if (Get ## P() != Default->Get ## P()) \ + { \ + if (DifferentProperties != nullptr) \ + { \ + DifferentProperties->Add(# P); \ + } \ + Result = false; \ + } + + if (!AdvancedOnly) + { + BASE_PROPERTIES; + //META_PROPERTIES; + } + ADVANCED_PROPERTIES; +#undef PROPERTY + + for (intptr_t Index = 0; Index < static_cast(_countof(FBugs)); ++Index) + { + // PROPERTY(Bug[(TSshBug)Index]); + if (GetBug(static_cast(Index)) != Default->GetBug(static_cast(Index))) + return false; + } + for (intptr_t Index = 0; Index < static_cast(_countof(FSFTPBugs)); ++Index) + { + // PROPERTY(SFTPBug[(TSftpBug)Index]); + if (GetSFTPBug(static_cast(Index)) != Default->GetSFTPBug(static_cast(Index))) + return false; + } + + return Result; +} + +bool TSessionData::IsSame(const TSessionData * Default, bool AdvancedOnly) const +{ + return IsSame(Default, AdvancedOnly, nullptr); +} + +bool TSessionData::IsSameSite(const TSessionData * Other) const +{ + return + (GetFSProtocol() == Other->GetFSProtocol()) && + (GetHostName() == Other->GetHostName()) && + (GetPortNumber() == Other->GetPortNumber()) && + (SessionGetUserName() == Other->SessionGetUserName()); +} + +bool TSessionData::IsInFolderOrWorkspace(const UnicodeString & AFolder) const +{ + return ::StartsText(core::UnixIncludeTrailingBackslash(AFolder), GetName()); +} + +void TSessionData::DoLoad(THierarchicalStorage * Storage, bool & RewritePassword) +{ + SetSessionVersion(::StrToVersionNumber(Storage->ReadString("Version", L""))); + // Make sure we only ever use methods supported by TOptionsStorage + // (implemented by TOptionsIniFile) + + SetPortNumber(Storage->ReadInteger("PortNumber", GetPortNumber())); + SetUserName(Storage->ReadString("UserName", SessionGetUserName())); + // must be loaded after UserName, because HostName may be in format user@host + SetHostName(Storage->ReadString("HostName", GetHostName())); + + if (!GetConfiguration()->GetDisablePasswordStoring()) + { + if (Storage->ValueExists("PasswordPlain")) + { + SetPassword(Storage->ReadString("PasswordPlain", GetPassword())); + RewritePassword = true; + } + else + { + FPassword = Storage->ReadStringAsBinaryData("Password", FPassword); + } + } + SetHostKey(Storage->ReadString("HostKey", GetHostKey())); + SetNote(Storage->ReadString("Note", GetNote())); + // Putty uses PingIntervalSecs + intptr_t PingIntervalSecs = Storage->ReadInteger("PingIntervalSecs", -1); + if (PingIntervalSecs < 0) + { + PingIntervalSecs = Storage->ReadInteger("PingIntervalSec", GetPingInterval() % SecsPerMin); + } + SetPingInterval( + Storage->ReadInteger("PingInterval", GetPingInterval() / SecsPerMin) * SecsPerMin + + PingIntervalSecs); + if (GetPingInterval() == 0) + { + SetPingInterval(30); + } + SetPingType(static_cast(Storage->ReadInteger("PingType", GetPingType()))); + SetTimeout(Storage->ReadInteger("Timeout", GetTimeout())); + SetTryAgent(Storage->ReadBool("TryAgent", GetTryAgent())); + SetAgentFwd(Storage->ReadBool("AgentFwd", GetAgentFwd())); + SetAuthTIS(Storage->ReadBool("AuthTIS", GetAuthTIS())); + SetAuthKI(Storage->ReadBool("AuthKI", GetAuthKI())); + SetAuthKIPassword(Storage->ReadBool("AuthKIPassword", GetAuthKIPassword())); + // Continue to use setting keys of previous kerberos implementation (vaclav tomec), + // but fallback to keys of other implementations (official putty and vintela quest putty), + // to allow imports from all putty versions. + // Both vaclav tomec and official putty use AuthGSSAPI + SetAuthGSSAPI(Storage->ReadBool("AuthGSSAPI", Storage->ReadBool("AuthSSPI", GetAuthGSSAPI()))); + SetGSSAPIFwdTGT(Storage->ReadBool("GSSAPIFwdTGT", Storage->ReadBool("GssapiFwd", Storage->ReadBool("SSPIFwdTGT", GetGSSAPIFwdTGT())))); + SetGSSAPIServerRealm(Storage->ReadString("GSSAPIServerRealm", Storage->ReadString("KerbPrincipal", GetGSSAPIServerRealm()))); + SetChangeUsername(Storage->ReadBool("ChangeUsername", GetChangeUsername())); + SetCompression(Storage->ReadBool("Compression", GetCompression())); + TSshProt ASshProt = static_cast(Storage->ReadInteger(L"SshProt", GetSshProt())); + // Old sessions may contain the values correponding to the fallbacks we used to allow; migrate them + if (ASshProt == ssh2deprecated) + { + ASshProt = ssh2only; + } + else if (ASshProt == ssh1deprecated) + { + ASshProt = ssh1only; + } + SetSshProt(ASshProt); + SetSsh2DES(Storage->ReadBool("Ssh2DES", GetSsh2DES())); + SetSshNoUserAuth(Storage->ReadBool("SshNoUserAuth", GetSshNoUserAuth())); + SetCipherList(Storage->ReadString("Cipher", GetCipherList())); + SetKexList(Storage->ReadString("KEX", GetKexList())); + SetPublicKeyFile(Storage->ReadString("PublicKeyFile", GetPublicKeyFile())); + SetAddressFamily(static_cast + (Storage->ReadInteger("AddressFamily", GetAddressFamily()))); + SetRekeyData(Storage->ReadString("RekeyBytes", GetRekeyData())); + SetRekeyTime(Storage->ReadInteger("RekeyTime", GetRekeyTime())); + + if (GetSessionVersion() < ::GetVersionNumber2121()) + { + SetFSProtocol(TranslateFSProtocolNumber(Storage->ReadInteger("FSProtocol", GetFSProtocol()))); + } + else + { + SetFSProtocol(TranslateFSProtocol(Storage->ReadString("FSProtocol", GetFSProtocolStr()))); + } + SetLocalDirectory(Storage->ReadString("LocalDirectory", GetLocalDirectory())); + SetRemoteDirectory(Storage->ReadString("RemoteDirectory", GetRemoteDirectory())); + SetSynchronizeBrowsing(Storage->ReadBool("SynchronizeBrowsing", GetSynchronizeBrowsing())); + SetUpdateDirectories(Storage->ReadBool("UpdateDirectories", GetUpdateDirectories())); + SetCacheDirectories(Storage->ReadBool("CacheDirectories", GetCacheDirectories())); + SetCacheDirectoryChanges(Storage->ReadBool("CacheDirectoryChanges", GetCacheDirectoryChanges())); + SetPreserveDirectoryChanges(Storage->ReadBool("PreserveDirectoryChanges", GetPreserveDirectoryChanges())); + + SetResolveSymlinks(Storage->ReadBool("ResolveSymlinks", GetResolveSymlinks())); + SetFollowDirectorySymlinks(Storage->ReadBool("FollowDirectorySymlinks", GetFollowDirectorySymlinks())); + SetDSTMode(static_cast(Storage->ReadInteger("ConsiderDST", GetDSTMode()))); + SetLockInHome(Storage->ReadBool("LockInHome", GetLockInHome())); + SetSpecial(Storage->ReadBool("Special", GetSpecial())); + SetShell(Storage->ReadString("Shell", GetShell())); + SetClearAliases(Storage->ReadBool("ClearAliases", GetClearAliases())); + SetUnsetNationalVars(Storage->ReadBool("UnsetNationalVars", GetUnsetNationalVars())); + SetListingCommand(Storage->ReadString("ListingCommand", + Storage->ReadBool("AliasGroupList", false) ? UnicodeString("ls -gla") : GetListingCommand())); + SetIgnoreLsWarnings(Storage->ReadBool("IgnoreLsWarnings", GetIgnoreLsWarnings())); + SetSCPLsFullTime(static_cast(Storage->ReadInteger("SCPLsFullTime", GetSCPLsFullTime()))); + SetScp1Compatibility(Storage->ReadBool("Scp1Compatibility", GetScp1Compatibility())); + SetTimeDifference(TDateTime(Storage->ReadFloat("TimeDifference", GetTimeDifference()))); + SetTimeDifferenceAuto(Storage->ReadBool("TimeDifferenceAuto", (GetTimeDifference() == TDateTime()))); + SetDeleteToRecycleBin(Storage->ReadBool("DeleteToRecycleBin", GetDeleteToRecycleBin())); + SetOverwrittenToRecycleBin(Storage->ReadBool("OverwrittenToRecycleBin", GetOverwrittenToRecycleBin())); + SetRecycleBinPath(Storage->ReadString("RecycleBinPath", GetRecycleBinPath())); + SetPostLoginCommands(Storage->ReadString("PostLoginCommands", GetPostLoginCommands())); + + SetReturnVar(Storage->ReadString("ReturnVar", GetReturnVar())); + SetLookupUserGroups(static_cast(Storage->ReadInteger("LookupUserGroups", GetLookupUserGroups()))); + SetEOLType(static_cast(Storage->ReadInteger("EOLType", GetEOLType()))); + SetTrimVMSVersions(Storage->ReadBool("TrimVMSVersions", GetTrimVMSVersions())); + SetNotUtf(static_cast(Storage->ReadInteger("Utf", Storage->ReadInteger("SFTPUtfBug", GetNotUtf())))); + + SetTcpNoDelay(Storage->ReadBool("TcpNoDelay", GetTcpNoDelay())); + SetSendBuf(Storage->ReadInteger("SendBuf", Storage->ReadInteger("SshSendBuf", GetSendBuf()))); + SetSshSimple(Storage->ReadBool("SshSimple", GetSshSimple())); + + SetProxyMethod(static_cast(Storage->ReadInteger("ProxyMethod", ::pmNone))); + if (GetProxyMethod() != pmSystem) + { + SetProxyHost(Storage->ReadString("ProxyHost", GetProxyHost())); + SetProxyPort(Storage->ReadInteger("ProxyPort", GetProxyPort())); + } + SetProxyUsername(Storage->ReadString("ProxyUsername", GetProxyUsername())); + if (Storage->ValueExists("ProxyPassword")) + { + // encrypt unencrypted password + SetProxyPassword(Storage->ReadString("ProxyPassword", L"")); + } + else + { + // load encrypted password + FProxyPassword = Storage->ReadStringAsBinaryData("ProxyPasswordEnc", FProxyPassword); + } + if (GetProxyMethod() == pmCmd) + { + SetProxyLocalCommand(Storage->ReadStringRaw("ProxyTelnetCommand", GetProxyLocalCommand())); + } + else + { + SetProxyTelnetCommand(Storage->ReadStringRaw("ProxyTelnetCommand", GetProxyTelnetCommand())); + } + SetProxyDNS(static_cast((Storage->ReadInteger("ProxyDNS", (GetProxyDNS() + 2) % 3) + 1) % 3)); + SetProxyLocalhost(Storage->ReadBool("ProxyLocalhost", GetProxyLocalhost())); + +#define READ_BUG(BUG) \ + SetBug(sb##BUG, TAutoSwitch(2 - Storage->ReadInteger(MB_TEXT("Bug"#BUG), \ + 2 - GetBug(sb##BUG)))); + READ_BUG(Ignore1); + READ_BUG(PlainPW1); + READ_BUG(RSA1); + READ_BUG(HMAC2); + READ_BUG(DeriveKey2); + READ_BUG(RSAPad2); + READ_BUG(PKSessID2); + READ_BUG(Rekey2); + READ_BUG(MaxPkt2); + READ_BUG(Ignore2); + READ_BUG(OldGex2); + READ_BUG(WinAdj); +#undef READ_BUG + + if ((GetBug(sbHMAC2) == asAuto) && + Storage->ReadBool("BuggyMAC", false)) + { + SetBug(sbHMAC2, asOn); + } + + SetSftpServer(Storage->ReadString("SftpServer", GetSftpServer())); +#define READ_SFTP_BUG(BUG) \ + SetSFTPBug(sb##BUG, TAutoSwitch(Storage->ReadInteger(MB_TEXT("SFTP" #BUG "Bug"), GetSFTPBug(sb##BUG)))); + READ_SFTP_BUG(Symlink); + READ_SFTP_BUG(SignedTS); +#undef READ_SFTP_BUG + + SetSFTPMaxVersion(Storage->ReadInteger("SFTPMaxVersion", GetSFTPMaxVersion())); + SetSFTPMinPacketSize(Storage->ReadInteger("SFTPMinPacketSize", GetSFTPMinPacketSize())); + SetSFTPMaxPacketSize(Storage->ReadInteger("SFTPMaxPacketSize", GetSFTPMaxPacketSize())); + SetSFTPDownloadQueue(Storage->ReadInteger("SFTPDownloadQueue", GetSFTPDownloadQueue())); + SetSFTPUploadQueue(Storage->ReadInteger("SFTPUploadQueue", GetSFTPUploadQueue())); + SetSFTPListingQueue(Storage->ReadInteger("SFTPListingQueue", GetSFTPListingQueue())); + + SetColor(Storage->ReadInteger("Color", GetColor())); + + // ??? + // SetProtocolStr(Storage->ReadString("Protocol", GetProtocolStr())); + + SetPuttyProtocol(Storage->ReadString("Protocol", GetPuttyProtocol())); + + SetTunnel(Storage->ReadBool("Tunnel", GetTunnel())); + SetTunnelPortNumber(Storage->ReadInteger("TunnelPortNumber", GetTunnelPortNumber())); + SetTunnelUserName(Storage->ReadString("TunnelUserName", GetTunnelUserName())); + // must be loaded after TunnelUserName, + // because TunnelHostName may be in format user@host + SetTunnelHostName(Storage->ReadString("TunnelHostName", GetTunnelHostName())); + if (!GetConfiguration()->GetDisablePasswordStoring()) + { + if (Storage->ValueExists("TunnelPasswordPlain")) + { + SetTunnelPassword(Storage->ReadString("TunnelPasswordPlain", GetTunnelPassword())); + RewritePassword = true; + } + else + { + FTunnelPassword = Storage->ReadStringAsBinaryData("TunnelPassword", FTunnelPassword); + } + } + SetTunnelPublicKeyFile(Storage->ReadString("TunnelPublicKeyFile", GetTunnelPublicKeyFile())); + SetTunnelLocalPortNumber(Storage->ReadInteger("TunnelLocalPortNumber", GetTunnelLocalPortNumber())); + SetTunnelHostKey(Storage->ReadString("TunnelHostKey", GetTunnelHostKey())); + + // Ftp prefix + SetFtpPasvMode(Storage->ReadBool("FtpPasvMode", GetFtpPasvMode())); + SetFtpForcePasvIp(static_cast(Storage->ReadInteger("FtpForcePasvIp2", GetFtpForcePasvIp()))); + SetFtpUseMlsd(static_cast(Storage->ReadInteger("FtpUseMlsd", GetFtpUseMlsd()))); + SetFtpAccount(Storage->ReadString("FtpAccount", GetFtpAccount())); + SetFtpPingInterval(Storage->ReadInteger("FtpPingInterval", GetFtpPingInterval())); + SetFtpPingType(static_cast(Storage->ReadInteger("FtpPingType", GetFtpPingType()))); + SetFtpTransferActiveImmediately(static_cast(Storage->ReadInteger("FtpTransferActiveImmediately2", GetFtpTransferActiveImmediately()))); + SetFtps(static_cast(Storage->ReadInteger("Ftps", GetFtps()))); + SetFtpListAll(static_cast(Storage->ReadInteger("FtpListAll", GetFtpListAll()))); + SetFtpDupFF(Storage->ReadBool("FtpDupFF", GetFtpDupFF())); + SetFtpUndupFF(Storage->ReadBool("FtpUndupFF", GetFtpUndupFF())); + SetFtpHost(static_cast(Storage->ReadInteger("FtpHost", GetFtpHost()))); + SetSslSessionReuse(Storage->ReadBool("SslSessionReuse", GetSslSessionReuse())); + SetTlsCertificateFile(Storage->ReadString("TlsCertificateFile", GetTlsCertificateFile())); + + SetFtpProxyLogonType(Storage->ReadInteger("FtpProxyLogonType", GetFtpProxyLogonType())); + + SetMinTlsVersion(static_cast(Storage->ReadInteger("MinTlsVersion", GetMinTlsVersion()))); + SetMaxTlsVersion(static_cast(Storage->ReadInteger("MaxTlsVersion", GetMaxTlsVersion()))); + + // SetIsWorkspace(Storage->ReadBool("IsWorkspace", GetIsWorkspace())); + // SetLink(Storage->ReadString("Link", GetLink())); + + SetCustomParam1(Storage->ReadString("CustomParam1", GetCustomParam1())); + SetCustomParam2(Storage->ReadString("CustomParam2", GetCustomParam2())); + + SetCodePage(Storage->ReadString("CodePage", GetCodePage())); + SetLoginType(static_cast(Storage->ReadInteger("LoginType", GetLoginType()))); + SetFtpAllowEmptyPassword(Storage->ReadBool("FtpAllowEmptyPassword", GetFtpAllowEmptyPassword())); + if (GetSessionVersion() < ::GetVersionNumber2110()) + { + SetFtps(TranslateFtpEncryptionNumber(Storage->ReadInteger("FtpEncryption", -1))); + } + +#ifdef TEST + #define KEX_TEST(VALUE, EXPECTED) KexList = VALUE; DebugAssert(KexList == EXPECTED); + #define KEX_DEFAULT L"ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN" + // Empty source should result in default list + KEX_TEST(L"", KEX_DEFAULT); + // Default of pre 5.8.1 should result in new default + KEX_TEST(L"dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN", KEX_DEFAULT); + // Missing first two priority algos, and last non-priority algo => default + KEX_TEST(L"dh-group14-sha1,dh-group1-sha1,WARN", L"ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"); + // Missing first two priority algos, last non-priority algo and WARN => default + KEX_TEST(L"dh-group14-sha1,dh-group1-sha1", L"ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"); + // Old algos, with all but the first below WARN + KEX_TEST(L"dh-gex-sha1,WARN,dh-group14-sha1,dh-group1-sha1,rsa", L"ecdh,dh-gex-sha1,WARN,dh-group14-sha1,dh-group1-sha1,rsa"); + // Unknown algo at front + KEX_TEST(L"unknown,ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN", KEX_DEFAULT); + // Unknown algo at back + KEX_TEST(L"ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN,unknown", KEX_DEFAULT); + // Unknown algo in the middle + KEX_TEST(L"ecdh,dh-gex-sha1,dh-group14-sha1,unknown,dh-group1-sha1,rsa,WARN", KEX_DEFAULT); + #undef KEX_DEFAULT + #undef KEX_TEST + + #define CIPHER_TEST(VALUE, EXPECTED) CipherList = VALUE; DebugAssert(CipherList == EXPECTED); + #define CIPHER_DEFAULT L"aes,blowfish,chacha20,3des,WARN,arcfour,des" + // Empty source should result in default list + CIPHER_TEST(L"", CIPHER_DEFAULT); + // Default of pre 5.8.1 + CIPHER_TEST(L"aes,blowfish,3des,WARN,arcfour,des", L"aes,blowfish,3des,chacha20,WARN,arcfour,des"); + // Missing priority algo + CIPHER_TEST(L"blowfish,chacha20,3des,WARN,arcfour,des", CIPHER_DEFAULT); + // Missing non-priority algo + CIPHER_TEST(L"aes,chacha20,3des,WARN,arcfour,des", L"aes,chacha20,3des,blowfish,WARN,arcfour,des"); + // Missing last warn algo + CIPHER_TEST(L"aes,blowfish,chacha20,3des,WARN,arcfour", L"aes,blowfish,chacha20,3des,WARN,arcfour,des"); + // Missing first warn algo + CIPHER_TEST(L"aes,blowfish,chacha20,3des,WARN,des", L"aes,blowfish,chacha20,3des,WARN,des,arcfour"); + #undef CIPHER_DEFAULT + #undef CIPHER_TEST +#endif +} + +void TSessionData::Load(THierarchicalStorage * Storage) +{ + bool RewritePassword = false; + if (Storage->OpenSubKey(GetInternalStorageKey(), False)) + { + // In case we are re-loading, reset passwords, to avoid pointless + // re-cryption, while loading username/hostname. And moreover, when + // the password is wrongly encrypted (using a different master password), + // this breaks sites reload and consequently an overall operation, + // such as opening Sites menu + ClearSessionPasswords(); + SetProxyPassword(L""); + + DoLoad(Storage, RewritePassword); + + Storage->CloseSubKey(); + } + + if (RewritePassword) + { + TStorageAccessMode AccessMode = Storage->GetAccessMode(); + Storage->SetAccessMode(smReadWrite); + + try + { + if (Storage->OpenSubKey(GetInternalStorageKey(), true)) + { + Storage->DeleteValue("PasswordPlain"); + if (!GetPassword().IsEmpty()) + { + Storage->WriteBinaryDataAsString("Password", FPassword); + } + Storage->DeleteValue("TunnelPasswordPlain"); + if (!GetTunnelPassword().IsEmpty()) + { + Storage->WriteBinaryDataAsString("TunnelPassword", FTunnelPassword); + } + Storage->CloseSubKey(); + } + } + catch (...) + { + // ignore errors (like read-only INI file) + } + + Storage->SetAccessMode(AccessMode); + } + + FNumberOfRetries = 0; + FModified = false; + FSource = ssStored; +} + +void TSessionData::DoSave(THierarchicalStorage * Storage, + bool PuttyExport, const TSessionData * Default, bool DoNotEncryptPasswords) +{ +#define WRITE_DATA_EX(TYPE, NAME, PROPERTY, CONV) \ + if ((Default != nullptr) && (CONV(Default->PROPERTY) == CONV(PROPERTY))) \ + { \ + Storage->DeleteValue(NAME); \ + } \ + else \ + { \ + Storage->Write ## TYPE(NAME, CONV(PROPERTY)); \ + } +#define WRITE_DATA_EX2(TYPE, NAME, PROPERTY, CONV) \ + { \ + Storage->Write ## TYPE(NAME, CONV(PROPERTY)); \ + } +#define WRITE_DATA_CONV(TYPE, NAME, PROPERTY) WRITE_DATA_EX(TYPE, NAME, PROPERTY, WRITE_DATA_CONV_FUNC) +#define WRITE_DATA(TYPE, PROPERTY) WRITE_DATA_EX(TYPE, MB_TEXT(#PROPERTY), Get ## PROPERTY(), ) + + Storage->WriteString("Version", ::VersionNumberToStr(::GetCurrentVersionNumber())); + WRITE_DATA(String, HostName); + WRITE_DATA(Integer, PortNumber); + WRITE_DATA_EX(Integer, "PingInterval", GetPingInterval() / SecsPerMin, ); + WRITE_DATA_EX(Integer, "PingIntervalSecs", GetPingInterval() % SecsPerMin, ); + Storage->DeleteValue("PingIntervalSec"); // obsolete + WRITE_DATA(Integer, PingType); + WRITE_DATA(Integer, Timeout); + WRITE_DATA(Bool, TryAgent); + WRITE_DATA(Bool, AgentFwd); + WRITE_DATA(Bool, AuthTIS); + WRITE_DATA(Bool, AuthKI); + WRITE_DATA(Bool, AuthKIPassword); + WRITE_DATA(String, Note); + + WRITE_DATA(Bool, AuthGSSAPI); + WRITE_DATA(Bool, GSSAPIFwdTGT); + WRITE_DATA(String, GSSAPIServerRealm); + Storage->DeleteValue("TryGSSKEX"); + Storage->DeleteValue("UserNameFromEnvironment"); + Storage->DeleteValue("GSSAPIServerChoosesUserName"); + Storage->DeleteValue("GSSAPITrustDNS"); + if (PuttyExport) + { + // duplicate kerberos setting with keys of the vintela quest putty + WRITE_DATA_EX(Bool, "AuthSSPI", GetAuthGSSAPI(), ); + WRITE_DATA_EX(Bool, "SSPIFwdTGT", GetGSSAPIFwdTGT(), ); + WRITE_DATA_EX(String, "KerbPrincipal", GetGSSAPIServerRealm(), ); + // duplicate kerberos setting with keys of the official putty + WRITE_DATA_EX(Bool, "GssapiFwd", GetGSSAPIFwdTGT(), ); + } + + WRITE_DATA(Bool, ChangeUsername); + WRITE_DATA(Bool, Compression); + WRITE_DATA(Integer, SshProt); + WRITE_DATA(Bool, Ssh2DES); + WRITE_DATA(Bool, SshNoUserAuth); + WRITE_DATA_EX(String, "Cipher", GetCipherList(), ); + WRITE_DATA_EX(String, "KEX", GetKexList(), ); + WRITE_DATA(Integer, AddressFamily); + WRITE_DATA_EX(String, "RekeyBytes", GetRekeyData(), ); + WRITE_DATA(Integer, RekeyTime); + + WRITE_DATA(Bool, TcpNoDelay); + + if (PuttyExport) + { +// WRITE_DATA(StringRaw, UserName); + WRITE_DATA_EX(StringRaw, "UserName", SessionGetUserName(), ); + + WRITE_DATA(StringRaw, PublicKeyFile); + } + else + { +// WRITE_DATA(String, UserName); + WRITE_DATA_EX(String, "UserName", SessionGetUserName(), ); + WRITE_DATA(String, PublicKeyFile); + WRITE_DATA_EX2(String, "FSProtocol", GetFSProtocolStr(), ); + WRITE_DATA(String, LocalDirectory); + WRITE_DATA(String, RemoteDirectory); + WRITE_DATA(Bool, SynchronizeBrowsing); + WRITE_DATA(Bool, UpdateDirectories); + WRITE_DATA(Bool, CacheDirectories); + WRITE_DATA(Bool, CacheDirectoryChanges); + WRITE_DATA(Bool, PreserveDirectoryChanges); + + WRITE_DATA(Bool, ResolveSymlinks); + WRITE_DATA(Bool, FollowDirectorySymlinks); + WRITE_DATA_EX(Integer, "ConsiderDST", GetDSTMode(), ); + WRITE_DATA(Bool, LockInHome); + // Special is never stored (if it would, login dialog must be modified not to + // duplicate Special parameter when Special session is loaded and then stored + // under different name) + // WRITE_DATA(Bool, Special); + WRITE_DATA(String, Shell); + WRITE_DATA(Bool, ClearAliases); + WRITE_DATA(Bool, UnsetNationalVars); + WRITE_DATA(String, ListingCommand); + WRITE_DATA(Bool, IgnoreLsWarnings); + WRITE_DATA(Integer, SCPLsFullTime); + WRITE_DATA(Bool, Scp1Compatibility); + // TimeDifferenceAuto is valid for FTP protocol only. + // For other protocols it's typically true (default value), + // but ignored so TimeDifference is still taken into account (SCP only actually) + if (FTimeDifferenceAuto && (GetFSProtocol() == fsFTP)) + { + // Have to delete it as TimeDifferenceAuto is not saved when enabled, + // but the default is derived from value of TimeDifference. + Storage->DeleteValue("TimeDifference"); + } + else + { + WRITE_DATA(Float, TimeDifference); + } + WRITE_DATA(Bool, TimeDifferenceAuto); + WRITE_DATA(Bool, DeleteToRecycleBin); + WRITE_DATA(Bool, OverwrittenToRecycleBin); + WRITE_DATA(String, RecycleBinPath); + WRITE_DATA(String, PostLoginCommands); + + WRITE_DATA(String, ReturnVar); + WRITE_DATA_EX(Integer, "LookupUserGroups2", GetLookupUserGroups(), ); + WRITE_DATA(Integer, EOLType); + WRITE_DATA(Bool, TrimVMSVersions); + Storage->DeleteValue("SFTPUtfBug"); + WRITE_DATA_EX(Integer, "Utf", GetNotUtf(), ); + WRITE_DATA(Integer, SendBuf); + WRITE_DATA(Bool, SshSimple); + } + + WRITE_DATA(Integer, ProxyMethod); + if (GetProxyMethod() != pmSystem) + { + WRITE_DATA(String, ProxyHost); + WRITE_DATA(Integer, ProxyPort); + } + WRITE_DATA(String, ProxyUsername); + if (GetProxyMethod() == pmCmd) + { + WRITE_DATA_EX(StringRaw, "ProxyTelnetCommand", GetProxyLocalCommand(), ); + } + else + { + WRITE_DATA_EX(StringRaw, "ProxyTelnetCommand", GetProxyTelnetCommand(), ); + } +#define WRITE_DATA_CONV_FUNC(X) (((X) + 2) % 3) + WRITE_DATA_CONV(Integer, "ProxyDNS", GetProxyDNS()); +#undef WRITE_DATA_CONV_FUNC + WRITE_DATA_EX(Bool, "ProxyLocalhost", GetProxyLocalhost(), ); + +#define WRITE_DATA_CONV_FUNC(X) (2 - (X)) +#define WRITE_BUG(BUG) WRITE_DATA_CONV(Integer, MB_TEXT("Bug" #BUG), GetBug(sb##BUG)); + WRITE_BUG(Ignore1); + WRITE_BUG(PlainPW1); + WRITE_BUG(RSA1); + WRITE_BUG(HMAC2); + WRITE_BUG(DeriveKey2); + WRITE_BUG(RSAPad2); + WRITE_BUG(PKSessID2); + WRITE_BUG(Rekey2); + WRITE_BUG(MaxPkt2); + WRITE_BUG(Ignore2); + WRITE_BUG(OldGex2); + WRITE_BUG(WinAdj); +#undef WRITE_BUG +#undef WRITE_DATA_CONV_FUNC + + Storage->DeleteValue("BuggyMAC"); + Storage->DeleteValue("AliasGroupList"); + + if (PuttyExport) + { + WRITE_DATA_EX(String, "Protocol", GetNormalizedPuttyProtocol(), ); + } + + if (!PuttyExport) + { + WRITE_DATA(String, SftpServer); + +#define WRITE_SFTP_BUG(BUG) WRITE_DATA_EX(Integer, MB_TEXT("SFTP" #BUG "Bug"), GetSFTPBug(sb##BUG), ); + WRITE_SFTP_BUG(Symlink); + WRITE_SFTP_BUG(SignedTS); +#undef WRITE_SFTP_BUG + + WRITE_DATA(Integer, SFTPMaxVersion); + WRITE_DATA(Integer, SFTPMaxPacketSize); + WRITE_DATA(Integer, SFTPMinPacketSize); + WRITE_DATA(Integer, SFTPDownloadQueue); + WRITE_DATA(Integer, SFTPUploadQueue); + WRITE_DATA(Integer, SFTPListingQueue); + + WRITE_DATA(Integer, Color); + + WRITE_DATA(Bool, Tunnel); + WRITE_DATA(String, TunnelHostName); + WRITE_DATA(Integer, TunnelPortNumber); + WRITE_DATA(String, TunnelUserName); + WRITE_DATA(String, TunnelPublicKeyFile); + WRITE_DATA(Integer, TunnelLocalPortNumber); + + WRITE_DATA(Bool, FtpPasvMode); + WRITE_DATA_EX(Integer, "FtpForcePasvIp2", GetFtpForcePasvIp(), ); + WRITE_DATA(Integer, FtpUseMlsd); + WRITE_DATA(String, FtpAccount); + WRITE_DATA(Integer, FtpPingInterval); + WRITE_DATA(Integer, FtpPingType); + WRITE_DATA_EX(Integer, "FtpTransferActiveImmediately2", GetFtpTransferActiveImmediately(), ); + WRITE_DATA(Integer, Ftps); + WRITE_DATA(Integer, FtpListAll); + WRITE_DATA(Integer, FtpHost); + WRITE_DATA(Bool, FtpDupFF); + WRITE_DATA(Bool, FtpUndupFF); + WRITE_DATA(Bool, SslSessionReuse); + WRITE_DATA(String, TlsCertificateFile); + + WRITE_DATA(Integer, FtpProxyLogonType); + + WRITE_DATA(Integer, MinTlsVersion); + WRITE_DATA(Integer, MaxTlsVersion); + + // WRITE_DATA(Bool, IsWorkspace); + // WRITE_DATA(String, Link); + + WRITE_DATA(String, CustomParam1); + WRITE_DATA(String, CustomParam2); + + WRITE_DATA_EX(String, "CodePage", GetCodePage(), ); + WRITE_DATA_EX(Integer, "LoginType", GetLoginType(), ); + WRITE_DATA_EX(Bool, "FtpAllowEmptyPassword", GetFtpAllowEmptyPassword(), ); + } + + SavePasswords(Storage, PuttyExport, DoNotEncryptPasswords); +} + +TStrings * TSessionData::SaveToOptions(const TSessionData * Default) +{ + TODO("implement"); +#if 0 + std::unique_ptr Options(new TStringList()); + std::unique_ptr OptionsStorage(new TOptionsStorage(Options.get(), true, false)); + DoSave(OptionsStorage.get(), false, Default, true); + return Options.release(); +#endif + return nullptr; +} + +void TSessionData::Save(THierarchicalStorage * Storage, + bool PuttyExport, const TSessionData * Default) +{ + if (Storage->OpenSubKey(GetInternalStorageKey(), true)) + { + DoSave(Storage, PuttyExport, Default, false); + + Storage->CloseSubKey(); + } +} + +/*UnicodeString TSessionData::ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, const UnicodeString & Default) +{ + _di_IXMLNode TheNode = Node->ChildNodes->FindNode(Name); + UnicodeString Result; + if (TheNode != nullptr) + { + Result = TheNode->Text.Trim(); + } + + if (Result.IsEmpty()) + { + Result = Default; + } + + return Result; +} + +int TSessionData::ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, int Default) +{ + _di_IXMLNode TheNode = Node->ChildNodes->FindNode(Name); + int Result; + if (TheNode != nullptr) + { + Result = StrToIntDef(TheNode->Text.Trim(), Default); + } + else + { + Result = Default; + } + + return Result; +} + +void TSessionData::ImportFromFilezilla(_di_IXMLNode Node, const UnicodeString & APath) +{ + Name = UnixIncludeTrailingBackslash(Path) + MakeValidName(ReadXmlNode(Node, L"Name", Name)); + HostName = ReadXmlNode(Node, L"Host", HostName); + PortNumber = ReadXmlNode(Node, L"Port", PortNumber); + + int AProtocol = ReadXmlNode(Node, L"Protocol", 0); + // ServerProtocol enum + switch (AProtocol) + { + case 0: // FTP + default: // UNKNOWN, HTTP, HTTPS, INSECURE_FTP + FSProtocol = fsFTP; + break; + + case 1: // SFTP + FSProtocol = fsSFTP; + break; + + case 3: // FTPS + FSProtocol = fsFTP; + Ftps = ftpsImplicit; + break; + + case 4: // FTPES + FSProtocol = fsFTP; + Ftps = ftpsExplicitTls; + break; + } + + // LogonType enum + int LogonType = ReadXmlNode(Node, L"Logontype", 0); + if (LogonType == 0) // ANONYMOUS + { + UserName = ANONYMOUS_USER_NAME; + Password = ANONYMOUS_PASSWORD; + } + else + { + UserName = ReadXmlNode(Node, L"User", UserName); + FtpAccount = ReadXmlNode(Node, L"Account", FtpAccount); + + _di_IXMLNode PassNode = Node->ChildNodes->FindNode(L"Pass"); + if (PassNode != NULL) + { + UnicodeString APassword = PassNode->Text.Trim(); + OleVariant EncodingValue = PassNode->GetAttribute(L"encoding"); + if (!EncodingValue.IsNull()) + { + UnicodeString EncodingValueStr = EncodingValue; + if (SameText(EncodingValueStr, L"base64")) + { + TBytes Bytes = DecodeBase64(APassword); + APassword = TEncoding::UTF8->GetString(Bytes); + } + } + Password = APassword; + } + } + + int DefaultTimeDifference = TimeToSeconds(TimeDifference) / MSecsPerSec; + TimeDifference = + (double(ReadXmlNode(Node, L"TimezoneOffset", DefaultTimeDifference) / SecsPerDay)); + TimeDifferenceAuto = (TimeDifference == TDateTime()); + + UnicodeString PasvMode = ReadXmlNode(Node, L"PasvMode", L""); + if (SameText(PasvMode, L"MODE_PASSIVE")) + { + FtpPasvMode = true; + } + else if (SameText(PasvMode, L"MODE_ACTIVE")) + { + FtpPasvMode = false; + } + + UnicodeString EncodingType = ReadXmlNode(Node, L"EncodingType", L""); + if (SameText(EncodingType, L"Auto")) + { + NotUtf = asAuto; + } + else if (SameText(EncodingType, L"UTF-8")) + { + NotUtf = asOff; + } + + // todo PostLoginCommands + + Note = ReadXmlNode(Node, L"Comments", Note); + + LocalDirectory = ReadXmlNode(Node, L"LocalDir", LocalDirectory); + + UnicodeString RemoteDir = ReadXmlNode(Node, L"RemoteDir", L""); + if (!RemoteDir.IsEmpty()) + { + CutToChar(RemoteDir, L' ', false); // type + int PrefixSize = StrToIntDef(CutToChar(RemoteDir, L' ', false), 0); // prefix size + if (PrefixSize > 0) + { + RemoteDir.Delete(1, PrefixSize); + } + RemoteDirectory = L"/"; + while (!RemoteDir.IsEmpty()) + { + int SegmentSize = StrToIntDef(CutToChar(RemoteDir, L' ', false), 0); + UnicodeString Segment = RemoteDir.SubString(1, SegmentSize); + RemoteDirectory = UnixIncludeTrailingBackslash(RemoteDirectory) + Segment; + RemoteDir.Delete(1, SegmentSize + 1); + } + } + + SynchronizeBrowsing = (ReadXmlNode(Node, L"SyncBrowsing", SynchronizeBrowsing ? 1 : 0) != 0); +}*/ + +void TSessionData::SavePasswords(THierarchicalStorage * Storage, bool PuttyExport, bool DoNotEncryptPasswords) +{ + if (!GetConfiguration()->GetDisablePasswordStoring() && !PuttyExport && !FPassword.IsEmpty()) + { + // DoNotEncryptPasswords is set when called from GenerateOpenCommandArgs only + // and it never saves session password + DebugAssert(!DoNotEncryptPasswords); + + Storage->WriteBinaryDataAsString("Password", StronglyRecryptPassword(FPassword, SessionGetUserName() + GetHostName())); + } + else + { + Storage->DeleteValue("Password"); + } + Storage->DeleteValue("PasswordPlain"); + + if (PuttyExport) + { + // save password unencrypted + Storage->WriteString("ProxyPassword", GetProxyPassword()); + } + else + { + if (DoNotEncryptPasswords) + { + if (!FProxyPassword.IsEmpty()) + { + Storage->WriteString("ProxyPassword", FProxyPassword); + } + else + { + Storage->DeleteValue("ProxyPassword"); + } + Storage->DeleteValue("ProxyPasswordEnc"); + } + else + { + // save password encrypted + if (!FProxyPassword.IsEmpty()) + { + Storage->WriteBinaryDataAsString("ProxyPasswordEnc", StronglyRecryptPassword(FProxyPassword, GetProxyUsername() + GetProxyHost())); + } + else + { + Storage->DeleteValue("ProxyPasswordEnc"); + } + Storage->DeleteValue("ProxyPassword"); + } + + if (DoNotEncryptPasswords) + { + if (!FTunnelPassword.IsEmpty()) + { + Storage->WriteString("TunnelPasswordPlain", GetTunnelPassword()); + } + else + { + Storage->DeleteValue("TunnelPasswordPlain"); + } + } + else + { + if (!GetConfiguration()->GetDisablePasswordStoring() && !FTunnelPassword.IsEmpty()) + { + Storage->WriteBinaryDataAsString("TunnelPassword", StronglyRecryptPassword(FTunnelPassword, GetTunnelUserName() + GetTunnelHostName())); + } + else + { + Storage->DeleteValue("TunnelPassword"); + } + } + } +} + +void TSessionData::RecryptPasswords() +{ + SetPassword(GetPassword()); + SetProxyPassword(GetProxyPassword()); + SetTunnelPassword(GetTunnelPassword()); + SetPassphrase(GetPassphrase()); +} + +bool TSessionData::HasPassword() const +{ + return !FPassword.IsEmpty(); +} + +bool TSessionData::HasAnySessionPassword() const +{ + return HasPassword() || !FTunnelPassword.IsEmpty(); +} + +bool TSessionData::HasAnyPassword() const +{ + return HasAnySessionPassword() || !FProxyPassword.IsEmpty() || !FTunnelPassword.IsEmpty(); +} + +void TSessionData::ClearSessionPasswords() +{ + FPassword.Clear(); + FTunnelPassword.Clear(); +} + +void TSessionData::Modify() +{ + FModified = true; + if (FSource == ssStored) + { + FSource = ssStoredModified; + } +} + +UnicodeString TSessionData::GetSource() const +{ + switch (FSource) + { + case ::ssNone: + return L"Ad-Hoc site"; + + case ssStored: + return L"Site"; + + case ssStoredModified: + return L"Modified site"; + + default: + DebugFail(); + return L""; + } +} + +void TSessionData::SaveRecryptedPasswords(THierarchicalStorage * Storage) +{ + if (Storage->OpenSubKey(GetInternalStorageKey(), true)) + { + try__finally + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + RecryptPasswords(); + + SavePasswords(Storage, false, false); + } + __finally + { + Storage->CloseSubKey(); + }; + } +} + +void TSessionData::Remove() +{ + bool SessionList = true; + std::unique_ptr Storage(GetConfiguration()->CreateStorage(SessionList)); + try__finally + { + Storage->SetExplicit(true); + if (Storage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), false)) + { + Storage->RecursiveDeleteSubKey(GetInternalStorageKey()); + } + } + __finally + { +// delete Storage; + }; +} + +void TSessionData::CacheHostKeyIfNotCached() +{ + UnicodeString KeyType = GetKeyTypeFromFingerprint(GetHostKey()); + + UnicodeString TargetKey = GetConfiguration()->GetRegistryStorageKey() + L"\\" + GetConfiguration()->GetSshHostKeysSubKey(); + std::unique_ptr Storage(new TRegistryStorage(TargetKey)); + Storage->SetAccessMode(smReadWrite); + if (Storage->OpenRootKey(true)) + { + UnicodeString HostKeyName = PuttyMungeStr(FORMAT(L"%s@%d:%s", KeyType.c_str(), GetPortNumber(), GetHostName().c_str())); + if (!Storage->ValueExists(HostKeyName)) + { + // fingerprint is MD5 of host key, so it cannot be translated back to host key, + // so we store fingerprint and TSecureShell::VerifyHostKey was + // modified to accept also fingerprint + Storage->WriteString(HostKeyName, GetHostKey()); + } + } +} + +inline void MoveStr(UnicodeString & Source, UnicodeString * Dest, intptr_t Count) +{ + if (Dest != nullptr) + { + (*Dest) += Source.SubString(1, Count); + } + + Source.Delete(1, Count); +} + +bool TSessionData::DoIsProtocolUrl( + const UnicodeString & Url, const UnicodeString & Protocol, intptr_t & ProtocolLen) +{ + bool Result = ::SameText(Url.SubString(1, Protocol.Length() + 1), Protocol + L":"); + if (Result) + { + ProtocolLen = Protocol.Length() + 1; + } + return Result; +} + +bool TSessionData::IsProtocolUrl( + const UnicodeString & Url, const UnicodeString & Protocol, intptr_t & ProtocolLen) +{ + return + DoIsProtocolUrl(Url, Protocol, ProtocolLen) || + DoIsProtocolUrl(Url, WinSCPProtocolPrefix + Protocol, ProtocolLen); +} + +bool TSessionData::IsSensitiveOption(const UnicodeString & Option) +{ + return AnsiSameText(Option, PassphraseOption); +} + +bool TSessionData::ParseUrl(const UnicodeString & AUrl, TOptions * Options, + TStoredSessionList * AStoredSessions, bool & DefaultsOnly, UnicodeString * AFileName, + bool * AProtocolDefined, UnicodeString * MaskedUrl) +{ + UnicodeString Url = AUrl; + bool ProtocolDefined = false; + bool PortNumberDefined = false; + TFSProtocol AFSProtocol = fsSCPonly; + intptr_t APortNumber = 0; + TFtps AFtps = ftpsNone; + intptr_t ProtocolLen = 0; + if (Url.SubString(1, 7).LowerCase() == L"netbox:") + { + // Remove "netbox:" prefix + Url.Delete(1, 7); + if (Url.SubString(1, 2) == L"//") + { + // Remove "//" + Url.Delete(1, 2); + } + } + if (Url.SubString(1, 7).LowerCase() == L"webdav:") + { + AFSProtocol = fsWebDAV; + AFtps = ftpsNone; + APortNumber = HTTPPortNumber; + Url.Delete(1, 7); + ProtocolDefined = true; + } + if (IsProtocolUrl(Url, ScpProtocol, ProtocolLen)) + { + AFSProtocol = fsSCPonly; + APortNumber = SshPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, SftpProtocol, ProtocolLen)) + { + AFSProtocol = fsSFTPonly; + APortNumber = SshPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, FtpProtocol, ProtocolLen)) + { + AFSProtocol = fsFTP; + SetFtps(ftpsNone); + APortNumber = FtpPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, FtpsProtocol, ProtocolLen)) + { + AFSProtocol = fsFTP; + AFtps = ftpsImplicit; + APortNumber = FtpsImplicitPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, FtpesProtocol, ProtocolLen)) + { + AFSProtocol = fsFTP; + AFtps = ftpsExplicitTls; + APortNumber = FtpPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, WebDAVProtocol, ProtocolLen)) + { + AFSProtocol = fsWebDAV; + AFtps = ftpsNone; + APortNumber = HTTPPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, WebDAVSProtocol, ProtocolLen)) + { + AFSProtocol = fsWebDAV; + AFtps = ftpsImplicit; + APortNumber = HTTPSPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + else if (IsProtocolUrl(Url, SshProtocol, ProtocolLen)) + { + // For most uses, handling ssh:// the same way as sftp:// + // The only place where a difference is made is GetLoginData() in WinMain.cpp + AFSProtocol = fsSFTPonly; + SetPuttyProtocol(PuttySshProtocol); + APortNumber = SshPortNumber; + MoveStr(Url, MaskedUrl, ProtocolLen); + ProtocolDefined = true; + } + + if (ProtocolDefined && (Url.SubString(1, 2) == L"//")) + { + MoveStr(Url, MaskedUrl, 2); + } + + if (AProtocolDefined != nullptr) + { + *AProtocolDefined = ProtocolDefined; + } + + if (!Url.IsEmpty()) + { + UnicodeString DecodedUrl = DecodeUrlChars(Url); + // lookup stored session even if protocol was defined + // (this allows setting for example default username for host + // by creating stored session named by host) + TSessionData * Data = nullptr; + // When using to paste URL on Login dialog, we do not want to lookup the stored sites + if (AStoredSessions != nullptr) + { + // this can be optimized as the list is sorted + for (Integer Index = 0; Index < AStoredSessions->GetCountIncludingHidden(); ++Index) + { + TSessionData * AData = NB_STATIC_DOWNCAST(TSessionData, AStoredSessions->GetObj(Index)); + if (!AData->GetIsWorkspace()) + { + bool Match = false; + // Comparison optimizations as this is called many times + // e.g. when updating jumplist + if ((AData->GetName().Length() == DecodedUrl.Length()) && + SameText(AData->GetName(), DecodedUrl)) + { + Match = true; + } + else if ((AData->GetName().Length() < DecodedUrl.Length()) && + (DecodedUrl[AData->GetName().Length() + 1] == L'/') && + // StrLIComp is an equivalent of SameText + (StrLIComp(AData->GetName().c_str(), DecodedUrl.c_str(), (int)AData->GetName().Length()) == 0)) + { + Match = true; + } + + if (Match) + { + Data = AData; + break; + } + } + } + } + + UnicodeString RemoteDirectory; + + if (Data != nullptr) + { + Assign(Data); + intptr_t P = 1; + while (!::AnsiSameText(DecodeUrlChars(Url.SubString(1, P)), Data->GetName())) + { + P++; + DebugAssert(P <= Url.Length()); + } + RemoteDirectory = Url.SubString(P + 1, Url.Length() - P); + + if (Data->GetHidden()) + { + Data->Remove(); + AStoredSessions->Remove(Data); + // only modified, implicit + AStoredSessions->Save(false, false); + } + + if (MaskedUrl != nullptr) + { + (*MaskedUrl) += AUrl; + } + } + else + { + // This happens when pasting URL on Login dialog + if (AStoredSessions != nullptr) + { + CopyData(AStoredSessions->GetDefaultSettings()); + /*SetUserName(ANONYMOUS_USER_NAME); + SetLoginType(ltAnonymous);*/ + } + SetName(L""); + + intptr_t PSlash = Url.Pos(L"/"); + if (PSlash == 0) + { + PSlash = Url.Length() + 1; + } + + UnicodeString ConnectInfo = Url.SubString(1, PSlash - 1); + + intptr_t P = ConnectInfo.LastDelimiter(L"@"); + + UnicodeString UserInfo; + UnicodeString HostInfo; + + if (P > 0) + { + UserInfo = ConnectInfo.SubString(1, P - 1); + HostInfo = ConnectInfo.SubString(P + 1, ConnectInfo.Length() - P); + } + else + { + HostInfo = ConnectInfo; + } + + UnicodeString OrigHostInfo = HostInfo; + if ((HostInfo.Length() >= 2) && (HostInfo[1] == L'[') && ((P = HostInfo.Pos(L"]")) > 0)) + { + SetHostName(HostInfo.SubString(2, P - 2)); + HostInfo.Delete(1, P); + if (!HostInfo.IsEmpty() && (HostInfo[1] == L':')) + { + HostInfo.Delete(1, 1); + } + } + else + { + SetHostName(DecodeUrlChars(CutToChar(HostInfo, L':', true))); + } + + // expanded from ?: operator, as it caused strange "access violation" errors + if (!HostInfo.IsEmpty()) + { + SetPortNumber(::StrToIntDef(DecodeUrlChars(HostInfo), -1)); + PortNumberDefined = true; + } + else if (ProtocolDefined) + { + SetPortNumber(APortNumber); + } + + if (ProtocolDefined) + { + SetFtps(AFtps); + } + + UnicodeString UserInfoWithoutConnectionParams = CutToChar(UserInfo, UrlParamSeparator, false); + UnicodeString ConnectionParams = UserInfo; + UserInfo = UserInfoWithoutConnectionParams; + + while (!ConnectionParams.IsEmpty()) + { + UnicodeString ConnectionParam = CutToChar(ConnectionParams, UrlParamSeparator, false); + UnicodeString ConnectionParamName = CutToChar(ConnectionParam, UrlParamValueSeparator, false); + if (::AnsiSameText(ConnectionParamName, UrlHostKeyParamName)) + { + SetHostKey(ConnectionParam); + FOverrideCachedHostKey = false; + } + } + + UnicodeString RawUserName = CutToChar(UserInfo, L':', false); + if (!RawUserName.IsEmpty()) + SetUserName(DecodeUrlChars(RawUserName)); + + SetPassword(DecodeUrlChars(UserInfo)); + + UnicodeString RemoteDirectoryWithSessionParams = Url.SubString(PSlash, Url.Length() - PSlash + 1); + RemoteDirectory = CutToChar(RemoteDirectoryWithSessionParams, UrlParamSeparator, false); + UnicodeString SessionParams = RemoteDirectoryWithSessionParams; + + // We should handle session params in "stored session" branch too. + // And particularly if there's a "save" param, we should actually not try to match the + // URL against site names + while (!SessionParams.IsEmpty()) + { + UnicodeString SessionParam = CutToChar(SessionParams, UrlParamSeparator, false); + UnicodeString SessionParamName = CutToChar(SessionParam, UrlParamValueSeparator, false); + if (::AnsiSameText(SessionParamName, UrlSaveParamName)) + { + FSaveOnly = (::StrToIntDef(SessionParam, 1) != 0); + } + } + + if (MaskedUrl != nullptr) + { + (*MaskedUrl) += RawUserName; + if (!UserInfo.IsEmpty()) + { + (*MaskedUrl) += L":" + UnicodeString(PASSWORD_MASK); + } + if (!RawUserName.IsEmpty() || !UserInfo.IsEmpty()) + { + (*MaskedUrl) += L"@"; + } + (*MaskedUrl) += OrigHostInfo + RemoteDirectory; + } + + if (PSlash <= Url.Length()) + { + RemoteDirectory = Url.SubString(PSlash, Url.Length() - PSlash + 1); + } + } + + if (!RemoteDirectory.IsEmpty() && (RemoteDirectory != ROOTDIRECTORY)) + { + if ((RemoteDirectory[RemoteDirectory.Length()] != L'/') && + (AFileName != nullptr)) + { + *AFileName = DecodeUrlChars(base::UnixExtractFileName(RemoteDirectory)); + RemoteDirectory = core::UnixExtractFilePath(RemoteDirectory); + } + SetRemoteDirectory(DecodeUrlChars(RemoteDirectory)); + } + + DefaultsOnly = false; + } + else + { + // This happens when pasting URL on Login dialog + if (AStoredSessions != nullptr) + { + CopyData(AStoredSessions->GetDefaultSettings()); + } + + DefaultsOnly = true; + } + + if (ProtocolDefined) + { + SetFSProtocol(AFSProtocol); + } + + if (Options != nullptr) + { + // we deliberately do keep defaultonly to false, in presence of any option, + // as the option should not make session "connectable" + + UnicodeString Value; + if (Options->FindSwitch(SESSIONNAME_SWITCH, Value)) + { + SetName(Value); + } + if (Options->FindSwitch("privatekey", Value)) + { + SetPublicKeyFile(Value); + } + if (Options->FindSwitch(L"clientcert", Value)) + { + SetTlsCertificateFile(Value); + } + if (Options->FindSwitch(PassphraseOption, Value)) + { + SetPassphrase(Value); + } + if (Options->FindSwitch("timeout", Value)) + { + SetTimeout(static_cast(::StrToInt64(Value))); + } + if (Options->FindSwitch("hostkey", Value) || + Options->FindSwitch("certificate", Value)) + { + SetHostKey(Value); + FOverrideCachedHostKey = true; + } + SetFtpPasvMode(Options->SwitchValue("passive", GetFtpPasvMode())); + if (Options->FindSwitch("implicit")) + { + bool Enabled = Options->SwitchValue("implicit", true); + SetFtps(Enabled ? ftpsImplicit : ftpsNone); + if (!PortNumberDefined && Enabled) + { + SetPortNumber(FtpsImplicitPortNumber); + } + } + // BACKWARD COMPATIBILITY with 5.5.x + if (Options->FindSwitch("explicitssl", Value)) + { + bool Enabled = Options->SwitchValue("explicitssl", true); + SetFtps(Enabled ? ftpsExplicitSsl : ftpsNone); + if (!PortNumberDefined && Enabled) + { + SetPortNumber(FtpPortNumber); + } + } + if (Options->FindSwitch("explicit", Value) || + // BACKWARD COMPATIBILITY with 5.5.x + Options->FindSwitch(L"explicittls", Value)) + { + UnicodeString SwitchName = + Options->FindSwitch(L"explicit", Value) ? L"explicit" : L"explicittls"; + bool Enabled = Options->SwitchValue(SwitchName, true); + SetFtps(Enabled ? ftpsExplicitTls : ftpsNone); + if (!PortNumberDefined && Enabled) + { + SetPortNumber(FtpPortNumber); + } + } + if (Options->FindSwitch("rawsettings")) + { + std::unique_ptr RawSettings(new TStringList()); + std::unique_ptr OptionsStorage; + try__finally + { + if (Options->FindSwitch("rawsettings", RawSettings.get())) + { + OptionsStorage.reset(new TRegistryStorage(GetConfiguration()->GetRegistryStorageKey())); + ApplyRawSettings(OptionsStorage.get()); + } + } + __finally + { +// delete RawSettings; +// delete OptionsStorage; + }; + } + if (Options->FindSwitch("allowemptypassword", Value)) + { + SetFtpAllowEmptyPassword((::StrToIntDef(Value, 0) != 0)); + } + if (Options->FindSwitch("explicitssl", Value)) + { + bool Enabled = (::StrToIntDef(Value, 1) != 0); + SetFtps(Enabled ? ftpsExplicitSsl : ftpsNone); + if (!PortNumberDefined && Enabled) + { + SetPortNumber(FtpPortNumber); + } + } + if (Options->FindSwitch("username", Value)) + { + if (!Value.IsEmpty()) + { + SetUserName(Value); + } + } + if (Options->FindSwitch("password", Value)) + { + SetPassword(Value); + } + if (Options->FindSwitch("codepage", Value)) + { + intptr_t CodePage = ::StrToIntDef(Value, 0); + if (CodePage != 0) + { + SetCodePage(GetCodePageAsString(CodePage)); + } + } + } + + return true; +} + +void TSessionData::ApplyRawSettings(THierarchicalStorage * Storage) +{ + bool Dummy; + DoLoad(Storage, Dummy); +} + +void TSessionData::ConfigureTunnel(intptr_t APortNumber) +{ + FOrigHostName = GetHostName(); + FOrigPortNumber = GetPortNumber(); + FOrigProxyMethod = GetProxyMethod(); + + SetHostName("127.0.0.1"); + SetPortNumber(APortNumber); + // proxy settings is used for tunnel + SetProxyMethod(::pmNone); + FTunnelConfigured = true; +} + +void TSessionData::RollbackTunnel() +{ + if (FTunnelConfigured) + { + SetHostName(FOrigHostName); + SetPortNumber(FOrigPortNumber); + SetProxyMethod(FOrigProxyMethod); + FTunnelConfigured = false; + } +} + +void TSessionData::ExpandEnvironmentVariables() +{ + SetHostName(GetHostNameExpanded()); + SetUserName(GetUserNameExpanded()); + SetPublicKeyFile(::ExpandEnvironmentVariables(GetPublicKeyFile())); +} + +void TSessionData::ValidatePath(const UnicodeString & /*APath*/) +{ + // noop +} + +void TSessionData::ValidateName(const UnicodeString & Name) +{ + // keep consistent with MakeValidName + if (Name.LastDelimiter(L"/") > 0) + { + throw Exception(FMTLOAD(ITEM_NAME_INVALID, Name.c_str(), L"/")); + } +} + +UnicodeString TSessionData::MakeValidName(const UnicodeString & Name) +{ + // keep consistent with ValidateName + return ReplaceStr(Name, L"/", L"\\"); +} + +RawByteString TSessionData::EncryptPassword(const UnicodeString & Password, const UnicodeString & Key) +{ + return GetConfiguration()->EncryptPassword(Password, Key); +} + +RawByteString TSessionData::StronglyRecryptPassword(const RawByteString & Password, const UnicodeString & Key) +{ + return GetConfiguration()->StronglyRecryptPassword(Password, Key); +} + +UnicodeString TSessionData::DecryptPassword(const RawByteString & Password, const UnicodeString & Key) +{ + UnicodeString Result; + try + { + Result = GetConfiguration()->DecryptPassword(Password, Key); + } + catch (EAbort &) + { + // silently ignore aborted prompts for master password and return empty password + } + return Result; +} + +bool TSessionData::GetCanLogin() const +{ + return !FHostName.IsEmpty(); +} + +UnicodeString TSessionData::GetSessionKey() const +{ + UnicodeString Result = FORMAT(L"%s@%s", SessionGetUserName().c_str(), GetHostName().c_str()); + if (GetPortNumber() != GetDefaultPort(GetFSProtocol(), GetFtps())) + { + Result += FORMAT(L":%d", GetPortNumber()); + } + return Result; +} + +UnicodeString TSessionData::GetInternalStorageKey() const +{ + // This is probably useless remnant of previous use of this method from OpenSessionInPutty + // that needs the method to return something even for ad-hoc sessions + if (GetName().IsEmpty()) + { + return GetSessionKey(); + } + else + { + return GetName(); + } +} + +UnicodeString TSessionData::GetStorageKey() const +{ + return GetSessionName(); +} + +UnicodeString TSessionData::FormatSiteKey(const UnicodeString & HostName, intptr_t PortNumber) +{ + return FORMAT(L"%s:%d", HostName.c_str(), (int)PortNumber); +} + +UnicodeString TSessionData::GetSiteKey() const +{ + return FormatSiteKey(GetHostNameExpanded(), GetPortNumber()); +} + +void TSessionData::SetHostName(const UnicodeString & AValue) +{ + if (FHostName != AValue) + { + UnicodeString Value = AValue; + RemoveProtocolPrefix(Value); + // remove path + { + intptr_t Pos = 1; + Value = CopyToChars(Value, Pos, L"/", true, nullptr, false); + } + // HostName is key for password encryption + UnicodeString XPassword = GetPassword(); + + // This is now hardly used as hostname is parsed directly on login dialog. + // But can be used when importing sites from PuTTY, as it allows same format too. + intptr_t P = Value.LastDelimiter(L"@"); + if (P > 0) + { + SetUserName(Value.SubString(1, P - 1)); + Value = Value.SubString(P + 1, Value.Length() - P); + } + FHostName = Value; + Modify(); + + SetPassword(XPassword); + Shred(XPassword); + } +} + +UnicodeString TSessionData::GetHostNameExpanded() const +{ + return ::ExpandEnvironmentVariables(GetHostName()); +} + +void TSessionData::SetPortNumber(intptr_t Value) +{ + SET_SESSION_PROPERTY(PortNumber); +} + +void TSessionData::SetShell(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(Shell); +} + +void TSessionData::SetSftpServer(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(SftpServer); +} + +void TSessionData::SetClearAliases(bool Value) +{ + SET_SESSION_PROPERTY(ClearAliases); +} + +void TSessionData::SetListingCommand(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ListingCommand); +} + +void TSessionData::SetIgnoreLsWarnings(bool Value) +{ + SET_SESSION_PROPERTY(IgnoreLsWarnings); +} + +void TSessionData::SetUnsetNationalVars(bool Value) +{ + SET_SESSION_PROPERTY(UnsetNationalVars); +} + +void TSessionData::SetUserName(const UnicodeString & Value) +{ + // Avoid password recryption (what may popup master password prompt) + if (FUserName != Value) + { + // UserName is key for password encryption + UnicodeString XPassword = GetPassword(); + SET_SESSION_PROPERTY(UserName); + SetPassword(XPassword); + Shred(XPassword); + } +} + +UnicodeString TSessionData::GetUserNameExpanded() const +{ + return ::ExpandEnvironmentVariables(SessionGetUserName()); +} + +void TSessionData::SetPassword(const UnicodeString & AValue) +{ + RawByteString Value = EncryptPassword(AValue, SessionGetUserName() + GetHostName()); + SET_SESSION_PROPERTY(Password); +} + +UnicodeString TSessionData::GetPassword() const +{ + return DecryptPassword(FPassword, SessionGetUserName() + GetHostName()); +} + +void TSessionData::SetPingInterval(intptr_t Value) +{ + SET_SESSION_PROPERTY(PingInterval); +} + +void TSessionData::SetTryAgent(bool Value) +{ + SET_SESSION_PROPERTY(TryAgent); +} + +void TSessionData::SetAgentFwd(bool Value) +{ + SET_SESSION_PROPERTY(AgentFwd); +} + +void TSessionData::SetAuthTIS(bool Value) +{ + SET_SESSION_PROPERTY(AuthTIS); +} + +void TSessionData::SetAuthKI(bool Value) +{ + SET_SESSION_PROPERTY(AuthKI); +} + +void TSessionData::SetAuthKIPassword(bool Value) +{ + SET_SESSION_PROPERTY(AuthKIPassword); +} + +void TSessionData::SetAuthGSSAPI(bool Value) +{ + SET_SESSION_PROPERTY(AuthGSSAPI); +} + +void TSessionData::SetGSSAPIFwdTGT(bool Value) +{ + SET_SESSION_PROPERTY(GSSAPIFwdTGT); +} + +void TSessionData::SetGSSAPIServerRealm(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(GSSAPIServerRealm); +} + +void TSessionData::SetChangeUsername(bool Value) +{ + SET_SESSION_PROPERTY(ChangeUsername); +} + +void TSessionData::SetCompression(bool Value) +{ + SET_SESSION_PROPERTY(Compression); +} + +void TSessionData::SetSshProt(TSshProt Value) +{ + SET_SESSION_PROPERTY(SshProt); +} + +void TSessionData::SetSsh2DES(bool Value) +{ + SET_SESSION_PROPERTY(Ssh2DES); +} + +void TSessionData::SetSshNoUserAuth(bool Value) +{ + SET_SESSION_PROPERTY(SshNoUserAuth); +} + +UnicodeString TSessionData::GetSshProtStr() const +{ + return SshProtList[FSshProt]; +} + +bool TSessionData::GetUsesSsh() const +{ + return GetIsSshProtocol(GetFSProtocol()); +} + +void TSessionData::SetCipher(intptr_t Index, TCipher Value) +{ + DebugAssert(Index >= 0 && Index < CIPHER_COUNT); + SET_SESSION_PROPERTY(Ciphers[Index]); +} + +TCipher TSessionData::GetCipher(intptr_t Index) const +{ + DebugAssert(Index >= 0 && Index < CIPHER_COUNT); + return FCiphers[Index]; +} + +template +void TSessionData::SetAlgoList(AlgoT * List, const AlgoT * DefaultList, const UnicodeString * Names, + intptr_t Count, AlgoT WarnAlgo, const UnicodeString & AValue) +{ + UnicodeString Value = AValue; + + std::vector Used(Count); // initialized to false + std::vector NewList(Count); + + const AlgoT * WarnPtr = std::find(DefaultList, DefaultList + Count, WarnAlgo); + DebugAssert(WarnPtr != nullptr); + intptr_t WarnDefaultIndex = (WarnPtr - DefaultList); + + intptr_t Index = 0; + while (!Value.IsEmpty()) + { + UnicodeString AlgoStr = CutToChar(Value, L',', true); + for (intptr_t Algo = 0; Algo < Count; ++Algo) + { + if (!AlgoStr.CompareIC(Names[Algo]) && + !Used[Algo] && DebugAlwaysTrue(Index < Count)) + { + NewList[Index] = (AlgoT)Algo; + Used[Algo] = true; + ++Index; + break; + } + } + } + + if (!Used[WarnAlgo] && DebugAlwaysTrue(Index < Count)) + { + NewList[Index] = WarnAlgo; + Used[WarnAlgo] = true; + ++Index; + } + + intptr_t WarnIndex = std::find(NewList.begin(), NewList.end(), WarnAlgo) - NewList.begin(); + + bool Priority = true; + for (intptr_t DefaultIndex = 0; (DefaultIndex < Count); ++DefaultIndex) + { + AlgoT DefaultAlgo = DefaultList[DefaultIndex]; + if (!Used[DefaultAlgo] && DebugAlwaysTrue(Index < Count)) + { + intptr_t TargetIndex; + // Unused algs that are prioritized in the default list, + // should be merged before the existing custom list + if (Priority) + { + TargetIndex = DefaultIndex; + } + else + { + if (DefaultIndex < WarnDefaultIndex) + { + TargetIndex = WarnIndex; + } + else + { + TargetIndex = Index; + } + } + + NewList.insert(NewList.begin() + TargetIndex, DefaultAlgo); + DebugAssert(NewList.back() == AlgoT()); + NewList.pop_back(); + + if (TargetIndex <= WarnIndex) + { + ++WarnIndex; + } + + ++Index; + } + else + { + Priority = false; + } + } + + if (!std::equal(NewList.begin(), NewList.end(), List)) + { + std::copy(NewList.begin(), NewList.end(), List); + Modify(); + } +} + +void TSessionData::SetCipherList(const UnicodeString & Value) +{ +/*bool Used[CIPHER_COUNT]; + for (intptr_t C = 0; C < CIPHER_COUNT; C++) + { + Used[C] = false; + } + + UnicodeString CipherStr; + intptr_t Index = 0; + UnicodeString Value2 = Value; + while (!Value2.IsEmpty() && (Index < CIPHER_COUNT)) + { + CipherStr = CutToChar(Value2, L',', true); + for (intptr_t C = 0; C < CIPHER_COUNT; C++) + { + if (!CipherStr.CompareIC(CipherNames[C])) + { + SetCipher(Index, static_cast(C)); + Used[C] = true; + ++Index; + break; + } + } + } + + for (intptr_t C = 0; C < CIPHER_COUNT && Index < CIPHER_COUNT; C++) + { + if (!Used[DefaultCipherList[C]]) + { + SetCipher(Index++, DefaultCipherList[C]); + } + }*/ + SetAlgoList(FCiphers, DefaultCipherList, CipherNames, CIPHER_COUNT, cipWarn, Value); +} + +UnicodeString TSessionData::GetCipherList() const +{ + UnicodeString Result; + for (intptr_t Index = 0; Index < CIPHER_COUNT; ++Index) + { + Result += UnicodeString(Index ? L"," : L"") + CipherNames[GetCipher(Index)]; + } + return Result; +} + +void TSessionData::SetKex(intptr_t Index, TKex Value) +{ + DebugAssert(Index >= 0 && Index < KEX_COUNT); + SET_SESSION_PROPERTY(Kex[Index]); +} + +TKex TSessionData::GetKex(intptr_t Index) const +{ + DebugAssert(Index >= 0 && Index < KEX_COUNT); + return FKex[Index]; +} + +void TSessionData::SetKexList(const UnicodeString & Value) +{ +/*bool Used[KEX_COUNT]; + for (intptr_t K = 0; K < KEX_COUNT; K++) + { + Used[K] = false; + } + + UnicodeString KexStr; + intptr_t Index = 0; + UnicodeString Value2 = Value; + while (!Value2.IsEmpty() && (Index < KEX_COUNT)) + { + KexStr = CutToChar(Value2, L',', true); + for (intptr_t K = 0; K < KEX_COUNT; K++) + { + if (!KexStr.CompareIC(KexNames[K])) + { + SetKex(Index, static_cast(K)); + Used[K] = true; + ++Index; + break; + } + } + } + + for (intptr_t K = 0; K < KEX_COUNT && Index < KEX_COUNT; K++) + { + if (!Used[DefaultKexList[K]]) + { + SetKex(Index++, DefaultKexList[K]); + } + }*/ + SetAlgoList(FKex, DefaultKexList, KexNames, KEX_COUNT, kexWarn, Value); +} + +UnicodeString TSessionData::GetKexList() const +{ + UnicodeString Result; + for (intptr_t Index = 0; Index < KEX_COUNT; ++Index) + { + Result += UnicodeString(Index ? L"," : L"") + KexNames[GetKex(Index)]; + } + return Result; +} + +void TSessionData::SetPublicKeyFile(const UnicodeString & Value) +{ + if (FPublicKeyFile != Value) + { + // PublicKeyFile is key for Passphrase encryption + UnicodeString XPassphrase = GetPassphrase(); + + // StripPathQuotes should not be needed as we do not feed quotes anymore + FPublicKeyFile = StripPathQuotes(Value); + Modify(); + + SetPassphrase(XPassphrase); + Shred(XPassphrase); + } +} + +void TSessionData::SetPassphrase(const UnicodeString & AValue) +{ + RawByteString Value = EncryptPassword(AValue, GetPublicKeyFile()); + SET_SESSION_PROPERTY(Passphrase); +} + +UnicodeString TSessionData::GetPassphrase() const +{ + return DecryptPassword(FPassphrase, GetPublicKeyFile()); +} + +void TSessionData::SetReturnVar(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ReturnVar); +} + +void TSessionData::SetLookupUserGroups(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(LookupUserGroups); +} + +void TSessionData::SetEOLType(TEOLType Value) +{ + SET_SESSION_PROPERTY(EOLType); +} + +void TSessionData::SetTrimVMSVersions(bool Value) +{ + SET_SESSION_PROPERTY(TrimVMSVersions); +} + +TDateTime TSessionData::GetTimeoutDT() +{ + return SecToDateTime(GetTimeout()); +} + +void TSessionData::SetTimeout(intptr_t Value) +{ + SET_SESSION_PROPERTY(Timeout); +} + +void TSessionData::SetFSProtocol(TFSProtocol Value) +{ + SET_SESSION_PROPERTY(FSProtocol); +} + +UnicodeString TSessionData::GetFSProtocolStr() const +{ + UnicodeString Result; + DebugAssert(GetFSProtocol() >= 0); + if (GetFSProtocol() < FSPROTOCOL_COUNT) + { + Result = FSProtocolNames[GetFSProtocol()]; + } + // DebugAssert(!Result.IsEmpty()); + if (Result.IsEmpty()) + Result = FSProtocolNames[CONST_DEFAULT_PROTOCOL]; + return Result; +} + +void TSessionData::SetDetectReturnVar(bool Value) +{ + if (Value != GetDetectReturnVar()) + { + SetReturnVar(Value ? L"" : L"$?"); + } +} + +bool TSessionData::GetDetectReturnVar() const +{ + return GetReturnVar().IsEmpty(); +} + +void TSessionData::SetDefaultShell(bool Value) +{ + if (Value != GetDefaultShell()) + { + SetShell(Value ? L"" : L"/bin/bash"); + } +} + +bool TSessionData::GetDefaultShell() const +{ + return GetShell().IsEmpty(); +} + +void TSessionData::SetPuttyProtocol(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(PuttyProtocol); +} + +UnicodeString TSessionData::GetNormalizedPuttyProtocol() const +{ + return DefaultStr(GetPuttyProtocol(), PuttySshProtocol); +} + +void TSessionData::SetPingIntervalDT(const TDateTime & Value) +{ + uint16_t hour, min, sec, msec; + + Value.DecodeTime(hour, min, sec, msec); + SetPingInterval(hour * SecsPerHour + min * SecsPerMin + sec); +} + +TDateTime TSessionData::GetPingIntervalDT() const +{ + return SecToDateTime(GetPingInterval()); +} + +void TSessionData::SetPingType(TPingType Value) +{ + SET_SESSION_PROPERTY(PingType); +} + +void TSessionData::SetAddressFamily(TAddressFamily Value) +{ + SET_SESSION_PROPERTY(AddressFamily); +} + +void TSessionData::SetRekeyData(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(RekeyData); +} + +void TSessionData::SetRekeyTime(uintptr_t Value) +{ + SET_SESSION_PROPERTY(RekeyTime); +} + +UnicodeString TSessionData::GetDefaultSessionName() const +{ + UnicodeString Result; + UnicodeString HostName = ::TrimLeft(GetHostName()); + UnicodeString UserName = SessionGetUserName(); + RemoveProtocolPrefix(HostName); + // remove path + { + intptr_t Pos = 1; + HostName = CopyToChars(HostName, Pos, L"/", true, nullptr, false); + } + if (!HostName.IsEmpty() && !UserName.IsEmpty()) + { + // If we ever choose to include port number, + // we have to escape IPv6 literals in HostName + Result = FORMAT(L"%s@%s", UserName.c_str(), HostName.c_str()); + } + else if (!HostName.IsEmpty()) + { + Result = HostName; + } + else + { + Result = L"session"; + } + return Result; +} + +UnicodeString TSessionData::GetNameWithoutHiddenPrefix() const +{ + UnicodeString Result = GetName(); + if (GetHidden()) + { + UnicodeString HiddenPrefix(CONST_HIDDEN_PREFIX); + Result = Result.SubString(HiddenPrefix.Length() + 1, Result.Length() - HiddenPrefix.Length()); + } + return Result; +} + +bool TSessionData::HasSessionName() const +{ + return (!GetNameWithoutHiddenPrefix().IsEmpty() && (GetName() != DefaultName)); +} + +UnicodeString TSessionData::GetSessionName() const +{ + UnicodeString Result; + if (HasSessionName()) + { + Result = GetNameWithoutHiddenPrefix(); + } + else + { + Result = GetDefaultSessionName(); + } + return Result; +} + +bool TSessionData::GetIsSecure() const +{ + bool Result = false; + switch (GetFSProtocol()) + { + case fsSCPonly: + case fsSFTP: + case fsSFTPonly: + Result = true; + break; + + case fsFTP: + case fsWebDAV: + Result = (GetFtps() != ftpsNone); + break; + + default: + DebugFail(); + break; + } + return Result; +} + +UnicodeString TSessionData::GetProtocolUrl() const +{ + UnicodeString Url; + switch (GetFSProtocol()) + { + case fsSCPonly: + Url = ScpProtocol; + break; + + default: + DebugFail(); + // fallback + case fsSFTP: + case fsSFTPonly: + Url = SftpProtocol; + break; + + case fsFTP: + if (GetFtps() == ftpsImplicit) + { + Url = FtpsProtocol; + } + else if ((GetFtps() == ftpsExplicitTls) || (GetFtps() == ftpsExplicitSsl)) + { + Url = FtpesProtocol; + } + else + { + Url = FtpProtocol; + } + break; + + case fsWebDAV: + if (GetFtps() == ftpsImplicit) + { + Url = WebDAVSProtocol; + } + else + { + Url = WebDAVProtocol; + } + break; + } + + Url += ProtocolSeparator; + + return Url; +} + +static bool IsIPv6Literal(const UnicodeString & HostName) +{ + bool Result = (HostName.Pos(L":") > 0); + if (Result) + { + for (intptr_t Index = 1; Result && (Index <= HostName.Length()); Index++) + { + wchar_t C = HostName[Index]; + Result = IsHex(C) || (C == L':'); + } + } + return Result; +} + +UnicodeString TSessionData::GenerateSessionUrl(uintptr_t Flags) +{ + UnicodeString Url; + + if (FLAGSET(Flags, sufSpecific)) + { + Url += WinSCPProtocolPrefix; + } + + Url += GetProtocolUrl(); + + if (FLAGSET(Flags, sufUserName) && !GetUserNameExpanded().IsEmpty()) + { + Url += EncodeUrlString(GetUserNameExpanded()); + + if (FLAGSET(Flags, sufPassword) && !GetPassword().IsEmpty()) + { + Url += L":" + EncodeUrlString(GetPassword()); + } + + if (FLAGSET(Flags, sufHostKey) && !GetHostKey().IsEmpty()) + { + Url += + UnicodeString(UrlParamSeparator) + UrlHostKeyParamName + + UnicodeString(UrlParamValueSeparator) + NormalizeFingerprint(GetHostKey()); + } + + Url += L"@"; + } + + DebugAssert(!GetHostNameExpanded().IsEmpty()); + if (IsIPv6Literal(GetHostNameExpanded())) + { + Url += L"[" + GetHostNameExpanded() + L"]"; + } + else + { + Url += EncodeUrlString(GetHostNameExpanded()); + } + + if (GetPortNumber() != GetDefaultPort(GetFSProtocol(), GetFtps())) + { + Url += L":" + ::Int64ToStr(GetPortNumber()); + } + Url += L"/"; + + return Url; +} + +//UnicodeString ScriptCommandOpenLink = ScriptCommandLink(L"open"); + +void TSessionData::AddSwitchValue(UnicodeString & Result, const UnicodeString & Name, const UnicodeString & Value) +{ + AddSwitch(Result, FORMAT(L"%s=%s", Name.c_str(), Value.c_str())); +} + +void TSessionData::AddSwitch(UnicodeString & Result, const UnicodeString & Switch) +{ + Result += FORMAT(L" -%s", Switch.c_str()); +} + +void TSessionData::AddSwitch(UnicodeString & Result, const UnicodeString & Name, const UnicodeString & Value) +{ + AddSwitchValue(Result, Name, FORMAT(L"\"%s\"", EscapeParam(Value).c_str())); +} + +void TSessionData::AddSwitch(UnicodeString & Result, const UnicodeString & Name, intptr_t Value) +{ + AddSwitchValue(Result, Name, IntToStr(Value)); +} + +void TSessionData::LookupLastFingerprint() +{ + UnicodeString FingerprintType; + if (GetIsSshProtocol(GetFSProtocol())) + { + FingerprintType = SshFingerprintType; + } + else if (GetFtps() != ftpsNone) + { + FingerprintType = TlsFingerprintType; + } + + if (!FingerprintType.IsEmpty()) + { + SetHostKey(GetConfiguration()->GetLastFingerprint(GetSiteKey(), FingerprintType)); + } +} + +UnicodeString TSessionData::GenerateOpenCommandArgs() +{ + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + std::unique_ptr SessionData(new TSessionData(L"")); + + SessionData->Assign(this); + + UnicodeString Result = SessionData->GenerateSessionUrl(sufOpen); + + // Before we reset the FSProtocol + bool AUsesSsh = SessionData->GetUsesSsh(); + // SFTP-only is not reflected by the protocol prefix, we have to use rawsettings for that + if (SessionData->GetFSProtocol() != fsSFTPonly) + { + SessionData->SetFSProtocol(FactoryDefaults->GetFSProtocol()); + } + SessionData->SetHostName(FactoryDefaults->GetHostName()); + SessionData->SetPortNumber(FactoryDefaults->GetPortNumber()); + SessionData->SetUserName(FactoryDefaults->SessionGetUserName()); + SessionData->SetPassword(FactoryDefaults->GetPassword()); + SessionData->CopyNonCoreData(FactoryDefaults.get()); + SessionData->SetFtps(FactoryDefaults->GetFtps()); + + if (SessionData->GetHostKey() != FactoryDefaults->GetHostKey()) + { + UnicodeString SwitchName = AUsesSsh ? L"hostkey" : L"certificate"; + AddSwitch(Result, SwitchName, SessionData->GetHostKey()); + SessionData->SetHostKey(FactoryDefaults->GetHostKey()); + } + if (SessionData->GetPublicKeyFile() != FactoryDefaults->GetPublicKeyFile()) + { + AddSwitch(Result, L"privatekey", SessionData->GetPublicKeyFile()); + SessionData->SetPublicKeyFile(FactoryDefaults->GetPublicKeyFile()); + } + if (SessionData->GetTlsCertificateFile() != FactoryDefaults->GetTlsCertificateFile()) + { + AddSwitch(Result, L"clientcert", SessionData->GetTlsCertificateFile()); + SessionData->SetTlsCertificateFile(FactoryDefaults->GetTlsCertificateFile()); + } + if (SessionData->GetPassphrase() != FactoryDefaults->GetPassphrase()) + { + AddSwitch(Result, PassphraseOption, SessionData->GetPassphrase()); + SessionData->SetPassphrase(FactoryDefaults->GetPassphrase()); + } + if (SessionData->GetFtpPasvMode() != FactoryDefaults->GetFtpPasvMode()) + { + AddSwitch(Result, L"passive", SessionData->GetFtpPasvMode() ? 1 : 0); + SessionData->SetFtpPasvMode(FactoryDefaults->GetFtpPasvMode()); + } + if (SessionData->GetTimeout() != FactoryDefaults->GetTimeout()) + { + AddSwitch(Result, L"timeout", SessionData->GetTimeout()); + SessionData->SetTimeout(FactoryDefaults->GetTimeout()); + } + + std::unique_ptr RawSettings(SessionData->SaveToOptions(FactoryDefaults.get())); + + if (RawSettings->GetCount() > 0) + { + AddSwitch(Result, L"rawsettings"); + + TODO("implement"); +#if 0 + for (int Index = 0; Index < RawSettings->GetCount(); Index++) + { + UnicodeString Name = RawSettings->GetName(Index); + UnicodeString Value = RawSettings->GetValueFromIndex(Index); + // Do not quote if it is all-numeric + if (IntToStr(StrToIntDef(Value, -1)) != Value) + { + Value = FORMAT(L"\"%s\"", EscapeParam(Value).c_str()); + } + Result += FORMAT(L" %s=%s", Name.c_str(), Value.c_str()); + } +#endif + } + + return Result; +} + +/* +void TSessionData::AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & Name, const UnicodeString & Type, + const UnicodeString & Member) +{ + UnicodeString PropertyCode; + + switch (Language) + { + case alCSharp: + PropertyCode = L" %s = %s.%s,\n"; + break; + + case alVBNET: + PropertyCode = L" .%s = %s.%s\n"; + break; + + case alPowerShell: + PropertyCode = L"$sessionOptions.%s = [WinSCP.%s]::%s\n"; + break; + } + + Result += FORMAT(PropertyCode.c_str(), Name.c_str(), Type.c_str(), Member.c_str()); +} + +UnicodeString TSessionData::AssemblyString(TAssemblyLanguage Language, const UnicodeString & S) +{ + UnicodeString Result = S; + switch (Language) + { + case alCSharp: + if (Result.Pos(L"\\") > 0) + { + Result = FORMAT(L"@\"%s\"", ReplaceStr(Result, L"\"", L"\"\"").c_str()); + } + else + { + Result = FORMAT(L"\"%s\"", ReplaceStr(Result, L"\"", L"\\\"").c_str()); + } + break; + + case alVBNET: + Result = FORMAT(L"\"%s\"", ReplaceStr(Result, L"\"", L"\"\"").c_str()); + break; + + case alPowerShell: + Result = FORMAT(L"\"%s\"", ReplaceStr(Result, L"\"", L"`\"").c_str()); + break; + + default: + DebugFail(); + break; + } + + return Result; +} + +void TSessionData::AddAssemblyPropertyRaw( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & Name, const UnicodeString & Value) +{ + UnicodeString PropertyCode; + + switch (Language) + { + case alCSharp: + PropertyCode = L" %s = %s,\n"; + break; + + case alVBNET: + PropertyCode = L" .%s = %s\n"; + break; + + case alPowerShell: + PropertyCode = L"$sessionOptions.%s = %s\n"; + break; + } + + Result += FORMAT(PropertyCode.c_str(), Name.c_str(), Value.c_str()); +} +//--------------------------------------------------------------------- +void TSessionData::AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & Name, const UnicodeString & Value) +{ + AddAssemblyPropertyRaw(Result, Language, Name, AssemblyString(Language, Value)); +} + +void TSessionData::AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & Name, intptr_t Value) +{ + AddAssemblyPropertyRaw(Result, Language, Name, IntToStr(Value)); +} + +void TSessionData::AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & Name, bool Value) +{ + UnicodeString PropertyValue; + + switch (Language) + { + case alCSharp: + PropertyValue = (Value ? L"true" : L"false"); + break; + + case alVBNET: + PropertyValue = (Value ? L"True" : L"False"); + break; + + case alPowerShell: + PropertyValue = (Value ? L"$True" : L"$False"); + break; + } + + AddAssemblyPropertyRaw(Result, Language, Name, PropertyValue); +} + +UnicodeString TSessionData::GenerateAssemblyCode( + TAssemblyLanguage Language) +{ + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + std::unique_ptr SessionData(Clone()); + + UnicodeString Result; + + UnicodeString SessionOptionsPreamble; + switch (Language) + { + case alCSharp: + SessionOptionsPreamble = + L"// %s\n" + L"SessionOptions sessionOptions = new SessionOptions\n" + L"{\n"; + break; + + case alVBNET: + SessionOptionsPreamble = + L"' %s\n" + L"Dim mySessionOptions As New SessionOptions\n" + L"With mySessionOptions\n"; + break; + + case alPowerShell: + SessionOptionsPreamble = + FORMAT(L"# %s\n", LoadStr(CODE_PS_ADD_TYPE).c_str()) + + L"Add-Type -Path \"WinSCPnet.dll\"\n" + L"\n" + L"# %s\n" + L"$sessionOptions = New-Object WinSCP.SessionOptions\n"; + break; + + default: + DebugFail(); + break; + } + + Result = FORMAT(SessionOptionsPreamble.c_str(), LoadStr(CODE_SESSION_OPTIONS).c_str()); + + UnicodeString ProtocolMember; + switch (SessionData->GetFSProtocol()) + { + case fsSCPonly: + ProtocolMember = "Scp"; + break; + + default: + DebugFail(); + // fallback + case fsSFTP: + case fsSFTPonly: + ProtocolMember = "Sftp"; + break; + + case fsFTP: + ProtocolMember = "Ftp"; + break; + + case fsWebDAV: + ProtocolMember = "Webdav"; + break; + } + + // Before we reset the FSProtocol + bool AUsesSsh = SessionData->GetUsesSsh(); + + // Protocol is set unconditionally, we want even the default SFTP + AddAssemblyProperty(Result, Language, L"Protocol", L"Protocol", ProtocolMember); + // SFTP-only is not reflected by the protocol prefix, we have to use rawsettings for that + if (SessionData->GetFSProtocol() != fsSFTPonly) + { + SessionData->SetFSProtocol(FactoryDefaults->GetFSProtocol()); + } + if (SessionData->GetHostName() != FactoryDefaults->GetHostName()) + { + AddAssemblyProperty(Result, Language, L"HostName", GetHostName()); + SessionData->SetHostName(FactoryDefaults->GetHostName()); + } + if (SessionData->GetPortNumber() != FactoryDefaults->GetPortNumber()) + { + AddAssemblyProperty(Result, Language, L"PortNumber", GetPortNumber()); + SessionData->SetPortNumber(FactoryDefaults->GetPortNumber()); + } + if (SessionData->SessionGetUserName() != FactoryDefaults->SessionGetUserName()) + { + AddAssemblyProperty(Result, Language, L"UserName", SessionGetUserName()); + SessionData->SetUserName(FactoryDefaults->SessionGetUserName()); + } + if (SessionData->GetPassword() != FactoryDefaults->GetPassword()) + { + AddAssemblyProperty(Result, Language, L"Password", GetPassword()); + SessionData->SetPassword(FactoryDefaults->GetPassword()); + } + + SessionData->CopyNonCoreData(FactoryDefaults.get()); + + if (SessionData->GetFtps() != FactoryDefaults->GetFtps()) + { + // SessionData->FSProtocol is reset already + switch (GetFSProtocol()) + { + case fsFTP: + { + UnicodeString FtpSecureMember; + switch (SessionData->GetFtps()) + { + case ftpsNone: + // noop + break; + + case ftpsImplicit: + FtpSecureMember = L"Implicit"; + break; + + case ftpsExplicitTls: + case ftpsExplicitSsl: + FtpSecureMember = L"Explicit"; + break; + + default: + DebugFail(); + break; + } + AddAssemblyProperty(Result, Language, L"FtpSecure", L"FtpSecure", FtpSecureMember); + } + break; + + case fsWebDAV: + AddAssemblyProperty(Result, Language, L"WebdavSecure", (SessionData->GetFtps() != ftpsNone)); + break; + + default: + DebugFail(); + break; + } + SessionData->SetFtps(FactoryDefaults->GetFtps()); + } + + if (SessionData->GetHostKey() != FactoryDefaults->GetHostKey()) + { + UnicodeString PropertyName = AUsesSsh ? L"SshHostKeyFingerprint" : L"TlsHostCertificateFingerprint"; + AddAssemblyProperty(Result, Language, PropertyName, SessionData->GetHostKey()); + SessionData->SetHostKey(FactoryDefaults->GetHostKey()); + } + if (SessionData->GetPublicKeyFile() != FactoryDefaults->GetPublicKeyFile()) + { + AddAssemblyProperty(Result, Language, L"SshPrivateKeyPath", SessionData->GetPublicKeyFile()); + SessionData->SetPublicKeyFile(FactoryDefaults->GetPublicKeyFile()); + } + if (SessionData->GetTlsCertificateFile() != FactoryDefaults->GetTlsCertificateFile()) + { + AddAssemblyProperty(Result, Language, L"TlsClientCertificatePath", SessionData->GetTlsCertificateFile()); + SessionData->SetTlsCertificateFile(FactoryDefaults->GetTlsCertificateFile()); + } + if (SessionData->GetPassphrase() != FactoryDefaults->GetPassphrase()) + { + AddAssemblyProperty(Result, Language, L"PrivateKeyPassphrase", SessionData->GetPassphrase()); + SessionData->SetPassphrase(FactoryDefaults->GetPassphrase()); + } + if (SessionData->GetFtpPasvMode() != FactoryDefaults->GetFtpPasvMode()) + { + AddAssemblyProperty(Result, Language, L"FtpMode", L"FtpMode", (SessionData->GetFtpPasvMode() ? L"Passive" : L"Active")); + SessionData->SetFtpPasvMode(FactoryDefaults->GetFtpPasvMode()); + } + if (SessionData->GetTimeout() != FactoryDefaults->GetTimeout()) + { + AddAssemblyProperty(Result, Language, L"TimeoutInMilliseconds", SessionData->GetTimeout()); + SessionData->SetTimeout(FactoryDefaults->GetTimeout()); + } + + switch (Language) + { + case alCSharp: + Result += L"};\n"; + break; + + case alVBNET: + // noop + // Ending With only after AddRawSettings + break; + } + + std::unique_ptr RawSettings(SessionData->SaveToOptions(FactoryDefaults.get())); + + if (RawSettings->GetCount() > 0) + { + Result += L"\n"; + + TODO("implement"); +#if 0 + for (int Index = 0; Index < RawSettings->Count; Index++) + { + UnicodeString Name = RawSettings->Names[Index]; + UnicodeString Value = RawSettings->ValueFromIndex[Index]; + UnicodeString SettingsCode; + switch (Language) + { + case alCSharp: + SettingsCode = L"sessionOptions.AddRawSettings(\"%s\", %s);\n"; + break; + + case alVBNET: + SettingsCode = L" .AddRawSettings(\"%s\", %s)\n"; + break; + + case alPowerShell: + SettingsCode = L"$sessionOptions.AddRawSettings(\"%s\", %s)\n"; + break; + } + Result += FORMAT(SettingsCode, Name.c_str(), AssemblyString(Language, Value).c_str()); + } +#endif + } + + UnicodeString SessionCode; + +#if 0 + switch (Language) + { + case alCSharp: + SessionCode = + L"\n" + "using (Session session = new Session())\n" + "{\n" + " // %s\n" + " session.Open(sessionOptions);\n" + "\n" + " // %s\n" + "}\n"; + break; + + case alVBNET: + SessionCode = + L"End With\n" + "\n" + "Using mySession As Session = New Session\n" + " ' %s\n" + " mySession.Open(mySessionOptions)\n" + "\n" + " ' %s\n" + "End Using\n"; + break; + + case alPowerShell: + SessionCode = + L"\n" + "$session = New-Object WinSCP.Session\n" + "\n" + "try\n" + "{\n" + " # %s\n" + " $session.Open($sessionOptions)\n" + "\n" + " # %s\n" + "}\n" + "finally\n" + "{\n" + " $session.Dispose()\n" + "}\n"; + break; + } +#endif + + Result += FORMAT(SessionCode.c_str(), LoadStr(CODE_CONNECT).c_str(), LoadStr(CODE_YOUR_CODE).c_str()); + + return Result; +} +*/ +void TSessionData::SetTimeDifference(const TDateTime & Value) +{ + SET_SESSION_PROPERTY(TimeDifference); +} + +void TSessionData::SetTimeDifferenceAuto(bool Value) +{ + SET_SESSION_PROPERTY(TimeDifferenceAuto); +} + +void TSessionData::SetLocalDirectory(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(LocalDirectory); +} + +void TSessionData::SetRemoteDirectory(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(RemoteDirectory); +} + +void TSessionData::SetSynchronizeBrowsing(bool Value) +{ + SET_SESSION_PROPERTY(SynchronizeBrowsing); +} + +void TSessionData::SetUpdateDirectories(bool Value) +{ + SET_SESSION_PROPERTY(UpdateDirectories); +} + +void TSessionData::SetCacheDirectories(bool Value) +{ + SET_SESSION_PROPERTY(CacheDirectories); +} + +void TSessionData::SetCacheDirectoryChanges(bool Value) +{ + SET_SESSION_PROPERTY(CacheDirectoryChanges); +} + +void TSessionData::SetPreserveDirectoryChanges(bool Value) +{ + SET_SESSION_PROPERTY(PreserveDirectoryChanges); +} + +void TSessionData::SetResolveSymlinks(bool Value) +{ + SET_SESSION_PROPERTY(ResolveSymlinks); +} + +void TSessionData::SetFollowDirectorySymlinks(bool Value) +{ + SET_SESSION_PROPERTY(FollowDirectorySymlinks); +} + +void TSessionData::SetDSTMode(TDSTMode Value) +{ + SET_SESSION_PROPERTY(DSTMode); +} + +void TSessionData::SetDeleteToRecycleBin(bool Value) +{ + SET_SESSION_PROPERTY(DeleteToRecycleBin); +} + +void TSessionData::SetOverwrittenToRecycleBin(bool Value) +{ + SET_SESSION_PROPERTY(OverwrittenToRecycleBin); +} + +void TSessionData::SetRecycleBinPath(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(RecycleBinPath); +} + +void TSessionData::SetPostLoginCommands(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(PostLoginCommands); +} + +void TSessionData::SetLockInHome(bool Value) +{ + SET_SESSION_PROPERTY(LockInHome); +} + +void TSessionData::SetSpecial(bool Value) +{ + SET_SESSION_PROPERTY(Special); +} + +void TSessionData::SetScp1Compatibility(bool Value) +{ + SET_SESSION_PROPERTY(Scp1Compatibility); +} + +void TSessionData::SetTcpNoDelay(bool Value) +{ + SET_SESSION_PROPERTY(TcpNoDelay); +} + +void TSessionData::SetSendBuf(intptr_t Value) +{ + SET_SESSION_PROPERTY(SendBuf); +} + +void TSessionData::SetSshSimple(bool Value) +{ + SET_SESSION_PROPERTY(SshSimple); +} + +void TSessionData::SetProxyMethod(TProxyMethod Value) +{ + SET_SESSION_PROPERTY(ProxyMethod); +} + +void TSessionData::SetProxyHost(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ProxyHost); +} + +void TSessionData::SetProxyPort(intptr_t Value) +{ + SET_SESSION_PROPERTY(ProxyPort); +} + +void TSessionData::SetProxyUsername(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ProxyUsername); +} + +void TSessionData::SetProxyPassword(const UnicodeString & AValue) +{ + RawByteString Value = EncryptPassword(AValue, GetProxyUsername() + GetProxyHost()); + SET_SESSION_PROPERTY(ProxyPassword); +} + +TProxyMethod TSessionData::GetSystemProxyMethod() const +{ + PrepareProxyData(); + if ((GetProxyMethod() == pmSystem) && (nullptr != FIEProxyConfig)) + return FIEProxyConfig->ProxyMethod; + return pmNone; +} + +UnicodeString TSessionData::GetProxyHost() const +{ + PrepareProxyData(); + if ((GetProxyMethod() == pmSystem) && (nullptr != FIEProxyConfig)) + return FIEProxyConfig->ProxyHost; + return FProxyHost; +} + +intptr_t TSessionData::GetProxyPort() const +{ + PrepareProxyData(); + if ((GetProxyMethod() == pmSystem) && (nullptr != FIEProxyConfig)) + return FIEProxyConfig->ProxyPort; + return FProxyPort; +} + +UnicodeString TSessionData::GetProxyUsername() const +{ + return FProxyUsername; +} + +UnicodeString TSessionData::GetProxyPassword() const +{ + return DecryptPassword(FProxyPassword, GetProxyUsername() + GetProxyHost()); +} + +#ifndef __linux__ +static void FreeIEProxyConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * IEProxyConfig) +{ + DebugAssert(IEProxyConfig); + if (IEProxyConfig->lpszAutoConfigUrl) + GlobalFree(IEProxyConfig->lpszAutoConfigUrl); + if (IEProxyConfig->lpszProxy) + GlobalFree(IEProxyConfig->lpszProxy); + if (IEProxyConfig->lpszProxyBypass) + GlobalFree(IEProxyConfig->lpszProxyBypass); +} +#endif + +void TSessionData::PrepareProxyData() const +{ +#ifndef __linux__ + if ((GetProxyMethod() == pmSystem) && (nullptr == FIEProxyConfig)) + { + FIEProxyConfig = new TIEProxyConfig; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IEProxyConfig; + if (!WinHttpGetIEProxyConfigForCurrentUser(&IEProxyConfig)) + { + DWORD Err = ::GetLastError(); + DEBUG_PRINTF("Error reading system proxy configuration, code: %x", Err); + DebugUsedParam(Err); + } + else + { + FIEProxyConfig->AutoDetect = !!IEProxyConfig.fAutoDetect; + if (nullptr != IEProxyConfig.lpszAutoConfigUrl) + { + FIEProxyConfig->AutoConfigUrl = IEProxyConfig.lpszAutoConfigUrl; + } + if (nullptr != IEProxyConfig.lpszProxy) + { + FIEProxyConfig->Proxy = IEProxyConfig.lpszProxy; + } + if (nullptr != IEProxyConfig.lpszProxyBypass) + { + FIEProxyConfig->ProxyBypass = IEProxyConfig.lpszProxyBypass; + } + FreeIEProxyConfig(&IEProxyConfig); + ParseIEProxyConfig(); + } + } +#endif +} + +void TSessionData::ParseIEProxyConfig() const +{ + DebugAssert(FIEProxyConfig); + TStringList ProxyServerList; + ProxyServerList.SetDelimiter(L';'); + ProxyServerList.SetDelimitedText(FIEProxyConfig->Proxy); + UnicodeString ProxyUrl; + intptr_t ProxyPort = 0; + TProxyMethod ProxyMethod = pmNone; + UnicodeString ProxyUrlTmp; + intptr_t ProxyPortTmp = 0; + TProxyMethod ProxyMethodTmp = pmNone; + for (intptr_t Index = 0; Index < ProxyServerList.GetCount(); ++Index) + { + UnicodeString ProxyServer = ProxyServerList.GetString(Index).Trim(); + TStringList ProxyServerForScheme; + ProxyServerForScheme.SetDelimiter(L'='); + ProxyServerForScheme.SetDelimitedText(ProxyServer); + UnicodeString ProxyScheme; + UnicodeString ProxyURI; + if (ProxyServerForScheme.GetCount() == 2) + { + ProxyScheme = ProxyServerList.GetString(0).Trim(); + ProxyURI = ProxyServerList.GetString(1).Trim(); + } + else + { + if (ProxyServerForScheme.GetCount() == 1) + { + ProxyScheme = L"http"; + ProxyURI = ProxyServerList.GetString(0).Trim(); + ProxyMethodTmp = pmHTTP; + } + } + if (ProxyUrlTmp.IsEmpty() && (ProxyPortTmp == 0)) + { + FromURI(ProxyURI, ProxyUrlTmp, ProxyPortTmp, ProxyMethodTmp); + } + switch (GetFSProtocol()) + { + // case fsSCPonly: + // case fsSFTP: + // case fsSFTPonly: + // case fsFTP: + // case fsFTPS: + // break; + case fsWebDAV: + if ((ProxyScheme == L"http") || (ProxyScheme == L"https")) + { + FromURI(ProxyURI, ProxyUrl, ProxyPort, ProxyMethod); + } + break; + default: + break; + } + } + if (ProxyUrl.IsEmpty() && (ProxyPort == 0) && (ProxyMethod == pmNone)) + { + ProxyUrl = ProxyUrlTmp; + ProxyPort = ProxyPortTmp; + ProxyMethod = ProxyMethodTmp; + } + FIEProxyConfig->ProxyHost = ProxyUrl; + FIEProxyConfig->ProxyPort = ProxyPort; + FIEProxyConfig->ProxyMethod = ProxyMethod; +} + +void TSessionData::FromURI(const UnicodeString & ProxyURI, + UnicodeString & ProxyUrl, intptr_t & ProxyPort, TProxyMethod & ProxyMethod) const +{ + ProxyUrl.Clear(); + ProxyPort = 0; + ProxyMethod = pmNone; + intptr_t Pos = ProxyURI.RPos(L':'); + if (Pos > 0) + { + ProxyUrl = ProxyURI.SubString(1, Pos - 1).Trim(); + ProxyPort = ProxyURI.SubString(Pos + 1, -1).Trim().ToInt(); + } + // remove scheme from Url e.g. "socks5://" "https://" + Pos = ProxyUrl.Pos(L"://"); + if (Pos > 0) + { + UnicodeString ProxyScheme = ProxyUrl.SubString(1, Pos - 1); + ProxyUrl = ProxyUrl.SubString(Pos + 3, -1); + if (ProxyScheme == L"socks4") + { + ProxyMethod = pmSocks4; + } + else if (ProxyScheme == L"socks5") + { + ProxyMethod = pmSocks5; + } + else if (ProxyScheme == L"socks") + { + ProxyMethod = pmSocks5; + } + else if (ProxyScheme == L"http") + { + ProxyMethod = pmHTTP; + } + else if (ProxyScheme == L"https") + { + TODO("pmHTTPS"); + ProxyMethod = pmHTTP; + } + } + if (ProxyMethod == pmNone) + ProxyMethod = pmHTTP; // default Value +} + +void TSessionData::SetProxyTelnetCommand(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ProxyTelnetCommand); +} + +void TSessionData::SetProxyLocalCommand(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(ProxyLocalCommand); +} + +void TSessionData::SetProxyDNS(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(ProxyDNS); +} + +void TSessionData::SetProxyLocalhost(bool Value) +{ + SET_SESSION_PROPERTY(ProxyLocalhost); +} + +void TSessionData::SetFtpProxyLogonType(intptr_t Value) +{ + SET_SESSION_PROPERTY(FtpProxyLogonType); +} + +void TSessionData::SetBug(TSshBug Bug, TAutoSwitch Value) +{ + DebugAssert(Bug >= 0 && static_cast(Bug) < _countof(FBugs)); + SET_SESSION_PROPERTY(Bugs[Bug]); +} + +TAutoSwitch TSessionData::GetBug(TSshBug Bug) const +{ + DebugAssert(Bug >= 0 && static_cast(Bug) < _countof(FBugs)); + return FBugs[Bug]; +} + +void TSessionData::SetCustomParam1(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(CustomParam1); +} + +void TSessionData::SetCustomParam2(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(CustomParam2); +} + +void TSessionData::SetSFTPDownloadQueue(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPDownloadQueue); +} + +void TSessionData::SetSFTPUploadQueue(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPUploadQueue); +} + +void TSessionData::SetSFTPListingQueue(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPListingQueue); +} + +void TSessionData::SetSFTPMaxVersion(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPMaxVersion); +} + +void TSessionData::SetSFTPMinPacketSize(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPMinPacketSize); +} + +void TSessionData::SetSFTPMaxPacketSize(intptr_t Value) +{ + SET_SESSION_PROPERTY(SFTPMaxPacketSize); +} + +void TSessionData::SetSFTPBug(TSftpBug Bug, TAutoSwitch Value) +{ + DebugAssert(Bug >= 0 && static_cast(Bug) < _countof(FSFTPBugs)); + SET_SESSION_PROPERTY(SFTPBugs[Bug]); +} + +TAutoSwitch TSessionData::GetSFTPBug(TSftpBug Bug) const +{ + DebugAssert(Bug >= 0 && static_cast(Bug) < _countof(FSFTPBugs)); + return FSFTPBugs[Bug]; +} + +void TSessionData::SetSCPLsFullTime(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(SCPLsFullTime); +} + +void TSessionData::SetColor(intptr_t Value) +{ + SET_SESSION_PROPERTY(Color); +} + +void TSessionData::SetTunnel(bool Value) +{ + SET_SESSION_PROPERTY(Tunnel); +} + +void TSessionData::SetTunnelHostName(const UnicodeString & Value) +{ + if (FTunnelHostName != Value) + { + // HostName is key for password encryption + UnicodeString XTunnelPassword = GetTunnelPassword(); + + UnicodeString Value2 = Value; + intptr_t P = Value2.LastDelimiter(L"@"); + if (P > 0) + { + SetTunnelUserName(Value2.SubString(1, P - 1)); + Value2 = Value2.SubString(P + 1, Value2.Length() - P); + } + FTunnelHostName = Value2; + Modify(); + + SetTunnelPassword(XTunnelPassword); + Shred(XTunnelPassword); + } +} + +void TSessionData::SetTunnelPortNumber(intptr_t Value) +{ + SET_SESSION_PROPERTY(TunnelPortNumber); +} + +void TSessionData::SetTunnelUserName(const UnicodeString & Value) +{ + // Avoid password recryption (what may popup master password prompt) + if (FTunnelUserName != Value) + { + // TunnelUserName is key for password encryption + UnicodeString XTunnelPassword = GetTunnelPassword(); + SET_SESSION_PROPERTY(TunnelUserName); + SetTunnelPassword(XTunnelPassword); + Shred(XTunnelPassword); + } +} + +void TSessionData::SetTunnelPassword(const UnicodeString & AValue) +{ + RawByteString Value = EncryptPassword(AValue, GetTunnelUserName() + GetTunnelHostName()); + SET_SESSION_PROPERTY(TunnelPassword); +} + +UnicodeString TSessionData::GetTunnelPassword() const +{ + return DecryptPassword(FTunnelPassword, GetTunnelUserName() + GetTunnelHostName()); +} + +void TSessionData::SetTunnelPublicKeyFile(const UnicodeString & Value) +{ + if (FTunnelPublicKeyFile != Value) + { + // StripPathQuotes should not be needed as we do not feed quotes anymore + FTunnelPublicKeyFile = StripPathQuotes(Value); + Modify(); + } +} + +void TSessionData::SetTunnelLocalPortNumber(intptr_t Value) +{ + SET_SESSION_PROPERTY(TunnelLocalPortNumber); +} + +bool TSessionData::GetTunnelAutoassignLocalPortNumber() +{ + return (FTunnelLocalPortNumber <= 0); +} + +void TSessionData::SetTunnelPortFwd(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(TunnelPortFwd); +} + +void TSessionData::SetTunnelHostKey(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(TunnelHostKey); +} + +void TSessionData::SetFtpPasvMode(bool Value) +{ + SET_SESSION_PROPERTY(FtpPasvMode); +} + +void TSessionData::SetFtpAllowEmptyPassword(bool Value) +{ + SET_SESSION_PROPERTY(FtpAllowEmptyPassword); +} + +void TSessionData::SetFtpForcePasvIp(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(FtpForcePasvIp); +} + +void TSessionData::SetFtpUseMlsd(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(FtpUseMlsd); +} + +void TSessionData::SetFtpAccount(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(FtpAccount); +} + +void TSessionData::SetFtpPingInterval(intptr_t Value) +{ + SET_SESSION_PROPERTY(FtpPingInterval); +} + +TDateTime TSessionData::GetFtpPingIntervalDT() const +{ + return SecToDateTime(GetFtpPingInterval()); +} + +void TSessionData::SetFtpPingType(TPingType Value) +{ + SET_SESSION_PROPERTY(FtpPingType); +} + +void TSessionData::SetFtpTransferActiveImmediately(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(FtpTransferActiveImmediately); +} + +void TSessionData::SetFtps(TFtps Value) +{ + SET_SESSION_PROPERTY(Ftps); +} + +void TSessionData::SetMinTlsVersion(TTlsVersion Value) +{ + SET_SESSION_PROPERTY(MinTlsVersion); +} + +void TSessionData::SetMaxTlsVersion(TTlsVersion Value) +{ + SET_SESSION_PROPERTY(MaxTlsVersion); +} + +void TSessionData::SetFtpListAll(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(FtpListAll); +} + +void TSessionData::SetFtpHost(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(FtpHost); +} + +void TSessionData::SetFtpDupFF(bool Value) +{ + SET_SESSION_PROPERTY(FtpDupFF); +} + +void TSessionData::SetFtpUndupFF(bool Value) +{ + SET_SESSION_PROPERTY(FtpUndupFF); +} + +void TSessionData::SetSslSessionReuse(bool Value) +{ + SET_SESSION_PROPERTY(SslSessionReuse); +} + +void TSessionData::SetTlsCertificateFile(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(TlsCertificateFile); +} + +void TSessionData::SetNotUtf(TAutoSwitch Value) +{ + SET_SESSION_PROPERTY(NotUtf); +} + +void TSessionData::SetIsWorkspace(bool Value) +{ + SET_SESSION_PROPERTY(IsWorkspace); +} + +void TSessionData::SetLink(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(Link); +} + +void TSessionData::SetHostKey(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(HostKey); +} + +void TSessionData::SetNote(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(Note); +} + +UnicodeString TSessionData::GetInfoTip() const +{ + if (GetUsesSsh()) + { + return FMTLOAD(SESSION_INFO_TIP2, + GetHostName().c_str(), SessionGetUserName().c_str(), + (GetPublicKeyFile().IsEmpty() ? LoadStr(NO_STR).c_str() : LoadStr(YES_STR).c_str()), + GetFSProtocolStr().c_str()); + } + else + { + return FMTLOAD(SESSION_INFO_TIP_NO_SSH, + GetHostName().c_str(), SessionGetUserName().c_str(), GetFSProtocolStr().c_str()); + } +} + +UnicodeString TSessionData::ExtractLocalName(const UnicodeString & Name) +{ + UnicodeString Result = Name; + intptr_t P = Result.LastDelimiter(L"/"); + if (P > 0) + { + Result.Delete(1, P); + } + return Result; +} + +UnicodeString TSessionData::GetLocalName() const +{ + UnicodeString Result; + if (HasSessionName()) + { + Result = ExtractLocalName(GetName()); + } + else + { + Result = GetDefaultSessionName(); + } + return Result; +} + +UnicodeString TSessionData::ExtractFolderName(const UnicodeString & Name) +{ + UnicodeString Result; + intptr_t P = Name.LastDelimiter(L"/"); + if (P > 0) + { + Result = Name.SubString(1, P - 1); + } + return Result; +} + +UnicodeString TSessionData::GetFolderName() const +{ + UnicodeString Result; + if (HasSessionName() || GetIsWorkspace()) + { + Result = ExtractFolderName(GetName()); + } + return Result; +} + +UnicodeString TSessionData::ComposePath( + const UnicodeString & APath, const UnicodeString & Name) +{ + return core::UnixIncludeTrailingBackslash(APath) + Name; +} + +TLoginType TSessionData::GetLoginType() const +{ + return (SessionGetUserName() == ANONYMOUS_USER_NAME) && GetPassword().IsEmpty() ? + ltAnonymous : ltNormal; +} + +void TSessionData::SetLoginType(TLoginType Value) +{ + SET_SESSION_PROPERTY(LoginType); + if (GetLoginType() == ltAnonymous) + { + SetPassword(L""); + SetUserName(ANONYMOUS_USER_NAME); + } +} + +uintptr_t TSessionData::GetCodePageAsNumber() const +{ + if (FCodePageAsNumber == 0) + FCodePageAsNumber = ::GetCodePageAsNumber(GetCodePage()); + return FCodePageAsNumber; +} + +void TSessionData::SetCodePage(const UnicodeString & Value) +{ + SET_SESSION_PROPERTY(CodePage); + FCodePageAsNumber = 0; +} + +void TSessionData::AdjustHostName(UnicodeString & HostName, const UnicodeString & Prefix) const +{ + UnicodeString FullPrefix = Prefix + ProtocolSeparator; + if (::LowerCase(HostName.SubString(1, FullPrefix.Length())) == FullPrefix) + { + HostName.Delete(1, FullPrefix.Length()); + } +} + +void TSessionData::RemoveProtocolPrefix(UnicodeString & HostName) const +{ + AdjustHostName(HostName, ScpProtocol); + AdjustHostName(HostName, SftpProtocol); + AdjustHostName(HostName, FtpProtocol); + AdjustHostName(HostName, FtpsProtocol); + AdjustHostName(HostName, WebDAVProtocol); + AdjustHostName(HostName, WebDAVSProtocol); +} + +TFSProtocol TSessionData::TranslateFSProtocolNumber(intptr_t FSProtocol) +{ + TFSProtocol Result = static_cast(-1); + if (GetSessionVersion() >= ::GetVersionNumber2110()) + { + Result = static_cast(FSProtocol); + } + else + { + if (FSProtocol < fsFTPS_219) + { + Result = static_cast(FSProtocol); + } + switch (FSProtocol) + { + case fsFTPS_219: + SetFtps(ftpsExplicitSsl); + Result = fsFTP; + break; + case fsHTTP_219: + SetFtps(ftpsNone); + Result = fsWebDAV; + break; + case fsHTTPS_219: + SetFtps(ftpsImplicit); + Result = fsWebDAV; + break; + } + } + // DebugAssert(Result != -1); + return Result; +} + +TFSProtocol TSessionData::TranslateFSProtocol(const UnicodeString & ProtocolID) const +{ + // Find protocol by string id + TFSProtocol Result = static_cast(-1); + for (intptr_t Index = 0; Index < FSPROTOCOL_COUNT; ++Index) + { + if (FSProtocolNames[Index] == ProtocolID) + { + Result = static_cast(Index); + break; + } + } + if (Result == (TFSProtocol)-1) + Result = CONST_DEFAULT_PROTOCOL; + DebugAssert(Result != static_cast(-1)); + return Result; +} + +TFtps TSessionData::TranslateFtpEncryptionNumber(intptr_t FtpEncryption) const +{ + TFtps Result = GetFtps(); + if ((GetSessionVersion() < ::GetVersionNumber2110()) && + (GetFSProtocol() == fsFTP) && (GetFtps() != ftpsNone)) + { + switch (FtpEncryption) + { + case fesPlainFTP: + Result = ftpsNone; + break; + case fesExplicitSSL: + Result = ftpsExplicitSsl; + break; + case fesImplicit: + Result = ftpsImplicit; + break; + case fesExplicitTLS: + Result = ftpsExplicitTls; + break; + default: + break; + } + } + DebugAssert(Result != static_cast(-1)); + return Result; +} + +//=== TStoredSessionList ---------------------------------------------- +TStoredSessionList::TStoredSessionList(bool AReadOnly) : + TNamedObjectList(), + FDefaultSettings(new TSessionData(DefaultName)), + FReadOnly(AReadOnly) +{ + DebugAssert(GetConfiguration()); + SetOwnsObjects(true); +} + +TStoredSessionList::~TStoredSessionList() +{ + SAFE_DESTROY(FDefaultSettings); + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + delete AtObject(Index); + SetItem(Index, nullptr); + } +} + +void TStoredSessionList::Load(THierarchicalStorage * Storage, + bool AsModified, bool UseDefaults) +{ + std::unique_ptr SubKeys(new TStringList()); + std::unique_ptr Loaded(new TList()); + try__finally + { + SCOPE_EXIT + { + AutoSort = true; + AlphaSort(); + }; + + DebugAssert(AutoSort); + AutoSort = false; + bool WasEmpty = (GetCount() == 0); + + Storage->GetSubKeyNames(SubKeys.get()); + + for (intptr_t Index = 0; Index < SubKeys->GetCount(); ++Index) + { + UnicodeString SessionName = SubKeys->GetString(Index); + + bool ValidName = true; + try + { + TSessionData::ValidatePath(SessionName); + } + catch (...) + { + ValidName = false; + } + + if (ValidName) + { + TSessionData * SessionData = nullptr; + if (SessionName == FDefaultSettings->GetName()) + { + SessionData = FDefaultSettings; + } + else + { + // if the list was empty before loading, do not waste time trying to + // find existing sites to overwrite (we rely on underlying storage + // to secure uniqueness of the key names) + if (WasEmpty) + { + SessionData = nullptr; + } + else + { + SessionData = NB_STATIC_DOWNCAST(TSessionData, FindByName(SessionName)); + } + } + + if ((SessionData != FDefaultSettings) || !UseDefaults) + { + if (!SessionData) + { + SessionData = new TSessionData(L""); + if (UseDefaults) + { + SessionData->CopyData(GetDefaultSettings()); + } + SessionData->SetName(SessionName); + Add(SessionData); + } + Loaded->Add(SessionData); + SessionData->Load(Storage); + if (AsModified) + { + SessionData->SetModified(true); + } + } + } + } + + if (!AsModified) + { + for (intptr_t Index = 0; Index < TObjectList::GetCount(); ++Index) + { + if (Loaded->IndexOf(GetObj(Index)) < 0) + { + Delete(Index); + Index--; + } + } + } + } + __finally + { +// AutoSort = true; +// AlphaSort(); +// delete SubKeys; +// delete Loaded; + }; +} + +void TStoredSessionList::Load() +{ + bool SessionList = true; + std::unique_ptr Storage(GetConfiguration()->CreateStorage(SessionList)); + try__finally + { + if (Storage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), False)) + { + Load(Storage.get()); + } + } + __finally + { +// delete Storage; + }; +} + +void TStoredSessionList::DoSave(THierarchicalStorage * Storage, + TSessionData * Data, bool All, bool RecryptPasswordOnly, + TSessionData * FactoryDefaults) +{ + if (All || Data->GetModified()) + { + if (RecryptPasswordOnly) + { + Data->SaveRecryptedPasswords(Storage); + } + else + { + Data->Save(Storage, false, FactoryDefaults); + } + } +} + +void TStoredSessionList::DoSave(THierarchicalStorage * Storage, + bool All, bool RecryptPasswordOnly, TStrings * RecryptPasswordErrors) +{ + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + try__finally + { + DoSave(Storage, FDefaultSettings, All, RecryptPasswordOnly, FactoryDefaults.get()); + for (intptr_t Index = 0; Index < GetCountIncludingHidden(); ++Index) + { + TSessionData * SessionData = NB_STATIC_DOWNCAST(TSessionData, GetObj(Index)); + try + { + DoSave(Storage, SessionData, All, RecryptPasswordOnly, FactoryDefaults.get()); + } + catch (Exception & E) + { + UnicodeString Message; + if (RecryptPasswordOnly && DebugAlwaysTrue(RecryptPasswordErrors != nullptr) && + ExceptionMessage(&E, Message)) + { + RecryptPasswordErrors->Add(FORMAT(L"%s: %s", SessionData->GetSessionName().c_str(), Message.c_str())); + } + else + { + throw; + } + } + } + } + __finally + { +// delete FactoryDefaults; + }; +} + +void TStoredSessionList::Save(THierarchicalStorage * Storage, bool All) +{ + DoSave(Storage, All, false, nullptr); +} + +void TStoredSessionList::DoSave(bool All, bool Explicit, + bool RecryptPasswordOnly, TStrings * RecryptPasswordErrors) +{ + bool SessionList = true; + std::unique_ptr Storage(GetConfiguration()->CreateStorage(SessionList)); + try__finally + { + Storage->SetAccessMode(smReadWrite); + Storage->SetExplicit(Explicit); + if (Storage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), true)) + { + DoSave(Storage.get(), All, RecryptPasswordOnly, RecryptPasswordErrors); + } + } + __finally + { +// delete Storage; + }; + + Saved(); +} + +void TStoredSessionList::Save(bool All, bool Explicit) +{ + DoSave(All, Explicit, false, nullptr); +} + +void TStoredSessionList::RecryptPasswords(TStrings * RecryptPasswordErrors) +{ + DoSave(true, true, true, RecryptPasswordErrors); +} + +void TStoredSessionList::Saved() +{ + FDefaultSettings->SetModified(false); + for (intptr_t Index = 0; Index < GetCountIncludingHidden(); ++Index) + { + (NB_STATIC_DOWNCAST(TSessionData, GetObj(Index))->SetModified(false)); + } +} + +/*void TStoredSessionList::ImportLevelFromFilezilla(_di_IXMLNode Node, const UnicodeString & APath) +{ + for (int Index = 0; Index < Node->ChildNodes->Count; ++Index) + { + _di_IXMLNode ChildNode = Node->ChildNodes->Get(Index); + if (ChildNode->NodeName == L"Server") + { + std::unique_ptr SessionData(new TSessionData(L"")); + SessionData->CopyData(DefaultSettings); + SessionData->ImportFromFilezilla(ChildNode, Path); + Add(SessionData.release()); + } + else if (ChildNode->NodeName == L"Folder") + { + UnicodeString Name; + + for (int Index = 0; Index < ChildNode->ChildNodes->Count; ++Index) + { + _di_IXMLNode PossibleTextMode = ChildNode->ChildNodes->Get(Index); + if (PossibleTextMode->NodeType == ntText) + { + UnicodeString NodeValue = PossibleTextMode->NodeValue; + AddToList(Name, NodeValue.Trim(), L" "); + } + } + + Name = TSessionData::MakeValidName(Name).Trim(); + + ImportLevelFromFilezilla(ChildNode, TSessionData::ComposePath(Path, Name)); + } + } +}*/ + +void TStoredSessionList::ImportFromFilezilla(const UnicodeString & /*AFileName*/) +{ + ThrowNotImplemented(3004); +/* + const _di_IXMLDocument Document = interface_cast(new TXMLDocument(nullptr)); + Document->LoadFromFile(FileName); + _di_IXMLNode FileZilla3Node = Document->ChildNodes->FindNode(L"FileZilla3"); + if (FileZilla3Node != nullptr) + { + _di_IXMLNode ServersNode = FileZilla3Node->ChildNodes->FindNode(L"Servers"); + if (ServersNode != nullptr) + { + ImportLevelFromFilezilla(ServersNode, L""); + } + } +*/ +} + +void TStoredSessionList::Export(const UnicodeString & /*AFileName*/) +{ + ThrowNotImplemented(3003); +/*try__finally + { + std::unique_ptr Storage(new TIniFileStorage(FileName)); + Storage->SetAccessMode(smReadWrite); + if (Storage->OpenSubKey(GetConfiguration()->GetStoredSessionsSubKey(), true)) + { + Save(Storage, true); + } + } + __finally + { +// delete Storage; + };*/ +} + +void TStoredSessionList::SelectAll(bool Select) +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + GetSession(Index)->SetSelected(Select); + } +} + +void TStoredSessionList::Import(TStoredSessionList * From, + bool OnlySelected, TList * Imported) +{ + for (intptr_t Index = 0; Index < From->GetCount(); ++Index) + { + if (!OnlySelected || From->GetSession(Index)->GetSelected()) + { + TSessionData * Session = new TSessionData(L""); + Session->Assign(From->GetSession(Index)); + Session->SetModified(true); + Session->MakeUniqueIn(this); + Add(Session); + if (Imported != nullptr) + { + Imported->Add(Session); + } + } + } + // only modified, explicit + Save(false, true); +} + +void TStoredSessionList::SelectSessionsToImport( + TStoredSessionList * Dest, bool SSHOnly) +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + GetSession(Index)->SetSelected( + (!SSHOnly || (GetSession(Index)->GetNormalizedPuttyProtocol() == PuttySshProtocol)) && + !Dest->FindByName(GetSession(Index)->GetName())); + } +} + +void TStoredSessionList::Cleanup() +{ + try + { + if (GetConfiguration()->GetStorage() == stRegistry) + { + Clear(); + } + std::unique_ptr Storage(new TRegistryStorage(GetConfiguration()->GetRegistryStorageKey())); + try__finally + { + Storage->SetAccessMode(smReadWrite); + if (Storage->OpenRootKey(False)) + { + Storage->RecursiveDeleteSubKey(GetConfiguration()->GetStoredSessionsSubKey()); + } + } + __finally + { +// delete Storage; + }; + } + catch (Exception & E) + { + throw ExtException(&E, LoadStr(CLEANUP_SESSIONS_ERROR)); + } +} + +void TStoredSessionList::UpdateStaticUsage() +{ +#if 0 + intptr_t SCP = 0; + intptr_t SFTP = 0; + intptr_t FTP = 0; + intptr_t FTPS = 0; + intptr_t WebDAV = 0; + intptr_t WebDAVS = 0; + intptr_t Password = 0; + intptr_t Advanced = 0; + intptr_t Color = 0; + int Note = 0; + int Tunnel = 0; + bool Folders = false; + bool Workspaces = false; + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + std::unique_ptr DifferentAdvancedProperties(CreateSortedStringList()); + for (int Index = 0; Index < Count; ++Index) + { + TSessionData * Data = Sessions[Index]; + if (Data->IsWorkspace) + { + Workspaces = true; + } + else + { + switch (Data->FSProtocol) + { + case fsSCPonly: + SCP++; + break; + + case fsSFTP: + case fsSFTPonly: + SFTP++; + break; + + case fsFTP: + if (Data->Ftps == ftpsNone) + { + FTP++; + } + else + { + FTPS++; + } + break; + + case fsWebDAV: + if (Data->Ftps == ftpsNone) + { + WebDAV++; + } + else + { + WebDAVS++; + } + break; + } + + if (Data->HasAnySessionPassword()) + { + Password++; + } + + if (Data->Color != 0) + { + Color++; + } + + if (!Data->Note.IsEmpty()) + { + Note++; + } + + // this effectively does not take passwords (proxy + tunnel) into account, + // when master password is set, as master password handler in not set up yet + if (!Data->IsSame(FactoryDefaults.get(), true, DifferentAdvancedProperties.get())) + { + Advanced++; + } + + if (Data->Tunnel) + { + Tunnel++; + } + + if (!Data->FolderName.IsEmpty()) + { + Folders = true; + } + } + } + + Configuration->Usage->Set(L"StoredSessionsCountSCP", SCP); + Configuration->Usage->Set(L"StoredSessionsCountSFTP", SFTP); + Configuration->Usage->Set(L"StoredSessionsCountFTP", FTP); + Configuration->Usage->Set(L"StoredSessionsCountFTPS", FTPS); + Configuration->Usage->Set(L"StoredSessionsCountWebDAV", WebDAV); + Configuration->Usage->Set(L"StoredSessionsCountWebDAVS", WebDAVS); + Configuration->Usage->Set(L"StoredSessionsCountPassword", Password); + Configuration->Usage->Set(L"StoredSessionsCountColor", Color); + Configuration->Usage->Set(L"StoredSessionsCountNote", Note); + Configuration->Usage->Set(L"StoredSessionsCountAdvanced", Advanced); + DifferentAdvancedProperties->Delimiter = L','; + Configuration->Usage->Set(L"StoredSessionsAdvancedSettings", DifferentAdvancedProperties->DelimitedText); + Configuration->Usage->Set(L"StoredSessionsCountTunnel", Tunnel); + + // actually default might be true, see below for when the default is actually used + bool CustomDefaultStoredSession = false; + try + { + // this can throw, when the default session settings have password set + // (and no other basic property, like hostname/username), + // and master password is enabled as we are called before master password + // handler is set + CustomDefaultStoredSession = !FDefaultSettings->IsSame(FactoryDefaults.get(), false); + } + catch (...) + { + } + Configuration->Usage->Set(L"UsingDefaultStoredSession", CustomDefaultStoredSession); + + Configuration->Usage->Set(L"UsingStoredSessionsFolders", Folders); + Configuration->Usage->Set(L"UsingWorkspaces", Workspaces); +#endif +} + +const TSessionData * TStoredSessionList::FindSame(TSessionData * Data) const +{ + const TSessionData * Result; + if (Data->GetHidden() || Data->GetName().IsEmpty()) // || Data->GetIsWorkspace()) + { + Result = nullptr; + } + else + { + const TNamedObject * Obj = FindByName(Data->GetName()); + Result = NB_STATIC_DOWNCAST_CONST(TSessionData, Obj); + } + return Result; +} + +intptr_t TStoredSessionList::IndexOf(TSessionData * Data) const +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (Data == GetSession(Index)) + { + return Index; + } + } + return -1; +} + +TSessionData * TStoredSessionList::NewSession( + const UnicodeString & SessionName, TSessionData * Session) +{ + TSessionData * DuplicateSession = NB_STATIC_DOWNCAST(TSessionData, FindByName(SessionName)); + if (!DuplicateSession) + { + DuplicateSession = new TSessionData(L""); + DuplicateSession->Assign(Session); + DuplicateSession->SetName(SessionName); + // make sure, that new stored session is saved to registry + DuplicateSession->SetModified(true); + Add(DuplicateSession); + } + else + { + DuplicateSession->Assign(Session); + DuplicateSession->SetName(SessionName); + DuplicateSession->SetModified(true); + } + // list was saved here before to default storage, but it would not allow + // to work with special lists (export/import) not using default storage + return DuplicateSession; +} + +void TStoredSessionList::SetDefaultSettings(const TSessionData * Value) +{ + DebugAssert(FDefaultSettings); + if (FDefaultSettings != Value) + { + FDefaultSettings->Assign(Value); + // make sure default settings are saved + FDefaultSettings->SetModified(true); + FDefaultSettings->SetName(DefaultName); + if (!FReadOnly) + { + // only modified, explicit + Save(false, true); + } + } +} + +void TStoredSessionList::ImportHostKeys(const UnicodeString & TargetKey, + const UnicodeString & SourceKey, TStoredSessionList * Sessions, + bool OnlySelected) +{ + std::unique_ptr SourceStorage(new TRegistryStorage(SourceKey)); + std::unique_ptr TargetStorage(new TRegistryStorage(TargetKey)); + std::unique_ptr KeyList(new TStringList()); + try__finally + { + TargetStorage->SetAccessMode(smReadWrite); + + if (SourceStorage->OpenRootKey(false) && + TargetStorage->OpenRootKey(true)) + { + SourceStorage->GetValueNames(KeyList.get()); + + UnicodeString HostKeyName; + DebugAssert(Sessions != nullptr); + for (intptr_t Index = 0; Index < Sessions->GetCount(); ++Index) + { + TSessionData * Session = Sessions->GetSession(Index); + if (!OnlySelected || Session->GetSelected()) + { + HostKeyName = PuttyMungeStr(FORMAT(L"@%d:%s", Session->GetPortNumber(), Session->GetHostNameExpanded().c_str())); + UnicodeString KeyName; + for (intptr_t KeyIndex = 0; KeyIndex < KeyList->GetCount(); ++KeyIndex) + { + KeyName = KeyList->GetString(KeyIndex); + intptr_t P = KeyName.Pos(HostKeyName); + if ((P > 0) && (P == KeyName.Length() - HostKeyName.Length() + 1)) + { + TargetStorage->WriteStringRaw(KeyName, + SourceStorage->ReadStringRaw(KeyName, L"")); + } + } + } + } + } + } + __finally + { +// delete SourceStorage; +// delete TargetStorage; +// delete KeyList; + }; +} + +const TSessionData * TStoredSessionList::GetSessionByName(const UnicodeString & SessionName) const +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + const TSessionData * SessionData = GetSession(Index); + if (SessionData->GetName() == SessionName) + { + return SessionData; + } + } + return nullptr; +} + +void TStoredSessionList::Load(const UnicodeString & AKey, bool UseDefaults) +{ + std::unique_ptr Storage(new TRegistryStorage(AKey)); + if (Storage->OpenRootKey(false)) + { + Load(Storage.get(), false, UseDefaults); + } +} + +bool TStoredSessionList::IsFolderOrWorkspace( + const UnicodeString & Name, bool Workspace) const +{ + bool Result = false; + const TSessionData * FirstData = nullptr; + if (!Name.IsEmpty()) + { + for (intptr_t Index = 0; !Result && (Index < GetCount()); ++Index) + { + Result = GetSession(Index)->IsInFolderOrWorkspace(Name); + if (Result) + { + FirstData = GetSession(Index); + } + } + } + + return + Result && + DebugAlwaysTrue(FirstData != nullptr) && + (FirstData->GetIsWorkspace() == Workspace); +} + +bool TStoredSessionList::GetIsFolder(const UnicodeString & Name) const +{ + return IsFolderOrWorkspace(Name, false); +} + +bool TStoredSessionList::GetIsWorkspace(const UnicodeString & Name) const +{ + return IsFolderOrWorkspace(Name, true); +} + +TSessionData * TStoredSessionList::CheckIsInFolderOrWorkspaceAndResolve( + TSessionData * Data, const UnicodeString & Name) +{ + if (Data->IsInFolderOrWorkspace(Name)) + { + Data = ResolveWorkspaceData(Data); + + if ((Data != nullptr) && Data->GetCanLogin() && + DebugAlwaysTrue(Data->GetLink().IsEmpty())) + { + return Data; + } + } + return nullptr; +} + +void TStoredSessionList::GetFolderOrWorkspace(const UnicodeString & Name, TList * List) +{ + for (intptr_t Index = 0; (Index < GetCount()); ++Index) + { + TSessionData * RawData = GetSession(Index); + TSessionData * Data = + CheckIsInFolderOrWorkspaceAndResolve(RawData, Name); + + if (Data != nullptr) + { + TSessionData * Data2 = new TSessionData(L""); + Data2->Assign(Data); + + if (!RawData->GetLink().IsEmpty() && (DebugAlwaysTrue(Data != RawData)) && + // BACKWARD COMPATIBILITY + // When loading pre-5.6.4 workspace, that does not have state saved, + // do not overwrite the site "state" defaults + // with (empty) workspace state + RawData->HasStateData()) + { + Data2->CopyStateData(RawData); + } + + List->Add(Data2); + } + } +} + +TStrings * TStoredSessionList::GetFolderOrWorkspaceList( + const UnicodeString & Name) +{ + std::unique_ptr Result(new TStringList()); + + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TSessionData * Data = + CheckIsInFolderOrWorkspaceAndResolve(GetSession(Index), Name); + + if (Data != nullptr) + { + Result->Add(Data->GetSessionName()); + } + } + + return Result.release(); +} + +TStrings * TStoredSessionList::GetWorkspaces() +{ + std::unique_ptr Result(CreateSortedStringList()); + + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TSessionData * Data = GetSession(Index); + if (Data->GetIsWorkspace()) + { + Result->Add(Data->GetFolderName()); + } + } + + return Result.release(); +} + +void TStoredSessionList::NewWorkspace( + const UnicodeString & Name, TList * DataList) +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TSessionData * Data = GetSession(Index); + if (Data->IsInFolderOrWorkspace(Name)) + { + Data->Remove(); + Remove(Data); + Index--; + } + } + + for (intptr_t Index = 0; Index < DataList->GetCount(); ++Index) + { + TSessionData * Data = NB_STATIC_DOWNCAST(TSessionData, DataList->GetItem(Index)); + + TSessionData * Data2 = new TSessionData(L""); + Data2->Assign(Data); + Data2->SetName(TSessionData::ComposePath(Name, Data->GetName())); + // make sure, that new stored session is saved to registry + Data2->SetModified(true); + Add(Data2); + } +} + +bool TStoredSessionList::HasAnyWorkspace() +{ + bool Result = false; + for (intptr_t Index = 0; !Result && (Index < GetCount()); ++Index) + { + TSessionData * Data = GetSession(Index); + Result = Data->GetIsWorkspace(); + } + return Result; +} + +TSessionData * TStoredSessionList::ParseUrl(const UnicodeString & Url, + TOptions * Options, bool & DefaultsOnly, UnicodeString * AFileName, + bool * AProtocolDefined, UnicodeString * MaskedUrl) +{ + std::unique_ptr Data(new TSessionData(L"")); + try__catch + { + Data->ParseUrl(Url, Options, this, DefaultsOnly, AFileName, AProtocolDefined, MaskedUrl); + } + /*catch (...) + { + delete Data; + throw; + }*/ + return Data.release(); +} + +bool TStoredSessionList::IsUrl(const UnicodeString & Url) +{ + bool DefaultsOnly; + bool ProtocolDefined = false; + std::unique_ptr ParsedData(ParseUrl(Url, nullptr, DefaultsOnly, nullptr, &ProtocolDefined)); + bool Result = ProtocolDefined; + return Result; +} + +TSessionData * TStoredSessionList::ResolveWorkspaceData(TSessionData * Data) +{ + if (!Data->GetLink().IsEmpty()) + { + Data = NB_STATIC_DOWNCAST(TSessionData, FindByName(Data->GetLink())); + if (Data != nullptr) + { + Data = ResolveWorkspaceData(Data); + } + } + return Data; +} + +TSessionData * TStoredSessionList::SaveWorkspaceData(TSessionData * Data) +{ + std::unique_ptr Result(new TSessionData(L"")); + + const TSessionData * SameData = StoredSessions->FindSame(Data); + if (SameData != nullptr) + { + Result->CopyStateData(Data); + Result->SetLink(Data->GetName()); + } + else + { + Result->Assign(Data); + } + + Result->SetIsWorkspace(true); + + return Result.release(); +} + +bool TStoredSessionList::CanLogin(TSessionData * Data) +{ + Data = ResolveWorkspaceData(Data); + return (Data != nullptr) && Data->GetCanLogin(); +} + +bool GetCodePageInfo(UINT CodePage, CPINFOEX & CodePageInfoEx) +{ + if (!GetCPInfoEx(CodePage, 0, &CodePageInfoEx)) + { + CPINFO CodePageInfo; + + if (!GetCPInfo(CodePage, &CodePageInfo)) + return false; + + CodePageInfoEx.MaxCharSize = CodePageInfo.MaxCharSize; + CodePageInfoEx.CodePageName[0] = L'\0'; + } + + //if (CodePageInfoEx.MaxCharSize != 1) + // return false; + + return true; +} + +uintptr_t GetCodePageAsNumber(const UnicodeString & CodePage) +{ + uintptr_t codePage = _wtoi(CodePage.c_str()); + return static_cast(codePage == 0 ? CONST_DEFAULT_CODEPAGE : codePage); +} + +UnicodeString GetCodePageAsString(uintptr_t CodePage) +{ + CPINFOEX cpInfoEx; + if (::GetCodePageInfo(static_cast(CodePage), cpInfoEx)) + { + return UnicodeString(cpInfoEx.CodePageName); + } + return ::IntToStr(CONST_DEFAULT_CODEPAGE); +} + +UnicodeString GetExpandedLogFileName(const UnicodeString & LogFileName, TSessionData * SessionData) +{ + // StripPathQuotes should not be needed as we do not feed quotes anymore + UnicodeString Result = StripPathQuotes(::ExpandEnvironmentVariables(LogFileName)); + TDateTime N = Now(); + for (intptr_t Index = 1; Index < Result.Length(); ++Index) + { + if ((Result[Index] == L'&') && (Index < Result.Length())) + { + UnicodeString Replacement; + // keep consistent with TFileCustomCommand::PatternReplacement + uint16_t Y, M, D, H, NN, S, MS; + TDateTime DateTime = N; + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, NN, S, MS); + switch (::LowCase(Result[Index + 1])) + { + case L'y': + // Replacement = FormatDateTime(L"yyyy", N); + Replacement = FORMAT(L"%04d", Y); + break; + + case L'm': + // Replacement = FormatDateTime(L"mm", N); + Replacement = FORMAT(L"%02d", M); + break; + + case L'd': + // Replacement = FormatDateTime(L"dd", N); + Replacement = FORMAT(L"%02d", D); + break; + + case L't': + // Replacement = FormatDateTime("hhnnss", N); + Replacement = FORMAT(L"%02d%02d%02d", H, NN, S); + break; + + case 'p': + Replacement = ::Int64ToStr(static_cast(::GetCurrentProcessId())); + break; + + case L'@': + if (SessionData != nullptr) + { + Replacement = MakeValidFileName(SessionData->GetHostNameExpanded()); + } + else + { + Replacement = L"nohost"; + } + break; + + case L's': + if (SessionData != nullptr) + { + Replacement = MakeValidFileName(SessionData->GetSessionName()); + } + else + { + Replacement = L"nosession"; + } + break; + + case L'&': + Replacement = L"&"; + break; + + case L'!': + Replacement = L"!"; + break; + + default: + Replacement = UnicodeString(L"&") + Result[Index + 1]; + break; + } + Result.Delete(Index, 2); + Result.Insert(Replacement, Index); + Index += Replacement.Length() - 1; + } + } + return Result; +} + +bool GetIsSshProtocol(TFSProtocol FSProtocol) +{ + return + (FSProtocol == fsSFTPonly) || (FSProtocol == fsSFTP) || + (FSProtocol == fsSCPonly); +} + +intptr_t GetDefaultPort(TFSProtocol FSProtocol, TFtps Ftps) +{ + intptr_t Result; + switch (FSProtocol) + { + case fsFTP: + if (Ftps == ftpsImplicit) + { + Result = FtpsImplicitPortNumber; + } + else + { + Result = FtpPortNumber; + } + break; + + case fsWebDAV: + if (Ftps == ftpsNone) + { + Result = HTTPPortNumber; + } + else + { + Result = HTTPSPortNumber; + } + break; + + default: + if (GetIsSshProtocol(FSProtocol)) + { + Result = SshPortNumber; + } + else + { + DebugFail(); + Result = -1; + } + break; + } + return Result; +} + +NB_IMPLEMENT_CLASS(TSessionData, NB_GET_CLASS_INFO(TNamedObject), nullptr) + diff --git a/netbox/src/core/SessionData.h b/netbox/src/core/SessionData.h new file mode 100644 index 000000000..5725807c9 --- /dev/null +++ b/netbox/src/core/SessionData.h @@ -0,0 +1,926 @@ +#pragma once + +#include +#include + +#include "Option.h" +#include "NamedObjs.h" +#include "HierarchicalStorage.h" +#include "Configuration.h" + +#define SET_SESSION_PROPERTY(Property) \ + if (F##Property != Value) { F##Property = Value; Modify(); } + +enum TCipher +{ + cipWarn, + cip3DES, + cipBlowfish, + cipAES, + cipDES, + cipArcfour, + cipChaCha20, +}; +#define CIPHER_COUNT (cipChaCha20 + 1) + +// explicit values to skip obsoleted fsExternalSSH, fsExternalSFTP +enum TFSProtocol_219 +{ + fsFTPS_219 = 6, + fsHTTP_219 = 7, + fsHTTPS_219 = 8, +}; + +enum TFSProtocol +{ + fsSCPonly = 0, + fsSFTP = 1, + fsSFTPonly = 2, + fsFTP = 5, + fsWebDAV = 6, +}; +#define FSPROTOCOL_COUNT (fsWebDAV+1) + +enum TLoginType +{ + ltAnonymous = 0, + ltNormal = 1, +}; + +extern const wchar_t * ProxyMethodNames; + +enum TProxyMethod +{ + pmNone, pmSocks4, pmSocks5, pmHTTP, pmTelnet, pmCmd, pmSystem, +}; + +enum TSshProt +{ + ssh1only, ssh1deprecated, ssh2deprecated, ssh2only, +}; + +enum TKex +{ + kexWarn, kexDHGroup1, kexDHGroup14, kexDHGEx, kexRSA, kexECDH, +}; +#define KEX_COUNT (kexECDH + 1) + +enum TSshBug +{ + sbIgnore1, sbPlainPW1, sbRSA1, sbHMAC2, sbDeriveKey2, sbRSAPad2, + sbPKSessID2, sbRekey2, sbMaxPkt2, sbIgnore2, sbOldGex2, sbWinAdj, +}; +#define BUG_COUNT (sbWinAdj + 1) + +enum TSftpBug +{ + sbSymlink, + sbSignedTS, +}; +#define SFTP_BUG_COUNT (sbSignedTS + 1) + +extern const wchar_t * PingTypeNames; + +enum TPingType +{ + ptOff, ptNullPacket, ptDummyCommand, +}; + +enum TAddressFamily +{ + afAuto, afIPv4, afIPv6, +}; + +enum TFtps +{ + ftpsNone, ftpsImplicit, ftpsExplicitSsl, ftpsExplicitTls, +}; + +// has to match SSL_VERSION_XXX constants in AsyncSslSocketLayer.h +enum TTlsVersion { ssl2 = 2, ssl3 = 3, tls10 = 10, tls11 = 11, tls12 = 12 }; +enum TSessionSource { ssNone, ssStored, ssStoredModified }; +enum TSessionUrlFlags +{ + sufSpecific = 0x01, + sufUserName = 0x02, + sufPassword = 0x04, + sufHostKey = 0x08, + sufComplete = sufUserName | sufPassword | sufHostKey, + sufOpen = sufUserName | sufPassword, +}; + +extern const UnicodeString CipherNames[CIPHER_COUNT]; +extern const UnicodeString KexNames[KEX_COUNT]; +extern const wchar_t SshProtList[][10]; +extern const TCipher DefaultCipherList[CIPHER_COUNT]; +extern const TKex DefaultKexList[KEX_COUNT]; +extern const wchar_t FSProtocolNames[FSPROTOCOL_COUNT][16]; +extern const intptr_t DefaultSendBuf; +#define ANONYMOUS_USER_NAME L"anonymous" +#define ANONYMOUS_PASSWORD L"" +extern const intptr_t SshPortNumber; +extern const intptr_t FtpPortNumber; +extern const intptr_t FtpsImplicitPortNumber; +extern const intptr_t HTTPPortNumber; +extern const intptr_t HTTPSPortNumber; +extern const intptr_t TelnetPortNumber; +extern const intptr_t ProxyPortNumber; +extern const UnicodeString PuttySshProtocol; +extern const UnicodeString PuttyTelnetProtocol; +extern const UnicodeString SftpProtocol; +extern const UnicodeString ScpProtocol; +extern const UnicodeString FtpProtocol; +extern const UnicodeString FtpsProtocol; +extern const UnicodeString FtpesProtocol; +#define WebDAVProtocol HttpProtocol +#define WebDAVSProtocol HttpsProtocol +extern const UnicodeString SshProtocol; +//#define ProtocolSeparator L"://" +//#define WinSCPProtocolPrefix L"winscp-" +extern const wchar_t UrlParamSeparator; +extern const wchar_t UrlParamValueSeparator; +//#define UrlHostKeyParamName L"fingerprint" +//#define UrlSaveParamName L"save" +//#define PassphraseOption L"passphrase" + +extern const intptr_t SFTPMinVersion; +extern const intptr_t SFTPMaxVersion; + +struct TIEProxyConfig : public TObject +{ + TIEProxyConfig() : + AutoDetect(false), + ProxyPort(0), + ProxyMethod(pmNone) + {} + bool AutoDetect; // not used + UnicodeString AutoConfigUrl; // not used + UnicodeString Proxy; //< string in format "http=host:80;https=host:443;ftp=ftpproxy:20;socks=socksproxy:1080" + UnicodeString ProxyBypass; //< string in format "*.local, foo.com, google.com" + UnicodeString ProxyHost; + intptr_t ProxyPort; + TProxyMethod ProxyMethod; +}; + +class TStoredSessionList; + +class TSessionData : public TNamedObject +{ +friend class TStoredSessionList; +NB_DISABLE_COPY(TSessionData) +NB_DECLARE_CLASS(TSessionData) +public: +private: + UnicodeString FHostName; + intptr_t FPortNumber; + UnicodeString FUserName; + RawByteString FPassword; + intptr_t FPingInterval; + TPingType FPingType; + bool FTryAgent; + bool FAgentFwd; + UnicodeString FListingCommand; + bool FAuthTIS; + bool FAuthKI; + bool FAuthKIPassword; + bool FAuthGSSAPI; + bool FGSSAPIFwdTGT; // not supported anymore + UnicodeString FGSSAPIServerRealm; // not supported anymore + bool FChangeUsername; + bool FCompression; + TSshProt FSshProt; + bool FSsh2DES; + bool FSshNoUserAuth; + TCipher FCiphers[CIPHER_COUNT]; + TKex FKex[KEX_COUNT]; + bool FClearAliases; + TEOLType FEOLType; + bool FTrimVMSVersions; + UnicodeString FPublicKeyFile; + UnicodeString FPassphrase; + UnicodeString FPuttyProtocol; + TFSProtocol FFSProtocol; + bool FModified; + UnicodeString FLocalDirectory; + UnicodeString FRemoteDirectory; + bool FLockInHome; + bool FSpecial; + bool FSynchronizeBrowsing; + bool FUpdateDirectories; + bool FCacheDirectories; + bool FCacheDirectoryChanges; + bool FPreserveDirectoryChanges; + bool FSelected; + TAutoSwitch FLookupUserGroups; + UnicodeString FReturnVar; + bool FScp1Compatibility; + UnicodeString FShell; + UnicodeString FSftpServer; + intptr_t FTimeout; + bool FUnsetNationalVars; + bool FIgnoreLsWarnings; + bool FTcpNoDelay; + intptr_t FSendBuf; + bool FSshSimple; + TProxyMethod FProxyMethod; + UnicodeString FProxyHost; + intptr_t FProxyPort; + UnicodeString FProxyUsername; + RawByteString FProxyPassword; + UnicodeString FProxyTelnetCommand; + UnicodeString FProxyLocalCommand; + TAutoSwitch FProxyDNS; + bool FProxyLocalhost; + intptr_t FFtpProxyLogonType; + TAutoSwitch FBugs[BUG_COUNT]; + UnicodeString FCustomParam1; + UnicodeString FCustomParam2; + bool FResolveSymlinks; + bool FFollowDirectorySymlinks; + TDateTime FTimeDifference; + bool FTimeDifferenceAuto; + intptr_t FSFTPDownloadQueue; + intptr_t FSFTPUploadQueue; + intptr_t FSFTPListingQueue; + intptr_t FSFTPMaxVersion; + intptr_t FSFTPMinPacketSize; + intptr_t FSFTPMaxPacketSize; + TDSTMode FDSTMode; + TAutoSwitch FSFTPBugs[SFTP_BUG_COUNT]; + bool FDeleteToRecycleBin; + bool FOverwrittenToRecycleBin; + UnicodeString FRecycleBinPath; + UnicodeString FPostLoginCommands; + TAutoSwitch FSCPLsFullTime; + TAutoSwitch FFtpListAll; + TAutoSwitch FFtpHost; + bool FFtpDupFF; + bool FFtpUndupFF; + bool FSslSessionReuse; + UnicodeString FTlsCertificateFile; + TAddressFamily FAddressFamily; + UnicodeString FRekeyData; + uintptr_t FRekeyTime; + intptr_t FColor; + bool FTunnel; + UnicodeString FTunnelHostName; + intptr_t FTunnelPortNumber; + UnicodeString FTunnelUserName; + RawByteString FTunnelPassword; + UnicodeString FTunnelPublicKeyFile; + intptr_t FTunnelLocalPortNumber; + UnicodeString FTunnelPortFwd; + UnicodeString FTunnelHostKey; + bool FFtpPasvMode; + TAutoSwitch FFtpForcePasvIp; + TAutoSwitch FFtpUseMlsd; + UnicodeString FFtpAccount; + intptr_t FFtpPingInterval; + TPingType FFtpPingType; + TAutoSwitch FFtpTransferActiveImmediately; + TFtps FFtps; + TTlsVersion FMinTlsVersion; + TTlsVersion FMaxTlsVersion; + TAutoSwitch FNotUtf; + bool FIsWorkspace; + UnicodeString FLink; + UnicodeString FHostKey; + bool FFingerprintScan; + bool FOverrideCachedHostKey; + UnicodeString FNote; + + UnicodeString FOrigHostName; + intptr_t FOrigPortNumber; + TProxyMethod FOrigProxyMethod; + bool FTunnelConfigured; + TSessionSource FSource; + bool FSaveOnly; + UnicodeString FCodePage; + mutable uintptr_t FCodePageAsNumber; + bool FFtpAllowEmptyPassword; + TLoginType FLoginType; + intptr_t FNumberOfRetries; + uintptr_t FSessionVersion; + + mutable TIEProxyConfig * FIEProxyConfig; + +public: + void SetHostName(const UnicodeString & AValue); + UnicodeString GetHostNameExpanded() const; + void SetPortNumber(intptr_t Value); + void SetUserName(const UnicodeString & Value); + UnicodeString GetUserNameExpanded() const; + void SetPassword(const UnicodeString & Value); + UnicodeString GetPassword() const; + void SetPingInterval(intptr_t Value); + void SetTryAgent(bool Value); + void SetAgentFwd(bool Value); + void SetAuthTIS(bool Value); + void SetAuthKI(bool Value); + void SetAuthKIPassword(bool Value); + void SetAuthGSSAPI(bool Value); + void SetGSSAPIFwdTGT(bool Value); + void SetGSSAPIServerRealm(const UnicodeString & Value); + void SetChangeUsername(bool Value); + void SetCompression(bool Value); + void SetSshProt(TSshProt Value); + void SetSsh2DES(bool Value); + void SetSshNoUserAuth(bool Value); + void SetCipher(intptr_t Index, TCipher Value); + TCipher GetCipher(intptr_t Index) const; + void SetKex(intptr_t Index, TKex Value); + TKex GetKex(intptr_t Index) const; + void SetPublicKeyFile(const UnicodeString & Value); + UnicodeString GetPassphrase() const; + void SetPassphrase(const UnicodeString & Value); + + void SetPuttyProtocol(const UnicodeString & Value); + bool GetCanLogin() const; + void SetPingIntervalDT(const TDateTime & Value); + TDateTime GetPingIntervalDT() const; + TDateTime GetFtpPingIntervalDT() const; + void SetTimeDifference(const TDateTime & Value); + void SetTimeDifferenceAuto(bool Value); + void SetPingType(TPingType Value); + UnicodeString GetSessionName() const; + bool HasSessionName() const; + UnicodeString GetDefaultSessionName() const; + UnicodeString GetProtocolUrl() const; + void SetFSProtocol(TFSProtocol Value); + UnicodeString GetFSProtocolStr() const; + void SetLocalDirectory(const UnicodeString & Value); + void SetRemoteDirectory(const UnicodeString & Value); + void SetSynchronizeBrowsing(bool Value); + void SetUpdateDirectories(bool Value); + void SetCacheDirectories(bool Value); + void SetCacheDirectoryChanges(bool Value); + void SetPreserveDirectoryChanges(bool Value); + void SetLockInHome(bool Value); + void SetSpecial(bool Value); + UnicodeString GetInfoTip() const; + bool GetDefaultShell() const; + void SetDetectReturnVar(bool Value); + bool GetDetectReturnVar() const; + void SetListingCommand(const UnicodeString & Value); + void SetClearAliases(bool Value); + void SetDefaultShell(bool Value); + void SetEOLType(TEOLType Value); + void SetTrimVMSVersions(bool Value); + void SetLookupUserGroups(TAutoSwitch Value); + void SetReturnVar(const UnicodeString & Value); + void SetScp1Compatibility(bool Value); + void SetShell(const UnicodeString & Value); + void SetSftpServer(const UnicodeString & Value); + void SetTimeout(intptr_t Value); + void SetUnsetNationalVars(bool Value); + void SetIgnoreLsWarnings(bool Value); + void SetTcpNoDelay(bool Value); + void SetSendBuf(intptr_t Value); + void SetSshSimple(bool Value); + UnicodeString GetSshProtStr() const; + bool GetUsesSsh() const; + void SetCipherList(const UnicodeString & Value); + UnicodeString GetCipherList() const; + void SetKexList(const UnicodeString & Value); + UnicodeString GetKexList() const; + void SetProxyMethod(TProxyMethod Value); + void SetProxyHost(const UnicodeString & Value); + void SetProxyPort(intptr_t Value); + void SetProxyUsername(const UnicodeString & Value); + void SetProxyPassword(const UnicodeString & Value); + void SetProxyTelnetCommand(const UnicodeString & Value); + void SetProxyLocalCommand(const UnicodeString & Value); + void SetProxyDNS(TAutoSwitch Value); + void SetProxyLocalhost(bool Value); + UnicodeString GetProxyPassword() const; + void SetFtpProxyLogonType(intptr_t Value); + void SetBug(TSshBug Bug, TAutoSwitch Value); + TAutoSwitch GetBug(TSshBug Bug) const; + UnicodeString GetSessionKey() const; + void SetCustomParam1(const UnicodeString & Value); + void SetCustomParam2(const UnicodeString & Value); + void SetResolveSymlinks(bool Value); + void SetFollowDirectorySymlinks(bool Value); + void SetSFTPDownloadQueue(intptr_t Value); + void SetSFTPUploadQueue(intptr_t Value); + void SetSFTPListingQueue(intptr_t Value); + void SetSFTPMaxVersion(intptr_t Value); + void SetSFTPMaxPacketSize(intptr_t Value); + void SetSFTPMinPacketSize(intptr_t Value); + void SetSFTPBug(TSftpBug Bug, TAutoSwitch Value); + TAutoSwitch GetSFTPBug(TSftpBug Bug) const; + void SetSCPLsFullTime(TAutoSwitch Value); + void SetFtpListAll(TAutoSwitch Value); + void SetFtpHost(TAutoSwitch Value); + void SetFtpDupFF(bool Value); + void SetFtpUndupFF(bool Value); + void SetSslSessionReuse(bool Value); + void SetTlsCertificateFile(const UnicodeString & Value); + UnicodeString GetStorageKey() const; + UnicodeString GetInternalStorageKey() const; + UnicodeString GetSiteKey() const; + void SetDSTMode(TDSTMode Value); + void SetDeleteToRecycleBin(bool Value); + void SetOverwrittenToRecycleBin(bool Value); + void SetRecycleBinPath(const UnicodeString & Value); + void SetPostLoginCommands(const UnicodeString & Value); + void SetAddressFamily(TAddressFamily Value); + void SetRekeyData(const UnicodeString & Value); + void SetRekeyTime(uintptr_t Value); + void SetColor(intptr_t Value); + void SetTunnel(bool Value); + void SetTunnelHostName(const UnicodeString & Value); + void SetTunnelPortNumber(intptr_t Value); + void SetTunnelUserName(const UnicodeString & Value); + void SetTunnelPassword(const UnicodeString & Value); + UnicodeString GetTunnelPassword() const; + void SetTunnelPublicKeyFile(const UnicodeString & Value); + void SetTunnelPortFwd(const UnicodeString & Value); + void SetTunnelLocalPortNumber(intptr_t Value); + bool GetTunnelAutoassignLocalPortNumber(); + void SetTunnelHostKey(const UnicodeString & Value); + void SetFtpPasvMode(bool Value); + void SetFtpForcePasvIp(TAutoSwitch Value); + void SetFtpUseMlsd(TAutoSwitch Value); + void SetFtpAccount(const UnicodeString & Value); + void SetFtpPingInterval(intptr_t Value); + void SetFtpPingType(TPingType Value); + void SetFtpTransferActiveImmediately(TAutoSwitch Value); + void SetFtps(TFtps Value); + void SetMinTlsVersion(TTlsVersion Value); + void SetMaxTlsVersion(TTlsVersion Value); + void SetNotUtf(TAutoSwitch Value); + void SetIsWorkspace(bool Value); + void SetLink(const UnicodeString & Value); + void SetHostKey(const UnicodeString & Value); + void SetFingerprintScan(bool Value) { FFingerprintScan = Value; } + void SetNote(const UnicodeString & Value); + TDateTime GetTimeoutDT(); + void SavePasswords(THierarchicalStorage * Storage, bool PuttyExport, bool DoNotEncryptPasswords); + UnicodeString GetLocalName() const; + UnicodeString GetFolderName() const; + void Modify(); + UnicodeString GetSource() const; + bool GetSaveOnly() const { return FSaveOnly; } + void DoLoad(THierarchicalStorage * Storage, bool & RewritePassword); + void DoSave(THierarchicalStorage * Storage, + bool PuttyExport, const TSessionData * Default, bool DoNotEncryptPasswords); + /*UnicodeString ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, const UnicodeString & Default); + int ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, int Default); + bool IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties);*/ + UnicodeString GetNameWithoutHiddenPrefix() const; + bool HasStateData() const; + void CopyStateData(TSessionData * SourceData); + void CopyNonCoreData(TSessionData * SourceData); + UnicodeString GetNormalizedPuttyProtocol() const; + static RawByteString EncryptPassword(const UnicodeString & Password, const UnicodeString & Key); + static UnicodeString DecryptPassword(const RawByteString & Password, const UnicodeString & Key); + static RawByteString StronglyRecryptPassword(const RawByteString & Password, const UnicodeString & Key); + static bool DoIsProtocolUrl(const UnicodeString & Url, const UnicodeString & Protocol, intptr_t & ProtocolLen); + static bool IsProtocolUrl(const UnicodeString & Url, const UnicodeString & Protocol, intptr_t & ProtocolLen); + static void AddSwitchValue(UnicodeString & Result, const UnicodeString & Name, const UnicodeString & Value); + static void AddSwitch(UnicodeString & Result, const UnicodeString & Switch); + static void AddSwitch(UnicodeString & Result, const UnicodeString & AName, const UnicodeString & Value); + static void AddSwitch(UnicodeString & Result, const UnicodeString & AName, intptr_t Value); + /*static UnicodeString AssemblyString(TAssemblyLanguage Language, const UnicodeString & S); + static void AddAssemblyPropertyRaw( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & AName, const UnicodeString & Value); + static void AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & AName, const UnicodeString & Value); + static void AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & AName, const UnicodeString & Type, + const UnicodeString & Member); + static void AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & AName, intptr_t Value); + void AddAssemblyProperty( + UnicodeString & Result, TAssemblyLanguage Language, + const UnicodeString & AName, bool Value);*/ + TStrings * SaveToOptions(const TSessionData * Default); + template + void SetAlgoList(AlgoT * List, const AlgoT * DefaultList, const UnicodeString * Names, + intptr_t Count, AlgoT WarnAlgo, const UnicodeString & AValue); + + //__property UnicodeString InternalStorageKey = { read = GetInternalStorageKey }; + +public: + explicit TSessionData(const UnicodeString & AName); + virtual ~TSessionData(); + TSessionData * Clone(); + void Default(); + void NonPersistant(); + void Load(THierarchicalStorage * Storage); + void ApplyRawSettings(THierarchicalStorage * Storage); + // void ImportFromFilezilla(_di_IXMLNode Node, const UnicodeString & APath); + void Save(THierarchicalStorage * Storage, bool PuttyExport, + const TSessionData * Default = nullptr); + void SaveRecryptedPasswords(THierarchicalStorage * Storage); + void RecryptPasswords(); + bool HasPassword() const; + bool HasAnySessionPassword() const; + bool HasAnyPassword() const; + void ClearSessionPasswords(); + void Remove(); + void CacheHostKeyIfNotCached(); + virtual void Assign(const TPersistent * Source); + virtual intptr_t Compare(const TNamedObject * Other) const; + void CopyData(TSessionData * Source); + void CopyDirectoriesStateData(TSessionData * SourceData); + bool ParseUrl(const UnicodeString & AUrl, TOptions * Options, + TStoredSessionList * AStoredSessions, bool & DefaultsOnly, + UnicodeString * AFileName, bool * AProtocolDefined, UnicodeString * MaskedUrl); + bool ParseOptions(TOptions * Options); + void ConfigureTunnel(intptr_t PortNumber); + void RollbackTunnel(); + void ExpandEnvironmentVariables(); + bool IsSame(const TSessionData * Default, bool AdvancedOnly) const; + bool IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties) const; + bool IsSameSite(const TSessionData * Default) const; + bool IsInFolderOrWorkspace(const UnicodeString & Name) const; + UnicodeString GenerateSessionUrl(uintptr_t Flags); + UnicodeString GenerateOpenCommandArgs(); +// UnicodeString GenerateAssemblyCode(TAssemblyLanguage Language); + void LookupLastFingerprint(); + bool GetIsSecure() const; + static void ValidatePath(const UnicodeString & APath); + static void ValidateName(const UnicodeString & AName); + static UnicodeString MakeValidName(const UnicodeString & Name); + static UnicodeString ExtractLocalName(const UnicodeString & Name); + static UnicodeString ExtractFolderName(const UnicodeString & Name); + static UnicodeString ComposePath(const UnicodeString & APath, const UnicodeString & Name); + static bool IsSensitiveOption(const UnicodeString & Option); + static UnicodeString FormatSiteKey(const UnicodeString & HostName, intptr_t PortNumber); + + /*__property UnicodeString HostName = { read=FHostName, write=SetHostName }; + __property UnicodeString HostNameExpanded = { read=GetHostNameExpanded }; + __property int PortNumber = { read=FPortNumber, write=SetPortNumber }; + __property UnicodeString UserName = { read=FUserName, write=SetUserName }; + __property UnicodeString UserNameExpanded = { read=GetUserNameExpanded }; + __property UnicodeString Password = { read=GetPassword, write=SetPassword }; + __property int PingInterval = { read=FPingInterval, write=SetPingInterval }; + __property bool TryAgent = { read=FTryAgent, write=SetTryAgent }; + __property bool AgentFwd = { read=FAgentFwd, write=SetAgentFwd }; + __property UnicodeString ListingCommand = { read = FListingCommand, write = SetListingCommand }; + __property bool AuthTIS = { read=FAuthTIS, write=SetAuthTIS }; + __property bool AuthKI = { read=FAuthKI, write=SetAuthKI }; + __property bool AuthKIPassword = { read=FAuthKIPassword, write=SetAuthKIPassword }; + __property bool AuthGSSAPI = { read=FAuthGSSAPI, write=SetAuthGSSAPI }; + __property bool GSSAPIFwdTGT = { read=FGSSAPIFwdTGT, write=SetGSSAPIFwdTGT }; + __property UnicodeString GSSAPIServerRealm = { read=FGSSAPIServerRealm, write=SetGSSAPIServerRealm }; + __property bool ChangeUsername = { read=FChangeUsername, write=SetChangeUsername }; + __property bool Compression = { read=FCompression, write=SetCompression }; + __property TSshProt SshProt = { read=FSshProt, write=SetSshProt }; + __property bool UsesSsh = { read = GetUsesSsh }; + __property bool Ssh2DES = { read=FSsh2DES, write=SetSsh2DES }; + __property bool SshNoUserAuth = { read=FSshNoUserAuth, write=SetSshNoUserAuth }; + __property TCipher Cipher[int Index] = { read=GetCipher, write=SetCipher }; + __property TKex Kex[int Index] = { read=GetKex, write=SetKex }; + __property UnicodeString PublicKeyFile = { read=FPublicKeyFile, write=SetPublicKeyFile }; + __property UnicodeString Passphrase = { read=GetPassphrase, write=SetPassphrase }; + __property UnicodeString PuttyProtocol = { read=FPuttyProtocol, write=SetPuttyProtocol }; + __property TFSProtocol FSProtocol = { read=FFSProtocol, write=SetFSProtocol }; + __property UnicodeString FSProtocolStr = { read=GetFSProtocolStr }; + __property bool Modified = { read=FModified, write=FModified }; + __property bool CanLogin = { read=GetCanLogin }; + __property bool ClearAliases = { read = FClearAliases, write = SetClearAliases }; + __property TDateTime PingIntervalDT = { read = GetPingIntervalDT, write = SetPingIntervalDT }; + __property TDateTime TimeDifference = { read = FTimeDifference, write = SetTimeDifference }; + __property bool TimeDifferenceAuto = { read = FTimeDifferenceAuto, write = SetTimeDifferenceAuto }; + __property TPingType PingType = { read = FPingType, write = SetPingType }; + __property UnicodeString SessionName = { read=GetSessionName }; + __property UnicodeString DefaultSessionName = { read=GetDefaultSessionName }; + __property UnicodeString LocalDirectory = { read=FLocalDirectory, write=SetLocalDirectory }; + __property UnicodeString RemoteDirectory = { read=FRemoteDirectory, write=SetRemoteDirectory }; + __property bool SynchronizeBrowsing = { read=FSynchronizeBrowsing, write=SetSynchronizeBrowsing }; + __property bool UpdateDirectories = { read=FUpdateDirectories, write=SetUpdateDirectories }; + __property bool CacheDirectories = { read=FCacheDirectories, write=SetCacheDirectories }; + __property bool CacheDirectoryChanges = { read=FCacheDirectoryChanges, write=SetCacheDirectoryChanges }; + __property bool PreserveDirectoryChanges = { read=FPreserveDirectoryChanges, write=SetPreserveDirectoryChanges }; + __property bool LockInHome = { read=FLockInHome, write=SetLockInHome }; + __property bool Special = { read=FSpecial, write=SetSpecial }; + __property bool Selected = { read=FSelected, write=FSelected }; + __property UnicodeString InfoTip = { read=GetInfoTip }; + __property bool DefaultShell = { read = GetDefaultShell, write = SetDefaultShell }; + __property bool DetectReturnVar = { read = GetDetectReturnVar, write = SetDetectReturnVar }; + __property TEOLType EOLType = { read = FEOLType, write = SetEOLType }; + __property bool TrimVMSVersions = { read = FTrimVMSVersions, write = SetTrimVMSVersions }; + __property TAutoSwitch LookupUserGroups = { read = FLookupUserGroups, write = SetLookupUserGroups }; + __property UnicodeString ReturnVar = { read = FReturnVar, write = SetReturnVar }; + __property bool Scp1Compatibility = { read = FScp1Compatibility, write = SetScp1Compatibility }; + __property UnicodeString Shell = { read = FShell, write = SetShell }; + __property UnicodeString SftpServer = { read = FSftpServer, write = SetSftpServer }; + __property int Timeout = { read = FTimeout, write = SetTimeout }; + __property TDateTime TimeoutDT = { read = GetTimeoutDT }; + __property bool UnsetNationalVars = { read = FUnsetNationalVars, write = SetUnsetNationalVars }; + __property bool IgnoreLsWarnings = { read=FIgnoreLsWarnings, write=SetIgnoreLsWarnings }; + __property bool TcpNoDelay = { read=FTcpNoDelay, write=SetTcpNoDelay }; + __property int SendBuf = { read=FSendBuf, write=SetSendBuf }; + __property bool SshSimple = { read=FSshSimple, write=SetSshSimple }; + __property UnicodeString SshProtStr = { read=GetSshProtStr }; + __property UnicodeString CipherList = { read=GetCipherList, write=SetCipherList }; + __property UnicodeString KexList = { read=GetKexList, write=SetKexList }; + __property TProxyMethod ProxyMethod = { read=FProxyMethod, write=SetProxyMethod }; + __property UnicodeString ProxyHost = { read=FProxyHost, write=SetProxyHost }; + __property int ProxyPort = { read=FProxyPort, write=SetProxyPort }; + __property UnicodeString ProxyUsername = { read=FProxyUsername, write=SetProxyUsername }; + __property UnicodeString ProxyPassword = { read=GetProxyPassword, write=SetProxyPassword }; + __property UnicodeString ProxyTelnetCommand = { read=FProxyTelnetCommand, write=SetProxyTelnetCommand }; + __property UnicodeString ProxyLocalCommand = { read=FProxyLocalCommand, write=SetProxyLocalCommand }; + __property TAutoSwitch ProxyDNS = { read=FProxyDNS, write=SetProxyDNS }; + __property bool ProxyLocalhost = { read=FProxyLocalhost, write=SetProxyLocalhost }; + __property int FtpProxyLogonType = { read=FFtpProxyLogonType, write=SetFtpProxyLogonType }; + __property TAutoSwitch Bug[TSshBug Bug] = { read=GetBug, write=SetBug }; + __property UnicodeString CustomParam1 = { read = FCustomParam1, write = SetCustomParam1 }; + __property UnicodeString CustomParam2 = { read = FCustomParam2, write = SetCustomParam2 }; + __property UnicodeString SessionKey = { read = GetSessionKey }; + __property bool ResolveSymlinks = { read = FResolveSymlinks, write = SetResolveSymlinks }; + __property bool FollowDirectorySymlinks = { read = FFollowDirectorySymlinks, write = SetFollowDirectorySymlinks }; + __property int SFTPDownloadQueue = { read = FSFTPDownloadQueue, write = SetSFTPDownloadQueue }; + __property int SFTPUploadQueue = { read = FSFTPUploadQueue, write = SetSFTPUploadQueue }; + __property int SFTPListingQueue = { read = FSFTPListingQueue, write = SetSFTPListingQueue }; + __property int SFTPMaxVersion = { read = FSFTPMaxVersion, write = SetSFTPMaxVersion }; + __property unsigned long SFTPMaxPacketSize = { read = FSFTPMaxPacketSize, write = SetSFTPMaxPacketSize }; + __property TAutoSwitch SFTPBug[TSftpBug Bug] = { read=GetSFTPBug, write=SetSFTPBug }; + __property TAutoSwitch SCPLsFullTime = { read = FSCPLsFullTime, write = SetSCPLsFullTime }; + __property TAutoSwitch FtpListAll = { read = FFtpListAll, write = SetFtpListAll }; + __property TAutoSwitch FtpHost = { read = FFtpHost, write = SetFtpHost }; + __property bool SslSessionReuse = { read = FSslSessionReuse, write = SetSslSessionReuse }; + __property UnicodeString TlsCertificateFile = { read=FTlsCertificateFile, write=SetTlsCertificateFile }; + __property TDSTMode DSTMode = { read = FDSTMode, write = SetDSTMode }; + __property bool DeleteToRecycleBin = { read = FDeleteToRecycleBin, write = SetDeleteToRecycleBin }; + __property bool OverwrittenToRecycleBin = { read = FOverwrittenToRecycleBin, write = SetOverwrittenToRecycleBin }; + __property UnicodeString RecycleBinPath = { read = FRecycleBinPath, write = SetRecycleBinPath }; + __property UnicodeString PostLoginCommands = { read = FPostLoginCommands, write = SetPostLoginCommands }; + __property TAddressFamily AddressFamily = { read = FAddressFamily, write = SetAddressFamily }; + __property UnicodeString RekeyData = { read = FRekeyData, write = SetRekeyData }; + __property unsigned int RekeyTime = { read = FRekeyTime, write = SetRekeyTime }; + __property int Color = { read = FColor, write = SetColor }; + __property bool Tunnel = { read = FTunnel, write = SetTunnel }; + __property UnicodeString TunnelHostName = { read = FTunnelHostName, write = SetTunnelHostName }; + __property int TunnelPortNumber = { read = FTunnelPortNumber, write = SetTunnelPortNumber }; + __property UnicodeString TunnelUserName = { read = FTunnelUserName, write = SetTunnelUserName }; + __property UnicodeString TunnelPassword = { read = GetTunnelPassword, write = SetTunnelPassword }; + __property UnicodeString TunnelPublicKeyFile = { read = FTunnelPublicKeyFile, write = SetTunnelPublicKeyFile }; + __property bool TunnelAutoassignLocalPortNumber = { read = GetTunnelAutoassignLocalPortNumber }; + __property int TunnelLocalPortNumber = { read = FTunnelLocalPortNumber, write = SetTunnelLocalPortNumber }; + __property UnicodeString TunnelPortFwd = { read = FTunnelPortFwd, write = SetTunnelPortFwd }; + __property UnicodeString TunnelHostKey = { read = FTunnelHostKey, write = SetTunnelHostKey }; + __property bool FtpPasvMode = { read = FFtpPasvMode, write = SetFtpPasvMode }; + __property TAutoSwitch FtpForcePasvIp = { read = FFtpForcePasvIp, write = SetFtpForcePasvIp }; + __property TAutoSwitch FtpUseMlsd = { read = FFtpUseMlsd, write = SetFtpUseMlsd }; + __property UnicodeString FtpAccount = { read = FFtpAccount, write = SetFtpAccount }; + __property int FtpPingInterval = { read=FFtpPingInterval, write=SetFtpPingInterval }; + __property TDateTime FtpPingIntervalDT = { read=GetFtpPingIntervalDT }; + __property TPingType FtpPingType = { read = FFtpPingType, write = SetFtpPingType }; + __property TAutoSwitch FtpTransferActiveImmediately = { read = FFtpTransferActiveImmediately, write = SetFtpTransferActiveImmediately }; + __property TFtps Ftps = { read = FFtps, write = SetFtps }; + __property TTlsVersion MinTlsVersion = { read = FMinTlsVersion, write = SetMinTlsVersion }; + __property TTlsVersion MaxTlsVersion = { read = FMaxTlsVersion, write = SetMaxTlsVersion }; + __property TAutoSwitch NotUtf = { read = FNotUtf, write = SetNotUtf }; + __property bool IsWorkspace = { read = FIsWorkspace, write = SetIsWorkspace }; + __property UnicodeString Link = { read = FLink, write = SetLink }; + __property UnicodeString HostKey = { read = FHostKey, write = SetHostKey }; + __property bool FingerprintScan = { read = FFingerprintScan, write = FFingerprintScan }; + __property bool OverrideCachedHostKey = { read = FOverrideCachedHostKey }; + __property UnicodeString Note = { read = FNote, write = SetNote }; + __property UnicodeString StorageKey = { read = GetStorageKey }; + __property UnicodeString SiteKey = { read = GetSiteKey }; + __property UnicodeString OrigHostName = { read = FOrigHostName }; + __property int OrigPortNumber = { read = FOrigPortNumber }; + __property UnicodeString LocalName = { read = GetLocalName }; + __property UnicodeString FolderName = { read = GetFolderName }; + __property UnicodeString Source = { read = GetSource }; + __property bool SaveOnly = { read = FSaveOnly };*/ + + bool GetTimeDifferenceAuto() const { return FTimeDifferenceAuto; } + UnicodeString GetNote() const { return FNote; } + UnicodeString GetProtocolStr() const; + void SetProtocolStr(const UnicodeString & Value); + + UnicodeString GetHostName() const { return FHostName; } + intptr_t GetPortNumber() const { return FPortNumber; } + TLoginType GetLoginType() const; + void SetLoginType(TLoginType Value); + UnicodeString SessionGetUserName() const { return FUserName; } + intptr_t GetPingInterval() const { return FPingInterval; } + bool GetTryAgent() const { return FTryAgent; } + bool GetAgentFwd() const { return FAgentFwd; } + const UnicodeString GetListingCommand() const { return FListingCommand; } + bool GetAuthTIS() const { return FAuthTIS; } + bool GetAuthKI() const { return FAuthKI; } + bool GetAuthKIPassword() const { return FAuthKIPassword; } + bool GetAuthGSSAPI() const { return FAuthGSSAPI; } + bool GetGSSAPIFwdTGT() const { return FGSSAPIFwdTGT; } + const UnicodeString GetGSSAPIServerRealm() const { return FGSSAPIServerRealm; } + bool GetChangeUsername() const { return FChangeUsername; } + bool GetCompression() const { return FCompression; } + TSshProt GetSshProt() const { return FSshProt; } + bool GetSsh2DES() const { return FSsh2DES; } + bool GetSshNoUserAuth() const { return FSshNoUserAuth; } + const UnicodeString GetPublicKeyFile() const { return FPublicKeyFile; } + UnicodeString GetPuttyProtocol() const { return FPuttyProtocol; } + TFSProtocol GetFSProtocol() const { return FFSProtocol; } + bool GetModified() const { return FModified; } + void SetModified(bool Value) { FModified = Value; } + bool GetClearAliases() const { return FClearAliases; } + TDateTime GetTimeDifference() const { return FTimeDifference; } + TPingType GetPingType() const { return FPingType; } + UnicodeString GetLocalDirectory() const { return FLocalDirectory; } + UnicodeString GetRemoteDirectory() const { return FRemoteDirectory; } + bool GetSynchronizeBrowsing() const { return FSynchronizeBrowsing; } + bool GetUpdateDirectories() const { return FUpdateDirectories; } + bool GetCacheDirectories() const { return FCacheDirectories; } + bool GetCacheDirectoryChanges() const { return FCacheDirectoryChanges; } + bool GetPreserveDirectoryChanges() const { return FPreserveDirectoryChanges; } + bool GetLockInHome() const { return FLockInHome; } + bool GetSpecial() const { return FSpecial; } + bool GetSelected() const { return FSelected; } + void SetSelected(bool Value) { FSelected = Value; } + TEOLType GetEOLType() const { return FEOLType; } + bool GetTrimVMSVersions() const { return FTrimVMSVersions; } + TAutoSwitch GetLookupUserGroups() const { return FLookupUserGroups; } + UnicodeString GetReturnVar() const { return FReturnVar; } + bool GetScp1Compatibility() const { return FScp1Compatibility; } + UnicodeString GetShell() const { return FShell; } + UnicodeString GetSftpServer() const { return FSftpServer; } + intptr_t GetTimeout() const { return FTimeout; } + bool GetUnsetNationalVars() const { return FUnsetNationalVars; } + bool GetIgnoreLsWarnings() const { return FIgnoreLsWarnings; } + bool GetTcpNoDelay() const { return FTcpNoDelay; } + intptr_t GetSendBuf() const { return FSendBuf; } + bool GetSshSimple() const { return FSshSimple; } + TProxyMethod GetProxyMethod() const { return FProxyMethod; } + TProxyMethod GetActualProxyMethod() const + { + return GetProxyMethod() == pmSystem ? GetSystemProxyMethod() : GetProxyMethod(); + } + UnicodeString GetProxyHost() const; + intptr_t GetProxyPort() const; + UnicodeString GetProxyUsername() const; + UnicodeString GetProxyTelnetCommand() const { return FProxyTelnetCommand; } + UnicodeString GetProxyLocalCommand() const { return FProxyLocalCommand; } + TAutoSwitch GetProxyDNS() const { return FProxyDNS; } + bool GetProxyLocalhost() const { return FProxyLocalhost; } + intptr_t GetFtpProxyLogonType() const { return FFtpProxyLogonType; } + UnicodeString GetCustomParam1() const { return FCustomParam1; } + UnicodeString GetCustomParam2() const { return FCustomParam2; } + bool GetResolveSymlinks() const { return FResolveSymlinks; } + bool GetFollowDirectorySymlinks() const { return FFollowDirectorySymlinks; } + intptr_t GetSFTPDownloadQueue() const { return FSFTPDownloadQueue; } + intptr_t GetSFTPUploadQueue() const { return FSFTPUploadQueue; } + intptr_t GetSFTPListingQueue() const { return FSFTPListingQueue; } + intptr_t GetSFTPMaxVersion() const { return FSFTPMaxVersion; } + intptr_t GetSFTPMinPacketSize() const { return FSFTPMinPacketSize; } + intptr_t GetSFTPMaxPacketSize() const { return FSFTPMaxPacketSize; } + TAutoSwitch GetSCPLsFullTime() const { return FSCPLsFullTime; } + TAutoSwitch GetFtpListAll() const { return FFtpListAll; } + TAutoSwitch GetFtpHost() const { return FFtpHost; } + bool GetFtpDupFF() const { return FFtpDupFF; } + bool GetFtpUndupFF() const { return FFtpUndupFF; } + bool GetSslSessionReuse() const { return FSslSessionReuse; } + UnicodeString GetTlsCertificateFile() const { return FTlsCertificateFile; } + TDSTMode GetDSTMode() const { return FDSTMode; } + bool GetDeleteToRecycleBin() const { return FDeleteToRecycleBin; } + bool GetOverwrittenToRecycleBin() const { return FOverwrittenToRecycleBin; } + UnicodeString GetRecycleBinPath() const { return FRecycleBinPath; } + UnicodeString GetPostLoginCommands() const { return FPostLoginCommands; } + TAddressFamily GetAddressFamily() const { return FAddressFamily; } + UnicodeString GetCodePage() const { return FCodePage; } + void SetCodePage(const UnicodeString & Value); + uintptr_t GetCodePageAsNumber() const; + UnicodeString GetRekeyData() const { return FRekeyData; } + uintptr_t GetRekeyTime() const { return FRekeyTime; } + intptr_t GetColor() const { return FColor; } + bool GetTunnel() const { return FTunnel; } + UnicodeString GetTunnelHostName() const { return FTunnelHostName; } + intptr_t GetTunnelPortNumber() const { return FTunnelPortNumber; } + UnicodeString GetTunnelUserName() const { return FTunnelUserName; } + UnicodeString GetTunnelPublicKeyFile() const { return FTunnelPublicKeyFile; } + intptr_t GetTunnelLocalPortNumber() const { return FTunnelLocalPortNumber; } + UnicodeString GetTunnelPortFwd() const { return FTunnelPortFwd; } + UnicodeString GetTunnelHostKey() const { return FTunnelHostKey; } + bool GetFtpPasvMode() const { return FFtpPasvMode; } + bool GetFtpAllowEmptyPassword() const { return FFtpAllowEmptyPassword; } + void SetFtpAllowEmptyPassword(bool Value); + TAutoSwitch GetFtpForcePasvIp() const { return FFtpForcePasvIp; } + TAutoSwitch GetFtpUseMlsd() const { return FFtpUseMlsd; } + UnicodeString GetFtpAccount() const { return FFtpAccount; } + intptr_t GetFtpPingInterval() const { return FFtpPingInterval; } + TPingType GetFtpPingType() const { return FFtpPingType; } + TAutoSwitch GetFtpTransferActiveImmediately() const { return FFtpTransferActiveImmediately; } + TFtps GetFtps() const { return FFtps; } + TTlsVersion GetMinTlsVersion() const { return FMinTlsVersion; } + TTlsVersion GetMaxTlsVersion() const { return FMaxTlsVersion; } + TAutoSwitch GetNotUtf() const { return FNotUtf; } + bool GetIsWorkspace() const { return FIsWorkspace; } + UnicodeString GetLink() const { return FLink; } + UnicodeString GetHostKey() const { return FHostKey; } + bool GetFingerprintScan() const { return FFingerprintScan; } + bool GetOverrideCachedHostKey() const { return FOverrideCachedHostKey; } + UnicodeString GetOrigHostName() const { return FOrigHostName; } + intptr_t GetOrigPortNumber() const { return FOrigPortNumber; } + void SetPasswordless(bool Value); + + intptr_t GetNumberOfRetries() const { return FNumberOfRetries; } + void SetNumberOfRetries(intptr_t Value) { FNumberOfRetries = Value; } + uintptr_t GetSessionVersion() const { return FSessionVersion; } + void SetSessionVersion(uintptr_t Value) { FSessionVersion = Value; } + void RemoveProtocolPrefix(UnicodeString & HostName) const; + +private: + uintptr_t GetDefaultVersion() const { return ::GetCurrentVersionNumber(); } + TFSProtocol TranslateFSProtocolNumber(intptr_t FSProtocol); + TFSProtocol TranslateFSProtocol(const UnicodeString & ProtocolID) const; + TFtps TranslateFtpEncryptionNumber(intptr_t FtpEncryption) const; + +private: + TProxyMethod GetSystemProxyMethod() const; + void PrepareProxyData() const; + void ParseIEProxyConfig() const; + void FromURI(const UnicodeString & ProxyURI, + UnicodeString & ProxyUrl, intptr_t & ProxyPort, TProxyMethod & ProxyMethod) const; + void AdjustHostName(UnicodeString & HostName, const UnicodeString & Prefix) const; + +}; + +class TStoredSessionList : public TNamedObjectList +{ +NB_DISABLE_COPY(TStoredSessionList) +public: + explicit TStoredSessionList(bool AReadOnly = false); + void Load(const UnicodeString & AKey, bool UseDefaults); + void Load(); + void Save(bool All, bool Explicit); + void Saved(); + void ImportFromFilezilla(const UnicodeString & AFileName); + void Export(const UnicodeString & AFileName); + void Load(THierarchicalStorage * Storage, bool AsModified = false, + bool UseDefaults = false); + void Save(THierarchicalStorage * Storage, bool All = false); + void SelectAll(bool Select); + void Import(TStoredSessionList * From, bool OnlySelected, TList * Imported); + void RecryptPasswords(TStrings * RecryptPasswordErrors); + TSessionData * AtSession(int Index) + { return (TSessionData*)AtObject(Index); } + void SelectSessionsToImport(TStoredSessionList * Dest, bool SSHOnly); + void Cleanup(); + void UpdateStaticUsage(); + intptr_t IndexOf(TSessionData * Data) const; + const TSessionData * FindSame(TSessionData * Data) const; + TSessionData * NewSession(const UnicodeString & SessionName, TSessionData * Session); + void NewWorkspace(const UnicodeString & Name, TList * DataList); + bool GetIsFolder(const UnicodeString & Name) const; + bool GetIsWorkspace(const UnicodeString & Name) const; + TSessionData * ParseUrl(const UnicodeString & Url, TOptions * Options, bool & DefaultsOnly, + UnicodeString * AFileName = nullptr, bool * ProtocolDefined = nullptr, UnicodeString * MaskedUrl = nullptr); + bool IsUrl(const UnicodeString & Url); + bool CanLogin(TSessionData * Data); + void GetFolderOrWorkspace(const UnicodeString & Name, TList * List); + TStrings * GetFolderOrWorkspaceList(const UnicodeString & Name); + TStrings * GetWorkspaces(); + bool HasAnyWorkspace(); + TSessionData * SaveWorkspaceData(TSessionData * Data); + virtual ~TStoredSessionList(); +/* + __property TSessionData * Sessions[int Index] = { read=AtSession }; + __property TSessionData * DefaultSettings = { read=FDefaultSettings, write=SetDefaultSettings }; +*/ + const TSessionData * GetSession(intptr_t Index) const { return NB_STATIC_DOWNCAST_CONST(TSessionData, AtObject(Index)); } + TSessionData * GetSession(intptr_t Index) { return NB_STATIC_DOWNCAST(TSessionData, AtObject(Index)); } + const TSessionData * GetDefaultSettings() const { return FDefaultSettings; } + TSessionData * GetDefaultSettings() { return FDefaultSettings; } + const TSessionData * GetSessionByName(const UnicodeString & SessionName) const; + void SetDefaultSettings(const TSessionData * Value); + + static void ImportHostKeys(const UnicodeString & TargetKey, + const UnicodeString & SourceKey, TStoredSessionList * Sessions, + bool OnlySelected); + +private: + TSessionData * FDefaultSettings; + bool FReadOnly; + void DoSave(THierarchicalStorage * Storage, bool All, + bool RecryptPasswordOnly, TStrings * RecryptPasswordErrors); + void DoSave(bool All, bool Explicit, bool RecryptPasswordOnly, + TStrings * RecryptPasswordErrors); + void DoSave(THierarchicalStorage * Storage, + TSessionData * Data, bool All, bool RecryptPasswordOnly, + TSessionData * FactoryDefaults); + TSessionData * ResolveWorkspaceData(TSessionData * Data); + bool IsFolderOrWorkspace(const UnicodeString & Name, bool Workspace) const; + TSessionData * CheckIsInFolderOrWorkspaceAndResolve( + TSessionData * Data, const UnicodeString & Name); +// void ImportLevelFromFilezilla(_di_IXMLNode Node, const UnicodeString & Path); +}; + +bool GetCodePageInfo(UINT CodePage, CPINFOEX & CodePageInfoEx); +uintptr_t GetCodePageAsNumber(const UnicodeString & CodePage); +UnicodeString GetCodePageAsString(uintptr_t CodePage); + +UnicodeString GetExpandedLogFileName(const UnicodeString & LogFileName, TSessionData * SessionData); +bool GetIsSshProtocol(TFSProtocol FSProtocol); +intptr_t GetDefaultPort(TFSProtocol FSProtocol, TFtps Ftps); + diff --git a/netbox/src/core/SessionInfo.cpp b/netbox/src/core/SessionInfo.cpp new file mode 100644 index 000000000..dd5255cc1 --- /dev/null +++ b/netbox/src/core/SessionInfo.cpp @@ -0,0 +1,1678 @@ +#include +#pragma hdrstop + +#include +#ifndef __linux__ +#include +#include +#define SECURITY_WIN32 +#include +#include +#endif + +#include +#include + +#include "SessionInfo.h" +#include "TextsCore.h" + +static UnicodeString DoXmlEscape(const UnicodeString & AStr, bool NewLine) +{ + UnicodeString Str = AStr; + for (intptr_t Index = 1; Index <= Str.Length(); ++Index) + { + const wchar_t * Repl = nullptr; + switch (Str[Index]) + { + case L'&': + Repl = L"amp;"; + break; + + case L'>': + Repl = L"gt;"; + break; + + case L'<': + Repl = L"lt;"; + break; + + case L'"': + Repl = L"quot;"; + break; + + case L'\n': + if (NewLine) + { + Repl = L"#10;"; + } + break; + + case L'\r': + Str.Delete(Index, 1); + Index--; + break; + } + + if (Repl != nullptr) + { + Str[Index] = L'&'; + Str.Insert(Repl, Index + 1); + Index += wcslen(Repl); + } + } + return Str; +} + +static UnicodeString XmlEscape(const UnicodeString & Str) +{ + return DoXmlEscape(Str, false); +} + +static UnicodeString XmlAttributeEscape(const UnicodeString & Str) +{ + return DoXmlEscape(Str, true); +} + +class TSessionActionRecord : public TObject +{ +NB_DECLARE_CLASS(TSessionActionRecord) +public: + explicit TSessionActionRecord(TActionLog * Log, TLogAction Action) : + FLog(Log), + FAction(Action), + FState(Opened), + FRecursive(false), + FErrorMessages(nullptr), + FNames(new TStringList()), + FValues(new TStringList()), + FFileList(nullptr), + FFile(nullptr) + { + FLog->AddPendingAction(this); + } + + ~TSessionActionRecord() + { + SAFE_DESTROY(FErrorMessages); + SAFE_DESTROY(FNames); + SAFE_DESTROY(FValues); + SAFE_DESTROY(FFileList); + SAFE_DESTROY(FFile); + } + + void Restart() + { + FState = Opened; + FRecursive = false; + SAFE_DESTROY(FErrorMessages); + SAFE_DESTROY(FFileList); + SAFE_DESTROY(FFile); + FNames->Clear(); + FValues->Clear(); + } + + bool Record() + { + bool Result = (FState != Opened); +#if 0 + if (Result) + { + if (FLog->FLogging && (FState != Cancelled)) + { + const wchar_t * Name = ActionName(); + UnicodeString Attrs; + if (FRecursive) + { + Attrs = L" recursive=\"true\""; + } + FLog->AddIndented(FORMAT("<%s%s>", Name, Attrs.c_str())); + for (intptr_t Index = 0; Index < FNames->GetCount(); ++Index) + { + UnicodeString Value = FValues->GetString(Index); + if (Value.IsEmpty()) + { + FLog->AddIndented(FORMAT(" <%s />", FNames->GetString(Index).c_str())); + } + else + { + FLog->AddIndented(FORMAT(" <%s value=\"%s\" />", + FNames->GetString(Index).c_str(), XmlAttributeEscape(Value).c_str())); + } + } + if (FFileList != nullptr) + { + FLog->AddIndented(L" "); + for (intptr_t Index = 0; Index < FFileList->GetCount(); ++Index) + { + TRemoteFile * File = FFileList->GetFile(Index); + + FLog->AddIndented(L" "); + FLog->AddIndented(FORMAT(" ", XmlAttributeEscape(File->GetFileName()).c_str())); + FLog->AddIndented(FORMAT(" ", XmlAttributeEscape(File->GetType()).c_str())); + if (!File->GetIsDirectory()) + { + FLog->AddIndented(FORMAT(" ", ::Int64ToStr(File->GetSize()).c_str())); + } + FLog->AddIndented(FORMAT(" ", StandardTimestamp(File->GetModification()).c_str())); + FLog->AddIndented(FORMAT(" ", XmlAttributeEscape(File->GetRights()->GetText()).c_str())); + FLog->AddIndented(L" "); + } + FLog->AddIndented(L" "); + if (File->Owner.IsSet) + { + FLog->AddIndented(FORMAT(L" ", XmlAttributeEscape(File->Owner.DisplayText))); + } + if (File->Group.IsSet) + { + FLog->AddIndented(FORMAT(L" ", XmlAttributeEscape(File->Group.DisplayText))); + } + } + if (FFile != nullptr) + { + FLog->AddIndented(L" "); + FLog->AddIndented(FORMAT(" ", XmlAttributeEscape(FFile->GetType()).c_str())); + if (!FFile->GetIsDirectory()) + { + FLog->AddIndented(FORMAT(" ", ::Int64ToStr(FFile->GetSize()).c_str())); + } + FLog->AddIndented(FORMAT(" ", StandardTimestamp(FFile->GetModification()).c_str())); + FLog->AddIndented(FORMAT(" ", XmlAttributeEscape(FFile->GetRights()->GetText()).c_str())); + FLog->AddIndented(L" "); + } + if (FState == RolledBack) + { + if (FErrorMessages != nullptr) + { + FLog->AddIndented(L" "); + FLog->AddMessages(L" ", FErrorMessages); + FLog->AddIndented(L" "); + } + else + { + FLog->AddIndented(L" "); + } + } + else + { + FLog->AddIndented(L" "); + } + FLog->AddIndented(FORMAT("", Name)); + } + delete this; + } +#endif + return Result; + } + + void Commit() + { + Close(Committed); + } + + void Rollback(Exception * E) + { + DebugAssert(FErrorMessages == nullptr); + FErrorMessages = ExceptionToMoreMessages(E); + Close(RolledBack); + } + + void Cancel() + { + Close(Cancelled); + } + + void SetFileName(const UnicodeString & AFileName) + { + Parameter(L"filename", AFileName); + } + + void Destination(const UnicodeString & Destination) + { + Parameter(L"destination", Destination); + } + + void Rights(const TRights & Rights) + { + Parameter(L"permissions", Rights.GetText()); + } + + void Modification(const TDateTime & DateTime) + { + Parameter(L"modification", StandardTimestamp(DateTime)); + } + + void Recursive() + { + FRecursive = true; + } + + void Command(const UnicodeString & Command) + { + Parameter(L"command", Command); + } + + void AddOutput(const UnicodeString & Output, bool StdError) + { + const wchar_t * Name = (StdError ? L"erroroutput" : L"output"); + intptr_t Index = FNames->IndexOf(Name); + if (Index >= 0) + { + FValues->SetString(Index, FValues->GetString(Index) + L"\r\n" + Output); + } + else + { + Parameter(Name, Output); + } + } + + void ExitCode(int ExitCode) + { + Parameter(L"exitcode", ::IntToStr(ExitCode)); + } + + void Checksum(const UnicodeString & Alg, const UnicodeString & Checksum) + { + Parameter(L"algorithm", Alg); + Parameter(L"checksum", Checksum); + } + + void Cwd(const UnicodeString & Path) + { + Parameter(L"cwd", Path); + } + + void FileList(TRemoteFileList * FileList) + { + if (FFileList == nullptr) + { + FFileList = new TRemoteFileList(); + } + FileList->DuplicateTo(FFileList); + } + + void File(TRemoteFile * AFile) + { + if (FFile != nullptr) + { + SAFE_DESTROY(FFile); + } + FFile = AFile->Duplicate(true); + } + +protected: + enum TState + { + Opened, + Committed, + RolledBack, + Cancelled, + }; + + inline void Close(TState State) + { + DebugAssert(FState == Opened); + FState = State; + FLog->RecordPendingActions(); + } + + const wchar_t * ActionName() const + { + switch (FAction) + { + case laUpload: return L"upload"; + case laDownload: return L"download"; + case laTouch: return L"touch"; + case laChmod: return L"chmod"; + case laMkdir: return L"mkdir"; + case laRm: return L"rm"; + case laMv: return L"mv"; + case laCall: return L"call"; + case laLs: return L"ls"; + case laStat: return L"stat"; + case laChecksum: return L"checksum"; + case laCwd: return L"cwd"; + default: + DebugFail(); + return L""; + } + } + + void Parameter(const UnicodeString & Name, const UnicodeString & Value = L"") + { + FNames->Add(Name); + FValues->Add(Value); + } + +private: + TActionLog * FLog; + TLogAction FAction; + TState FState; + bool FRecursive; + TStrings * FErrorMessages; + TStrings * FNames; + TStrings * FValues; + TRemoteFileList * FFileList; + TRemoteFile * FFile; +}; + +TSessionAction::TSessionAction(TActionLog * Log, TLogAction Action) +{ + if (Log->FLogging) + { + FRecord = new TSessionActionRecord(Log, Action); + } + else + { + FRecord = nullptr; + } +} + +TSessionAction::~TSessionAction() +{ + if (FRecord != nullptr) + { + Commit(); + } +} + +void TSessionAction::Restart() +{ + if (FRecord != nullptr) + { + FRecord->Restart(); + } +} + +void TSessionAction::Commit() +{ + if (FRecord != nullptr) + { + TSessionActionRecord * Record = FRecord; + FRecord = nullptr; + Record->Commit(); + } +} + +void TSessionAction::Rollback(Exception * E) +{ + if (FRecord != nullptr) + { + TSessionActionRecord * Record = FRecord; + FRecord = nullptr; + Record->Rollback(E); + } +} + +void TSessionAction::Cancel() +{ + if (FRecord != nullptr) + { + TSessionActionRecord * Record = FRecord; + FRecord = nullptr; + Record->Cancel(); + } +} + + +TFileSessionAction::TFileSessionAction(TActionLog * Log, TLogAction Action) : + TSessionAction(Log, Action) +{ +} + +TFileSessionAction::TFileSessionAction( + TActionLog * Log, TLogAction Action, const UnicodeString & AFileName) : + TSessionAction(Log, Action) +{ + SetFileName(AFileName); +} + +void TFileSessionAction::SetFileName(const UnicodeString & AFileName) +{ + if (FRecord != nullptr) + { + FRecord->SetFileName(AFileName); + } +} + + +TFileLocationSessionAction::TFileLocationSessionAction( + TActionLog * Log, TLogAction Action) : + TFileSessionAction(Log, Action) +{ +} + +TFileLocationSessionAction::TFileLocationSessionAction( + TActionLog * Log, TLogAction Action, const UnicodeString & AFileName) : + TFileSessionAction(Log, Action, AFileName) +{ +} + +void TFileLocationSessionAction::Destination(const UnicodeString & Destination) +{ + if (FRecord != nullptr) + { + FRecord->Destination(Destination); + } +} + + +TUploadSessionAction::TUploadSessionAction(TActionLog * Log) : + TFileLocationSessionAction(Log, laUpload) +{ +} + + +TDownloadSessionAction::TDownloadSessionAction(TActionLog * Log) : + TFileLocationSessionAction(Log, laDownload) +{ +} + + +TChmodSessionAction::TChmodSessionAction( + TActionLog * Log, const UnicodeString & AFileName) : + TFileSessionAction(Log, laChmod, AFileName) +{ +} + +void TChmodSessionAction::Recursive() +{ + if (FRecord != nullptr) + { + FRecord->Recursive(); + } +} + +TChmodSessionAction::TChmodSessionAction( + TActionLog * Log, const UnicodeString & AFileName, const TRights & ARights) : + TFileSessionAction(Log, laChmod, AFileName) +{ + Rights(ARights); +} + +void TChmodSessionAction::Rights(const TRights & Rights) +{ + if (FRecord != nullptr) + { + FRecord->Rights(Rights); + } +} + +TTouchSessionAction::TTouchSessionAction( + TActionLog * Log, const UnicodeString & AFileName, const TDateTime & Modification) : + TFileSessionAction(Log, laTouch, AFileName) +{ + if (FRecord != nullptr) + { + FRecord->Modification(Modification); + } +} + +TMkdirSessionAction::TMkdirSessionAction( + TActionLog * Log, const UnicodeString & AFileName) : + TFileSessionAction(Log, laMkdir, AFileName) +{ +} + +TRmSessionAction::TRmSessionAction( + TActionLog * Log, const UnicodeString & AFileName) : + TFileSessionAction(Log, laRm, AFileName) +{ +} + +void TRmSessionAction::Recursive() +{ + if (FRecord != nullptr) + { + FRecord->Recursive(); + } +} + +TMvSessionAction::TMvSessionAction(TActionLog * Log, + const UnicodeString & AFileName, const UnicodeString & ADestination) : + TFileLocationSessionAction(Log, laMv, AFileName) +{ + Destination(ADestination); +} + +TCallSessionAction::TCallSessionAction(TActionLog * Log, + const UnicodeString & Command, const UnicodeString & Destination) : + TSessionAction(Log, laCall) +{ + if (FRecord != nullptr) + { + FRecord->Command(Command); + FRecord->Destination(Destination); + } +} + +void TCallSessionAction::AddOutput(const UnicodeString & Output, bool StdError) +{ + if (FRecord != nullptr) + { + FRecord->AddOutput(Output, StdError); + } +} + +void TCallSessionAction::ExitCode(int ExitCode) +{ + if (FRecord != nullptr) + { + FRecord->ExitCode(ExitCode); + } +} + +TLsSessionAction::TLsSessionAction(TActionLog * Log, + const UnicodeString & Destination) : + TSessionAction(Log, laLs) +{ + if (FRecord != nullptr) + { + FRecord->Destination(Destination); + } +} + +void TLsSessionAction::FileList(TRemoteFileList * FileList) +{ + if (FRecord != nullptr) + { + FRecord->FileList(FileList); + } +} + +TStatSessionAction::TStatSessionAction(TActionLog * Log, const UnicodeString & AFileName) : + TFileSessionAction(Log, laStat, AFileName) +{ +} + +void TStatSessionAction::File(TRemoteFile * AFile) +{ + if (FRecord != nullptr) + { + FRecord->File(AFile); + } +} + + +TChecksumSessionAction::TChecksumSessionAction(TActionLog * Log) : + TFileSessionAction(Log, laChecksum) +{ +} + +void TChecksumSessionAction::Checksum(const UnicodeString & Alg, const UnicodeString & Checksum) +{ + if (FRecord != nullptr) + { + FRecord->Checksum(Alg, Checksum); + } +} + + +TCwdSessionAction::TCwdSessionAction(TActionLog * Log, const UnicodeString & Path) : + TSessionAction(Log, laCwd) +{ + if (FRecord != nullptr) + { + FRecord->Cwd(Path); + } +} + +TSessionInfo::TSessionInfo() : + LoginTime(Now()) +{ +} + +TFileSystemInfo::TFileSystemInfo() +{ + ::memset(&IsCapable, 0, sizeof(IsCapable)); +} + +static FILE * OpenFile(const UnicodeString & LogFileName, TSessionData * SessionData, bool Append, UnicodeString & ANewFileName) +{ + UnicodeString NewFileName = StripPathQuotes(GetExpandedLogFileName(LogFileName, SessionData)); + // Result = _wfopen(ANewFileName.c_str(), (Append ? L"a" : L"w")); +#ifndef __linux__ + FILE * Result = _fsopen(::W2MB(ApiPath(NewFileName).c_str()).c_str(), + Append ? "a" : "w", SH_DENYWR); // _SH_DENYNO); // +#else + FILE * Result = _wfopen(ANewFileName.c_str(), (Append ? L"a" : L"w")); +#endif + if (Result != nullptr) + { + setvbuf(Result, nullptr, _IONBF, BUFSIZ); + ANewFileName = NewFileName; + } + else + { + throw ECRTExtException(FMTLOAD(LOG_OPENERROR, NewFileName.c_str())); + } + return Result; +} + +const wchar_t * LogLineMarks = L"<>!.*"; + +TSessionLog::TSessionLog(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration) : + TStringList(), + FConfiguration(Configuration), + FParent(nullptr), + FLogging(false), + FFile(nullptr), + FLoggedLines(0), + FTopIndex(-1), + FUI(UI), + FSessionData(SessionData), + FClosed(false) +{ +} + +TSessionLog::~TSessionLog() +{ + FClosed = true; + ReflectSettings(); + DebugAssert(FFile == nullptr); +} + +void TSessionLog::Lock() +{ + FCriticalSection.Enter(); +} + +void TSessionLog::Unlock() +{ + FCriticalSection.Leave(); +} + +UnicodeString TSessionLog::GetSessionName() const +{ + DebugAssert(FSessionData != nullptr); + return FSessionData->GetSessionName(); +} + +UnicodeString TSessionLog::GetLine(intptr_t Index) const +{ + return GetString(Index - FTopIndex); +} + +TLogLineType TSessionLog::GetType(intptr_t Index) const +{ + return static_cast(reinterpret_cast(GetObj(Index - FTopIndex))); +} + +void TSessionLog::DoAddToParent(TLogLineType AType, const UnicodeString & ALine) +{ + DebugAssert(FParent != nullptr); + FParent->Add(AType, ALine); +} + +void TSessionLog::DoAddToSelf(TLogLineType AType, const UnicodeString & ALine) +{ + if (FTopIndex < 0) + { + FTopIndex = 0; + } + + TStringList::AddObject(ALine, static_cast(ToPtr(static_cast(AType)))); + + FLoggedLines++; + + if (LogToFile()) + { + if (FFile == nullptr) + { + OpenLogFile(); + } + + if (FFile != nullptr) + { +#if defined(__BORLANDC__) + UnicodeString Timestamp = FormatDateTime(L" yyyy-mm-dd hh:nn:ss.zzz ", Now()); + UTF8String UtfLine = UTF8String(UnicodeString(LogLineMarks[Type]) + Timestamp + Line + "\n"); + fwrite(UtfLine.c_str(), UtfLine.Length(), 1, (FILE *)FFile); +#else + uint16_t Y, M, D, H, N, S, MS; + TDateTime DateTime = Now(); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, N, S, MS); + UnicodeString dt = FORMAT(L" %04d-%02d-%02d %02d:%02d:%02d.%03d ", Y, M, D, H, N, S, MS); + UnicodeString Timestamp = dt; + UTF8String UtfLine = UTF8String(UnicodeString(LogLineMarks[AType]) + Timestamp + ALine + "\n"); + fprintf(static_cast(FFile), "%s", const_cast(AnsiString(UtfLine).c_str())); +#endif + } + } +} + +void TSessionLog::DoAdd(TLogLineType AType, const UnicodeString & ALine, + TDoAddLogEvent Event) +{ + UnicodeString Prefix; + + if (!GetName().IsEmpty()) + { + Prefix = L"[" + GetName() + L"] "; + } + + UnicodeString Line = ALine; + while (!Line.IsEmpty()) + { + Event(AType, Prefix + CutToChar(Line, L'\n', false)); + } +} + +void TSessionLog::Add(TLogLineType Type, const UnicodeString & Line) +{ + DebugAssert(FConfiguration); + if (GetLogging()) + { + try + { + if (FParent != nullptr) + { + DoAdd(Type, Line, MAKE_CALLBACK(TSessionLog::DoAddToParent, this)); + } + else + { + TGuard Guard(FCriticalSection); + + BeginUpdate(); + + try__finally + { + SCOPE_EXIT + { + DeleteUnnecessary(); + + EndUpdate(); + }; + DoAdd(Type, Line, MAKE_CALLBACK(TSessionLog::DoAddToSelf, this)); + } + __finally + { + DeleteUnnecessary(); + + EndUpdate(); + }; + } + } + catch (Exception & E) + { + // We failed logging, turn it off and notify user. + FConfiguration->SetLogging(false); + try + { + throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); + } + catch (Exception & E) + { + AddException(&E); + FUI->HandleExtendedException(&E); + } + } + } +} + +void TSessionLog::AddException(Exception * E) +{ + if (E != nullptr) + { + Add(llException, ExceptionLogString(E)); + } +} + +void TSessionLog::ReflectSettings() +{ + TGuard Guard(FCriticalSection); + + bool ALogging = + !FClosed && + ((FParent != nullptr) || FConfiguration->GetLogging()); + + bool Changed = false; + if (FLogging != ALogging) + { + FLogging = ALogging; + Changed = true; + } + + // if logging to file was turned off or log file was changed -> close current log file + if ((FFile != nullptr) && + (!LogToFile() || (FCurrentLogFileName != FConfiguration->GetLogFileName()))) + { + CloseLogFile(); + } + + DeleteUnnecessary(); + + // trigger event only once we are in a consistent state + if (Changed) + { + StateChange(); + } + +} + +bool TSessionLog::LogToFile() const +{ + return GetLogging() && FConfiguration->GetLogToFile() && (FParent == nullptr); +} + +void TSessionLog::CloseLogFile() +{ + if (FFile != nullptr) + { + fclose(static_cast(FFile)); + FFile = nullptr; + } + FCurrentLogFileName.Clear(); + FCurrentFileName.Clear(); + StateChange(); +} + +void TSessionLog::OpenLogFile() +{ + try + { + DebugAssert(FFile == nullptr); + DebugAssert(FConfiguration != nullptr); + FCurrentLogFileName = FConfiguration->GetLogFileName(); + FFile = OpenFile(FCurrentLogFileName, FSessionData, FConfiguration->GetLogFileAppend(), FCurrentFileName); + } + catch (Exception & E) + { + // We failed logging to file, turn it off and notify user. + FCurrentLogFileName.Clear(); + FCurrentFileName.Clear(); + FConfiguration->SetLogFileName(UnicodeString()); + try + { + throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); + } + catch (Exception & E) + { + AddException(&E); + FUI->HandleExtendedException(&E); + } + } + StateChange(); +} + +void TSessionLog::StateChange() +{ + if (FOnStateChange != nullptr) + { + FOnStateChange(this); + } +} + +void TSessionLog::DeleteUnnecessary() +{ + BeginUpdate(); + try__finally + { + SCOPE_EXIT + { + EndUpdate(); + }; + if (!GetLogging() || (FParent != nullptr)) + { + Clear(); + } + else + { + while (!FConfiguration->GetLogWindowComplete() && (GetCount() > FConfiguration->GetLogWindowLines())) + { + Delete(0); + ++FTopIndex; + } + } + } + __finally + { + EndUpdate(); + }; +} + +void TSessionLog::AddSystemInfo() +{ + AddStartupInfo(true); +} + +void TSessionLog::AddStartupInfo() +{ + AddStartupInfo(false); +} + +void TSessionLog::AddStartupInfo(bool System) +{ + TSessionData * Data = (System ? nullptr : FSessionData); + if (GetLogging()) + { + if (FParent != nullptr) + { + // do not add session info for secondary session + // (this should better be handled in the TSecondaryTerminal) + } + else + { + DoAddStartupInfo(Data); + } + } +} + +UnicodeString TSessionLog::GetTlsVersionName(TTlsVersion TlsVersion) +{ + switch (TlsVersion) + { + default: + DebugFail(); + case ssl2: + return "SSLv2"; + case ssl3: + return "SSLv3"; + case tls10: + return "TLSv1.0"; + case tls11: + return "TLSv1.1"; + case tls12: + return "TLSv1.2"; + } +} + +UnicodeString TSessionLog::LogSensitive(const UnicodeString & Str) +{ + if (FConfiguration->GetLogSensitive() && !Str.IsEmpty()) + { + return Str; + } + else + { + return BooleanToEngStr(!Str.IsEmpty()); + } +} + +UnicodeString TSessionLog::GetCmdLineLog() +{ + TODO("GetCmdLine()"); + UnicodeString Result = L""; + + if (!FConfiguration->GetLogSensitive()) + { +#if 0 + TManagementScript Script(StoredSessions, false); + Script.MaskPasswordInCommandLine(Result, true); +#endif + } + + return Result; +} + +template +UnicodeString EnumName(T Value, const UnicodeString & ANames) +{ + int N = int(Value); + + UnicodeString Names(ANames); + + do + { + UnicodeString Name = CutToChar(Names, L';', true); + if (N == 0) + { + return Name; + } + N--; + } + while ((N >= 0) && !Names.IsEmpty()); + + return L"(unknown)"; +} + +#define ADSTR(S) DoAdd(llMessage, S, MAKE_CALLBACK(TSessionLog::DoAddToSelf, this)); +#define ADF(S, ...) DoAdd(llMessage, FORMAT(S, ##__VA_ARGS__), MAKE_CALLBACK(TSessionLog::DoAddToSelf, this)); + +void TSessionLog::DoAddStartupInfo(TSessionData * Data) +{ + TGuard Guard(FCriticalSection); + + BeginUpdate(); + try__finally + { + SCOPE_EXIT + { + DeleteUnnecessary(); + EndUpdate(); + }; + if (Data == nullptr) + { + AddSeparator(); + ADF(L"NetBox %s (OS %s)", FConfiguration->GetProductVersionStr().c_str(), FConfiguration->GetOSVersionStr().c_str()); + std::unique_ptr Storage(FConfiguration->CreateConfigStorage()); + DebugAssert(Storage.get()); + ADF(L"Configuration: %s", Storage->GetSource().c_str()); +#ifndef __linux__ + if (0) + { + typedef BOOL (WINAPI * TGetUserNameEx)(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer, PULONG nSize); + HINSTANCE Secur32 = LoadLibrary(L"secur32.dll"); + TGetUserNameEx GetUserNameEx = + (Secur32 != nullptr) ? reinterpret_cast(::GetProcAddress(Secur32, "GetUserNameExW")) : nullptr; + wchar_t UserName[UNLEN + 1]; + ULONG UserNameSize = _countof(UserName); + if ((GetUserNameEx == nullptr) || DebugAlwaysFalse(!GetUserNameEx(NameSamCompatible, (LPWSTR)UserName, &UserNameSize))) + { + wcscpy_s(UserName, UNLEN, L""); + } + UnicodeString LogStr; + if (FConfiguration->GetLogProtocol() <= 0) + { + LogStr = L"Normal"; + } + else if (FConfiguration->GetLogProtocol() == 1) + { + LogStr = L"Debug 1"; + } + else if (FConfiguration->GetLogProtocol() >= 2) + { + LogStr = L"Debug 2"; + } + if (FConfiguration->GetLogSensitive()) + { + LogStr += L", Logging passwords"; + } + ADF(L"Log level: %s", LogStr.c_str()); + ADF(L"Local account: %s", UserName); + } +#endif + uint16_t Y, M, D, H, N, S, MS; + TDateTime DateTime = Now(); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, N, S, MS); + UnicodeString dt = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d", D, M, Y, H, N, S); + // ADF(L"Login time: %s", FormatDateTime(L"dddddd tt", Now()).c_str()); + ADF(L"Working directory: %s", ::GetCurrentDir().c_str()); + // ADF(L"Command-line: %s", GetCmdLineLog().c_str()); +// if (FConfiguration->LogProtocol >= 1) +// { +// AddOptions(GetGlobalOptions()); +// } + // ADF(L"Time zone: %s", GetTimeZoneLogString().c_str()); + if (!AdjustClockForDSTEnabled()) + { + ADF(L"Warning: System option \"Automatically adjust clock for Daylight Saving Time\" is disabled, timestamps will not be represented correctly"); + } + ADF(L"Login time: %s", dt.c_str()); + AddSeparator(); + } + else + { + if (0) + { + ADF(L"Session name: %s (%s)", Data->GetSessionName().c_str(), Data->GetSource().c_str()); + } + ADF(L"Host name: %s (Port: %d)", Data->GetHostNameExpanded().c_str(), Data->GetPortNumber()); + if (0) + { + ADF(L"User name: %s (Password: %s, Key file: %s)", + Data->GetUserNameExpanded().c_str(), BooleanToEngStr(!Data->GetPassword().IsEmpty()).c_str(), + BooleanToEngStr(!Data->GetPublicKeyFile().IsEmpty()).c_str()) + } + if (Data->GetUsesSsh()) + { + ADF(L"Tunnel: %s", BooleanToEngStr(Data->GetTunnel()).c_str()); + if (Data->GetTunnel()) + { + ADF(L"Tunnel: Host name: %s (Port: %d)", Data->GetTunnelHostName().c_str(), Data->GetTunnelPortNumber()); + if (0) + { + ADF(L"Tunnel: User name: %s (Password: %s, Key file: %s)", + Data->GetTunnelUserName().c_str(), BooleanToEngStr(!Data->GetTunnelPassword().IsEmpty()).c_str(), + BooleanToEngStr(!Data->GetTunnelPublicKeyFile().IsEmpty()).c_str()); + ADF(L"Tunnel: Local port number: %d", Data->GetTunnelLocalPortNumber()); + } + } + } + ADF(L"Transfer Protocol: %s", Data->GetFSProtocolStr().c_str()); + ADF(L"Code Page: %d", Data->GetCodePageAsNumber()); + if (Data->GetUsesSsh() || (Data->GetFSProtocol() == fsFTP)) + { + // wchar_t * PingTypes = (wchar_t *)L"-NC"; + TPingType PingType; + intptr_t PingInterval; + if (Data->GetFSProtocol() == fsFTP) + { + PingType = Data->GetFtpPingType(); + PingInterval = Data->GetFtpPingInterval(); + } + else + { + PingType = Data->GetPingType(); + PingInterval = Data->GetPingInterval(); + } + ADF(L"Ping type: %s, Ping interval: %d sec; Timeout: %d sec", + EnumName(PingType, PingTypeNames).c_str(), PingInterval, Data->GetTimeout()); + ADF(L"Disable Nagle: %s", + BooleanToEngStr(Data->GetTcpNoDelay()).c_str()); + } + TProxyMethod ProxyMethod = Data->GetActualProxyMethod(); + { + UnicodeString fp = FORMAT(L"FTP proxy %d", Data->GetFtpProxyLogonType()); + ADF(L"Proxy: %s", + (Data->GetFtpProxyLogonType() != 0) ? + fp.c_str() : + EnumName(ProxyMethod, ProxyMethodNames).c_str()); + } + if ((Data->GetFtpProxyLogonType() != 0) || (ProxyMethod != ::pmNone)) + { + ADF(L"ProxyHostName: %s (Port: %d); ProxyUsername: %s; Passwd: %s", + Data->GetProxyHost().c_str(), Data->GetProxyPort(), + Data->GetProxyUsername().c_str(), BooleanToEngStr(!Data->GetProxyPassword().IsEmpty()).c_str()); + if (ProxyMethod == pmTelnet) + { + ADF(L"Telnet command: %s", Data->GetProxyTelnetCommand().c_str()); + } + if (ProxyMethod == pmCmd) + { + ADF(L"Local command: %s", Data->GetProxyLocalCommand().c_str()); + } + } + if (Data->GetUsesSsh() || (Data->GetFSProtocol() == fsFTP)) + { + ADF(L"Send buffer: %d", Data->GetSendBuf()); + } + if (Data->GetUsesSsh()) + { + ADF(L"SSH protocol version: %s; Compression: %s", + Data->GetSshProtStr().c_str(), BooleanToEngStr(Data->GetCompression()).c_str()); + ADF(L"Bypass authentication: %s", + BooleanToEngStr(Data->GetSshNoUserAuth()).c_str()); + ADF(L"Try agent: %s; Agent forwarding: %s; TIS/CryptoCard: %s; KI: %s; GSSAPI: %s", + BooleanToEngStr(Data->GetTryAgent()).c_str(), BooleanToEngStr(Data->GetAgentFwd()).c_str(), BooleanToEngStr(Data->GetAuthTIS()).c_str(), + BooleanToEngStr(Data->GetAuthKI()).c_str(), BooleanToEngStr(Data->GetAuthGSSAPI()).c_str()); + if (Data->GetAuthGSSAPI()) + { + ADF(L"GSSAPI: Forwarding: %s; Server realm: %s", + BooleanToEngStr(Data->GetGSSAPIFwdTGT()).c_str(), Data->GetGSSAPIServerRealm().c_str()); + } + ADF(L"Ciphers: %s; Ssh2DES: %s", + Data->GetCipherList().c_str(), BooleanToEngStr(Data->GetSsh2DES()).c_str()); +// ADF(L"KEX: %s", (Data->KexList)); + UnicodeString Bugs; + for (intptr_t Index = 0; Index < BUG_COUNT; ++Index) + { + AddToList(Bugs, EnumName(Data->GetBug(static_cast(Index)), AutoSwitchNames), L","); + } + ADF(L"SSH Bugs: %s", Bugs.c_str()); + ADF(L"Simple channel: %s", BooleanToEngStr(Data->GetSshSimple()).c_str()); + ADF(L"Return code variable: %s; Lookup user groups: %c", + Data->GetDetectReturnVar() ? UnicodeString(L"Autodetect").c_str() : Data->GetReturnVar().c_str(), + EnumName(Data->GetLookupUserGroups(), AutoSwitchNames).c_str()); + ADF(L"Shell: %s", Data->GetShell().IsEmpty() ? UnicodeString(L"default").c_str() : Data->GetShell().c_str()); + ADF(L"EOL: %s, UTF: %s", EnumName(Data->GetEOLType(), EOLTypeNames).c_str(), EnumName(Data->GetNotUtf(), NotAutoSwitchNames).c_str()); // NotUtf duplicated in FTP branch + ADF(L"Clear aliases: %s, Unset nat.vars: %s, Resolve symlinks: %s; Follow directory symlinks: %s", + BooleanToEngStr(Data->GetClearAliases()).c_str(), BooleanToEngStr(Data->GetUnsetNationalVars()).c_str(), + BooleanToEngStr(Data->GetResolveSymlinks()).c_str(), BooleanToEngStr(Data->GetFollowDirectorySymlinks()).c_str()); + ADF(L"LS: %s, Ign LS warn: %s, Scp1 Comp: %s", + Data->GetListingCommand().c_str(), + BooleanToEngStr(Data->GetIgnoreLsWarnings()).c_str(), + BooleanToEngStr(Data->GetScp1Compatibility()).c_str()); + } + if ((Data->GetFSProtocol() == fsSFTP) || (Data->GetFSProtocol() == fsSFTPonly)) + { + UnicodeString Bugs; + for (intptr_t Index = 0; Index < SFTP_BUG_COUNT; ++Index) + { + AddToList(Bugs, EnumName(Data->GetSFTPBug(static_cast(Index)), AutoSwitchNames), L","); + } + ADF(L"SFTP Bugs: %s", Bugs.c_str()); + ADF(L"SFTP Server: %s", Data->GetSftpServer().IsEmpty()? UnicodeString(L"default").c_str() : Data->GetSftpServer().c_str()); + } + bool FtpsOn = false; + if (Data->GetFSProtocol() == fsFTP) + { + ADF(L"UTF: %s", EnumName(Data->GetNotUtf(), NotAutoSwitchNames).c_str()); // duplicated in UsesSsh branch + UnicodeString Ftps; + switch (Data->GetFtps()) + { + case ftpsImplicit: + Ftps = L"Implicit TLS/SSL"; + FtpsOn = true; + break; + + case ftpsExplicitSsl: + Ftps = L"Explicit SSL/TLS"; + FtpsOn = true; + break; + + case ftpsExplicitTls: + Ftps = L"Explicit TLS/SSL"; + FtpsOn = true; + break; + + default: + DebugAssert(Data->GetFtps() == ftpsNone); + Ftps = L"None"; + break; + } + // kind of hidden option, so do not reveal it unless it is set + if (Data->GetFtpTransferActiveImmediately() != asAuto) + { + ADF(L"Transfer active immediately: %s", EnumName(Data->GetFtpTransferActiveImmediately(), AutoSwitchNames).c_str()); + } + ADF(L"FTPS: %s; [Client certificate: %s]", + Ftps.c_str(), LogSensitive(Data->GetTlsCertificateFile()).c_str()); + ADF(L"FTP: Passive: %s [Force IP: %s]; MLSD: %s [List all: %s]; HOST: %s", + BooleanToEngStr(Data->GetFtpPasvMode()).c_str(), + EnumName(Data->GetFtpForcePasvIp(), AutoSwitchNames).c_str(), + EnumName(Data->GetFtpUseMlsd(), AutoSwitchNames).c_str(), + EnumName(Data->GetFtpListAll(), AutoSwitchNames).c_str(), + EnumName(Data->GetFtpHost(), AutoSwitchNames).c_str()); + } + if (Data->GetFSProtocol() == fsWebDAV) + { + FtpsOn = (Data->GetFtps() != ftpsNone); + ADF(L"HTTPS: %s [Client certificate: %s]", + BooleanToEngStr(FtpsOn).c_str(), LogSensitive(Data->GetTlsCertificateFile()).c_str()); + } + if (FtpsOn) + { + if (Data->GetFSProtocol() == fsFTP) + { + ADF(L"Session reuse: %s", BooleanToEngStr(Data->GetSslSessionReuse()).c_str()); + } + ADF(L"TLS/SSL versions: %s-%s", GetTlsVersionName(Data->GetMinTlsVersion()).c_str(), GetTlsVersionName(Data->GetMaxTlsVersion()).c_str()); + } + ADF(L"Local directory: %s, Remote directory: %s, Update: %s, Cache: %s", + Data->GetLocalDirectory().IsEmpty() ? UnicodeString(L"default").c_str() : Data->GetLocalDirectory().c_str(), + Data->GetRemoteDirectory().IsEmpty() ? UnicodeString(L"home").c_str() : Data->GetRemoteDirectory().c_str(), + BooleanToEngStr(Data->GetUpdateDirectories()).c_str(), + BooleanToEngStr(Data->GetCacheDirectories()).c_str()); + ADF(L"Cache directory changes: %s, Permanent: %s", + BooleanToEngStr(Data->GetCacheDirectoryChanges()).c_str(), + BooleanToEngStr(Data->GetPreserveDirectoryChanges()).c_str()); + ADF(L"Recycle bin: Delete to: %s, Overwritten to: %s, Bin path: %s", + BooleanToEngStr(Data->GetDeleteToRecycleBin()).c_str(), + BooleanToEngStr(Data->GetOverwrittenToRecycleBin()).c_str(), + Data->GetRecycleBinPath().c_str()); + if (Data->GetTrimVMSVersions()) + { + ADF(L"Trim VMS versions: %s", + BooleanToEngStr(Data->GetTrimVMSVersions()).c_str()); + } + UnicodeString TimeInfo; + if ((Data->GetFSProtocol() == fsSFTP) || (Data->GetFSProtocol() == fsSFTPonly) || (Data->GetFSProtocol() == fsSCPonly) || (Data->GetFSProtocol() == fsWebDAV)) + { + AddToList(TimeInfo, FORMAT(L"DST mode: %s", EnumName(static_cast(Data->GetDSTMode()), DSTModeNames).c_str()), L";"); + } + if ((Data->GetFSProtocol() == fsSCPonly) || (Data->GetFSProtocol() == fsFTP)) + { + intptr_t TimeDifferenceMin = TimeToMinutes(Data->GetTimeDifference()); + AddToList(TimeInfo, FORMAT(L"Timezone offset: %dh %dm", TimeDifferenceMin / MinsPerHour, TimeDifferenceMin % MinsPerHour), L";"); + } + ADSTR(TimeInfo); + + if (Data->GetFSProtocol() == fsWebDAV) + { + ADF(L"Compression: %s", + BooleanToEngStr(Data->GetCompression()).c_str()); + } + + AddSeparator(); + } + } + __finally + { + DeleteUnnecessary(); + + EndUpdate(); + }; +} + +void TSessionLog::AddOption(const UnicodeString & LogStr) +{ + ADSTR(LogStr); +} + +void TSessionLog::AddOptions(TOptions * Options) +{ + Options->LogOptions(MAKE_CALLBACK(TSessionLog::AddOption, this)); +} + +#undef ADF +#undef ADSTR + +void TSessionLog::AddSeparator() +{ +// Add(llMessage, L"--------------------------------------------------------------------------"); +} + +intptr_t TSessionLog::GetBottomIndex() const +{ + return (GetCount() > 0 ? (GetTopIndex() + GetCount() - 1) : -1); +} + +bool TSessionLog::GetLoggingToFile() const +{ + DebugAssert((FFile == nullptr) || LogToFile()); + return (FFile != nullptr); +} + +void TSessionLog::Clear() +{ + TGuard Guard(FCriticalSection); + + FTopIndex += GetCount(); + TStringList::Clear(); +} + +TSessionLog * TSessionLog::GetParent() +{ + return FParent; +} + +void TSessionLog::SetParent(TSessionLog * Value) +{ + FParent = Value; +} + +bool TSessionLog::GetLogging() const +{ + return FLogging; +} + +TNotifyEvent & TSessionLog::GetOnChange() +{ + return TStringList::GetOnChange(); +} + +void TSessionLog::SetOnChange(TNotifyEvent Value) +{ + TStringList::SetOnChange(Value); +} + +TNotifyEvent & TSessionLog::GetOnStateChange() +{ + return FOnStateChange; +} + +void TSessionLog::SetOnStateChange(TNotifyEvent Value) +{ + FOnStateChange = Value; +} + +UnicodeString TSessionLog::GetCurrentFileName() const +{ + return FCurrentFileName; +} + +intptr_t TSessionLog::GetTopIndex() const +{ + return FTopIndex; +} + +UnicodeString TSessionLog::GetName() const +{ + return FName; +} + +void TSessionLog::SetName(const UnicodeString & Value) +{ + FName = Value; +} + +intptr_t TSessionLog::GetCount() const +{ + return TStringList::GetCount(); +} + +TActionLog::TActionLog(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration) : + FConfiguration(Configuration), + FLogging(false), + FFile(nullptr), + FUI(UI), + FSessionData(SessionData), + FPendingActions(new TList()), + FClosed(false), + FInGroup(false), + FEnabled(true), + FIndent(L" ") +{ + DebugAssert(UI != nullptr); + DebugAssert(SessionData != nullptr); + Init(UI, SessionData, Configuration); +} + +TActionLog::TActionLog(TConfiguration * Configuration) +{ + Init(nullptr, nullptr, Configuration); + // not associated with session, so no need to waiting for anything + ReflectSettings(); +} + +void TActionLog::Init(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration) +{ +// FCriticalSection = new TCriticalSection; + FConfiguration = Configuration; + FUI = UI; + FSessionData = SessionData; + FFile = nullptr; + FCurrentLogFileName.Clear(); + FCurrentFileName.Clear(); + FLogging = false; + FClosed = false; + FPendingActions = new TList(); + FIndent = L" "; + FInGroup = false; + FEnabled = true; +} + +TActionLog::~TActionLog() +{ + DebugAssert(FPendingActions->GetCount() == 0); + SAFE_DESTROY(FPendingActions); + FClosed = true; + ReflectSettings(); + DebugAssert(FFile == nullptr); +} + +void TActionLog::Add(const UnicodeString & Line) +{ + DebugAssert(FConfiguration); + if (FLogging) + { + try + { + TGuard Guard(FCriticalSection); + if (FFile == nullptr) + { + OpenLogFile(); + } + + if (FFile != nullptr) + { + UTF8String UtfLine = UTF8String(Line); + fwrite(UtfLine.c_str(), 1, UtfLine.Length(), static_cast(FFile)); + fwrite("\n", 1, 1, static_cast(FFile)); + } + } + catch (Exception & E) + { + // We failed logging, turn it off and notify user. + FConfiguration->SetLogActions(false); + try + { + throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); + } + catch (Exception & E) + { + if (FUI != nullptr) + { + FUI->HandleExtendedException(&E); + } + } + } + } +} + +void TActionLog::AddIndented(const UnicodeString & Line) +{ + Add(FIndent + Line); +} + +void TActionLog::AddFailure(TStrings * Messages) +{ + AddIndented(L""); + AddMessages(L" ", Messages); + AddIndented(L""); +} + +void TActionLog::AddFailure(Exception * E) +{ + std::unique_ptr Messages(ExceptionToMoreMessages(E)); + if (Messages.get() != nullptr) + { + try__finally + { + AddFailure(Messages.get()); + } + __finally + { +// delete Messages; + }; + } +} + +void TActionLog::AddMessages(const UnicodeString & Indent, TStrings * Messages) +{ + for (intptr_t Index = 0; Index < Messages->GetCount(); ++Index) + { + AddIndented( + FORMAT((Indent + L"%s").c_str(), XmlEscape(Messages->GetString(Index)).c_str())); + } +} + +void TActionLog::ReflectSettings() +{ + TGuard Guard(FCriticalSection); + + bool ALogging = + !FClosed && FConfiguration->GetLogActions() && GetEnabled(); + + if (ALogging && !FLogging) + { + FLogging = true; +#if 0 + Add(L""); + UnicodeString SessionName = + (FSessionData != NULL) ? XmlAttributeEscape(FSessionData->SessionName) : UnicodeString(L"nosession"); + Add(FORMAT(L"", + (SessionName, StandardTimestamp()))); +#endif + } + else if (!ALogging && FLogging) + { + if (FInGroup) + { + EndGroup(); + } + // do not try to close the file, if it has not been opened, to avoid recursion + if (FFile != nullptr) + { +// Add(L""); + } + CloseLogFile(); + FLogging = false; + } + +} + +void TActionLog::CloseLogFile() +{ + if (FFile != nullptr) + { + fclose(static_cast(FFile)); + FFile = nullptr; + } + FCurrentLogFileName.Clear(); + FCurrentFileName.Clear(); +} + +void TActionLog::OpenLogFile() +{ + try + { + DebugAssert(FFile == nullptr); + DebugAssert(FConfiguration != nullptr); + FCurrentLogFileName = FConfiguration->GetActionsLogFileName(); + FFile = OpenFile(FCurrentLogFileName, FSessionData, false, FCurrentFileName); + } + catch (Exception & E) + { + // We failed logging to file, turn it off and notify user. + FCurrentLogFileName.Clear(); + FCurrentFileName.Clear(); + FConfiguration->SetLogActions(false); + try + { + throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); + } + catch (Exception & E) + { + if (FUI != nullptr) + { + FUI->HandleExtendedException(&E); + } + } + } +} + +void TActionLog::AddPendingAction(TSessionActionRecord * Action) +{ + FPendingActions->Add(Action); +} + +void TActionLog::RecordPendingActions() +{ + while ((FPendingActions->GetCount() > 0) && + NB_STATIC_DOWNCAST(TSessionActionRecord, FPendingActions->GetItem(0))->Record()) + { + FPendingActions->Delete(0); + } +} + +void TActionLog::BeginGroup(const UnicodeString & Name) +{ + DebugAssert(!FInGroup); + FInGroup = true; + DebugAssert(FIndent == L" "); + AddIndented(FORMAT(L"", + XmlAttributeEscape(Name).c_str(), StandardTimestamp().c_str())); + FIndent = L" "; +} + +void TActionLog::EndGroup() +{ + DebugAssert(FInGroup); + FInGroup = false; + DebugAssert(FIndent == L" "); + FIndent = L" "; + // this is called from ReflectSettings that in turn is called when logging fails, + // so do not try to close the group, if it has not been opened, to avoid recursion + if (FFile != nullptr) + { + AddIndented(""); + } +} + +void TActionLog::SetEnabled(bool Value) +{ + if (GetEnabled() != Value) + { + FEnabled = Value; + ReflectSettings(); + } +} + +NB_IMPLEMENT_CLASS(TSessionActionRecord, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/SessionInfo.h b/netbox/src/core/SessionInfo.h new file mode 100644 index 000000000..d3c8fe83a --- /dev/null +++ b/netbox/src/core/SessionInfo.h @@ -0,0 +1,414 @@ +#pragma once + +#include "SessionData.h" +#include "Interface.h" + +enum TSessionStatus +{ + ssClosed, + ssOpening, + ssOpened, +}; + +struct TSessionInfo : public TObject +{ + TSessionInfo(); + + TDateTime LoginTime; + UnicodeString ProtocolBaseName; + UnicodeString ProtocolName; + UnicodeString SecurityProtocolName; + + UnicodeString CSCipher; + UnicodeString CSCompression; + UnicodeString SCCipher; + UnicodeString SCCompression; + + UnicodeString SshVersionString; + UnicodeString SshImplementation; + UnicodeString HostKeyFingerprint; + + UnicodeString CertificateFingerprint; + UnicodeString Certificate; +}; + +enum TFSCapability +{ + fcUserGroupListing = 0, fcModeChanging, fcGroupChanging, + fcOwnerChanging, fcGroupOwnerChangingByID, fcAnyCommand, fcHardLink, + fcSymbolicLink, + // With WebDAV this is always true, to avoid double-click on + // file try to open the file as directory. It does no harm atm as + // WebDAV never produce a symlink in listing. + fcResolveSymlink, + fcTextMode, fcRename, fcNativeTextMode, fcNewerOnlyUpload, fcRemoteCopy, + fcTimestampChanging, fcRemoteMove, fcLoadingAdditionalProperties, + fcCheckingSpaceAvailable, fcIgnorePermErrors, fcCalculatingChecksum, + fcModeChangingUpload, fcPreservingTimestampUpload, fcShellAnyCommand, + fcSecondaryShell, fcRemoveCtrlZUpload, fcRemoveBOMUpload, fcMoveToQueue, + fcLocking, fcPreservingTimestampDirs, fcResumeSupport, + fcCount, +}; + +struct TFileSystemInfo : public TObject +{ + TFileSystemInfo(); + + UnicodeString ProtocolBaseName; + UnicodeString ProtocolName; + UnicodeString RemoteSystem; + UnicodeString AdditionalInfo; + bool IsCapable[fcCount]; +}; + +class TSessionUI +{ +//NB_DECLARE_CLASS(TSessionUI) +public: + explicit TSessionUI() {} + virtual ~TSessionUI() {} + virtual void Information(const UnicodeString & Str, bool Status) = 0; + virtual uintptr_t QueryUser(const UnicodeString & Query, + TStrings * MoreMessages, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType = qtConfirmation) = 0; + virtual uintptr_t QueryUserException(const UnicodeString & Query, + Exception * E, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType = qtConfirmation) = 0; + virtual bool PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Results) = 0; + virtual void DisplayBanner(const UnicodeString & Banner) = 0; + virtual void FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L"") = 0; + virtual void HandleExtendedException(Exception * E) = 0; + virtual void Closed() = 0; + virtual void ProcessGUI() = 0; +}; + +// Duplicated in LogMemo.h for design-time-only purposes +enum TLogLineType +{ + llOutput, + llInput, + llStdError, + llMessage, + llException, +}; + +enum TLogAction +{ + laUpload, + laDownload, + laTouch, + laChmod, + laMkdir, + laRm, + laMv, + laCall, + laLs, + laStat, + laChecksum, + laCwd, +}; + +enum TCaptureOutputType +{ + cotOutput, + cotError, + cotExitCode, +}; + +//typedef void (__closure *TCaptureOutputEvent)( +// const UnicodeString & Str, TCaptureOutputType OutputType); +DEFINE_CALLBACK_TYPE2(TCaptureOutputEvent, void, + const UnicodeString & /*Str*/, TCaptureOutputType /*OutputType*/); +//typedef void (__closure *TCalculatedChecksumEvent)( +// const UnicodeString & FileName, const UnicodeString & Alg, const UnicodeString & Hash); +DEFINE_CALLBACK_TYPE3(TCalculatedChecksumEvent, void, + const UnicodeString & /*FileName*/, const UnicodeString & /*Alg*/, + const UnicodeString & /*Hash*/); + +class TSessionActionRecord; +class TActionLog; + +class TSessionAction : public TObject +{ +NB_DISABLE_COPY(TSessionAction) +public: + explicit TSessionAction(TActionLog * Log, TLogAction Action); + virtual ~TSessionAction(); + + void Restart(); + + void Commit(); + void Rollback(Exception * E = nullptr); + void Cancel(); + +protected: + TSessionActionRecord * FRecord; +}; + +class TFileSessionAction : public TSessionAction +{ +public: + explicit TFileSessionAction(TActionLog * Log, TLogAction Action); + explicit TFileSessionAction(TActionLog * Log, TLogAction Action, const UnicodeString & AFileName); + + void SetFileName(const UnicodeString & AFileName); +}; + +class TFileLocationSessionAction : public TFileSessionAction +{ +public: + explicit TFileLocationSessionAction(TActionLog * Log, TLogAction Action); + explicit TFileLocationSessionAction(TActionLog * Log, TLogAction Action, const UnicodeString & AFileName); + + void Destination(const UnicodeString & Destination); +}; + +class TUploadSessionAction : public TFileLocationSessionAction +{ +public: + explicit TUploadSessionAction(TActionLog * Log); +}; + +class TDownloadSessionAction : public TFileLocationSessionAction +{ +public: + explicit TDownloadSessionAction(TActionLog * Log); +}; + +class TRights; + +class TChmodSessionAction : public TFileSessionAction +{ +public: + explicit TChmodSessionAction(TActionLog * Log, const UnicodeString & AFileName); + explicit TChmodSessionAction(TActionLog * Log, const UnicodeString & AFileName, + const TRights & Rights); + + void Rights(const TRights & Rights); + void Recursive(); +}; + +class TTouchSessionAction : public TFileSessionAction +{ +public: + explicit TTouchSessionAction(TActionLog * Log, const UnicodeString & AFileName, + const TDateTime & Modification); +}; + +class TMkdirSessionAction : public TFileSessionAction +{ +public: + explicit TMkdirSessionAction(TActionLog * Log, const UnicodeString & AFileName); +}; + +class TRmSessionAction : public TFileSessionAction +{ +public: + explicit TRmSessionAction(TActionLog * Log, const UnicodeString & AFileName); + + void Recursive(); +}; + +class TMvSessionAction : public TFileLocationSessionAction +{ +public: + explicit TMvSessionAction(TActionLog * Log, const UnicodeString & AFileName, + const UnicodeString & Destination); +}; + +class TCallSessionAction : public TSessionAction +{ +public: + explicit TCallSessionAction(TActionLog * Log, const UnicodeString & Command, + const UnicodeString & Destination); + + void AddOutput(const UnicodeString & Output, bool StdError); + void ExitCode(int ExitCode); +}; + +class TLsSessionAction : public TSessionAction +{ +public: + explicit TLsSessionAction(TActionLog * Log, const UnicodeString & Destination); + + void FileList(TRemoteFileList * FileList); +}; + +class TStatSessionAction : public TFileSessionAction +{ +public: + explicit TStatSessionAction(TActionLog * Log, const UnicodeString & AFileName); + + void File(TRemoteFile * AFile); +}; + +class TChecksumSessionAction : public TFileSessionAction +{ +public: + explicit TChecksumSessionAction(TActionLog * Log); + + void Checksum(const UnicodeString & Alg, const UnicodeString & Checksum); +}; + +class TCwdSessionAction : public TSessionAction +{ +public: + TCwdSessionAction(TActionLog * Log, const UnicodeString & Path); +}; + +// void (__closure *f)(TLogLineType Type, const UnicodeString & Line)); +DEFINE_CALLBACK_TYPE2(TDoAddLogEvent, void, + TLogLineType /*Type*/, const UnicodeString & /*Line*/); + +class TSessionLog : protected TStringList +{ +CUSTOM_MEM_ALLOCATION_IMPL +friend class TSessionAction; +friend class TSessionActionRecord; +NB_DISABLE_COPY(TSessionLog) +public: + explicit TSessionLog(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration); + virtual ~TSessionLog(); + HIDESBASE void Add(TLogLineType Type, const UnicodeString & Line); + void AddSystemInfo(); + void AddStartupInfo(); + void AddException(Exception * E); + void AddSeparator(); + + virtual void Clear(); + void ReflectSettings(); + void Lock(); + void Unlock(); + +/* + __property TSessionLog * Parent = { read = FParent, write = FParent }; + __property bool Logging = { read = FLogging }; + __property int BottomIndex = { read = GetBottomIndex }; + __property UnicodeString Line[int Index] = { read=GetLine }; + __property TLogLineType Type[int Index] = { read=GetType }; + __property OnChange; + __property TNotifyEvent OnStateChange = { read = FOnStateChange, write = FOnStateChange }; + __property UnicodeString CurrentFileName = { read = FCurrentFileName }; + __property bool LoggingToFile = { read = GetLoggingToFile }; + __property int TopIndex = { read = FTopIndex }; + __property UnicodeString SessionName = { read = GetSessionName }; + __property UnicodeString Name = { read = FName, write = FName }; + __property Count; +*/ + TSessionLog * GetParent(); + void SetParent(TSessionLog * Value); + bool GetLogging() const; + TNotifyEvent & GetOnChange(); + void SetOnChange(TNotifyEvent Value); + TNotifyEvent & GetOnStateChange(); + void SetOnStateChange(TNotifyEvent Value); + UnicodeString GetCurrentFileName() const; + intptr_t GetTopIndex() const; + UnicodeString GetName() const; + void SetName(const UnicodeString & Value); + virtual intptr_t GetCount() const; + +protected: + void CloseLogFile(); + bool LogToFile() const; + +private: + TConfiguration * FConfiguration; + TSessionLog * FParent; + TCriticalSection FCriticalSection; + bool FLogging; + void * FFile; + UnicodeString FCurrentLogFileName; + UnicodeString FCurrentFileName; + int FLoggedLines; + intptr_t FTopIndex; + TSessionUI * FUI; + TSessionData * FSessionData; + UnicodeString FName; + bool FClosed; + TNotifyEvent FOnStateChange; + +public: + UnicodeString GetLine(intptr_t Index) const; + TLogLineType GetType(intptr_t Index) const; + void DeleteUnnecessary(); + void StateChange(); + void OpenLogFile(); + intptr_t GetBottomIndex() const; + UnicodeString GetLogFileName() const; + bool GetLoggingToFile() const; + UnicodeString GetSessionName() const; + +private: + void DoAdd(TLogLineType AType, const UnicodeString & ALine, + TDoAddLogEvent Event); + void DoAddToParent(TLogLineType AType, const UnicodeString & ALine); + void DoAddToSelf(TLogLineType AType, const UnicodeString & ALine); + void AddStartupInfo(bool System); + void DoAddStartupInfo(TSessionData * Data); + UnicodeString GetTlsVersionName(TTlsVersion TlsVersion); + UnicodeString LogSensitive(const UnicodeString & Str); + void AddOption(const UnicodeString & LogStr); + void AddOptions(TOptions * Options); + UnicodeString GetCmdLineLog(); +}; + +class TActionLog : public TObject +{ +friend class TSessionAction; +friend class TSessionActionRecord; +NB_DISABLE_COPY(TActionLog) +public: + explicit TActionLog(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration); + explicit TActionLog(TConfiguration * Configuration); + virtual ~TActionLog(); + + void ReflectSettings(); + void AddFailure(Exception * E); + void AddFailure(TStrings * Messages); + void BeginGroup(const UnicodeString & Name); + void EndGroup(); + +/* + __property UnicodeString CurrentFileName = { read = FCurrentFileName }; + __property bool Enabled = { read = FEnabled, write = SetEnabled }; +*/ + UnicodeString GetCurrentFileName() const { return FCurrentFileName; } + bool GetEnabled() const { return FEnabled; } + +protected: + void CloseLogFile(); + inline void AddPendingAction(TSessionActionRecord * Action); + void RecordPendingActions(); + void Add(const UnicodeString & Line); + void AddIndented(const UnicodeString & Line); + void AddMessages(const UnicodeString & Indent, TStrings * Messages); + void Init(TSessionUI * UI, TSessionData * SessionData, + TConfiguration * Configuration); + +private: + TConfiguration * FConfiguration; + TCriticalSection FCriticalSection; + bool FLogging; + void * FFile; + UnicodeString FCurrentLogFileName; + UnicodeString FCurrentFileName; + TSessionUI * FUI; + TSessionData * FSessionData; + TList * FPendingActions; + bool FClosed; + bool FInGroup; + UnicodeString FIndent; + bool FEnabled; + + void OpenLogFile(); + +public: + UnicodeString GetLogFileName() const; + void SetEnabled(bool Value); +}; + diff --git a/netbox/src/core/SftpFileSystem.cpp b/netbox/src/core/SftpFileSystem.cpp new file mode 100644 index 000000000..a1d064cfb --- /dev/null +++ b/netbox/src/core/SftpFileSystem.cpp @@ -0,0 +1,6457 @@ +#include +#pragma hdrstop + +#define CLEAN_SPACE_AVAILABLE + +#include +#include + +#include "SftpFileSystem.h" +#include "PuttyTools.h" +#include "Interface.h" +#include "Terminal.h" +#include "TextsCore.h" +#include "HelpCore.h" +#include "SecureShell.h" +#ifdef __linux__ +#include +#endif + +static const SSH_FX_TYPES +SSH_FX_OK = 0, +SSH_FX_EOF = 1, +SSH_FX_NO_SUCH_FILE = 2, +SSH_FX_PERMISSION_DENIED = 3, +SSH_FX_FAILURE = 4, +SSH_FX_OP_UNSUPPORTED = 8; + +static const SSH_FXP_TYPES +SSH_FXP_INIT = 1, +SSH_FXP_VERSION = 2, +SSH_FXP_OPEN = 3, +SSH_FXP_CLOSE = 4, +SSH_FXP_READ = 5, +SSH_FXP_WRITE = 6, +SSH_FXP_LSTAT = 7, +SSH_FXP_FSTAT = 8, +SSH_FXP_SETSTAT = 9, +SSH_FXP_FSETSTAT = 10, +SSH_FXP_OPENDIR = 11, +SSH_FXP_READDIR = 12, +SSH_FXP_REMOVE = 13, +SSH_FXP_MKDIR = 14, +SSH_FXP_RMDIR = 15, +SSH_FXP_REALPATH = 16, +SSH_FXP_STAT = 17, +SSH_FXP_RENAME = 18, +SSH_FXP_READLINK = 19, +SSH_FXP_SYMLINK = 20, +SSH_FXP_LINK = 21, +SSH_FXP_STATUS = 101, +SSH_FXP_HANDLE = 102, +SSH_FXP_DATA = 103, +SSH_FXP_NAME = 104, +SSH_FXP_ATTRS = 105, +SSH_FXP_EXTENDED = 200, +SSH_FXP_EXTENDED_REPLY = 201; + +static const SSH_FILEXFER_ATTR_TYPES +SSH_FILEXFER_ATTR_SIZE = 0x00000001, +SSH_FILEXFER_ATTR_UIDGID = 0x00000002, +SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004, +SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008, +SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008, +SSH_FILEXFER_ATTR_CREATETIME = 0x00000010, +SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020, +SSH_FILEXFER_ATTR_ACL = 0x00000040, +SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080, +SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100, +SSH_FILEXFER_ATTR_BITS = 0x00000200, +SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400, +SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800, +SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000, +SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000, +SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000, +SSH_FILEXFER_ATTR_CTIME = 0x00008000, +SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; + +// SSH_FILEXFER_ATTR_COMMON +static const SSH_FILEXFER_ATTR_TYPES +SSH_FILEXFER_ATTR_COMMON = + (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_OWNERGROUP | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACCESSTIME | + SSH_FILEXFER_ATTR_MODIFYTIME); + +static const SSH_FILEXFER_TYPES +SSH_FILEXFER_TYPE_REGULAR = 1, +SSH_FILEXFER_TYPE_DIRECTORY = 2, +SSH_FILEXFER_TYPE_SYMLINK = 3, +SSH_FILEXFER_TYPE_SPECIAL = 4, +SSH_FILEXFER_TYPE_UNKNOWN = 5; + +static const SSH_FXF_TYPES +SSH_FXF_READ = 0x00000001, +SSH_FXF_WRITE = 0x00000002, +SSH_FXF_APPEND = 0x00000004, +SSH_FXF_CREAT = 0x00000008, +SSH_FXF_TRUNC = 0x00000010, +SSH_FXF_EXCL = 0x00000020, +SSH_FXF_TEXT = 0x00000040, + +SSH_FXF_ACCESS_DISPOSITION = 0x00000007, + SSH_FXF_CREATE_NEW = 0x00000000, + SSH_FXF_CREATE_TRUNCATE = 0x00000001, + SSH_FXF_OPEN_EXISTING = 0x00000002, + SSH_FXF_OPEN_OR_CREATE = 0x00000003, + SSH_FXF_TRUNCATE_EXISTING = 0x00000004, +SSH_FXF_ACCESS_APPEND_DATA = 0x00000008, +SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010, +SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; + +static const ACE4_TYPES +ACE4_READ_DATA = 0x00000001, +ACE4_LIST_DIRECTORY = 0x00000001, +ACE4_WRITE_DATA = 0x00000002, +ACE4_ADD_FILE = 0x00000002, +ACE4_APPEND_DATA = 0x00000004, +ACE4_ADD_SUBDIRECTORY = 0x00000004, +ACE4_READ_NAMED_ATTRS = 0x00000008, +ACE4_WRITE_NAMED_ATTRS = 0x00000010, +ACE4_EXECUTE = 0x00000020, +ACE4_DELETE_CHILD = 0x00000040, +ACE4_READ_ATTRIBUTES = 0x00000080, +ACE4_WRITE_ATTRIBUTES = 0x00000100, +ACE4_DELETE = 0x00010000, +ACE4_READ_ACL = 0x00020000, +ACE4_WRITE_ACL = 0x00040000, +ACE4_WRITE_OWNER = 0x00080000, +ACE4_SYNCHRONIZE = 0x00100000; + +static const uint32_t SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; + +typedef uint8_t SSH_FXP_REALPATH_TYPES; +static const SSH_FXP_REALPATH_TYPES +SSH_FXP_REALPATH_NO_CHECK = 0x00000001, +SSH_FXP_REALPATH_STAT_IF = 0x00000002, +SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003; + +static const intptr_t SFTP_MAX_PACKET_LEN = 1000 * 1024; + +#define SFTP_EXT_OWNER_GROUP "owner-group-query@generic-extensions" +#define SFTP_EXT_OWNER_GROUP_REPLY "owner-group-query-reply@generic-extensions" +#define SFTP_EXT_NEWLINE "newline" +#define SFTP_EXT_SUPPORTED "supported" +#define SFTP_EXT_SUPPORTED2 "supported2" +#define SFTP_EXT_FSROOTS "fs-roots@vandyke.com" +#define SFTP_EXT_VENDOR_ID "vendor-id" +#define SFTP_EXT_VERSIONS "versions" +#define SFTP_EXT_SPACE_AVAILABLE "space-available" +#define SFTP_EXT_CHECK_FILE "check-file" +#define SFTP_EXT_CHECK_FILE_NAME "check-file-name" +#define SFTP_EXT_STATVFS "statvfs@openssh.com" +#define SFTP_EXT_STATVFS_VALUE_V2 L"2" +#define SFTP_EXT_STATVFS_ST_RDONLY 0x1 +#define SFTP_EXT_STATVFS_ST_NOSUID 0x2 +#define SFTP_EXT_HARDLINK "hardlink@openssh.com" +#define SFTP_EXT_HARDLINK_VALUE_V1 L"1" +#define SFTP_EXT_COPY_FILE "copy-file" + +static const wchar_t OGQ_LIST_OWNERS = 0x01; +static const wchar_t OGQ_LIST_GROUPS = 0x02; + +static const uint32_t SFTPNoMessageNumber = static_cast(-1); + +static const SSH_FX_TYPES asNo = 0; +static const SSH_FX_TYPES asOK = 1 << SSH_FX_OK; +static const SSH_FX_TYPES asEOF = 1 << SSH_FX_EOF; +static const SSH_FX_TYPES asPermDenied = 1 << SSH_FX_PERMISSION_DENIED; +static const SSH_FX_TYPES asOpUnsupported = 1 << SSH_FX_OP_UNSUPPORTED; +static const SSH_FX_TYPES asNoSuchFile = 1 << SSH_FX_NO_SUCH_FILE; +static const SSH_FX_TYPES asAll = (SSH_FX_TYPES)0xFFFF; + +static const uintptr_t SFTP_PACKET_ALLOC_DELTA = 256; + +struct TSFTPSupport : public TObject +{ +NB_DISABLE_COPY(TSFTPSupport) +public: + TSFTPSupport() : + AttributeMask(0), + AttributeBits(0), + OpenFlags(0), + AccessMask(0), + MaxReadSize(0), + OpenBlockVector(0), + BlockVector(0), + AttribExtensions(new TStringList()), + Extensions(new TStringList()), + Loaded(false) + { + Reset(); + } + + ~TSFTPSupport() + { + SAFE_DESTROY(AttribExtensions); + SAFE_DESTROY(Extensions); + } + + void Reset() + { + AttributeMask = 0; + AttributeBits = 0; + OpenFlags = 0; + AccessMask = 0; + MaxReadSize = 0; + OpenBlockVector = 0; + BlockVector = 0; + AttribExtensions->Clear(); + Extensions->Clear(); + Loaded = false; + } + + SSH_FILEXFER_ATTR_TYPES AttributeMask; + uint32_t AttributeBits; + uint32_t OpenFlags; + uint32_t AccessMask; + uint32_t MaxReadSize; + uint32_t OpenBlockVector; + uint32_t BlockVector; + TStrings * AttribExtensions; + TStrings * Extensions; + bool Loaded; +}; + +class TSFTPPacket : public TObject +{ +NB_DECLARE_CLASS(TSFTPPacket) +public: + explicit TSFTPPacket(uintptr_t codePage) + { + Init(codePage); + } + + explicit TSFTPPacket(const TSFTPPacket & other) + { + this->operator=(other); + } + + explicit TSFTPPacket(const TSFTPPacket & Source, uintptr_t codePage) + { + Init(codePage); + *this = Source; + } + + explicit TSFTPPacket(SSH_FXP_TYPES AType, uintptr_t codePage) + { + Init(codePage); + ChangeType(AType); + } + + explicit TSFTPPacket(const uint8_t * Source, uintptr_t Len, uintptr_t codePage) + { + Init(codePage); + FLength = Len; + SetCapacity(FLength); + memmove(GetData(), Source, Len); + } + + explicit TSFTPPacket(const RawByteString & Source, uintptr_t codePage) + { + Init(codePage); + FLength = static_cast(Source.Length()); + SetCapacity(FLength); + memmove(GetData(), Source.c_str(), Source.Length()); + } + + ~TSFTPPacket() + { + if (FData != nullptr) + { + nb_free(FData - FSendPrefixLen); + } + if (FReservedBy) + { + FReservedBy->UnreserveResponse(this); + } + } + + void ChangeType(SSH_FXP_TYPES AType) + { + FPosition = 0; + FLength = 0; + SetCapacity(0); + FType = AType; + AddByte((uint8_t)FType); + if (FType != SSH_FXP_INIT) // && (FType != 1) + { + AssignNumber(); + AddCardinal(FMessageNumber); + } + } + + void Reuse() + { + AssignNumber(); + + DebugAssert(GetLength() >= 5); + + // duplicated in AddCardinal() + uint8_t Buf[4]; + PUT_32BIT(Buf, FMessageNumber); + + memmove(FData + 1, Buf, sizeof(Buf)); + } + + void AddByte(uint8_t Value) + { + Add(&Value, sizeof(Value)); + } + + void AddBool(bool Value) + { + AddByte(Value ? 1 : 0); + } + + void AddCardinal(uint32_t Value) + { + // duplicated in Reuse() + uint8_t Buf[4]; + PUT_32BIT(Buf, Value); + Add(&Buf, sizeof(Buf)); + } + + void AddInt64(int64_t Value) + { + AddCardinal(static_cast(Value >> 32)); + AddCardinal(static_cast(Value & 0xFFFFFFFF)); + } + + void AddData(const void * Data, int32_t ALength) + { + AddCardinal(ALength); + Add(Data, ALength); + } + + void AddStringW(const UnicodeString & ValueW) + { + AddString(::W2MB(ValueW.c_str(), static_cast(FCodePage)).c_str()); + } + + void AddString(const RawByteString & Value) + { + AddCardinal(static_cast(Value.Length())); + Add(Value.c_str(), Value.Length()); + } + + inline void AddUtfString(const UTF8String & Value) + { + AddString(Value); + } + + inline void AddUtfString(const UnicodeString & Value) + { + AddUtfString(UTF8String(Value)); + } + + inline void AddString(const UnicodeString & Value, TAutoSwitch /*Utf*/) + { + AddStringW(Value); +#if 0 + // asAuto: Using UTF until we receive non-UTF string from the server + if ((Utf == asOn) || (Utf == asAuto)) + { + AddUtfString(Value); + } + else + { + AddString(RawByteString(AnsiString(Value))); + } +#endif + } + + // now purposeless alias to AddString + inline void AddPathString(const UnicodeString & Value, TAutoSwitch Utf) + { + AddString(Value, Utf); + } + + SSH_FILEXFER_ATTR_TYPES AllocationSizeAttribute(intptr_t Version) const + { + return (Version >= 6) ? SSH_FILEXFER_ATTR_ALLOCATION_SIZE : SSH_FILEXFER_ATTR_SIZE; + } + + void AddProperties(uint16_t * Rights, TRemoteToken * Owner, + TRemoteToken * Group, int64_t * MTime, int64_t * ATime, + int64_t * Size, bool IsDirectory, intptr_t Version, TAutoSwitch Utf) + { + SSH_FILEXFER_ATTR_TYPES Flags = 0; + if (Size != nullptr) + { + Flags |= AllocationSizeAttribute(Version); + } + // both or neither + DebugAssert((Owner != nullptr) == (Group != nullptr)); + if ((Owner != nullptr) && (Group != nullptr)) + { + if (Version < 4) + { + DebugAssert(Owner->GetIDValid() && Group->GetIDValid()); + Flags |= SSH_FILEXFER_ATTR_UIDGID; + } + else + { + DebugAssert(Owner->GetNameValid() && Group->GetNameValid()); + Flags |= SSH_FILEXFER_ATTR_OWNERGROUP; + } + } + if (Rights != nullptr) + { + Flags |= SSH_FILEXFER_ATTR_PERMISSIONS; + } + if ((Version < 4) && ((MTime != nullptr) || (ATime != nullptr))) + { + Flags |= SSH_FILEXFER_ATTR_ACMODTIME; + } + if ((Version >= 4) && (ATime != nullptr)) + { + Flags |= SSH_FILEXFER_ATTR_ACCESSTIME; + } + if ((Version >= 4) && (MTime != nullptr)) + { + Flags |= SSH_FILEXFER_ATTR_MODIFYTIME; + } + AddCardinal(Flags); + + if (Version >= 4) + { + AddByte(IsDirectory ? SSH_FILEXFER_TYPE_DIRECTORY : SSH_FILEXFER_TYPE_REGULAR); + } + + if (Size != nullptr) + { + // this is SSH_FILEXFER_ATTR_SIZE for version <= 5, but + // SSH_FILEXFER_ATTR_ALLOCATION_SIZE for version >= 6 + AddInt64(*Size); + } + + if ((Owner != nullptr) && (Group != nullptr)) + { + if (Version < 4) + { + DebugAssert(Owner->GetIDValid() && Group->GetIDValid()); + AddCardinal(static_cast(Owner->GetID())); + AddCardinal(static_cast(Group->GetID())); + } + else + { + DebugAssert(Owner->GetNameValid() && Group->GetNameValid()); + AddString(Owner->GetName(), Utf); + AddString(Group->GetName(), Utf); + } + } + + if (Rights != nullptr) + { + AddCardinal(*Rights); + } + + if ((Version < 4) && ((MTime != nullptr) || (ATime != nullptr))) + { + // any way to reflect sbSignedTS here? + // (note that casting int64_t > 2^31 < 2^32 to uint32_t is wrapped, + // thus we never can set time after 2038, even if the server supports it) + AddCardinal(static_cast(ATime != nullptr ? *ATime : MTime != nullptr ? *MTime : 0)); + AddCardinal(static_cast(MTime != nullptr ? *MTime : ATime != nullptr ? *ATime : 0)); + } + if ((Version >= 4) && (ATime != nullptr)) + { + AddInt64(*ATime); + } + if ((Version >= 4) && (MTime != nullptr)) + { + AddInt64(*MTime); + } + } + + void AddProperties(const TRemoteProperties * Properties, + uint16_t BaseRights, bool IsDirectory, intptr_t Version, TAutoSwitch Utf, + TChmodSessionAction * Action) + { + enum TValid + { + valNone = 0, valRights = 0x01, valOwner = 0x02, valGroup = 0x04, + valMTime = 0x08, valATime = 0x10, + } Valid = valNone; + uint16_t RightsNum = 0; + TRemoteToken Owner; + TRemoteToken Group; + int64_t MTime; + int64_t ATime; + + if (Properties != nullptr) + { + if (Properties->Valid.Contains(vpGroup)) + { + Valid = static_cast(Valid | valGroup); + Group = Properties->Group; + } + + if (Properties->Valid.Contains(vpOwner)) + { + Valid = static_cast(Valid | valOwner); + Owner = Properties->Owner; + } + + if (Properties->Valid.Contains(vpRights)) + { + Valid = static_cast(Valid | valRights); + TRights Rights = TRights(BaseRights); + Rights |= Properties->Rights.GetNumberSet(); + Rights &= static_cast(~Properties->Rights.GetNumberUnset()); + if (IsDirectory && Properties->AddXToDirectories) + { + Rights.AddExecute(); + } + RightsNum = Rights; + + if (Action != nullptr) + { + Action->Rights(Rights); + } + } + + if (Properties->Valid.Contains(vpLastAccess) && Properties->LastAccess) + { + Valid = static_cast(Valid | valATime); + ATime = Properties->LastAccess; + } + + if (Properties->Valid.Contains(vpModification) && Properties->Modification) + { + Valid = static_cast(Valid | valMTime); + MTime = Properties->Modification; + } + } + + AddProperties( + (Valid & valRights) ? &RightsNum : nullptr, + (Valid & valOwner) ? &Owner : nullptr, + (Valid & valGroup) ? &Group : nullptr, + (Valid & valMTime) ? &MTime : nullptr, + (Valid & valATime) ? &ATime : nullptr, + nullptr, IsDirectory, Version, Utf); + } + + uint8_t GetByte() const + { + Need(sizeof(uint8_t)); + uint8_t Result = FData[FPosition]; + DataConsumed(sizeof(uint8_t)); + return Result; + } + + bool GetBool() const + { + return (GetByte() != 0); + } + + bool CanGetBool() const + { + return (GetRemainingLength() >= sizeof(uint8_t)); + } + + uint32_t GetCardinal() const + { + uint32_t Result = PeekCardinal(); + DataConsumed(sizeof(Result)); + return Result; + } + + bool CanGetCardinal() const + { + return (GetRemainingLength() >= sizeof(uint32_t)); + } + + uint32_t GetSmallCardinal() const + { + uint32_t Result; + Need(2); + Result = (FData[FPosition] << 8) + FData[FPosition + 1]; + DataConsumed(2); + return Result; + } + + bool CanGetSmallCardinal() const + { + return (GetRemainingLength() >= 2); + } + + int64_t GetInt64() const + { + int64_t Hi = GetCardinal(); + int64_t Lo = GetCardinal(); + return (Hi << 32) + Lo; + } + + RawByteString GetRawByteString() const + { + RawByteString Result; + uint32_t Len = GetCardinal(); + Need(Len); + // cannot happen anyway as Need() would raise exception + DebugAssert(Len < SFTP_MAX_PACKET_LEN); + Result.SetLength(Len); + memmove(reinterpret_cast(const_cast(Result.c_str())), FData + FPosition, Len); + DataConsumed(Len); + return Result; + } + + bool CanGetString(uint32_t & Size) const + { + bool Result = CanGetCardinal(); + if (Result) + { + uint32_t Len = PeekCardinal(); + Size = (sizeof(Len) + Len); + Result = (Size <= GetRemainingLength()); + } + return Result; + } + + // For reading strings that are character strings (not byte strings as + // as file handles), and SFTP spec does not say explicitly that they + // are in UTF. For most of them it actually does not matter as + // the content should be pure ASCII (e.g. extension names, etc.) + inline UnicodeString GetAnsiString() const + { + return AnsiToString(GetRawByteString().c_str()).c_str(); + } + + inline RawByteString GetFileHandle() const + { + return GetRawByteString(); + } + + inline UnicodeString GetStringW() const + { + return ::MB2W(GetRawByteString().c_str(), static_cast(FCodePage)); + } + + inline UnicodeString GetString(TAutoSwitch /*Utf*/) const + { + return GetStringW(); +#if 0 + if (Utf != asOff) + { + return GetUtfString(Utf); + } + else + { + return GetAnsiString(); + } +#endif + } + + // now purposeless alias to GetString(bool) + inline UnicodeString GetPathString(TAutoSwitch Utf) const + { + return GetString(Utf); + } + + void GetFile(TRemoteFile * AFile, intptr_t Version, TDSTMode DSTMode, TAutoSwitch & Utf, bool SignedTS, bool Complete) + { + DebugAssert(AFile); + SSH_FILEXFER_ATTR_TYPES Flags; + UnicodeString ListingStr; + uint32_t Permissions = 0; + bool ParsingFailed = false; + if (GetType() != SSH_FXP_ATTRS) + { + AFile->SetFileName(GetPathString(Utf)); + if (Version < 4) + { + ListingStr = GetString(Utf); + } + } + Flags = GetCardinal(); + if (Version >= 4) + { + uint8_t FXType = GetByte(); + // -:regular, D:directory, L:symlink, S:special, U:unknown + // O:socket, C:char device, B:block device, F:fifo + + // SSH-2.0-cryptlib returns file type 0 in response to SSH_FXP_LSTAT, + // handle this undefined value as "unknown" + static wchar_t * Types = (wchar_t *)L"U-DLSUOCBF"; + if (FXType > static_cast(wcslen(Types))) + { + throw Exception(FMTLOAD(SFTP_UNKNOWN_FILE_TYPE, static_cast(FXType))); + } + AFile->SetType(Types[FXType]); + } + if (Flags & SSH_FILEXFER_ATTR_SIZE) + { + AFile->SetSize(GetInt64()); + } + // SFTP-6 only + if (Flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) + { + GetInt64(); // skip + } + // SSH-2.0-3.2.0 F-SECURE SSH - Process Software MultiNet + // sets SSH_FILEXFER_ATTR_UIDGID for v4, but does not include the UID/GUID + if ((Flags & SSH_FILEXFER_ATTR_UIDGID) && (Version < 4)) + { + AFile->GetFileOwner().SetID(GetCardinal()); + AFile->GetFileGroup().SetID(GetCardinal()); + } + if (Flags & SSH_FILEXFER_ATTR_OWNERGROUP) + { + DebugAssert(Version >= 4); + AFile->GetFileOwner().SetName(GetString(Utf)); + AFile->GetFileGroup().SetName(GetString(Utf)); + } + if (Flags & SSH_FILEXFER_ATTR_PERMISSIONS) + { + Permissions = GetCardinal(); + } + if (Version < 4) + { + if (Flags & SSH_FILEXFER_ATTR_ACMODTIME) + { + AFile->SetLastAccess(::UnixToDateTime( + SignedTS ? + static_cast(static_cast(GetCardinal())) : + static_cast(GetCardinal()), + DSTMode)); + AFile->SetModification(::UnixToDateTime( + SignedTS ? + static_cast(static_cast(GetCardinal())) : + static_cast(GetCardinal()), + DSTMode)); + } + } + else + { + if (Flags & SSH_FILEXFER_ATTR_ACCESSTIME) + { + AFile->SetLastAccess(::UnixToDateTime(GetInt64(), DSTMode)); + if (Flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) + { + GetCardinal(); // skip access time subseconds + } + } + else + { + AFile->SetLastAccess(Now()); + } + if (Flags & SSH_FILEXFER_ATTR_CREATETIME) + { + GetInt64(); // skip create time + if (Flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) + { + GetCardinal(); // skip create time subseconds + } + } + if (Flags & SSH_FILEXFER_ATTR_MODIFYTIME) + { + AFile->SetModification(::UnixToDateTime(GetInt64(), DSTMode)); + if (Flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) + { + GetCardinal(); // skip modification time subseconds + } + } + else + { + AFile->SetModification(Now()); + } + // SFTP-6 + if (Flags & SSH_FILEXFER_ATTR_CTIME) + { + GetInt64(); // skip attribute modification time + if (Flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) + { + GetCardinal(); // skip attribute modification time subseconds + } + } + } + + if (Flags & SSH_FILEXFER_ATTR_ACL) + { + GetRawByteString(); + } + + if (Flags & SSH_FILEXFER_ATTR_BITS) + { + // while SSH_FILEXFER_ATTR_BITS is defined for SFTP5 only, vandyke 2.3.3 sets it + // for SFTP4 as well + SSH_FILEXFER_ATTR_TYPES Bits = GetCardinal(); + if (Version >= 6) + { + uint32_t BitsValid = GetCardinal(); + Bits = Bits & BitsValid; + } + if (FLAGSET(Bits, SSH_FILEXFER_ATTR_FLAGS_HIDDEN)) + { + AFile->SetIsHidden(true); + } + } + + // skip some SFTP-6 only fields + if (Flags & SSH_FILEXFER_ATTR_TEXT_HINT) + { + GetByte(); + } + if (Flags & SSH_FILEXFER_ATTR_MIME_TYPE) + { + GetAnsiString(); + } + if (Flags & SSH_FILEXFER_ATTR_LINK_COUNT) + { + GetCardinal(); + } + if (Flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) + { + GetPathString(Utf); + } + + if ((Version < 4) && (GetType() != SSH_FXP_ATTRS)) + { + try + { + // update permissions and user/group name + // modification time and filename is ignored + AFile->SetListingStr(ListingStr); + } + catch (...) + { + // ignore any error while parsing listing line, + // SFTP specification do not recommend to parse it + ParsingFailed = true; + } + } + + if (GetType() == SSH_FXP_ATTRS || Version >= 4 || ParsingFailed) + { + wchar_t Type = FILETYPE_DEFAULT; + if (FLAGSET(Flags, SSH_FILEXFER_ATTR_PERMISSIONS)) + { + AFile->GetRights()->SetNumber(static_cast(Permissions & TRights::rfAllSpecials)); + if (FLAGSET(Permissions, TRights::rfDirectory)) + { + Type = FILETYPE_DIRECTORY; + } + } + + if (Version < 4) + { + AFile->SetType(Type); + } + } + + if (Flags & SSH_FILEXFER_ATTR_EXTENDED) + { + uint32_t ExtendedCount = GetCardinal(); + for (uint32_t Index = 0; Index < ExtendedCount; ++Index) + { + GetRawByteString(); // skip extended_type + GetRawByteString(); // skip extended_data + } + } + + if (Complete) + { + AFile->Complete(); + } + } + + uint8_t * GetNextData(uintptr_t Size = 0) + { + if (Size > 0) + { + Need(Size); + } + return FPosition < FLength ? FData + FPosition : nullptr; + } + + void DataConsumed(uint32_t Size) const + { + FPosition += Size; + } + + void DataUpdated(uintptr_t ALength) + { + FPosition = 0; + FLength = ALength; + FType = GetByte(); + if (FType != SSH_FXP_VERSION) + { + FMessageNumber = GetCardinal(); + } + else + { + FMessageNumber = SFTPNoMessageNumber; + } + } + + void LoadFromFile(const UnicodeString & AFileName) + { + std::unique_ptr DumpLines(new TStringList()); + RawByteString Dump; + try__finally + { + DumpLines->LoadFromFile(AFileName); + Dump = RawByteString(AnsiString(DumpLines->GetText())); + } + __finally + { +// delete DumpLines; + }; + + SetCapacity(20 * 1024); + uint8_t Byte[3]; + ::memset(Byte, 0, sizeof(Byte)); + intptr_t Index = 1; + uintptr_t Length = 0; + while (Index < Dump.Length()) + { + char C = Dump[Index]; + if (IsHex(C)) + { + if (Byte[0] == '\0') + { + Byte[0] = C; + } + else + { + Byte[1] = C; + DebugAssert(Length < GetCapacity()); + GetData()[Length] = HexToByte(UnicodeString(reinterpret_cast(Byte))); + Length++; + ::memset(Byte, 0, sizeof(Byte)); + } + } + ++Index; + } + DataUpdated(Length); + } + + UnicodeString Dump() const + { + UnicodeString Result; + for (uintptr_t Index = 0; Index < GetLength(); ++Index) + { + Result += ByteToHex(GetData()[Index]) + L","; + if (((Index + 1) % 25) == 0) + { + Result += L"\n"; + } + } + return Result; + } + + TSFTPPacket & operator = (const TSFTPPacket & Source) + { + SetCapacity(0); + Add(Source.GetData(), Source.GetLength()); + DataUpdated(Source.GetLength()); + FPosition = Source.FPosition; + FReservedBy = Source.FReservedBy; + FCodePage = Source.FCodePage; + return *this; + } + +#if 0 + __property unsigned int Length = { read = FLength }; + __property unsigned int RemainingLength = { read = GetRemainingLength }; + __property unsigned char * Data = { read = FData }; + __property unsigned char * SendData = { read = GetSendData }; + __property unsigned int SendLength = { read = GetSendLength }; + __property unsigned int Capacity = { read = FCapacity, write = SetCapacity }; + __property unsigned char Type = { read = FType }; + __property unsigned char RequestType = { read = GetRequestType }; + __property unsigned int MessageNumber = { read = FMessageNumber, write = FMessageNumber }; + __property TSFTPFileSystem * ReservedBy = { read = FReservedBy, write = FReservedBy }; + __property UnicodeString TypeName = { read = GetTypeName }; +#endif + uintptr_t GetLength() const { return FLength; } + uint8_t * GetData() const { return FData; } + uintptr_t GetCapacity() const { return FCapacity; } + SSH_FXP_TYPES GetType() const { return FType; } + uintptr_t GetMessageNumber() const { return static_cast(FMessageNumber); } + void SetMessageNumber(uint32_t Value) { FMessageNumber = Value; } + TSFTPFileSystem * GetReservedBy() const { return FReservedBy; } + void SetReservedBy(TSFTPFileSystem * Value) { FReservedBy = Value; } + +private: + uint8_t * FData; + uintptr_t FLength; + uintptr_t FCapacity; + mutable uintptr_t FPosition; + SSH_FXP_TYPES FType; + uint32_t FMessageNumber; + TSFTPFileSystem * FReservedBy; + + static uint32_t FMessageCounter; + static const intptr_t FSendPrefixLen = 4; + uintptr_t FCodePage; + + void Init(uintptr_t codePage) + { + FData = nullptr; + FCapacity = 0; + FLength = 0; + FPosition = 0; + FMessageNumber = SFTPNoMessageNumber; + FType = static_cast(-1); + FReservedBy = nullptr; + FCodePage = codePage; + } + + void AssignNumber() + { + // this is not strictly thread-safe, but as it is accessed from multiple + // threads only for multiple connection, it is not problem if two threads get + // the same number + FMessageNumber = (FMessageCounter << 8) + FType; + FMessageCounter++; + } + +public: + uint8_t GetRequestType() const + { + if (FMessageNumber != SFTPNoMessageNumber) + { + return static_cast(FMessageNumber & 0xFF); + } + else + { + DebugAssert(GetType() == SSH_FXP_VERSION); + return SSH_FXP_INIT; + } + } + + void Add(const void * AData, uintptr_t ALength) + { + if (GetLength() + ALength > GetCapacity()) + { + SetCapacity(GetLength() + ALength + SFTP_PACKET_ALLOC_DELTA); + } + memmove(FData + GetLength(), AData, ALength); + FLength += ALength; + } + + void SetCapacity(uintptr_t ACapacity) + { + if (ACapacity != GetCapacity()) + { + FCapacity = ACapacity; + if (FCapacity > 0) + { + uint8_t * NData = static_cast( + nb_malloc(FCapacity + FSendPrefixLen)); + NData += FSendPrefixLen; + if (FData) + { + memmove(NData - FSendPrefixLen, FData - FSendPrefixLen, + (FLength < FCapacity ? FLength : FCapacity) + FSendPrefixLen); + nb_free(FData - FSendPrefixLen); + } + FData = NData; + } + else + { + if (FData) + { + nb_free(FData - FSendPrefixLen); + } + FData = nullptr; + } + if (FLength > FCapacity) + { + FLength = FCapacity; + } + } + } + + UnicodeString GetTypeName() const + { + #define TYPE_CASE(TYPE) case TYPE: return MB_TEXT(#TYPE) + switch (GetType()) + { + TYPE_CASE(SSH_FXP_INIT); + TYPE_CASE(SSH_FXP_VERSION); + TYPE_CASE(SSH_FXP_OPEN); + TYPE_CASE(SSH_FXP_CLOSE); + TYPE_CASE(SSH_FXP_READ); + TYPE_CASE(SSH_FXP_WRITE); + TYPE_CASE(SSH_FXP_LSTAT); + TYPE_CASE(SSH_FXP_FSTAT); + TYPE_CASE(SSH_FXP_SETSTAT); + TYPE_CASE(SSH_FXP_FSETSTAT); + TYPE_CASE(SSH_FXP_OPENDIR); + TYPE_CASE(SSH_FXP_READDIR); + TYPE_CASE(SSH_FXP_REMOVE); + TYPE_CASE(SSH_FXP_MKDIR); + TYPE_CASE(SSH_FXP_RMDIR); + TYPE_CASE(SSH_FXP_REALPATH); + TYPE_CASE(SSH_FXP_STAT); + TYPE_CASE(SSH_FXP_RENAME); + TYPE_CASE(SSH_FXP_READLINK); + TYPE_CASE(SSH_FXP_SYMLINK); + TYPE_CASE(SSH_FXP_LINK); + TYPE_CASE(SSH_FXP_STATUS); + TYPE_CASE(SSH_FXP_HANDLE); + TYPE_CASE(SSH_FXP_DATA); + TYPE_CASE(SSH_FXP_NAME); + TYPE_CASE(SSH_FXP_ATTRS); + TYPE_CASE(SSH_FXP_EXTENDED); + TYPE_CASE(SSH_FXP_EXTENDED_REPLY); + default: + return FORMAT(L"Unknown message (%d)", static_cast(GetType())); + } + } + + uint8_t * GetSendData() const + { + uint8_t * Result = FData - FSendPrefixLen; + // this is not strictly const-object operation + PUT_32BIT(Result, GetLength()); + return Result; + } + + uintptr_t GetSendLength() const + { + return FSendPrefixLen + GetLength(); + } + + uintptr_t GetRemainingLength() const + { + return GetLength() - FPosition; + } + +private: + inline void Need(uintptr_t Size) const + { + if (Size > GetRemainingLength()) + { + throw Exception(FMTLOAD(SFTP_PACKET_ERROR, static_cast(FPosition), static_cast(Size), static_cast(FLength))); + } + } + + uint32_t PeekCardinal() const + { + Need(sizeof(uint32_t)); + uint8_t * cp = FData + FPosition; + uint32_t Result = GET_32BIT(cp); + return Result; + } + + inline UnicodeString GetUtfString(TAutoSwitch & Utf) const + { + DebugAssert(Utf != asOff); + UnicodeString Result; + RawByteString S = GetRawByteString(); + + if (Utf == asAuto) + { + TEncodeType EncodeType = ::DetectUTF8Encoding(S); + if (EncodeType == etANSI) + { + Utf = asOff; + Result = AnsiToString(S); + } + } + + if (Utf != asOff) + { + Result = UTF8ToString(S); + } + + return Result; + } +}; + +uint32_t TSFTPPacket::FMessageCounter = 0; + +class TSFTPQueuePacket : public TSFTPPacket +{ +NB_DISABLE_COPY(TSFTPQueuePacket) +NB_DECLARE_CLASS(TSFTPQueuePacket) +public: + explicit TSFTPQueuePacket(uintptr_t CodePage) : + TSFTPPacket(CodePage), + Token(nullptr) + { + } + + void * Token; +}; + +class TSFTPQueue : public TObject +{ +NB_DISABLE_COPY(TSFTPQueue) +public: + explicit TSFTPQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + FRequests(new TList()), + FResponses(new TList()), + FFileSystem(AFileSystem), + FCodePage(CodePage) + { + DebugAssert(FFileSystem); + } + + virtual ~TSFTPQueue() + { + DebugAssert(FResponses->GetCount() == FRequests->GetCount()); + for (intptr_t Index = 0; Index < FRequests->GetCount(); ++Index) + { + TSFTPQueuePacket * Request = NB_STATIC_DOWNCAST(TSFTPQueuePacket, FRequests->GetItem(Index)); + DebugAssert(Request); + SAFE_DESTROY(Request); + + TSFTPPacket * Response = NB_STATIC_DOWNCAST(TSFTPPacket, FResponses->GetItem(Index)); + DebugAssert(Response); + SAFE_DESTROY(Response); + } + SAFE_DESTROY(FRequests); + SAFE_DESTROY(FResponses); + } + + bool Init() + { + return SendRequests(); + } + + virtual void Dispose() + { + DebugAssert(FFileSystem->FTerminal->GetActive()); + + while (FRequests->GetCount()) + { + DebugAssert(FResponses->GetCount()); + + TSFTPQueuePacket * Request = NB_STATIC_DOWNCAST(TSFTPQueuePacket, FRequests->GetItem(0)); + DebugAssert(Request); + + TSFTPPacket * Response = NB_STATIC_DOWNCAST(TSFTPPacket, FResponses->GetItem(0)); + DebugAssert(Response); + + try + { + ReceiveResponse(Request, Response); + } + catch (Exception & E) + { + if (FFileSystem->FTerminal->GetActive()) + { + FFileSystem->FTerminal->LogEvent("Error while disposing the SFTP queue."); + FFileSystem->FTerminal->GetLog()->AddException(&E); + } + else + { + FFileSystem->FTerminal->LogEvent("Fatal error while disposing the SFTP queue."); + throw; + } + } + + FRequests->Delete(0); + SAFE_DESTROY(Request); + FResponses->Delete(0); + SAFE_DESTROY(Response); + } + } + + void DisposeSafe() + { + if (FFileSystem->FTerminal->GetActive()) + { + Dispose(); + } + } + + bool ReceivePacket(TSFTPPacket * Packet, + SSH_FXP_TYPES ExpectedType = -1, SSH_FX_TYPES AllowStatus = -1, void ** Token = nullptr, bool TryOnly = false) + { + DebugAssert(FRequests->GetCount()); + bool Result = false; + std::unique_ptr Request(NB_STATIC_DOWNCAST(TSFTPQueuePacket, FRequests->GetItem(0))); + try__finally + { + FRequests->Delete(0); + DebugAssert(Request.get()); + if (Token != nullptr) + { + *Token = Request->Token; + } + + std::unique_ptr Response(NB_STATIC_DOWNCAST(TSFTPPacket, FResponses->GetItem(0))); + FResponses->Delete(0); + DebugAssert(Response.get()); + + ReceiveResponse(Request.get(), Response.get(), ExpectedType, AllowStatus, TryOnly); + + if ((Response->GetCapacity() == 0) && DebugAlwaysTrue(TryOnly)) + { + FRequests->Insert(0, Request.get()); + Request.release(); + FResponses->Insert(0, Response.get()); + Response.release(); + Result = true; + } + else + { + if (Packet) + { + *Packet = *Response.get(); + } + + Result = !End(Response.get()); + if (Result) + { + SendRequests(); + } + } + } + __finally + { +// delete Request; +// delete Response; + }; + + return Result; + } + + bool Next(SSH_FXP_TYPES ExpectedType = -1, SSH_FX_TYPES AllowStatus = -1) + { + return ReceivePacket(nullptr, ExpectedType, AllowStatus); + } + +protected: + TList * FRequests; + TList * FResponses; + TSFTPFileSystem * FFileSystem; + uintptr_t FCodePage; + + virtual bool InitRequest(TSFTPQueuePacket * Request) = 0; + + virtual bool End(TSFTPPacket * Response) = 0; + + virtual void SendPacket(TSFTPQueuePacket * Packet) + { + FFileSystem->SendPacket(Packet); + } + + virtual void ReceiveResponse( + const TSFTPPacket * Packet, TSFTPPacket * Response, SSH_FXP_TYPES ExpectedType = -1, + SSH_FX_TYPES AllowStatus = -1, bool TryOnly = false) + { + FFileSystem->ReceiveResponse(Packet, Response, ExpectedType, AllowStatus, TryOnly); + } + + // sends as many requests as allowed by implementation + virtual bool SendRequests() = 0; + + virtual bool SendRequest() + { + std::unique_ptr Request(new TSFTPQueuePacket(FCodePage)); + try__catch + { + if (!InitRequest(Request.get())) + { + Request.reset(); + } + } + /*catch(...) + { + delete Request; + throw; + }*/ + + if (Request.get() != nullptr) + { + TSFTPPacket * Response = new TSFTPPacket(FCodePage); + FRequests->Add(Request.get()); + FResponses->Add(Response); + + // make sure the response is reserved before actually ending the message + // as we may receive response asynchronously before SendPacket finishes + FFileSystem->ReserveResponse(Request.get(), Response); + SendPacket(Request.release()); + return true; + } + + return false; + } +}; + +class TSFTPFixedLenQueue : public TSFTPQueue +{ +public: + explicit TSFTPFixedLenQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + TSFTPQueue(AFileSystem, CodePage), + FMissedRequests(0) + { + } + virtual ~TSFTPFixedLenQueue() {} + + bool Init(intptr_t QueueLen) + { + FMissedRequests = QueueLen - 1; + return TSFTPQueue::Init(); + } + +protected: + intptr_t FMissedRequests; + + // sends as many requests as allowed by implementation + virtual bool SendRequests() + { + bool Result = false; + FMissedRequests++; + while ((FMissedRequests > 0) && SendRequest()) + { + Result = true; + FMissedRequests--; + } + return Result; + } +}; + +class TSFTPAsynchronousQueue : public TSFTPQueue +{ +public: + explicit TSFTPAsynchronousQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : TSFTPQueue(AFileSystem, CodePage) + { + FFileSystem->FSecureShell->RegisterReceiveHandler(MAKE_CALLBACK(TSFTPAsynchronousQueue::ReceiveHandler, this)); + FReceiveHandlerRegistered = true; + } + + virtual ~TSFTPAsynchronousQueue() + { + UnregisterReceiveHandler(); + } + + virtual void Dispose() + { + // we do not want to receive asynchronous notifications anymore, + // while waiting synchronously for pending responses + UnregisterReceiveHandler(); + TSFTPQueue::Dispose(); + } + + bool Continue() + { + return SendRequest(); + } + +protected: + + // event handler for incoming data + void ReceiveHandler(TObject * /*Sender*/) + { + try + { + while (// optimization only as we call ReceivePacket with TryOnly anyway + FFileSystem->PeekPacket() && + ReceivePacketAsynchronously()) + { + // loop + } + } + catch (Exception & E) // prevent crash when server unexpectedly closes connection + { + DEBUG_PRINTF("ReceiveHandler: %s\n", E.Message.c_str()); + DebugUsedParam(E); + } + } + + virtual bool ReceivePacketAsynchronously() = 0; + + // sends as many requests as allowed by implementation + virtual bool SendRequests() + { + // noop + return true; + } + + void UnregisterReceiveHandler() + { + if (FReceiveHandlerRegistered) + { + FReceiveHandlerRegistered = false; + FFileSystem->FSecureShell->UnregisterReceiveHandler(MAKE_CALLBACK(TSFTPAsynchronousQueue::ReceiveHandler, this)); + } + } + +private: + bool FReceiveHandlerRegistered; +}; + +class TSFTPDownloadQueue : public TSFTPFixedLenQueue +{ +NB_DISABLE_COPY(TSFTPDownloadQueue) +public: + explicit TSFTPDownloadQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + TSFTPFixedLenQueue(AFileSystem, CodePage), + OperationProgress(nullptr), + FTransfered(0) + { + } + virtual ~TSFTPDownloadQueue() {} + + bool Init(intptr_t QueueLen, const RawByteString & AHandle, int64_t ATransfered, + TFileOperationProgressType * AOperationProgress) + { + FHandle = AHandle; + FTransfered = ATransfered; + OperationProgress = AOperationProgress; + + return TSFTPFixedLenQueue::Init(QueueLen); + } + + void InitFillGapRequest(int64_t Offset, uint32_t MissingLen, + TSFTPPacket * Packet) + { + InitRequest(Packet, Offset, MissingLen); + } + + bool ReceivePacket(TSFTPPacket * Packet, uintptr_t & BlockSize) + { + void * Token; + bool Result = TSFTPFixedLenQueue::ReceivePacket(Packet, SSH_FXP_DATA, asEOF, &Token); + BlockSize = reinterpret_cast(Token); + return Result; + } + +protected: + virtual bool InitRequest(TSFTPQueuePacket * Request) + { + uint32_t BlockSize = FFileSystem->DownloadBlockSize(OperationProgress); + InitRequest(Request, FTransfered, BlockSize); + Request->Token = ToPtr(BlockSize); + FTransfered += BlockSize; + return true; + } + + void InitRequest(TSFTPPacket * Request, int64_t Offset, + uint32_t Size) + { + Request->ChangeType(SSH_FXP_READ); + Request->AddString(FHandle); + Request->AddInt64(Offset); + Request->AddCardinal(Size); + } + + virtual bool End(TSFTPPacket * Response) + { + return (Response->GetType() != SSH_FXP_DATA); + } + +private: + TFileOperationProgressType * OperationProgress; + int64_t FTransfered; + RawByteString FHandle; +}; + +class TSFTPUploadQueue : public TSFTPAsynchronousQueue +{ +NB_DISABLE_COPY(TSFTPUploadQueue) +public: + explicit TSFTPUploadQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + TSFTPAsynchronousQueue(AFileSystem, CodePage), + FStream(nullptr), + OperationProgress(nullptr), + FLastBlockSize(0), + FEnd(false), + FTransfered(0), + FConvertToken(false), + FConvertParams(0), + FTerminal(nullptr) + { + } + + virtual ~TSFTPUploadQueue() + { + SAFE_DESTROY(FStream); + } + + bool Init(const UnicodeString & AFileName, + HANDLE AFile, TFileOperationProgressType * AOperationProgress, + const RawByteString & AHandle, int64_t ATransfered, + intptr_t ConvertParams) + { + FFileName = AFileName; + FStream = new TSafeHandleStream(AFile); + OperationProgress = AOperationProgress; + FHandle = AHandle; + FTransfered = ATransfered; + FConvertParams = ConvertParams; + + return TSFTPAsynchronousQueue::Init(); + } + +protected: + virtual bool InitRequest(TSFTPQueuePacket * Request) + { + FTerminal = FFileSystem->FTerminal; + // Buffer for one block of data + TFileBuffer BlockBuf; + + intptr_t BlockSize = GetBlockSize(); + bool Result = (BlockSize > 0); + + if (Result) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(READ_ERROR, FFileName.c_str()), "", + [&]() + { + BlockBuf.LoadStream(FStream, BlockSize, false); + }); + + FEnd = (BlockBuf.GetSize() == 0); + Result = !FEnd; + if (Result) + { + OperationProgress->AddLocallyUsed(BlockBuf.GetSize()); + + // We do ASCII transfer: convert EOL of current block + if (OperationProgress->AsciiTransfer) + { + int64_t PrevBufSize = BlockBuf.GetSize(); + BlockBuf.Convert(FTerminal->GetConfiguration()->GetLocalEOLType(), + FFileSystem->GetEOL(), FConvertParams, FConvertToken); + // update transfer size with difference raised from EOL conversion + OperationProgress->ChangeTransferSize(OperationProgress->TransferSize - + PrevBufSize + BlockBuf.GetSize()); + } + + if (FFileSystem->FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1) + { + FFileSystem->FTerminal->LogEvent(FORMAT(L"Write request offset: %d, len: %d", + int(FTransfered), int(BlockBuf.GetSize()))); + } + + Request->ChangeType(SSH_FXP_WRITE); + Request->AddString(FHandle); + Request->AddInt64(FTransfered); + Request->AddData(BlockBuf.GetData(), static_cast(BlockBuf.GetSize())); + FLastBlockSize = static_cast(BlockBuf.GetSize()); + + FTransfered += BlockBuf.GetSize(); + } + } + + FTerminal = nullptr; + return Result; + } + + virtual void SendPacket(TSFTPQueuePacket * Packet) + { + TSFTPAsynchronousQueue::SendPacket(Packet); + OperationProgress->AddTransfered(FLastBlockSize); + } + + virtual void ReceiveResponse( + const TSFTPPacket * Packet, TSFTPPacket * Response, SSH_FXP_TYPES ExpectedType = -1, + SSH_FX_TYPES AllowStatus = -1, bool TryOnly = false) + { + TSFTPAsynchronousQueue::ReceiveResponse(Packet, Response, ExpectedType, AllowStatus, TryOnly); + if (Response->GetCapacity() > 0) + { + // particularly when uploading a file that completely fits into send buffer + // over slow line, we may end up seemingly completing the transfer immediately + // but hanging the application for a long time waiting for responses + // (common is that the progress window would not even manage to draw itself, + // showing that upload finished, before the application "hangs") + FFileSystem->Progress(OperationProgress); + } + else + { + DebugAssert(TryOnly); + } + } + + virtual bool ReceivePacketAsynchronously() + { + // do not read response to close request + bool Result = (FRequests->GetCount() > 0); + if (Result) + { + // Try only: We cannot read from the socket here as we are already called + // from TSecureShell::HandleNetworkEvents as it would cause a recursion + // that would potentially make PuTTY code process the SSH packets in wrong order. + ReceivePacket(nullptr, SSH_FXP_STATUS, -1, nullptr, true); + + } + return Result; + } + + inline intptr_t GetBlockSize() + { + return FFileSystem->UploadBlockSize(FHandle, OperationProgress); + } + + virtual bool End(TSFTPPacket * /*Response*/) + { + return FEnd; + } + +private: + TStream * FStream; + TFileOperationProgressType * OperationProgress; + UnicodeString FFileName; + uint32_t FLastBlockSize; + bool FEnd; + int64_t FTransfered; + RawByteString FHandle; + bool FConvertToken; + intptr_t FConvertParams; + TTerminal * FTerminal; +}; + +class TSFTPLoadFilesPropertiesQueue : public TSFTPFixedLenQueue +{ +NB_DISABLE_COPY(TSFTPLoadFilesPropertiesQueue) +public: + explicit TSFTPLoadFilesPropertiesQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + TSFTPFixedLenQueue(AFileSystem, CodePage), + FFileList(nullptr), + FIndex(0) + { + } + virtual ~TSFTPLoadFilesPropertiesQueue() {} + + bool Init(uintptr_t QueueLen, TStrings * AFileList) + { + FFileList = AFileList; + + return TSFTPFixedLenQueue::Init(QueueLen); + } + + bool ReceivePacket(TSFTPPacket * Packet, TRemoteFile *& File) + { + void * Token; + bool Result = TSFTPFixedLenQueue::ReceivePacket(Packet, SSH_FXP_ATTRS, asAll, &Token); + File = NB_STATIC_DOWNCAST(TRemoteFile, Token); + return Result; + } + +protected: + virtual bool InitRequest(TSFTPQueuePacket * Request) + { + bool Result = false; + while (!Result && (FIndex < FFileList->GetCount())) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, FFileList->GetObj(FIndex)); + ++FIndex; + + bool MissingRights = + (FFileSystem->FSupport->Loaded && + FLAGSET(FFileSystem->FSupport->AttributeMask, SSH_FILEXFER_ATTR_PERMISSIONS) && + File->GetRights()->GetUnknown()); + bool MissingOwnerGroup = + (FFileSystem->FSecureShell->GetSshImplementation() == sshiBitvise) || + ((FFileSystem->FSupport->Loaded && + FLAGSET(FFileSystem->FSupport->AttributeMask, SSH_FILEXFER_ATTR_OWNERGROUP) && + !File->GetFileOwner().GetIsSet()) || !File->GetFileGroup().GetIsSet()); + + Result = (MissingRights || MissingOwnerGroup); + if (Result) + { + Request->ChangeType(SSH_FXP_LSTAT); + Request->AddPathString(FFileSystem->LocalCanonify(File->GetFileName()), + FFileSystem->FUtfStrings); + if (FFileSystem->FVersion >= 4) + { + Request->AddCardinal( + FLAGMASK(MissingRights, SSH_FILEXFER_ATTR_PERMISSIONS) | + FLAGMASK(MissingOwnerGroup, SSH_FILEXFER_ATTR_OWNERGROUP)); + } + Request->Token = File; + } + } + + return Result; + } + + virtual bool SendRequest() + { + bool Result = + (FIndex < FFileList->GetCount()) && + TSFTPFixedLenQueue::SendRequest(); + return Result; + } + + virtual bool End(TSFTPPacket * /*Response*/) + { + return (FRequests->GetCount() == 0); + } + +private: + TStrings * FFileList; + intptr_t FIndex; +}; + +class TSFTPCalculateFilesChecksumQueue : public TSFTPFixedLenQueue +{ +NB_DISABLE_COPY(TSFTPCalculateFilesChecksumQueue) +public: + explicit TSFTPCalculateFilesChecksumQueue(TSFTPFileSystem * AFileSystem, uintptr_t CodePage) : + TSFTPFixedLenQueue(AFileSystem, CodePage), + FFileList(nullptr), + FIndex(0) + { + } + virtual ~TSFTPCalculateFilesChecksumQueue() {} + + bool Init(intptr_t QueueLen, const UnicodeString & Alg, TStrings * AFileList) + { + FAlg = Alg; + FFileList = AFileList; + + return TSFTPFixedLenQueue::Init(QueueLen); + } + + bool ReceivePacket(TSFTPPacket * Packet, TRemoteFile *& File) + { + void * Token = nullptr; + bool Result = false; + try__finally + { + SCOPE_EXIT + { + File = NB_STATIC_DOWNCAST(TRemoteFile, Token); + }; + Result = TSFTPFixedLenQueue::ReceivePacket(Packet, SSH_FXP_EXTENDED_REPLY, asNo, &Token); + } + __finally + { + File = NB_STATIC_DOWNCAST(TRemoteFile, Token); + }; + return Result; + } + +protected: + virtual bool InitRequest(TSFTPQueuePacket * Request) + { + bool Result = false; + while (!Result && (FIndex < FFileList->GetCount())) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, FFileList->GetObj(FIndex)); + DebugAssert(File != nullptr); + ++FIndex; + + Result = !File->GetIsDirectory(); + if (Result) + { + DebugAssert(!File->GetIsParentDirectory() && !File->GetIsThisDirectory()); + + Request->ChangeType(SSH_FXP_EXTENDED); + Request->AddString(SFTP_EXT_CHECK_FILE_NAME); + Request->AddPathString(FFileSystem->LocalCanonify(File->GetFullFileName()), + FFileSystem->FUtfStrings); + Request->AddString(FAlg); + Request->AddInt64(0); // offset + Request->AddInt64(0); // length (0 = till end) + Request->AddCardinal(0); // block size (0 = no blocks or "one block") + + Request->Token = File; + } + } + + return Result; + } + + virtual bool SendRequest() + { + bool Result = + (FIndex < FFileList->GetCount()) && + TSFTPFixedLenQueue::SendRequest(); + return Result; + } + + virtual bool End(TSFTPPacket * /*Response*/) + { + return (FRequests->GetCount() == 0); + } + +private: + UnicodeString FAlg; + TStrings * FFileList; + intptr_t FIndex; +}; + +class TSFTPBusy : public TObject +{ +NB_DISABLE_COPY(TSFTPBusy) +public: + explicit TSFTPBusy(TSFTPFileSystem * FileSystem) : + FFileSystem(FileSystem) + { + DebugAssert(FFileSystem != nullptr); + FFileSystem->BusyStart(); + } + + ~TSFTPBusy() + { + FFileSystem->BusyEnd(); + } + +private: + TSFTPFileSystem * FFileSystem; +}; + +//=========================================================================== +//=========================================================================== +TSFTPFileSystem::TSFTPFileSystem(TTerminal * ATerminal) : + TCustomFileSystem(ATerminal), + FSecureShell(nullptr), + FFileSystemInfoValid(false), + FVersion(0), + FPacketReservations(nullptr), + FPreviousLoggedPacket(0), + FNotLoggedPackets(0), + FBusy(0), + FBusyToken(nullptr), + FAvoidBusy(false), + FExtensions(nullptr), + FSupport(nullptr), + FUtfStrings(asAuto), + FUtfDisablingAnnounced(false), + FSignedTS(false), + FFixedPaths(nullptr), + FMaxPacketSize(0), + FSupportsStatVfsV2(false), + FSupportsHardlink(false) +{ + FCodePage = GetSessionData()->GetCodePageAsNumber(); +} + +void TSFTPFileSystem::Init(void * Data) +{ + FSecureShell = NB_STATIC_DOWNCAST(TSecureShell, Data); + DebugAssert(FSecureShell); + FFileSystemInfoValid = false; + FVersion = NPOS; + FPacketReservations = new TList(); + FPacketNumbers.clear(); + FPreviousLoggedPacket = 0; + FNotLoggedPackets = 0; + FBusy = 0; + FAvoidBusy = false; + FUtfStrings = asOff; + FUtfDisablingAnnounced = true; + FSignedTS = false; + FSupport = new TSFTPSupport(); + FExtensions = new TStringList(); + FFixedPaths = nullptr; + FFileSystemInfoValid = false; + + FChecksumAlgs.reset(new TStringList()); + FChecksumSftpAlgs.reset(new TStringList()); + // List as defined by draft-ietf-secsh-filexfer-extensions-00 + // MD5 moved to the back + RegisterChecksumAlg(Sha1ChecksumAlg, L"sha1"); + RegisterChecksumAlg(Sha224ChecksumAlg, L"sha224"); + RegisterChecksumAlg(Sha256ChecksumAlg, L"sha256"); + RegisterChecksumAlg(Sha384ChecksumAlg, L"sha384"); + RegisterChecksumAlg(Sha512ChecksumAlg, L"sha512"); + RegisterChecksumAlg(Md5ChecksumAlg, L"md5"); + RegisterChecksumAlg(Crc32ChecksumAlg, L"crc32"); +} + +TSFTPFileSystem::~TSFTPFileSystem() +{ + SAFE_DESTROY(FSupport); + ResetConnection(); + SAFE_DESTROY(FPacketReservations); + SAFE_DESTROY(FExtensions); + SAFE_DESTROY(FFixedPaths); + SAFE_DESTROY(FSecureShell); +} + +void TSFTPFileSystem::Open() +{ + // this is used for reconnects only + ResetConnection(); + FSecureShell->Open(); +} + +void TSFTPFileSystem::Close() +{ + FSecureShell->Close(); +} + +bool TSFTPFileSystem::GetActive() const +{ + return FSecureShell->GetActive(); +} + +void TSFTPFileSystem::CollectUsage() +{ + FSecureShell->CollectUsage(); + + UnicodeString VersionCounter; + switch (FVersion) + { + case 0: + VersionCounter = L"OpenedSessionsSFTP0"; + break; + case 1: + VersionCounter = L"OpenedSessionsSFTP1"; + break; + case 2: + VersionCounter = L"OpenedSessionsSFTP2"; + break; + case 3: + VersionCounter = L"OpenedSessionsSFTP3"; + break; + case 4: + VersionCounter = L"OpenedSessionsSFTP4"; + break; + case 5: + VersionCounter = L"OpenedSessionsSFTP5"; + break; + case 6: + VersionCounter = L"OpenedSessionsSFTP6"; + break; + default: + DebugFail(); + } +// FTerminal->Configuration->Usage->Inc(VersionCounter); +} + +const TSessionInfo & TSFTPFileSystem::GetSessionInfo() const +{ + return FSecureShell->GetSessionInfo(); +} + +const TFileSystemInfo & TSFTPFileSystem::GetFileSystemInfo(bool /*Retrieve*/) +{ + if (!FFileSystemInfoValid) + { + FFileSystemInfo.AdditionalInfo.Clear(); + + if (!IsCapable(fcRename)) + { + FFileSystemInfo.AdditionalInfo += LoadStr(FS_RENAME_NOT_SUPPORTED) + L"\r\n\r\n"; + } + + if (FExtensions->GetCount() > 0) + { + FFileSystemInfo.AdditionalInfo += LoadStr(SFTP_EXTENSION_INFO) + L"\r\n"; + for (intptr_t Index = 0; Index < FExtensions->GetCount(); ++Index) + { + UnicodeString Name = FExtensions->GetName(Index); + UnicodeString Value = FExtensions->GetValue(Name); + UnicodeString Line; + if (Value.IsEmpty()) + { + Line = Name; + } + else + { + Line = FORMAT(L"%s=%s", Name.c_str(), DisplayableStr(Value).c_str()); + } + FFileSystemInfo.AdditionalInfo += FORMAT(L" %s\r\n", Line.c_str()); + } + } + else + { + FFileSystemInfo.AdditionalInfo += LoadStr(SFTP_NO_EXTENSION_INFO) + L"\r\n"; + } + + FFileSystemInfo.ProtocolBaseName = L"SFTP"; + FFileSystemInfo.ProtocolName = FMTLOAD(SFTP_PROTOCOL_NAME2, FVersion); + FTerminal->SaveCapabilities(FFileSystemInfo); + + FFileSystemInfoValid = true; + } + + return FFileSystemInfo; +} + +bool TSFTPFileSystem::TemporaryTransferFile(const UnicodeString & AFileName) +{ + return ::AnsiSameText(base::UnixExtractFileExt(AFileName), PARTIAL_EXT); +} + +bool TSFTPFileSystem::GetStoredCredentialsTried() const +{ + return FSecureShell->GetStoredCredentialsTried(); +} + +UnicodeString TSFTPFileSystem::FSGetUserName() const +{ + return FSecureShell->ShellGetUserName(); +} + +void TSFTPFileSystem::Idle() +{ + // Keep session alive + if ((GetSessionData()->GetPingType() != ptOff) && + ((Now() - FSecureShell->GetLastDataSent()) > GetSessionData()->GetPingIntervalDT())) + { + if ((GetSessionData()->GetPingType() == ptDummyCommand) && + FSecureShell->GetReady()) + { + FTerminal->LogEvent("Sending dummy command to keep session alive."); + TSFTPPacket Packet(SSH_FXP_REALPATH, FCodePage); + Packet.AddPathString(ROOTDIRECTORY, FUtfStrings); + SendPacketAndReceiveResponse(&Packet, &Packet); + } + else + { + FSecureShell->KeepAlive(); + } + } + + FSecureShell->Idle(); +} + +void TSFTPFileSystem::ResetConnection() +{ + // there must be no valid packet reservation at the end + for (intptr_t Index = 0; Index < FPacketReservations->GetCount(); ++Index) + { + DebugAssert(FPacketReservations->GetItem(Index) == nullptr); + TSFTPPacket * Item = NB_STATIC_DOWNCAST(TSFTPPacket, FPacketReservations->GetItem(Index)); + SAFE_DESTROY(Item); + } + FPacketReservations->Clear(); + FPacketNumbers.clear(); +} + +bool TSFTPFileSystem::IsCapable(intptr_t Capability) const +{ + DebugAssert(FTerminal); + switch (Capability) + { + case fcAnyCommand: + case fcShellAnyCommand: + return false; + + case fcNewerOnlyUpload: + case fcTimestampChanging: + case fcIgnorePermErrors: + case fcPreservingTimestampUpload: + case fcSecondaryShell: + case fcRemoveCtrlZUpload: + case fcRemoveBOMUpload: + case fcMoveToQueue: + case fcPreservingTimestampDirs: + case fcResumeSupport: + return true; + + case fcRename: + case fcRemoteMove: + return (FVersion >= 2); + + case fcSymbolicLink: + case fcResolveSymlink: + return (FVersion >= 3); + + case fcModeChanging: + case fcModeChangingUpload: + return !FSupport->Loaded || + FLAGSET(FSupport->AttributeMask, SSH_FILEXFER_ATTR_PERMISSIONS); + + case fcGroupOwnerChangingByID: + return (FVersion <= 3); + + case fcOwnerChanging: + case fcGroupChanging: + return + (FVersion <= 3) || + ((FVersion >= 4) && + (!FSupport->Loaded || + FLAGSET(FSupport->AttributeMask, SSH_FILEXFER_ATTR_OWNERGROUP))); + + case fcNativeTextMode: + return (FVersion >= 4); + + case fcTextMode: + return (FVersion >= 4) || + strcmp(GetEOL(), EOLToStr(FTerminal->GetConfiguration()->GetLocalEOLType())) != 0; + + case fcUserGroupListing: + return SupportsExtension(SFTP_EXT_OWNER_GROUP); + + case fcLoadingAdditionalProperties: + // We allow loading properties only, if "supported" extension is supported and + // the server supports "permissions" and/or "owner/group" attributes + // (no other attributes are loaded). + // This is here only because of VShell + // (it supports owner/group, but does not include them into response to + // SSH_FXP_READDIR) + // and Bitwise (the same as VShell, but it does not even bother to provide "supported" extension until 6.21) + // No other use is known. + return + (FSupport->Loaded && + ((FSupport->AttributeMask & + (SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_OWNERGROUP)) != 0)) || + (FSecureShell->GetSshImplementation() == sshiBitvise); + + case fcCheckingSpaceAvailable: + return + // extension announced in extension list of by + // SFTP_EXT_SUPPORTED/SFTP_EXT_SUPPORTED2 extension + // (SFTP version 5 and newer only) + SupportsExtension(SFTP_EXT_SPACE_AVAILABLE) || + // extension announced by proprietary SFTP_EXT_STATVFS extension + FSupportsStatVfsV2 || + // Bitwise (until 6.21) fails to report it's supported extensions. + (FSecureShell->GetSshImplementation() == sshiBitvise); + + case fcCalculatingChecksum: + return + // Specification says that "check-file" should be announced, + // yet Vandyke VShell (as of 4.0.3) announce "check-file-name" + // https://forums.vandyke.com/showthread.php?t=11597 + SupportsExtension(SFTP_EXT_CHECK_FILE) || + SupportsExtension(SFTP_EXT_CHECK_FILE_NAME) || + // see above + (FSecureShell->GetSshImplementation() == sshiBitvise); + + case fcRemoteCopy: + return + SupportsExtension(SFTP_EXT_COPY_FILE) || + // see above + (FSecureShell->GetSshImplementation() == sshiBitvise); + + case fcHardLink: + return + (FVersion >= 6) || + FSupportsHardlink; + + case fcLocking: + return false; + + default: + DebugFail(); + return false; + } +} + +bool TSFTPFileSystem::SupportsExtension(const UnicodeString & Extension) const +{ + return FSupport->Loaded && (FSupport->Extensions->IndexOf(Extension.c_str()) >= 0); +} + +inline void TSFTPFileSystem::BusyStart() +{ + if (FBusy == 0 && FTerminal->GetUseBusyCursor() && !FAvoidBusy) + { + FBusyToken = ::BusyStart(); + } + FBusy++; + DebugAssert(FBusy < 10); +} + +inline void TSFTPFileSystem::BusyEnd() +{ + DebugAssert(FBusy > 0); + FBusy--; + if (FBusy == 0 && FTerminal->GetUseBusyCursor() && !FAvoidBusy) + { + ::BusyEnd(FBusyToken); + FBusyToken = nullptr; + } +} + +uint32_t TSFTPFileSystem::TransferBlockSize(uint32_t Overhead, + TFileOperationProgressType * OperationProgress, + uint32_t MinPacketSize, + uint32_t MaxPacketSize) +{ + const uint32_t minPacketSize = MinPacketSize ? MinPacketSize : 32 * 1024; + + // size + message number + type + const uint32_t SFTPPacketOverhead = 4 + 4 + 1; + uint32_t AMinPacketSize = FSecureShell->MinPacketSize(); + (void)AMinPacketSize; + uint32_t AMaxPacketSize = FSecureShell->MaxPacketSize(); + bool MaxPacketSizeValid = (AMaxPacketSize > 0); + uint32_t Result = static_cast(OperationProgress->CPS()); + + if ((MaxPacketSize > 0) && + ((MaxPacketSize < AMaxPacketSize) || !MaxPacketSizeValid)) + { + AMaxPacketSize = MaxPacketSize; + MaxPacketSizeValid = true; + } + + if ((FMaxPacketSize > 0) && + ((FMaxPacketSize < AMaxPacketSize) || !MaxPacketSizeValid)) + { + AMaxPacketSize = FMaxPacketSize; + MaxPacketSizeValid = true; + } + + if (Result == 0) + { + Result = static_cast(OperationProgress->StaticBlockSize()); + } + + if (Result < minPacketSize) + { + Result = minPacketSize; + } + + if (MaxPacketSizeValid) + { + Overhead += SFTPPacketOverhead; + if (AMaxPacketSize < Overhead) + { + // do not send another request + // (generally should happen only if upload buffer is full) + Result = 0; + } + else + { + AMaxPacketSize -= Overhead; + if (Result > AMaxPacketSize) + { + Result = AMaxPacketSize; + } + } + } + + Result = static_cast(OperationProgress->AdjustToCPSLimit(Result)); + + return Result; +} + +uint32_t TSFTPFileSystem::UploadBlockSize(const RawByteString & Handle, + TFileOperationProgressType * OperationProgress) +{ + // handle length + offset + data size + const uintptr_t UploadPacketOverhead = + sizeof(uint32_t) + sizeof(int64_t) + sizeof(uint32_t); + return TransferBlockSize(static_cast(UploadPacketOverhead + Handle.Length()), OperationProgress, + static_cast(GetSessionData()->GetSFTPMinPacketSize()), + static_cast(GetSessionData()->GetSFTPMaxPacketSize())); +} + +uint32_t TSFTPFileSystem::DownloadBlockSize( + TFileOperationProgressType * OperationProgress) +{ + uint32_t Result = TransferBlockSize(sizeof(uint32_t), OperationProgress, + static_cast(GetSessionData()->GetSFTPMinPacketSize()), + static_cast(GetSessionData()->GetSFTPMaxPacketSize())); + if (FSupport->Loaded && (FSupport->MaxReadSize > 0) && + (Result > FSupport->MaxReadSize)) + { + Result = FSupport->MaxReadSize; + } + return Result; +} + +void TSFTPFileSystem::Progress(TFileOperationProgressType * OperationProgress) +{ + FTerminal->Progress(OperationProgress); +} + +void TSFTPFileSystem::SendPacket(const TSFTPPacket * Packet) +{ + // putting here for a lack of better place + if (!FUtfDisablingAnnounced && (FUtfStrings == asOff)) + { + FTerminal->LogEvent("Strings received in non-UTF-8 encoding in a previous packet, will not use UTF-8 anymore"); + FUtfDisablingAnnounced = true; + } + + BusyStart(); + try__finally + { + SCOPE_EXIT + { + this->BusyEnd(); + }; + if (FTerminal->GetLog()->GetLogging()) + { + if ((FPreviousLoggedPacket != SSH_FXP_READ && + FPreviousLoggedPacket != SSH_FXP_WRITE) || + (Packet->GetType() != FPreviousLoggedPacket) || + (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1)) + { + if (FNotLoggedPackets) + { + FTerminal->LogEvent(FORMAT(L"%d skipped SSH_FXP_WRITE, SSH_FXP_READ, SSH_FXP_DATA and SSH_FXP_STATUS packets.", + FNotLoggedPackets)); + FNotLoggedPackets = 0; + } + FTerminal->GetLog()->Add(llInput, FORMAT(L"Type: %s, Size: %d, Number: %d", + Packet->GetTypeName().c_str(), + static_cast(Packet->GetLength()), + static_cast(Packet->GetMessageNumber()))); +#if 0 + if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 2) + { + FTerminal->GetLog()->Add(llInput, Packet->Dump()); + } +#endif + FPreviousLoggedPacket = Packet->GetType(); + } + else + { + FNotLoggedPackets++; + } + } + FSecureShell->Send(Packet->GetSendData(), Packet->GetSendLength()); + } + __finally + { + this->BusyEnd(); + }; +} + +SSH_FX_TYPES TSFTPFileSystem::GotStatusPacket(TSFTPPacket * Packet, + SSH_FX_TYPES AllowStatus) +{ + SSH_FX_TYPES Code = Packet->GetCardinal(); + + static intptr_t Messages[] = + { + SFTP_STATUS_OK, + SFTP_STATUS_EOF, + SFTP_STATUS_NO_SUCH_FILE, + SFTP_STATUS_PERMISSION_DENIED, + SFTP_STATUS_FAILURE, + SFTP_STATUS_BAD_MESSAGE, + SFTP_STATUS_NO_CONNECTION, + SFTP_STATUS_CONNECTION_LOST, + SFTP_STATUS_OP_UNSUPPORTED, + SFTP_STATUS_INVALID_HANDLE, + SFTP_STATUS_NO_SUCH_PATH, + SFTP_STATUS_FILE_ALREADY_EXISTS, + SFTP_STATUS_WRITE_PROTECT, + SFTP_STATUS_NO_MEDIA, + SFTP_STATUS_NO_SPACE_ON_FILESYSTEM, + SFTP_STATUS_QUOTA_EXCEEDED, + SFTP_STATUS_UNKNOWN_PRINCIPAL, + SFTP_STATUS_LOCK_CONFLICT, + SFTP_STATUS_DIR_NOT_EMPTY, + SFTP_STATUS_NOT_A_DIRECTORY, + SFTP_STATUS_INVALID_FILENAME, + SFTP_STATUS_LINK_LOOP, + SFTP_STATUS_CANNOT_DELETE, + SFTP_STATUS_INVALID_PARAMETER, + SFTP_STATUS_FILE_IS_A_DIRECTORY, + SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT, + SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED, + SFTP_STATUS_DELETE_PENDING, + SFTP_STATUS_FILE_CORRUPT, + SFTP_STATUS_OWNER_INVALID, + SFTP_STATUS_GROUP_INVALID, + SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK + }; + + if ((AllowStatus & (0x01LL << Code)) == 0) + { + intptr_t Message; + if (Code >= _countof(Messages)) + { + Message = SFTP_STATUS_UNKNOWN; + } + else + { + Message = Messages[Code]; + } + UnicodeString MessageStr = LoadStr(Message); + UnicodeString ServerMessage; + UnicodeString LanguageTag; + if ((FVersion >= 3) || + // if version is not decided yet (i.e. this is status response + // to the init request), go on, only if there are any more data + ((FVersion < 0) && (Packet->GetRemainingLength() > 0))) + { + if (Packet->GetRemainingLength() > 0) + { + // message is in UTF only since SFTP specification 01 (specification 00 + // is also version 3) + // (in other words, always use UTF unless server is known to be buggy) + ServerMessage = Packet->GetString(FUtfStrings); + } + // SSH-2.0-Maverick_SSHD and SSH-2.0-CIGNA SFTP Server Ready! omit the language tag + // and I believe I've seen one more server doing the same. + if (Packet->GetRemainingLength() > 0) + { + LanguageTag = Packet->GetAnsiString(); + if ((FVersion >= 5) && (Message == SFTP_STATUS_UNKNOWN_PRINCIPAL)) + { + UnicodeString Principals; + while (Packet->GetNextData() != nullptr) + { + if (!Principals.IsEmpty()) + { + Principals += L", "; + } + Principals += Packet->GetAnsiString(); + } + MessageStr = FORMAT(MessageStr.c_str(), Principals.c_str()); + } + } + } + else + { + ServerMessage = LoadStr(SFTP_SERVER_MESSAGE_UNSUPPORTED); + } + if (FTerminal->GetLog()->GetLogging()) + { + FTerminal->GetLog()->Add(llOutput, FORMAT(L"Status code: %d, Message: %d, Server: %s, Language: %s ", + static_cast(Code), + static_cast(Packet->GetMessageNumber()), + ServerMessage.c_str(), + LanguageTag.c_str())); + } + if (!LanguageTag.IsEmpty()) + { + LanguageTag = FORMAT(L" (%s)", LanguageTag.c_str()); + } + UnicodeString HelpKeyword; + switch (Code) + { + case SSH_FX_FAILURE: + HelpKeyword = HELP_SFTP_STATUS_FAILURE; + break; + + case SSH_FX_PERMISSION_DENIED: + HelpKeyword = HELP_SFTP_STATUS_PERMISSION_DENIED; + break; + } + UnicodeString Error = FMTLOAD(SFTP_ERROR_FORMAT3, MessageStr.c_str(), + int(Code), LanguageTag.c_str(), ServerMessage.c_str()); + if (Code == SSH_FX_FAILURE) + { + // FTerminal->Configuration->Usage->Inc(L"SftpFailureErrors"); + Error += L"\n\n" + LoadStr(SFTP_STATUS_4); + } + FTerminal->TerminalError(nullptr, Error, HelpKeyword); + return 0; + } + else + { + if (!FNotLoggedPackets || Code) + { + FTerminal->GetLog()->Add(llOutput, FORMAT(L"Status code: %d", static_cast(Code))); + } + return Code; + } +} + +void TSFTPFileSystem::RemoveReservation(intptr_t Reservation) +{ + for (intptr_t Index = Reservation + 1; Index < FPacketReservations->GetCount(); ++Index) + { + FPacketNumbers[Index - 1] = FPacketNumbers[Index]; + } + TSFTPPacket * Packet = NB_STATIC_DOWNCAST(TSFTPPacket, FPacketReservations->GetItem(Reservation)); + if (Packet) + { + DebugAssert((Packet->GetReservedBy() == nullptr) || (Packet->GetReservedBy() == this)); + Packet->SetReservedBy(nullptr); + } + FPacketReservations->Delete(Reservation); +} + +intptr_t TSFTPFileSystem::PacketLength(uint8_t * LenBuf, SSH_FXP_TYPES ExpectedType) +{ + intptr_t Length = GET_32BIT(LenBuf); + if (Length > SFTP_MAX_PACKET_LEN) + { + UnicodeString Message = FMTLOAD(SFTP_PACKET_TOO_BIG, + int(Length), SFTP_MAX_PACKET_LEN); + if (ExpectedType == SSH_FXP_VERSION) + { + RawByteString LenString(reinterpret_cast(LenBuf), 4); + Message = FMTLOAD(SFTP_PACKET_TOO_BIG_INIT_EXPLAIN, + Message.c_str(), DisplayableStr(LenString).c_str()); + } + FTerminal->FatalError(nullptr, Message, HELP_SFTP_PACKET_TOO_BIG); + } + return Length; +} + +const TSessionData * TSFTPFileSystem::GetSessionData() const +{ + return FTerminal->GetSessionData(); +} + +bool TSFTPFileSystem::PeekPacket() +{ + uint8_t * Buf = nullptr; + bool Result = FSecureShell->Peek(Buf, 4); + if (Result) + { + intptr_t Length = PacketLength(Buf, (SSH_FXP_TYPES)-1); + Result = FSecureShell->Peek(Buf, 4 + Length); + } + return Result; +} + +SSH_FX_TYPES TSFTPFileSystem::ReceivePacket(TSFTPPacket * Packet, + SSH_FXP_TYPES ExpectedType, SSH_FX_TYPES AllowStatus, bool TryOnly) +{ + TSFTPBusy Busy(this); + + SSH_FX_TYPES Result = SSH_FX_OK; + intptr_t Reservation = FPacketReservations->IndexOf(Packet); + + if ((Reservation < 0) || (Packet->GetCapacity() == 0)) + { + bool IsReserved; + do + { + IsReserved = false; + + DebugAssert(Packet); + + if (TryOnly && !PeekPacket()) + { + // Reset packet in case it was filled by previous out-of-order + // reserved packet + *Packet = TSFTPPacket(FCodePage); + } + else + { + uint8_t LenBuf[4]; + FSecureShell->Receive(LenBuf, sizeof(LenBuf)); + intptr_t Length = PacketLength(LenBuf, ExpectedType); + Packet->SetCapacity(Length); + FSecureShell->Receive(Packet->GetData(), Length); + Packet->DataUpdated(Length); + + if (FTerminal->GetLog()->GetLogging()) + { + if ((FPreviousLoggedPacket != SSH_FXP_READ && + FPreviousLoggedPacket != SSH_FXP_WRITE) || + (Packet->GetType() != SSH_FXP_STATUS && Packet->GetType() != SSH_FXP_DATA) || + (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1)) + { + if (FNotLoggedPackets) + { + FTerminal->LogEvent(FORMAT(L"%d skipped SSH_FXP_WRITE, SSH_FXP_READ, SSH_FXP_DATA and SSH_FXP_STATUS packets.", + FNotLoggedPackets)); + FNotLoggedPackets = 0; + } + FTerminal->GetLog()->Add(llOutput, FORMAT(L"Type: %s, Size: %d, Number: %d", + Packet->GetTypeName().c_str(), + static_cast(Packet->GetLength()), + static_cast(Packet->GetMessageNumber()))); +#if 0 + if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 2) + { + FTerminal->GetLog()->Add(llOutput, Packet->Dump()); + } +#endif + } + else + { + FNotLoggedPackets++; + } + } + + if ((Reservation < 0) || + Packet->GetMessageNumber() != FPacketNumbers[Reservation]) + { + TSFTPPacket * ReservedPacket; + for (intptr_t Index = 0; Index < FPacketReservations->GetCount(); ++Index) + { + uint32_t MessageNumber = static_cast(FPacketNumbers[Index]); + if (MessageNumber == Packet->GetMessageNumber()) + { + ReservedPacket = NB_STATIC_DOWNCAST(TSFTPPacket, FPacketReservations->GetItem(Index)); + IsReserved = true; + if (ReservedPacket) + { + FTerminal->LogEvent("Storing reserved response"); + *ReservedPacket = *Packet; + } + else + { + FTerminal->LogEvent("Discarding reserved response"); + RemoveReservation(Index); + if ((Reservation >= 0) && (Reservation > Index)) + { + Reservation--; + DebugAssert(Reservation == FPacketReservations->IndexOf(Packet)); + } + } + break; + } + } + } + } + } + while (IsReserved); + } + + if ((Packet->GetCapacity() == 0) && DebugAlwaysTrue(TryOnly)) + { + // noop + } + else + { + // before we removed the reservation after check for packet type, + // but if it raises exception, removal is unnecessarily + // postponed until the packet is removed + // (and it have not worked anyway until recent fix to UnreserveResponse) + if (Reservation >= 0) + { + DebugAssert(Packet->GetMessageNumber() == FPacketNumbers[Reservation]); + RemoveReservation(Reservation); + } + + if (ExpectedType != (SSH_FXP_TYPES)-1) + { + if (Packet->GetType() == SSH_FXP_STATUS) + { + if (AllowStatus < 0) + { + AllowStatus = (ExpectedType == SSH_FXP_STATUS ? asOK : asNo); + } + Result = GotStatusPacket(Packet, AllowStatus); + } + else if (ExpectedType != Packet->GetType()) + { + FTerminal->FatalError(nullptr, FMTLOAD(SFTP_INVALID_TYPE, static_cast(Packet->GetType()))); + } + } + } + + return Result; +} + +void TSFTPFileSystem::ReserveResponse(const TSFTPPacket * Packet, + TSFTPPacket * Response) +{ + if (Response != nullptr) + { + DebugAssert(FPacketReservations->IndexOf(Response) < 0); + // mark response as not received yet + Response->SetCapacity(0); + Response->SetReservedBy(this); + } + FPacketReservations->Add(Response); + if (static_cast(FPacketReservations->GetCount()) >= FPacketNumbers.size()) + { + FPacketNumbers.resize(FPacketReservations->GetCount() + 10); + } + FPacketNumbers[FPacketReservations->GetCount() - 1] = Packet->GetMessageNumber(); +} + +void TSFTPFileSystem::UnreserveResponse(TSFTPPacket * Response) +{ + intptr_t Reservation = FPacketReservations->IndexOf(Response); + if (Response->GetCapacity() != 0) + { + // added check for already received packet + // (it happens when the reserved response is received out of order, + // unexpectedly soon, and then receivepacket() on the packet + // is not actually called, due to exception) + RemoveReservation(Reservation); + } + else + { + if (Reservation >= 0) + { + // we probably do not remove the item at all, because + // we must remember that the response was expected, so we skip it + // in receivepacket() + FPacketReservations->SetItem(Reservation, nullptr); + } + } +} + +SSH_FX_TYPES TSFTPFileSystem::ReceiveResponse( + const TSFTPPacket * Packet, TSFTPPacket * AResponse, SSH_FXP_TYPES ExpectedType, + SSH_FX_TYPES AllowStatus, bool TryOnly) +{ + SSH_FX_TYPES Result; + uintptr_t MessageNumber = Packet->GetMessageNumber(); + std::unique_ptr Response; + try__finally + { + if (!AResponse) + { + Response.reset(new TSFTPPacket(FCodePage)); + AResponse = Response.get(); + } + Result = ReceivePacket(AResponse, ExpectedType, AllowStatus, TryOnly); + if (MessageNumber != AResponse->GetMessageNumber()) + { + FTerminal->FatalError(nullptr, FMTLOAD(SFTP_MESSAGE_NUMBER, + static_cast(AResponse->GetMessageNumber()), + static_cast(MessageNumber))); + } + } + __finally + { + if (!Response) + { +// delete AResponse; + } + }; + return Result; +} + +SSH_FX_TYPES TSFTPFileSystem::SendPacketAndReceiveResponse(const TSFTPPacket * Packet, TSFTPPacket * Response, SSH_FXP_TYPES ExpectedType, + SSH_FX_TYPES AllowStatus) +{ + TSFTPBusy Busy(this); + SendPacket(Packet); + SSH_FX_TYPES Result = ReceiveResponse(Packet, Response, ExpectedType, AllowStatus); + return Result; +} + +UnicodeString TSFTPFileSystem::GetRealPath(const UnicodeString & APath) +{ + try + { + FTerminal->LogEvent(FORMAT(L"Getting real path for '%s'", + APath.c_str())); + + TSFTPPacket Packet(SSH_FXP_REALPATH, FCodePage); + Packet.AddPathString(APath, FUtfStrings); + + // In SFTP-6 new optional field control-byte is added that defaults to + // SSH_FXP_REALPATH_NO_CHECK=0x01, meaning it won't fail, if the path does not exist. + // That differs from SFTP-5 recommendation that + // "The server SHOULD fail the request if the path is not present on the server." + // Earlier versions had no recommendation, though canonical SFTP-3 implementation + // in OpenSSH fails. + + // While we really do not care much, we anyway set the flag to ~ & 0x01 to make the request fail. + // First for consistency. + // Second to workaround a bug in ProFTPD/mod_sftp version 1.3.5rc1 through 1.3.5-stable + // that sends a completely malformed response for non-existing paths, + // when SSH_FXP_REALPATH_NO_CHECK (even implicitly) is used. + // See http://bugs.proftpd.org/show_bug.cgi?id=4160 + + // Note that earlier drafts of SFTP-6 (filexfer-07 and -08) had optional compose-path field + // before control-byte field. If we ever use this against a server conforming to those drafts, + // if may cause trouble. + if (FVersion >= 6) + { + if (FSecureShell->GetSshImplementation() != sshiProFTPD) + { + Packet.AddByte(SSH_FXP_REALPATH_STAT_ALWAYS); + } + else + { + // Cannot use SSH_FXP_REALPATH_STAT_ALWAYS as ProFTPD does wrong bitwise test + // so it incorrectly evaluates SSH_FXP_REALPATH_STAT_ALWAYS (0x03) as + // SSH_FXP_REALPATH_NO_CHECK (0x01). The only value conforming to the + // specification, yet working with ProFTPD is SSH_FXP_REALPATH_STAT_IF (0x02). + Packet.AddByte(SSH_FXP_REALPATH_STAT_IF); + } + } + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_NAME); + if (Packet.GetCardinal() != 1) + { + FTerminal->FatalError(nullptr, LoadStr(SFTP_NON_ONE_FXP_NAME_PACKET)); + } + + UnicodeString RealDir = core::UnixExcludeTrailingBackslash(Packet.GetPathString(FUtfStrings)); + // ignore rest of SSH_FXP_NAME packet + + FTerminal->LogEvent(FORMAT(L"Real path is '%s'", RealDir.c_str())); + + return RealDir; + } + catch (Exception & E) + { + if (FTerminal->GetActive()) + { + throw ExtException(&E, FMTLOAD(SFTP_REALPATH_ERROR, APath.c_str())); + } + else + { + throw; + } + } + return UnicodeString(); +} + +UnicodeString TSFTPFileSystem::GetRealPath(const UnicodeString & APath, + const UnicodeString & ABaseDir) +{ + UnicodeString Path; + + if (core::UnixIsAbsolutePath(APath)) + { + Path = APath; + } + else + { + if (!APath.IsEmpty()) + { + // this condition/block was outside (before) current block + // but it did not work when Path was empty + if (!ABaseDir.IsEmpty()) + { + Path = core::UnixIncludeTrailingBackslash(ABaseDir); + } + Path = Path + APath; + } + if (Path.IsEmpty()) + { + Path = core::UnixIncludeTrailingBackslash(L"."); + } + } + return GetRealPath(Path); +} + +UnicodeString TSFTPFileSystem::LocalCanonify(const UnicodeString & APath) const +{ + TODO("improve (handle .. etc.)"); + if (core::UnixIsAbsolutePath(APath) || + (!FCurrentDirectory.IsEmpty() && core::UnixSamePath(FCurrentDirectory, APath))) + { + return APath; + } + else + { + return core::AbsolutePath(FCurrentDirectory, APath); + } +} + +UnicodeString TSFTPFileSystem::Canonify(const UnicodeString & APath) +{ + // inspired by canonify() from PSFTP.C + UnicodeString Result; + FTerminal->LogEvent(FORMAT(L"Canonifying: \"%s\"", APath.c_str())); + UnicodeString Path = LocalCanonify(APath); + bool TryParent = false; + try + { + Result = GetRealPath(Path); + } + catch (...) + { + if (FTerminal->GetActive()) + { + TryParent = true; + } + else + { + throw; + } + } + + if (TryParent) + { + UnicodeString Path2 = core::UnixExcludeTrailingBackslash(Path); + UnicodeString Name = base::UnixExtractFileName(Path2); + if (Name == THISDIRECTORY || Name == PARENTDIRECTORY) + { + Result = Path; + } + else + { + UnicodeString FPath = core::UnixExtractFilePath(Path2); + try + { + Result = GetRealPath(FPath); + Result = core::UnixIncludeTrailingBackslash(Result) + Name; + } + catch (...) + { + if (FTerminal->GetActive()) + { + Result = Path; + } + else + { + throw; + } + } + } + } + + FTerminal->LogEvent(FORMAT(L"Canonified: \"%s\"", Result.c_str())); + + return Result; +} + +UnicodeString TSFTPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool Local) const +{ + return const_cast(this)->GetAbsolutePath(APath, Local); +} + +UnicodeString TSFTPFileSystem::GetAbsolutePath(const UnicodeString & APath, bool Local) +{ + if (Local) + { + return LocalCanonify(APath); + } + else + { + return GetRealPath(APath, GetCurrDirectory()); + } +} + +UnicodeString TSFTPFileSystem::GetHomeDirectory() +{ + if (FHomeDirectory.IsEmpty()) + { + FHomeDirectory = GetRealPath(THISDIRECTORY); + } + return FHomeDirectory; +} + +void TSFTPFileSystem::LoadFile(TRemoteFile * AFile, TSFTPPacket * Packet, + bool Complete) +{ + Packet->GetFile(AFile, FVersion, GetSessionData()->GetDSTMode(), + FUtfStrings, FSignedTS, Complete); +} + +TRemoteFile * TSFTPFileSystem::LoadFile(TSFTPPacket * Packet, + TRemoteFile * ALinkedByFile, const UnicodeString & AFileName, + TRemoteFileList * TempFileList, bool Complete) +{ + std::unique_ptr File(new TRemoteFile(ALinkedByFile)); + try__catch + { + File->SetTerminal(FTerminal); + if (!AFileName.IsEmpty()) + { + File->SetFileName(AFileName); + } + // to get full path for symlink completion + File->SetDirectory(TempFileList); + LoadFile(File.get(), Packet, Complete); + File->SetDirectory(nullptr); + } + /*catch (...) + { + delete File; + throw; + }*/ + return File.release(); +} + +UnicodeString TSFTPFileSystem::GetCurrDirectory() const +{ + return FCurrentDirectory; +} + +void TSFTPFileSystem::DoStartup() +{ + // do not know yet + FVersion = -1; + FFileSystemInfoValid = false; + TSFTPPacket Packet1(SSH_FXP_INIT, FCodePage); + intptr_t MaxVersion = GetSessionData()->GetSFTPMaxVersion(); + if (MaxVersion > SFTPMaxVersion) + { + MaxVersion = SFTPMaxVersion; + } + Packet1.AddCardinal(static_cast(MaxVersion)); + + try + { + SendPacketAndReceiveResponse(&Packet1, &Packet1, SSH_FXP_VERSION); + } + catch (Exception & E) + { + FTerminal->FatalError(&E, LoadStr(SFTP_INITIALIZE_ERROR), HELP_SFTP_INITIALIZE_ERROR); + } + + FVersion = Packet1.GetCardinal(); + FTerminal->LogEvent(FORMAT(L"SFTP version %d negotiated.", FVersion)); + if (FVersion < SFTPMinVersion || FVersion > SFTPMaxVersion) + { + FTerminal->FatalError(nullptr, FMTLOAD(SFTP_VERSION_NOT_SUPPORTED, + FVersion, SFTPMinVersion, SFTPMaxVersion)); + } + + FExtensions->Clear(); + FEOL = "\r\n"; + FSupport->Loaded = false; + FSupportsStatVfsV2 = false; + FSupportsHardlink = false; + SAFE_DESTROY(FFixedPaths); + + if (FVersion >= 3) + { + while (Packet1.GetNextData() != nullptr) + { + UnicodeString ExtensionName = Packet1.GetAnsiString(); + RawByteString ExtensionData = Packet1.GetRawByteString(); + UnicodeString ExtensionDisplayData = DisplayableStr(ExtensionData); + + if (ExtensionName == SFTP_EXT_NEWLINE) + { + FEOL = AnsiString(ExtensionData); + FTerminal->LogEvent(FORMAT(L"Server requests EOL sequence %s.", + ExtensionDisplayData.c_str())); + if (FEOL.Length() < 1 || FEOL.Length() > 2) + { + FTerminal->FatalError(nullptr, FMTLOAD(SFTP_INVALID_EOL, ExtensionDisplayData.c_str())); + } + } + // do not allow "supported" to override "supported2" if both are received + else if (((ExtensionName == SFTP_EXT_SUPPORTED) && !FSupport->Loaded) || + (ExtensionName == SFTP_EXT_SUPPORTED2)) + { + FSupport->Reset(); + TSFTPPacket SupportedStruct(ExtensionData, FCodePage); + FSupport->Loaded = true; + FSupport->AttributeMask = SupportedStruct.GetCardinal(); + FSupport->AttributeBits = SupportedStruct.GetCardinal(); + FSupport->OpenFlags = SupportedStruct.GetCardinal(); + FSupport->AccessMask = SupportedStruct.GetCardinal(); + FSupport->MaxReadSize = SupportedStruct.GetCardinal(); + if (ExtensionName == SFTP_EXT_SUPPORTED) + { + while (SupportedStruct.GetNextData() != nullptr) + { + FSupport->Extensions->Add(SupportedStruct.GetAnsiString()); + } + } + else + { + // note that supported-open-block-vector, supported-block-vector, + // attrib-extension-count and attrib-extension-names fields + // were added only in rev 08, while "supported2" was defined in rev 07 + FSupport->OpenBlockVector = SupportedStruct.GetSmallCardinal(); + FSupport->BlockVector = SupportedStruct.GetSmallCardinal(); + uint32_t ExtensionCount; + ExtensionCount = SupportedStruct.GetCardinal(); + for (uint32_t Index = 0; Index < ExtensionCount; ++Index) + { + FSupport->AttribExtensions->Add(SupportedStruct.GetAnsiString()); + } + ExtensionCount = SupportedStruct.GetCardinal(); + for (uint32_t Index = 0; Index < ExtensionCount; ++Index) + { + FSupport->Extensions->Add(SupportedStruct.GetAnsiString()); + } + } + + if (FTerminal->GetLog()->GetLogging()) + { + FTerminal->LogEvent(FORMAT( + L"Server support information (%s):\n" + L" Attribute mask: %x, Attribute bits: %x, Open flags: %x\n" + L" Access mask: %x, Open block vector: %x, Block vector: %x, Max read size: %d\n", + ExtensionName.c_str(), + int(FSupport->AttributeMask), + int(FSupport->AttributeBits), + int(FSupport->OpenFlags), + int(FSupport->AccessMask), + int(FSupport->OpenBlockVector), + int(FSupport->BlockVector), + int(FSupport->MaxReadSize))); + FTerminal->LogEvent(FORMAT(L" Attribute extensions (%d)\n", FSupport->AttribExtensions->GetCount())); + for (intptr_t Index = 0; Index < FSupport->AttribExtensions->GetCount(); ++Index) + { + FTerminal->LogEvent( + FORMAT(L" %s", FSupport->AttribExtensions->GetString(Index).c_str())); + } + FTerminal->LogEvent(FORMAT(L" Extensions (%d)\n", FSupport->Extensions->GetCount())); + for (intptr_t Index = 0; Index < FSupport->Extensions->GetCount(); ++Index) + { + FTerminal->LogEvent( + FORMAT(L" %s", FSupport->Extensions->GetString(Index).c_str())); + } + } + } + else if (ExtensionName == SFTP_EXT_VENDOR_ID) + { + TSFTPPacket VendorIdStruct(ExtensionData, FCodePage); + UnicodeString VendorName(VendorIdStruct.GetAnsiString()); + UnicodeString ProductName(VendorIdStruct.GetAnsiString()); + UnicodeString ProductVersion(VendorIdStruct.GetAnsiString()); + int64_t ProductBuildNumber = VendorIdStruct.GetInt64(); + FTerminal->LogEvent(FORMAT(L"Server software: %s %s (%d) by %s", + ProductName.c_str(), ProductVersion.c_str(), int(ProductBuildNumber), VendorName.c_str())); + } + else if (ExtensionName == SFTP_EXT_FSROOTS) + { + FTerminal->LogEvent("File system roots:\n"); + DebugAssert(FFixedPaths == nullptr); + FFixedPaths = new TStringList(); + try + { + TSFTPPacket RootsPacket(ExtensionData, FCodePage); + while (RootsPacket.GetNextData() != nullptr) + { + uint32_t Dummy = RootsPacket.GetCardinal(); + if (Dummy != 1) + { + break; + } + else + { + uint8_t Drive = RootsPacket.GetByte(); + uint8_t MaybeType = RootsPacket.GetByte(); + FTerminal->LogEvent(FORMAT(L" %c: (type %d)", static_cast(Drive), static_cast(MaybeType))); + FFixedPaths->Add(FORMAT(L"%c:", static_cast(Drive))); + } + } + } + catch (Exception & E) + { + DEBUG_PRINTF("before FTerminal->HandleException"); + FFixedPaths->Clear(); + FTerminal->LogEvent(FORMAT(L"Failed to decode %s extension", + SFTP_EXT_FSROOTS)); + FTerminal->HandleException(&E); + } + } + else if (ExtensionName == SFTP_EXT_VERSIONS) + { + // first try legacy decoding according to incorrect encoding + // (structure-like) as of VShell (bug no longer present as of 4.0.3). + TSFTPPacket VersionsPacket(ExtensionData, FCodePage); + uint32_t StringSize; + if (VersionsPacket.CanGetString(StringSize) && + (StringSize == VersionsPacket.GetRemainingLength())) + { + UnicodeString Versions = VersionsPacket.GetAnsiString(); + FTerminal->LogEvent(FORMAT(L"SFTP versions supported by the server (VShell format): %s", + Versions.c_str())); + } + else + { + // if that fails, fallback to proper decoding + FTerminal->LogEvent(FORMAT(L"SFTP versions supported by the server: %s", + AnsiToString(ExtensionData).c_str())); + } + } + else if (ExtensionName == SFTP_EXT_STATVFS) + { + UnicodeString StatVfsVersion = AnsiToString(ExtensionData); + if (StatVfsVersion == SFTP_EXT_STATVFS_VALUE_V2) + { + FSupportsStatVfsV2 = true; + FTerminal->LogEvent(FORMAT(L"Supports %s extension version %s", ExtensionName.c_str(), ExtensionDisplayData.c_str())); + } + else + { + FTerminal->LogEvent(FORMAT(L"Unsupported %s extension version %s", ExtensionName.c_str(), ExtensionDisplayData.c_str())); + } + } + else if (ExtensionName == SFTP_EXT_HARDLINK) + { + UnicodeString HardlinkVersion = AnsiToString(ExtensionData); + if (HardlinkVersion == SFTP_EXT_HARDLINK_VALUE_V1) + { + FSupportsHardlink = true; + FTerminal->LogEvent(FORMAT(L"Supports %s extension version %s", ExtensionName.c_str(), ExtensionDisplayData.c_str())); + } + else + { + FTerminal->LogEvent(FORMAT(L"Unsupported %s extension version %s", ExtensionName.c_str(), ExtensionDisplayData.c_str())); + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Unknown server extension %s=%s", + ExtensionName.c_str(), ExtensionDisplayData.c_str())); + } + FExtensions->SetValue(ExtensionName, ExtensionDisplayData); + } + + if (SupportsExtension(SFTP_EXT_VENDOR_ID)) + { + const TConfiguration * Configuration = FTerminal->GetConfiguration(); + TSFTPPacket Packet2(SSH_FXP_EXTENDED, FCodePage); + Packet2.AddString(RawByteString(SFTP_EXT_VENDOR_ID)); + Packet2.AddString(Configuration->GetCompanyName()); + Packet2.AddString(Configuration->GetProductName()); + Packet2.AddString(Configuration->GetProductVersion()); +#ifndef __linux__ + WORD dwFileVersionLS = LOWORD(FTerminal->GetConfiguration()->GetFixedApplicationInfo()->dwFileVersionLS); +#else + WORD dwFileVersionLS = 0; +#endif + Packet2.AddInt64(dwFileVersionLS); + SendPacket(&Packet2); + // we are not interested in the response, do not wait for it + ReceiveResponse(&Packet2, &Packet2); + //ReserveResponse(&Packet, nullptr); + } + } + + if (FVersion < 4) + { + // currently enable the bug for all servers (really known on OpenSSH) + FSignedTS = (GetSessionData()->GetSFTPBug(sbSignedTS) == asOn) || + (GetSessionData()->GetSFTPBug(sbSignedTS) == asAuto); + if (FSignedTS) + { + FTerminal->LogEvent("We believe the server has signed timestamps bug"); + } + } + else + { + FSignedTS = false; + } + + const TSessionInfo & Info = GetSessionInfo(); + switch (GetSessionData()->GetNotUtf()) + { + case asOff: + FUtfStrings = asOn; + FTerminal->LogEvent("We will use UTF-8 strings as configured"); + break; + + case asAuto: + // Nb, Foxit server does not exist anymore + if (GetSessionInfo().SshImplementation.Pos(L"Foxit-WAC-Server") == 1) + { + FUtfStrings = asOff; + FTerminal->LogEvent("We will not use UTF-8 strings as the server is known not to use them"); + } + else + { + if (FVersion >= 4) + { + FTerminal->LogEvent("We will use UTF-8 strings as it is mandatory with SFTP version 4 and newer"); + FUtfStrings = asOn; + } + else + { + FTerminal->LogEvent("We will use UTF-8 strings until server sends an invalid UTF-8 string as with SFTP version 3 and older UTF-8 strings are not mandatory"); + FUtfStrings = asAuto; + FUtfDisablingAnnounced = false; + } + } + break; + + case asOn: + FTerminal->LogEvent("We will not use UTF-8 strings as configured"); + FUtfStrings = asOff; + break; + + default: + DebugFail(); + break; + } + + FMaxPacketSize = static_cast(GetSessionData()->GetSFTPMaxPacketSize()); + if (FMaxPacketSize == 0) + { + if ((FSecureShell->GetSshImplementation() == sshiOpenSSH) && (FVersion == 3) && !FSupport->Loaded) + { + FMaxPacketSize = 4 + (256 * 1024); // len + 256kB payload + FTerminal->LogEvent(FORMAT(L"Limiting packet size to OpenSSH sftp-server limit of %d bytes", + int(FMaxPacketSize))); + } + // full string is "1.77 sshlib: Momentum SSH Server", + // possibly it is sshlib-related + else if (Info.SshImplementation.Pos(L"Momentum SSH Server") != 0) + { + FMaxPacketSize = 4 + (32 * 1024); + FTerminal->LogEvent(FORMAT(L"Limiting packet size to Momentum sftp-server limit of %d bytes", + int(FMaxPacketSize))); + } + } +} + +char * TSFTPFileSystem::GetEOL() const +{ + if (FVersion >= 4) + { + DebugAssert(!FEOL.IsEmpty()); + return const_cast(FEOL.c_str()); + } + else + { + return EOLToStr(GetSessionData()->GetEOLType()); + } +} + +void TSFTPFileSystem::LookupUsersGroups() +{ + DebugAssert(SupportsExtension(SFTP_EXT_OWNER_GROUP)); + + TSFTPPacket PacketOwners(SSH_FXP_EXTENDED, FCodePage); + TSFTPPacket PacketGroups(SSH_FXP_EXTENDED, FCodePage); + + TSFTPPacket * Packets[] = { &PacketOwners, &PacketGroups }; + TRemoteTokenList * Lists[] = { FTerminal->GetUsers(), FTerminal->GetGroups() }; + wchar_t ListTypes[] = { OGQ_LIST_OWNERS, OGQ_LIST_GROUPS }; + + for (intptr_t Index = 0; Index < static_cast(_countof(Packets)); ++Index) + { + TSFTPPacket * Packet = Packets[Index]; + Packet->AddString(RawByteString(SFTP_EXT_OWNER_GROUP)); + Packet->AddByte(static_cast(ListTypes[Index])); + SendPacket(Packet); + ReserveResponse(Packet, Packet); + } + + for (intptr_t Index = 0; Index < static_cast(_countof(Packets)); ++Index) + { + TSFTPPacket * Packet = Packets[Index]; + + ReceiveResponse(Packet, Packet, SSH_FXP_EXTENDED_REPLY, asOpUnsupported); + + if ((Packet->GetType() != SSH_FXP_EXTENDED_REPLY) || + (Packet->GetAnsiString() != SFTP_EXT_OWNER_GROUP_REPLY)) + { + FTerminal->LogEvent(FORMAT(L"Invalid response to %s", SFTP_EXT_OWNER_GROUP)); + } + else + { + TRemoteTokenList & List = *Lists[Index]; + uint32_t Count = Packet->GetCardinal(); + + List.Clear(); + for (uint32_t Item = 0; Item < Count; Item++) + { + TRemoteToken Token(Packet->GetString(FUtfStrings)); + List.Add(Token); + if (&List == FTerminal->GetGroups()) + { + FTerminal->GetMembership()->Add(Token); + } + } + } + } +} + +void TSFTPFileSystem::ReadCurrentDirectory() +{ + if (!FDirectoryToChangeTo.IsEmpty()) + { + FCurrentDirectory = FDirectoryToChangeTo; + FDirectoryToChangeTo.Clear(); + } + else if (FCurrentDirectory.IsEmpty()) + { + // this happens only after startup when default remote directory is not specified + FCurrentDirectory = GetHomeDirectory(); + } +} + +void TSFTPFileSystem::HomeDirectory() +{ + ChangeDirectory(GetHomeDirectory()); +} + +void TSFTPFileSystem::TryOpenDirectory(const UnicodeString & Directory) +{ + FTerminal->LogEvent(FORMAT(L"Trying to open directory \"%s\".", Directory.c_str())); + TRemoteFile * File = nullptr; + CustomReadFile(Directory, File, SSH_FXP_LSTAT, nullptr, asOpUnsupported); + if (File == nullptr) + { + // File can be NULL only when server does not support SSH_FXP_LSTAT. + // Fallback to legacy solution, which in turn does not allow entering + // traverse-only (chmod 110) directories. + // This is workaround for http://www.ftpshell.com/ + TSFTPPacket Packet(SSH_FXP_OPENDIR, FCodePage); + Packet.AddPathString(core::UnixExcludeTrailingBackslash(Directory), FUtfStrings); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE); + RawByteString Handle = Packet.GetFileHandle(); + Packet.ChangeType(SSH_FXP_CLOSE); + Packet.AddString(Handle); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS, asAll); + } + else + { + SAFE_DESTROY(File); + } +} + +void TSFTPFileSystem::AnnounceFileListOperation() +{ +} + +void TSFTPFileSystem::ChangeDirectory(const UnicodeString & Directory) +{ + UnicodeString Path, Current; + + Current = !FDirectoryToChangeTo.IsEmpty() ? FDirectoryToChangeTo : FCurrentDirectory; + Path = GetRealPath(Directory, Current); + + // to verify existence of directory try to open it (SSH_FXP_REALPATH succeeds + // for invalid paths on some systems, like CygWin) + TryOpenDirectory(Path); + + // if open dir did not fail, directory exists -> success. + FDirectoryToChangeTo = Path; +} + +void TSFTPFileSystem::CachedChangeDirectory(const UnicodeString & Directory) +{ + FDirectoryToChangeTo = core::UnixExcludeTrailingBackslash(Directory); +} + +void TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList) +{ + DebugAssert(FileList && !FileList->GetDirectory().IsEmpty()); + + UnicodeString Directory; + Directory = core::UnixExcludeTrailingBackslash(LocalCanonify(FileList->GetDirectory())); + FTerminal->LogEvent(FORMAT(L"Listing directory \"%s\".", Directory.c_str())); + + // moved before SSH_FXP_OPENDIR, so directory listing does not retain + // old data (e.g. parent directory) when reading fails + FileList->Reset(); + + TSFTPPacket Packet(SSH_FXP_OPENDIR, FCodePage); + RawByteString Handle; + + try + { + Packet.AddPathString(Directory, FUtfStrings); + + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE); + + Handle = Packet.GetFileHandle(); + } + catch (...) + { + if (FTerminal->GetActive()) + { + FileList->AddFile(new TRemoteParentDirectory(FTerminal)); + } + throw; + } + + TSFTPPacket Response(FCodePage); + try__finally + { + SCOPE_EXIT + { + if (FTerminal->GetActive()) + { + Packet.ChangeType(SSH_FXP_CLOSE); + Packet.AddString(Handle); + SendPacket(&Packet); + // we are not interested in the response, do not wait for it + ReserveResponse(&Packet, nullptr); + } + }; + bool isEOF = false; + intptr_t Total = 0; + bool HasParentDirectory = false; + TRemoteFile * File = nullptr; + + Packet.ChangeType(SSH_FXP_READDIR); + Packet.AddString(Handle); + + SendPacket(&Packet); + + do + { + ReceiveResponse(&Packet, &Response); + if (Response.GetType() == SSH_FXP_NAME) + { + TSFTPPacket ListingPacket(Response, FCodePage); + + Packet.ChangeType(SSH_FXP_READDIR); + Packet.AddString(Handle); + + SendPacket(&Packet); + ReserveResponse(&Packet, &Response); + + uint32_t Count = ListingPacket.GetCardinal(); + + intptr_t ResolvedLinks = 0; + for (uint32_t Index = 0; !isEOF && (Index < Count); ++Index) + { + File = LoadFile(&ListingPacket, nullptr, L"", FileList); + if (FTerminal->GetConfiguration()->GetActualLogProtocol() >= 1) + { + FTerminal->LogEvent(FORMAT(L"Read file '%s' from listing", File->GetFileName().c_str())); + } + if (File->GetLinkedFile() != nullptr) + { + ResolvedLinks++; + } + if (File->GetIsParentDirectory()) + { + HasParentDirectory = true; + } + FileList->AddFile(File); + Total++; + + if (Total % 10 == 0) + { + FTerminal->DoReadDirectoryProgress(Total, ResolvedLinks, isEOF); + if (isEOF) + { + FTerminal->DoReadDirectoryProgress(-2, 0, isEOF); + } + } + } + + if ((FVersion >= 6) && + // As of 7.0.9 the Cerberus SFTP server always sets the end-of-list to true. + (FSecureShell->GetSshImplementation() != sshiCerberus) && + ListingPacket.CanGetBool()) + { + isEOF = ListingPacket.GetBool(); + } + + if (Count == 0) + { + FTerminal->LogEvent("Empty directory listing packet. Aborting directory reading."); + isEOF = true; + } + } + else if (Response.GetType() == SSH_FXP_STATUS) + { + isEOF = (GotStatusPacket(&Response, asEOF) == SSH_FX_EOF); + } + else + { + FTerminal->FatalError(nullptr, FMTLOAD(SFTP_INVALID_TYPE, static_cast(Response.GetType()))); + } + } + while (!isEOF); + + if (Total == 0) + { + bool Failure = false; + // no point reading parent of root directory, + // moreover CompleteFTP terminates session upon attempt to do so + if (core::IsUnixRootPath(FileList->GetDirectory())) + { + File = nullptr; + } + else + { + // Empty file list -> probably "permission denied", we + // at least get link to parent directory ("..") + try + { + FTerminal->SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + FTerminal->SetExceptionOnFail(false); + }; + File = nullptr; + FTerminal->ReadFile( + core::UnixIncludeTrailingBackslash(FileList->GetDirectory()) + PARENTDIRECTORY, File); + } + __finally + { + FTerminal->SetExceptionOnFail(false); + }; + } + catch (Exception & E) + { + if (NB_STATIC_DOWNCAST(EFatal, &E) != nullptr) + { + throw; + } + else + { + File = nullptr; + Failure = true; + } + } + } + + // on some systems even getting ".." fails, we create dummy ".." instead + if (File == nullptr) + { + File = new TRemoteParentDirectory(FTerminal); + } + + DebugAssert(File && File->GetIsParentDirectory()); + FileList->AddFile(File); + + if (Failure) + { + throw ExtException( + nullptr, FMTLOAD(EMPTY_DIRECTORY, FileList->GetDirectory().c_str()), + HELP_EMPTY_DIRECTORY); + } + } + else + { + if (!HasParentDirectory) + { + FileList->AddFile(new TRemoteParentDirectory(FTerminal)); + } + } + } + __finally + { + if (FTerminal->GetActive()) + { + Packet.ChangeType(SSH_FXP_CLOSE); + Packet.AddString(Handle); + SendPacket(&Packet); + // we are not interested in the response, do not wait for it + ReserveResponse(&Packet, nullptr); + } + }; +} + +void TSFTPFileSystem::ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& AFile) +{ + DebugAssert(SymlinkFile && SymlinkFile->GetIsSymLink()); + DebugAssert(FVersion >= 3); // symlinks are supported with SFTP version 3 and later + + // need to use full filename when resolving links within subdirectory + // (i.e. for download) + UnicodeString FileName = LocalCanonify( + SymlinkFile->GetDirectory() != nullptr ? SymlinkFile->GetFullFileName() : SymlinkFile->GetFileName()); + + TSFTPPacket ReadLinkPacket(SSH_FXP_READLINK, FCodePage); + ReadLinkPacket.AddPathString(FileName, FUtfStrings); + SendPacket(&ReadLinkPacket); + ReserveResponse(&ReadLinkPacket, &ReadLinkPacket); + + // send second request before reading response to first one + // (performance benefit) + TSFTPPacket AttrsPacket(SSH_FXP_STAT, FCodePage); + AttrsPacket.AddPathString(FileName, FUtfStrings); + if (FVersion >= 4) + { + AttrsPacket.AddCardinal(SSH_FILEXFER_ATTR_COMMON); + } + SendPacket(&AttrsPacket); + ReserveResponse(&AttrsPacket, &AttrsPacket); + + ReceiveResponse(&ReadLinkPacket, &ReadLinkPacket, SSH_FXP_NAME); + if (ReadLinkPacket.GetCardinal() != 1) + { + FTerminal->FatalError(nullptr, LoadStr(SFTP_NON_ONE_FXP_NAME_PACKET)); + } + SymlinkFile->SetLinkTo(ReadLinkPacket.GetPathString(FUtfStrings)); + FTerminal->LogEvent(FORMAT(L"Link resolved to \"%s\".", SymlinkFile->GetLinkTo().c_str())); + + ReceiveResponse(&AttrsPacket, &AttrsPacket, SSH_FXP_ATTRS); + // SymlinkFile->FileName was used instead SymlinkFile->LinkTo before, why? + AFile = LoadFile(&AttrsPacket, SymlinkFile, + base::UnixExtractFileName(SymlinkFile->GetLinkTo())); +} + +void TSFTPFileSystem::ReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile) +{ + CustomReadFile(AFileName, AFile, SSH_FXP_LSTAT); +} + +bool TSFTPFileSystem::RemoteFileExists(const UnicodeString & FullPath, + TRemoteFile ** AFile) +{ + bool Result; + try + { + TRemoteFile * File = nullptr; + CustomReadFile(FullPath, File, SSH_FXP_LSTAT, nullptr, asNoSuchFile); + Result = (File != nullptr); + if (Result) + { + if (AFile) + { + *AFile = File; + } + else + { + SAFE_DESTROY(File); + } + } + } + catch (...) + { + if (!FTerminal->GetActive()) + { + throw; + } + Result = false; + } + return Result; +} + +void TSFTPFileSystem::SendCustomReadFile(TSFTPPacket * Packet, + TSFTPPacket * Response, uint32_t Flags) +{ + if (FVersion >= 4) + { + Packet->AddCardinal(Flags); + } + SendPacket(Packet); + ReserveResponse(Packet, Response); +} + +void TSFTPFileSystem::CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile, SSH_FXP_TYPES Type, TRemoteFile * ALinkedByFile, + SSH_FX_TYPES AllowStatus) +{ + SSH_FILEXFER_ATTR_TYPES Flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_OWNERGROUP; + TSFTPPacket Packet(Type, FCodePage); + Packet.AddPathString(LocalCanonify(AFileName), FUtfStrings); + SendCustomReadFile(&Packet, &Packet, Flags); + ReceiveResponse(&Packet, &Packet, SSH_FXP_ATTRS, AllowStatus); + + if (Packet.GetType() == SSH_FXP_ATTRS) + { + AFile = LoadFile(&Packet, ALinkedByFile, base::UnixExtractFileName(AFileName)); + } + else + { + DebugAssert(AllowStatus > 0); + AFile = nullptr; + } +} + +void TSFTPFileSystem::DoDeleteFile(const UnicodeString & AFileName, SSH_FXP_TYPES Type) +{ + TSFTPPacket Packet(Type, FCodePage); + UnicodeString RealFileName = LocalCanonify(AFileName); + Packet.AddPathString(RealFileName, FUtfStrings); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); +} + +void TSFTPFileSystem::RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action) +{ + uint8_t Type; + if (AFile && AFile->GetIsDirectory() && FTerminal->CanRecurseToDirectory(AFile)) + { + if (FLAGCLEAR(Params, dfNoRecursive)) + { + try + { + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TTerminal::RemoteDeleteFile, FTerminal), &Params); + } + catch (...) + { + Action.Cancel(); + throw; + } + } + Type = SSH_FXP_RMDIR; + } + else + { + Type = SSH_FXP_REMOVE; + } + + DoDeleteFile(AFileName, Type); +} + +void TSFTPFileSystem::RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + TSFTPPacket Packet(SSH_FXP_RENAME, FCodePage); + UnicodeString RealName = LocalCanonify(AFileName); + Packet.AddPathString(RealName, FUtfStrings); + UnicodeString TargetName; + if (core::UnixExtractFilePath(ANewName).IsEmpty()) + { + // rename case (TTerminal::RenameFile) + TargetName = core::UnixExtractFilePath(RealName) + ANewName; + } + else + { + TargetName = LocalCanonify(ANewName); + } + Packet.AddPathString(TargetName, FUtfStrings); + if (FVersion >= 5) + { + Packet.AddCardinal(0); + } + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); +} + +void TSFTPFileSystem::RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + // Implemented by ProFTPD/mod_sftp and Bitvise WinSSHD (without announcing it) + DebugAssert(SupportsExtension(SFTP_EXT_COPY_FILE) || (FSecureShell->GetSshImplementation() == sshiBitvise)); + TSFTPPacket Packet(SSH_FXP_EXTENDED, FCodePage); + Packet.AddString(SFTP_EXT_COPY_FILE); + Packet.AddPathString(Canonify(AFileName), FUtfStrings); + Packet.AddPathString(Canonify(ANewName), FUtfStrings); + Packet.AddBool(false); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); +} + +void TSFTPFileSystem::RemoteCreateDirectory(const UnicodeString & ADirName) +{ + TSFTPPacket Packet(SSH_FXP_MKDIR, FCodePage); + UnicodeString CanonifiedName = Canonify(ADirName); + Packet.AddPathString(CanonifiedName, FUtfStrings); + Packet.AddProperties(nullptr, 0, true, FVersion, FUtfStrings, nullptr); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); +} + +void TSFTPFileSystem::CreateLink(const UnicodeString & AFileName, + const UnicodeString & PointTo, bool Symbolic) +{ + // Cerberus server does not even response to LINK or SYMLINK, + // Although its log says: + // Unrecognized SFTP client command: (20) + // Unknown SFTP packet - Sending Unsupported OP response + + DebugAssert(FVersion >= 3); // links are supported with SFTP version 3 and later + bool UseLink = (FVersion >= 6); + bool UseHardlink = !Symbolic && !UseLink && FSupportsHardlink; + TSFTPPacket Packet(UseHardlink ? SSH_FXP_EXTENDED : (UseLink ? SSH_FXP_LINK : SSH_FXP_SYMLINK), FCodePage); + if (UseHardlink) + { + Packet.AddString(SFTP_EXT_HARDLINK); + } + + bool Buggy; + // OpenSSH hardlink extension always uses the "wrong" order + // as it's defined as such to mimic OpenSSH symlink bug + if (UseHardlink) + { + Buggy = true; //sic + } + else + { + if (GetSessionData()->GetSFTPBug(sbSymlink) == asOn) + { + Buggy = true; + FTerminal->LogEvent("Forcing workaround for SFTP link bug"); + } + else if (GetSessionData()->GetSFTPBug(sbSymlink) == asOff) + { + Buggy = false; + } + else + { + if (UseLink) + { + if (FSecureShell->GetSshImplementation() == sshiProFTPD) + { + // ProFTPD/mod_sftp followed OpenSSH symlink bug even for link implementation. + // This will be fixed with the next release with + // SSH version string bumped to "mod_sftp/1.0.0" + // http://bugs.proftpd.org/show_bug.cgi?id=4080 + UnicodeString ProFTPDVerStr = GetSessionInfo().SshImplementation; + CutToChar(ProFTPDVerStr, L'/', false); + intptr_t ProFTPDMajorVer = ::StrToIntDef(CutToChar(ProFTPDVerStr, L'.', false), 0); + Buggy = (ProFTPDMajorVer == 0); + if (Buggy) + { + FTerminal->LogEvent("We believe the server has SFTP link bug"); + } + } + else + { + Buggy = false; + } + } + else + { + // ProFTPD/mod_sftp deliberately follows OpenSSH bug. + // Though we should get here with ProFTPD only when user forced + // SFTP version < 6 or when connecting to an ancient version of ProFTPD. + Buggy = + (FSecureShell->GetSshImplementation() == sshiOpenSSH) || + (FSecureShell->GetSshImplementation() == sshiProFTPD); + if (Buggy) + { + FTerminal->LogEvent("We believe the server has SFTP symlink bug"); + } + } + } + } + + UnicodeString FinalPointTo = PointTo; + UnicodeString FinalFileName = Canonify(AFileName); + + if (!Symbolic) + { + FinalPointTo = Canonify(PointTo); + } + + if (!Buggy) + { + Packet.AddPathString(FinalFileName, FUtfStrings); + Packet.AddPathString(FinalPointTo, FUtfStrings); + } + else + { + Packet.AddPathString(FinalPointTo, FUtfStrings); + Packet.AddPathString(FinalFileName, FUtfStrings); + } + + if (UseLink) + { + Packet.AddBool(Symbolic); + } + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); +} + +void TSFTPFileSystem::ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * /*AFile*/, const TRemoteProperties * AProperties, + TChmodSessionAction & Action) +{ + DebugAssert(AProperties != nullptr); + + UnicodeString RealFileName = LocalCanonify(AFileName); + TRemoteFile * File = nullptr; + ReadFile(RealFileName, File); + + try__finally + { + std::unique_ptr FilePtr(File); + DebugAssert(FilePtr.get()); + if (FilePtr->GetIsDirectory() && FTerminal->CanRecurseToDirectory(FilePtr.get()) && AProperties->Recursive) + { + try + { + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TTerminal::ChangeFileProperties, FTerminal), + static_cast(const_cast(AProperties))); + } + catch (...) + { + Action.Cancel(); + throw; + } + } + + // SFTP can change owner and group at the same time only, not individually. + // Fortunately we know current owner/group, so if only one is present, + // we can supplement the other. + TRemoteProperties Properties(*AProperties); + if (Properties.Valid.Contains(vpGroup) && + !Properties.Valid.Contains(vpOwner)) + { + Properties.Owner = File->GetFileOwner(); + Properties.Valid << vpOwner; + } + else if (Properties.Valid.Contains(vpOwner) && + !Properties.Valid.Contains(vpGroup)) + { + Properties.Group = File->GetFileGroup(); + Properties.Valid << vpGroup; + } + + TSFTPPacket Packet(SSH_FXP_SETSTAT, FCodePage); + Packet.AddPathString(RealFileName, FUtfStrings); + Packet.AddProperties(&Properties, *File->GetRights(), File->GetIsDirectory(), FVersion, FUtfStrings, &Action); + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS); + } + __finally + { +// delete File; + }; +} + +bool TSFTPFileSystem::LoadFilesProperties(TStrings * AFileList) +{ + bool Result = false; + // without knowledge of server's capabilities, this all make no sense + if (FSupport->Loaded || (FSecureShell->GetSshImplementation() == sshiBitvise)) + { + TFileOperationProgressType Progress(MAKE_CALLBACK(TTerminal::DoProgress, FTerminal), MAKE_CALLBACK(TTerminal::DoFinished, FTerminal)); + Progress.Start(foGetProperties, osRemote, AFileList->GetCount()); + + FTerminal->SetOperationProgress(&Progress); //-V506 + + TSFTPLoadFilesPropertiesQueue Queue(this, FCodePage); + try__finally + { + SCOPE_EXIT + { + Queue.DisposeSafe(); + FTerminal->SetOperationProgress(nullptr); + Progress.Stop(); + }; + static intptr_t LoadFilesPropertiesQueueLen = 5; + if (Queue.Init(LoadFilesPropertiesQueueLen, AFileList)) + { + TRemoteFile * File = nullptr; + TSFTPPacket Packet(FCodePage); + bool Next; + do + { + Next = Queue.ReceivePacket(&Packet, File); + DebugAssert((Packet.GetType() == SSH_FXP_ATTRS) || (Packet.GetType() == SSH_FXP_STATUS)); + if (Packet.GetType() == SSH_FXP_ATTRS) + { + DebugAssert(File != nullptr); + Progress.SetFile(File->GetFileName()); + LoadFile(File, &Packet); + Result = true; + TOnceDoneOperation OnceDoneOperation; + Progress.Finish(File->GetFileName(), true, OnceDoneOperation); + } + + if (Progress.Cancel != csContinue) + { + Next = false; + } + } + while (Next); + } + } + __finally + { + Queue.DisposeSafe(); + FTerminal->FOperationProgress = nullptr; + Progress.Stop(); + }; + // queue is discarded here + } + + return Result; +} + +void TSFTPFileSystem::DoCalculateFilesChecksum( + const UnicodeString & Alg, const UnicodeString & SftpAlg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum, + TFileOperationProgressType * OperationProgress, bool FirstLevel) +{ + TOnceDoneOperation OnceDoneOperation; // not used + + // recurse into subdirectories only if we have callback function + if (OnCalculatedChecksum != nullptr) + { + for (intptr_t Index1 = 0; Index1 < AFileList->GetCount(); ++Index1) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(Index1)); + DebugAssert(File != nullptr); + if (File && File->GetIsDirectory() && FTerminal->CanRecurseToDirectory(File) && + !File->GetIsParentDirectory() && !File->GetIsThisDirectory()) + { + OperationProgress->SetFile(File->GetFileName()); + std::unique_ptr SubFiles( + FTerminal->CustomReadDirectoryListing(File->GetFullFileName(), false)); + + if (SubFiles.get() != nullptr) + { + std::unique_ptr SubFileList(new TStringList()); + try__finally + { + bool Success = false; + SCOPE_EXIT + { + if (FirstLevel && File) + { + OperationProgress->Finish(File->GetFileName(), Success, OnceDoneOperation); + } + }; + OperationProgress->SetFile(File->GetFileName()); + + for (intptr_t Index2 = 0; Index2 < SubFiles->GetCount(); ++Index2) + { + TRemoteFile * SubFile = SubFiles->GetFile(Index2); + SubFileList->AddObject(SubFile->GetFullFileName(), SubFile); + } + + // do not collect checksums for files in subdirectories, + // only send back checksums via callback + DoCalculateFilesChecksum(Alg, SftpAlg, SubFileList.get(), nullptr, + OnCalculatedChecksum, OperationProgress, false); + + Success = true; + } + __finally + { +#if 0 + delete SubFiles; + delete SubFileList; + + if (FirstLevel) + { + OperationProgress->Finish(File->FileName, Success, OnceDoneOperation); + } +#endif + }; + } + } + } + } + + TSFTPCalculateFilesChecksumQueue Queue(this, FCodePage); + try__finally + { + static int CalculateFilesChecksumQueueLen = 5; + SCOPE_EXIT + { + Queue.DisposeSafe(); + }; + if (Queue.Init(CalculateFilesChecksumQueueLen, Alg, AFileList)) + { + TSFTPPacket Packet(FCodePage); + bool Next = false; + do + { + bool Success = false; + UnicodeString Checksum; + TRemoteFile * File = nullptr; + + try__finally + { + SCOPE_EXIT + { + if (FirstLevel && File) + { + OperationProgress->Finish(File->GetFileName(), Success, OnceDoneOperation); + } + }; + TChecksumSessionAction Action(FTerminal->GetActionLog()); + try + { + Next = Queue.ReceivePacket(&Packet, File); + DebugAssert(Packet.GetType() == SSH_FXP_EXTENDED_REPLY); + + OperationProgress->SetFile(File->GetFileName()); + Action.SetFileName(FTerminal->GetAbsolutePath(File->GetFullFileName(), true)); + + // skip alg + Packet.GetAnsiString(); + Checksum = BytesToHex(reinterpret_cast(Packet.GetNextData(Packet.GetRemainingLength())), Packet.GetRemainingLength(), false); + if (OnCalculatedChecksum != nullptr) + { + OnCalculatedChecksum(File->GetFileName(), Alg, Checksum); + } + Action.Checksum(Alg, Checksum); + + Success = true; + } + catch (Exception & E) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + + // Error formatting expanded from inline to avoid strange exceptions + UnicodeString Error = + FMTLOAD(CHECKSUM_ERROR, + (File != nullptr ? File->GetFullFileName().c_str() : L"")); + FTerminal->CommandError(&E, Error); + TODO("retries? resume?"); + Next = false; + } + + if (Checksums != nullptr) + { + Checksums->Add(Checksum); + } + } + __finally + { + if (FirstLevel && File) + { + OperationProgress->Finish(File->GetFileName(), Success, OnceDoneOperation); + } + }; + + if (OperationProgress->Cancel != csContinue) + { + Next = false; + } + } + while (Next); + } + } + __finally + { + Queue.DisposeSafe(); + }; + // queue is discarded here +} + +void TSFTPFileSystem::CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum) +{ + TFileOperationProgressType Progress(MAKE_CALLBACK(TTerminal::DoProgress, FTerminal), MAKE_CALLBACK(TTerminal::DoFinished, FTerminal)); + Progress.Start(foCalculateChecksum, osRemote, AFileList->GetCount()); + + UnicodeString NormalizedAlg = FindIdent(Alg, FChecksumAlgs.get()); + UnicodeString SftpAlg; + intptr_t Index = FChecksumAlgs->IndexOf(NormalizedAlg); + if (Index >= 0) + { + SftpAlg = FChecksumSftpAlgs->GetString(Index); + } + else + { + // try user-specified alg + SftpAlg = NormalizedAlg; + } + + FTerminal->SetOperationProgress(&Progress); //-V506 + + try__finally + { + SCOPE_EXIT + { + FTerminal->SetOperationProgress(nullptr); + Progress.Stop(); + }; + DoCalculateFilesChecksum(NormalizedAlg, SftpAlg, AFileList, Checksums, OnCalculatedChecksum, + &Progress, true); + } + __finally + { + FTerminal->FOperationProgress = nullptr; + Progress.Stop(); + }; +} + +void TSFTPFileSystem::CustomCommandOnFile(const UnicodeString & /*AFileName*/, + const TRemoteFile * /*AFile*/, const UnicodeString & /*Command*/, intptr_t /*Params*/, + TCaptureOutputEvent /*OutputEvent*/) +{ + DebugFail(); +} + +void TSFTPFileSystem::AnyCommand(const UnicodeString & /*Command*/, + TCaptureOutputEvent /*OutputEvent*/) +{ + DebugFail(); +} + +TStrings * TSFTPFileSystem::GetFixedPaths() +{ + return FFixedPaths; +} + +void TSFTPFileSystem::SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable) +{ + if (SupportsExtension(SFTP_EXT_SPACE_AVAILABLE) || + // See comment in IsCapable + (FSecureShell->GetSshImplementation() == sshiBitvise)) + { + TSFTPPacket Packet(SSH_FXP_EXTENDED, FCodePage); + Packet.AddString(SFTP_EXT_SPACE_AVAILABLE); + Packet.AddPathString(LocalCanonify(APath), FUtfStrings); + + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_EXTENDED_REPLY); + + ASpaceAvailable.BytesOnDevice = Packet.GetInt64(); + ASpaceAvailable.UnusedBytesOnDevice = Packet.GetInt64(); + ASpaceAvailable.BytesAvailableToUser = Packet.GetInt64(); + ASpaceAvailable.UnusedBytesAvailableToUser = Packet.GetInt64(); + // bytes-per-allocation-unit was added later to the protocol + // (revision 07, while the extension was defined already in rev 06), + // be tolerant + if (Packet.CanGetCardinal()) + { + ASpaceAvailable.BytesPerAllocationUnit = Packet.GetCardinal(); + } + else if (Packet.CanGetSmallCardinal()) + { + // See http://bugs.proftpd.org/show_bug.cgi?id=4079 + FTerminal->LogEvent("Assuming ProFTPD/mod_sftp bug of 2-byte bytes-per-allocation-unit field"); + ASpaceAvailable.BytesPerAllocationUnit = Packet.GetSmallCardinal(); + } + else + { + FTerminal->LogEvent("Missing bytes-per-allocation-unit field"); + } + } + else if (DebugAlwaysTrue(FSupportsStatVfsV2)) + { + // http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD;content-type=text/plain + TSFTPPacket Packet(SSH_FXP_EXTENDED, FCodePage); + Packet.AddString(SFTP_EXT_STATVFS); + Packet.AddPathString(LocalCanonify(APath), FUtfStrings); + + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_EXTENDED_REPLY); + + int64_t BlockSize = Packet.GetInt64(); // file system block size + int64_t FundamentalBlockSize = Packet.GetInt64(); // fundamental fs block size + int64_t Blocks = Packet.GetInt64(); // number of blocks (unit f_frsize) + int64_t FreeBlocks = Packet.GetInt64(); // free blocks in file system + int64_t AvailableBlocks = Packet.GetInt64(); // free blocks for non-root + int64_t FileINodes = Packet.GetInt64(); // total file inodes + int64_t FreeFileINodes = Packet.GetInt64(); // free file inodes + int64_t AvailableFileINodes = Packet.GetInt64(); // free file inodes for to non-root + int64_t SID = Packet.GetInt64(); // file system id + int64_t Flags = Packet.GetInt64(); // bit mask of f_flag values + int64_t NameMax = Packet.GetInt64(); // maximum filename length + + FTerminal->LogEvent(FORMAT(L"Block size: %s", ::Int64ToStr(BlockSize).c_str())); + FTerminal->LogEvent(FORMAT(L"Fundamental block size: %s", ::Int64ToStr(FundamentalBlockSize).c_str())); + FTerminal->LogEvent(FORMAT(L"Total blocks: %s", ::Int64ToStr(Blocks).c_str())); + FTerminal->LogEvent(FORMAT(L"Free blocks: %s", ::Int64ToStr(FreeBlocks).c_str())); + FTerminal->LogEvent(FORMAT(L"Free blocks for non-root: %s", ::Int64ToStr(AvailableBlocks).c_str())); + FTerminal->LogEvent(FORMAT(L"Total file inodes: %s", ::Int64ToStr(FileINodes).c_str())); + FTerminal->LogEvent(FORMAT(L"Free file inodes: %s", ::Int64ToStr(FreeFileINodes).c_str())); + FTerminal->LogEvent(FORMAT(L"Free file inodes for non-root: %s", ::Int64ToStr(AvailableFileINodes).c_str())); + FTerminal->LogEvent(FORMAT(L"File system ID: %s", BytesToHex(reinterpret_cast(&SID), sizeof(SID)).c_str())); + UnicodeString FlagStr; + if (FLAGSET(Flags, SFTP_EXT_STATVFS_ST_RDONLY)) + { + AddToList(FlagStr, L"read-only", L","); + Flags -= SFTP_EXT_STATVFS_ST_RDONLY; + } + if (FLAGSET(Flags, SFTP_EXT_STATVFS_ST_NOSUID)) + { + AddToList(FlagStr, L"no-setuid", L","); + Flags -= SFTP_EXT_STATVFS_ST_NOSUID; + } + if (Flags != 0) + { + AddToList(FlagStr, UnicodeString(L"0x") + ::IntToHex(static_cast(Flags), 2), L","); + } + if (FlagStr.IsEmpty()) + { + FlagStr = L"none"; + } + FTerminal->LogEvent(FORMAT(L"Flags: %s", FlagStr.c_str())); + FTerminal->LogEvent(FORMAT(L"Max name length: %s", ::Int64ToStr(NameMax).c_str())); + + ASpaceAvailable.BytesOnDevice = BlockSize * Blocks; + ASpaceAvailable.UnusedBytesOnDevice = BlockSize * FreeBlocks; + ASpaceAvailable.BytesAvailableToUser = 0; + ASpaceAvailable.UnusedBytesAvailableToUser = BlockSize * AvailableBlocks; + ASpaceAvailable.BytesPerAllocationUnit = + (BlockSize > UINT_MAX /*std::numeric_limits::max()*/) ? 0 : static_cast(BlockSize); + } +} + +// transfer protocol + +void TSFTPFileSystem::CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + DebugAssert(AFilesToCopy && OperationProgress); + + UnicodeString FileName, FileNameOnly; + UnicodeString FullTargetDir = core::UnixIncludeTrailingBackslash(TargetDir); + intptr_t Index = 0; + while (Index < AFilesToCopy->GetCount() && !OperationProgress->Cancel) + { + bool Success = false; + FileName = AFilesToCopy->GetString(Index); + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(Index)); + UnicodeString RealFileName = File ? File->GetFileName() : FileName; + FileNameOnly = base::ExtractFileName(RealFileName, false); + DebugAssert(!FAvoidBusy); + FAvoidBusy = true; + + try__finally + { + SCOPE_EXIT + { + FAvoidBusy = false; + OperationProgress->Finish(RealFileName, Success, OnceDoneOperation); + }; + try + { + if (GetSessionData()->GetCacheDirectories()) + { + FTerminal->DirectoryModified(TargetDir, false); + + if (::DirectoryExists(ApiPath(::ExtractFilePath(FileName)))) + { + FTerminal->DirectoryModified(core::UnixIncludeTrailingBackslash(TargetDir) + + FileNameOnly, true); + } + } + SFTPSourceRobust(FileName, File, FullTargetDir, CopyParam, Params, OperationProgress, + tfFirstLevel); + Success = true; + } + catch (ESkipFile & E) + { + DEBUG_PRINTF("before FTerminal->HandleException"); + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + __finally + { + FAvoidBusy = false; + OperationProgress->Finish(RealFileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TSFTPFileSystem::SFTPConfirmOverwrite( + const UnicodeString & ASourceFullFileName, UnicodeString & ATargetFileName, + const TCopyParamType * CopyParam, intptr_t AParams, TFileOperationProgressType * OperationProgress, + const TOverwriteFileParams * FileParams, + OUT TOverwriteMode & OverwriteMode) +{ + bool CanAppend = (FVersion < 4) || !OperationProgress->AsciiTransfer; + bool CanResume = + (FileParams != nullptr) && + (FileParams->DestSize < FileParams->SourceSize); + uintptr_t Answer = 0; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + // abort = "append" + // retry = "resume" + // all = "yes to newer" + // ignore = "rename" + uintptr_t Answers = qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll | qaAll | qaIgnore; + + // possibly we can allow alternate resume at least in some cases + if (CanAppend) + { + Answers |= qaRetry; + } + TQueryButtonAlias Aliases[5]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(APPEND_BUTTON); + Aliases[0].GroupWith = qaNo; + Aliases[0].GrouppedShiftState = ssAlt; + Aliases[1].Button = qaAll; + Aliases[1].Alias = LoadStr(YES_TO_NEWER_BUTTON); + Aliases[1].GroupWith = qaYes; + Aliases[1].GrouppedShiftState = ssCtrl; + Aliases[2].Button = qaIgnore; + Aliases[2].Alias = LoadStr(RENAME_BUTTON); + Aliases[2].GroupWith = qaNo; + Aliases[2].GrouppedShiftState = ssCtrl; + Aliases[3].Button = qaYesToAll; + Aliases[3].GroupWith = qaYes; + Aliases[3].GrouppedShiftState = ssShift; + Aliases[4].Button = qaNoToAll; + Aliases[4].GroupWith = qaNo; + Aliases[4].GrouppedShiftState = ssShift; + TQueryParams QueryParams(qpNeverAskAgainCheck); + QueryParams.NoBatchAnswers = qaIgnore | qaAbort | qaRetry | qaAll; + QueryParams.Aliases = Aliases; + QueryParams.AliasesCount = _countof(Aliases); + Answer = FTerminal->ConfirmFileOverwrite( + ASourceFullFileName, ATargetFileName, FileParams, + Answers, &QueryParams, + OperationProgress->Side == osLocal ? osRemote : osLocal, + CopyParam, AParams, OperationProgress); + } + + if (CanAppend && + ((Answer == qaRetry) || (Answer == qaSkip))) + { + // duplicated in TTerminal::ConfirmFileOverwrite + bool CanAlternateResume = + FileParams ? (FileParams->DestSize < FileParams->SourceSize) && !OperationProgress->AsciiTransfer : false; + TBatchOverwrite BatchOverwrite = + FTerminal->EffectiveBatchOverwrite(ASourceFullFileName, CopyParam, AParams, OperationProgress, true); + // when mode is forced by batch, never query user + if (BatchOverwrite == boAppend) + { + OverwriteMode = omAppend; + } + else if (CanAlternateResume && CanResume && + ((BatchOverwrite == boResume) || (BatchOverwrite == boAlternateResume))) + { + OverwriteMode = omResume; + } + // no other option, but append + else if (!CanAlternateResume) + { + OverwriteMode = omAppend; + } + else + { + TQueryParams Params(0, HELP_APPEND_OR_RESUME); + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = FTerminal->QueryUser(FORMAT(LoadStr(APPEND_OR_RESUME2).c_str(), ASourceFullFileName.c_str()), + nullptr, qaYes | qaNo | qaNoToAll | qaCancel, &Params); + } + + switch (Answer) + { + case qaYes: + OverwriteMode = omAppend; + break; + + case qaNo: + OverwriteMode = omResume; + break; + + case qaNoToAll: + OverwriteMode = omResume; + OperationProgress->BatchOverwrite = boAlternateResume; + break; + + default: + DebugFail(); //fallthru + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + break; + } + } + } + else if (Answer == qaIgnore) + { + if (FTerminal->PromptUser(FTerminal->GetSessionData(), pkFileName, LoadStr(RENAME_TITLE), L"", + LoadStr(RENAME_PROMPT2), true, 0, ATargetFileName)) + { + OverwriteMode = omOverwrite; + } + else + { + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + } + } + else + { + OverwriteMode = omOverwrite; + switch (Answer) + { + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + break; + + case qaNo: + ThrowSkipFileNull(); + } + } +} + +bool TSFTPFileSystem::SFTPConfirmResume(const UnicodeString & DestFileName, + bool PartialBiggerThanSource, TFileOperationProgressType * OperationProgress) +{ + bool ResumeTransfer = false; + DebugAssert(OperationProgress); + if (PartialBiggerThanSource) + { + uintptr_t Answer = 0; + { + TSuspendFileOperationProgress Suspend(OperationProgress); + TQueryParams Params(qpAllowContinueOnError, HELP_PARTIAL_BIGGER_THAN_SOURCE); + Answer = FTerminal->QueryUser( + FMTLOAD(PARTIAL_BIGGER_THAN_SOURCE, DestFileName.c_str()), nullptr, + qaOK | qaAbort, &Params, qtWarning); + } + + if (Answer == qaAbort) + { + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + } + ResumeTransfer = false; + } + else if (FTerminal->GetConfiguration()->GetConfirmResume()) + { + uintptr_t Answer = 0; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + TQueryParams Params(qpAllowContinueOnError | qpNeverAskAgainCheck, + HELP_RESUME_TRANSFER); + // "abort" replaced with "cancel" to unify with "append/resume" query + Answer = FTerminal->QueryUser( + FMTLOAD(RESUME_TRANSFER2, DestFileName.c_str()), nullptr, qaYes | qaNo | qaCancel, + &Params); + } + + switch (Answer) + { + case qaNeverAskAgain: + FTerminal->GetConfiguration()->SetConfirmResume(false); + case qaYes: + ResumeTransfer = true; + break; + + case qaNo: + ResumeTransfer = false; + break; + + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + break; + } + } + else + { + ResumeTransfer = true; + } + return ResumeTransfer; +} + +void TSFTPFileSystem::SFTPSourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TFTPFileSystem + + TUploadSessionAction Action(FTerminal ? FTerminal->GetActionLog() : nullptr); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + TOpenRemoteFileParams OpenParams; + OpenParams.OverwriteMode = omOverwrite; + TOverwriteFileParams FileParams; + + do + { + bool ChildError = false; + try + { + SFTPSource(AFileName, AFile, TargetDir, CopyParam, Params, + OpenParams, FileParams, + OperationProgress, + Flags, Action, ChildError); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + if (!ChildError) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + // prevent overwrite and resume confirmations + // (should not be set for directories!) + Params |= cpNoConfirmation; + // enable resume even if we are uploading into new directory + Flags &= ~tfNewDirectory; + } + } + while (RobustLoop.Retry()); +} + +void TSFTPFileSystem::SFTPSource(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TOpenRemoteFileParams & OpenParams, + TOverwriteFileParams & FileParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action, bool & ChildError) +{ + UnicodeString RealFileName = AFile ? AFile->GetFileName() : AFileName; + + Action.SetFileName(::ExpandUNCFileName(RealFileName)); + + OperationProgress->SetFile(RealFileName, false); + + if (!FTerminal->AllowLocalFileTransfer(AFileName, CopyParam, OperationProgress)) + { + ThrowSkipFileNull(); + } + +#if 0 + TOpenRemoteFileParams OpenParams; + OpenParams.OverwriteMode = omOverwrite; +#endif + + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + int64_t MTime = 0, ATime = 0; + int64_t Size = 0; + + FTerminal->TerminalOpenLocalFile(AFileName, GENERIC_READ, + &LocalFileHandle, &OpenParams.LocalFileAttrs, nullptr, &MTime, &ATime, &Size); + + bool Dir = FLAGSET(OpenParams.LocalFileAttrs, faDirectory); + + try__finally + { + SCOPE_EXIT + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + }; + OperationProgress->SetFileInProgress(); + + if (Dir) + { + Action.Cancel(); + SFTPDirectorySource(::IncludeTrailingBackslash(AFileName), TargetDir, + OpenParams.LocalFileAttrs, CopyParam, Params, OperationProgress, Flags); + } + else + { + // File is regular file (not directory) + DebugAssert(LocalFileHandle != INVALID_HANDLE_VALUE); + + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(RealFileName, false), osLocal, + FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = LocalCanonify(TargetDir + DestFileName); + UnicodeString DestPartialFullName; + bool ResumeAllowed; + bool ResumeTransfer = false; + bool DestFileExists = false; + TRights DestRights; + + int64_t ResumeOffset = 0; + + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", RealFileName.c_str())); + + OperationProgress->SetLocalSize(Size); + + // Suppose same data size to transfer as to read + // (not true with ASCII transfer) + OperationProgress->SetTransferSize(OperationProgress->LocalSize); + OperationProgress->TransferingFile = false; + + TDateTime Modification = ::UnixToDateTime(MTime, GetSessionData()->GetDSTMode()); + + // Will we use ASCII of BINARY file transfer? + TFileMasks::TParams MaskParams; + MaskParams.Size = Size; + MaskParams.Modification = Modification; + UnicodeString BaseFileName = FTerminal->GetBaseFileName(RealFileName); + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osLocal, MaskParams)); + FTerminal->LogEvent( + UnicodeString((OperationProgress->AsciiTransfer ? "Ascii" : "Binary")) + + " transfer mode selected."); + + // should we check for interrupted transfer? + ResumeAllowed = !OperationProgress->AsciiTransfer && + CopyParam->AllowResume(OperationProgress->LocalSize) && + IsCapable(fcRename); + + // TOverwriteFileParams FileParams; + FileParams.SourceSize = OperationProgress->LocalSize; + FileParams.SourceTimestamp = Modification; + + if (ResumeAllowed) + { + DestPartialFullName = DestFullName + FTerminal->GetConfiguration()->GetPartialExt(); + + if (FLAGCLEAR(Flags, tfNewDirectory)) + { + FTerminal->LogEvent("Checking existence of file."); + TRemoteFile * File = nullptr; + DestFileExists = RemoteFileExists(DestFullName, &File); + + OperationProgress->Progress(); + + if (DestFileExists) + { + FTerminal->LogEvent(FORMAT(L"File exists: %s", FTerminal->GetRemoteFileInfo(File).c_str())); + OpenParams.DestFileSize = File->GetSize(); + FileParams.DestSize = OpenParams.DestFileSize; + FileParams.DestTimestamp = File->GetModification(); + DestRights = *File->GetRights(); + // If destination file is symlink, never do resumable transfer, + // as it would delete the symlink. + if (File->GetIsSymLink()) + { + ResumeAllowed = false; + FTerminal->LogEvent(L"Existing file is symbolic link, not doing resumable transfer."); + } + // Also bit of heuristics to detect symlink on SFTP-3 and older + // (which does not indicate symlink in SSH_FXP_ATTRS). + // if file has all permissions and is small, then it is likely symlink. + // also it is not likely that such a small file (if it is not symlink) + // gets overwritten by large file (that would trigger resumable transfer). + else if ((FVersion < 4) && + ((*File->GetRights() & static_cast(TRights::rfAll)) == static_cast(TRights::rfAll)) && + (File->GetSize() < 100)) + { + ResumeAllowed = false; + FTerminal->LogEvent(L"Existing file looks like a symbolic link, not doing resumable transfer."); + } + // Also never do resumable transfer for file owned by other user + // as deleting and recreating the file would change ownership. + // This won't for work for SFTP-3 (OpenSSH) as it does not provide + // owner name (only UID) and we know only logged in user name (not UID) + else if (!File->GetFileOwner().GetName().IsEmpty() && !core::SameUserName(File->GetFileOwner().GetName(), FTerminal->TerminalGetUserName())) + { + ResumeAllowed = false; + FTerminal->LogEvent( + FORMAT(L"Existing file is owned by another user [%s], not doing resumable transfer.", File->GetFileOwner().GetName().c_str())); + } + + SAFE_DESTROY(File); + } + + if (ResumeAllowed) + { + FTerminal->LogEvent("Checking existence of partially transfered file."); + if (RemoteFileExists(DestPartialFullName, &File)) + { + ResumeOffset = File->GetSize(); + SAFE_DESTROY(File); + + bool PartialBiggerThanSource = (ResumeOffset > OperationProgress->LocalSize); + if (FLAGCLEAR(Params, cpNoConfirmation) && + FLAGCLEAR(Params, cpResume) && + !CopyParam->ResumeTransfer(RealFileName)) + { + ResumeTransfer = SFTPConfirmResume(DestFileName, + PartialBiggerThanSource, OperationProgress); + } + else + { + ResumeTransfer = !PartialBiggerThanSource; + } + + if (!ResumeTransfer) + { + DoDeleteFile(DestPartialFullName, SSH_FXP_REMOVE); + OperationProgress->Progress(); + } + else + { + FTerminal->LogEvent("Resuming file transfer."); + } + } + else + { + // partial upload file does not exists, check for full file + if (DestFileExists) + { + UnicodeString PrevDestFileName = DestFileName; + SFTPConfirmOverwrite(AFileName, DestFileName, + CopyParam, Params, OperationProgress, &FileParams, + OpenParams.OverwriteMode); + if (PrevDestFileName != DestFileName) + { + // update paths in case user changes the file name + DestFullName = LocalCanonify(TargetDir + DestFileName); + DestPartialFullName = DestFullName + FTerminal->GetConfiguration()->GetPartialExt(); + FTerminal->LogEvent("Checking existence of new file."); + DestFileExists = RemoteFileExists(DestFullName, nullptr); + } + } + } + } + } + } + + // will the transfer be resumable? + bool DoResume = (ResumeAllowed && (OpenParams.OverwriteMode == omOverwrite)); + + UnicodeString RemoteFileName = DoResume ? DestPartialFullName : DestFullName; + OpenParams.FileName = AFileName; + OpenParams.RemoteFileName = RemoteFileName; + OpenParams.Resume = DoResume; + OpenParams.Resuming = ResumeTransfer; + OpenParams.OperationProgress = OperationProgress; + OpenParams.CopyParam = CopyParam; + OpenParams.Params = Params; + OpenParams.FileParams = &FileParams; + OpenParams.Confirmed = false; + + FTerminal->LogEvent("Opening remote file."); + FTerminal->FileOperationLoop(MAKE_CALLBACK(TSFTPFileSystem::SFTPOpenRemote, this), OperationProgress, true, + FMTLOAD(SFTP_CREATE_FILE_ERROR, OpenParams.RemoteFileName.c_str()), + &OpenParams); + OperationProgress->Progress(); + + if (OpenParams.RemoteFileName != RemoteFileName) + { + DebugAssert(!DoResume); + DebugAssert(core::UnixExtractFilePath(OpenParams.RemoteFileName) == core::UnixExtractFilePath(RemoteFileName)); + DestFullName = OpenParams.RemoteFileName; + UnicodeString NewFileName = base::UnixExtractFileName(DestFullName); + DebugAssert(DestFileName != NewFileName); + DestFileName = NewFileName; + } + + Action.Destination(DestFullName); + + TSFTPPacket CloseRequest(FCodePage); + bool SetRights = ((DoResume && DestFileExists) || CopyParam->GetPreserveRights()); + bool SetProperties = (CopyParam->GetPreserveTime() || SetRights); + TSFTPPacket PropertiesRequest(SSH_FXP_SETSTAT, FCodePage); + TSFTPPacket PropertiesResponse(FCodePage); + TRights Rights; + if (SetProperties) + { + PropertiesRequest.AddPathString(DestFullName, FUtfStrings); + if (CopyParam->GetPreserveRights()) + { + Rights = CopyParam->RemoteFileRights(OpenParams.LocalFileAttrs); + } + else if (DoResume && DestFileExists) + { + Rights = DestRights; + } + else + { + DebugAssert(!SetRights); + } + + uint16_t RightsNumber = Rights.GetNumberSet(); + PropertiesRequest.AddProperties( + SetRights ? &RightsNumber : nullptr, nullptr, nullptr, + CopyParam->GetPreserveTime() ? &MTime : nullptr, + CopyParam->GetPreserveTime() ? &ATime : nullptr, + nullptr, false, FVersion, FUtfStrings); + } + + bool TransferFinished = false; + try__finally + { + SCOPE_EXIT + { + if (FTerminal->GetActive()) + { + // if file transfer was finished, the close request was already sent + if (!OpenParams.RemoteFileHandle.IsEmpty()) + { + SFTPCloseRemote(OpenParams.RemoteFileHandle, DestFileName, + OperationProgress, TransferFinished, true, &CloseRequest); + } + // wait for the response + SFTPCloseRemote(OpenParams.RemoteFileHandle, DestFileName, + OperationProgress, TransferFinished, false, &CloseRequest); + + // delete file if transfer was not completed, resuming was not allowed and + // we were not appending (incl. alternate resume), + // shortly after plain transfer completes (eq. !ResumeAllowed) + if (!TransferFinished && !DoResume && (OpenParams.OverwriteMode == omOverwrite)) + { + DoDeleteFile(OpenParams.RemoteFileName, SSH_FXP_REMOVE); + } + } + }; + int64_t DestWriteOffset = 0; + if (OpenParams.OverwriteMode == omAppend) + { + FTerminal->LogEvent("Appending file."); + DestWriteOffset = OpenParams.DestFileSize; + } + else if (ResumeTransfer || (OpenParams.OverwriteMode == omResume)) + { + if (OpenParams.OverwriteMode == omResume) + { + FTerminal->LogEvent("Resuming file transfer (append style)."); + ResumeOffset = OpenParams.DestFileSize; + } + ::FileSeek(LocalFileHandle, ResumeOffset, 0); + OperationProgress->AddResumed(ResumeOffset); + } + + TSFTPUploadQueue Queue(this, FCodePage); + try__finally + { + SCOPE_EXIT + { + Queue.DisposeSafe(); + }; + intptr_t ConvertParams = + FLAGMASK(CopyParam->GetRemoveCtrlZ(), cpRemoveCtrlZ) | + FLAGMASK(CopyParam->GetRemoveBOM(), cpRemoveBOM); + Queue.Init(AFileName, LocalFileHandle, OperationProgress, + OpenParams.RemoteFileHandle, + DestWriteOffset + OperationProgress->TransferedSize, + ConvertParams); + + while (Queue.Continue()) + { + if (OperationProgress->Cancel) + { + Abort(); + } + } + + // send close request before waiting for pending read responses + SFTPCloseRemote(OpenParams.RemoteFileHandle, DestFileName, + OperationProgress, false, true, &CloseRequest); + OpenParams.RemoteFileHandle.Clear(); + + // when resuming is disabled, we can send "set properties" + // request before waiting for pending read/close responses + if (SetProperties && !DoResume) + { + SendPacket(&PropertiesRequest); + ReserveResponse(&PropertiesRequest, &PropertiesResponse); + } + } + __finally + { + Queue.DisposeSafe(); + }; + + TransferFinished = true; + // queue is discarded here + } + __finally + { + if (FTerminal->GetActive()) + { + // if file transfer was finished, the close request was already sent + if (!OpenParams.RemoteFileHandle.IsEmpty()) + { + SFTPCloseRemote(OpenParams.RemoteFileHandle, DestFileName, + OperationProgress, TransferFinished, true, &CloseRequest); + } + // wait for the response + SFTPCloseRemote(OpenParams.RemoteFileHandle, DestFileName, + OperationProgress, TransferFinished, false, &CloseRequest); + + // delete file if transfer was not completed, resuming was not allowed and + // we were not appending (incl. alternate resume), + // shortly after plain transfer completes (eq. !ResumeAllowed) + if (!TransferFinished && !DoResume && (OpenParams.OverwriteMode == omOverwrite)) + { + DoDeleteFile(OpenParams.RemoteFileName, SSH_FXP_REMOVE); + } + } + }; + + OperationProgress->Progress(); + + if (DoResume) + { + if (DestFileExists) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(DELETE_ON_RESUME_ERROR, + base::UnixExtractFileName(DestFullName).c_str(), DestFullName.c_str()), "", + [&]() + { + if (GetSessionData()->GetOverwrittenToRecycleBin() && + !FTerminal->GetSessionData()->GetRecycleBinPath().IsEmpty()) + { + FTerminal->RecycleFile(DestFullName, nullptr); + } + else + { + DoDeleteFile(DestFullName, SSH_FXP_REMOVE); + } + }); + } + + // originally this was before CLOSE (last __finally statement), + // on VShell it failed + FileOperationLoopCustom(FTerminal, OperationProgress, true, + FMTLOAD(RENAME_AFTER_RESUME_ERROR, + base::UnixExtractFileName(OpenParams.RemoteFileName.c_str()).c_str(), DestFileName.c_str()), + HELP_RENAME_AFTER_RESUME_ERROR, + [&]() + { + this->RemoteRenameFile(OpenParams.RemoteFileName, DestFileName); + }); + } + + if (SetProperties) + { + std::unique_ptr TouchAction; + if (CopyParam->GetPreserveTime()) + { + TDateTime MDateTime = ::UnixToDateTime(MTime, FTerminal->GetSessionData()->GetDSTMode()); + FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]", + StandardTimestamp(MDateTime).c_str())); + TouchAction.reset(new TTouchSessionAction(FTerminal->GetActionLog(), DestFullName, + MDateTime)); + } + std::unique_ptr ChmodAction; + // do record chmod only if it was explicitly requested, + // not when it was implicitly performed to apply timestamp + // of overwritten file to new file + if (CopyParam->GetPreserveRights()) + { + ChmodAction.reset(new TChmodSessionAction(FTerminal->GetActionLog(), DestFullName, Rights)); + } + try + { + // when resuming is enabled, the set properties request was not sent yet + if (DoResume) + { + SendPacket(&PropertiesRequest); + } + bool Resend = false; + FileOperationLoopCustom(FTerminal, OperationProgress, true, + FMTLOAD(PRESERVE_TIME_PERM_ERROR3, DestFileName.c_str()), + HELP_PRESERVE_TIME_PERM_ERROR, + [&]() + { + try + { + TSFTPPacket DummyResponse(FCodePage); + TSFTPPacket * Response = &PropertiesResponse; + if (Resend) + { + PropertiesRequest.Reuse(); + SendPacket(&PropertiesRequest); + // ReceiveResponse currently cannot receive twice into same packet, + // so DummyResponse is temporary workaround + Response = &DummyResponse; + } + Resend = true; + ReceiveResponse(&PropertiesRequest, Response, SSH_FXP_STATUS, + asOK | FLAGMASK(CopyParam->GetIgnorePermErrors(), asPermDenied)); + } + catch (...) + { + if (FTerminal->GetActive() && + (!CopyParam->GetPreserveRights() && !CopyParam->GetPreserveTime())) + { + DebugAssert(DoResume); + FTerminal->LogEvent("Ignoring error preserving permissions of overwritten file"); + } + else + { + throw; + } + } + }); + } + catch (Exception & E) + { + if (TouchAction.get() != nullptr) + { + TouchAction->Rollback(&E); + } + if (ChmodAction.get() != nullptr) + { + ChmodAction->Rollback(&E); + } + ChildError = true; + throw; + } + } + + FTerminal->LogFileDone(OperationProgress); + } + } + __finally + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + }; + + /* TODO : Delete also read-only files. */ + if (FLAGSET(Params, cpDelete)) + { + if (!Dir) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(AFileName)); + }); + } + } + else if (CopyParam->GetClearArchive() && FLAGSET(OpenParams.LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, AFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(AFileName), OpenParams.LocalFileAttrs & ~faArchive) == 0); + }); + } +} + +RawByteString TSFTPFileSystem::SFTPOpenRemoteFile( + const UnicodeString & AFileName, SSH_FXF_TYPES OpenType, int64_t Size) +{ + TSFTPPacket Packet(SSH_FXP_OPEN, FCodePage); + + Packet.AddPathString(AFileName, FUtfStrings); + if (FVersion < 5) + { + Packet.AddCardinal(OpenType); + } + else + { + ACE4_TYPES Access = + FLAGMASK(FLAGSET(OpenType, SSH_FXF_READ), ACE4_READ_DATA) | + FLAGMASK(FLAGSET(OpenType, SSH_FXF_WRITE), ACE4_WRITE_DATA | ACE4_APPEND_DATA); + + SSH_FXF_TYPES Flags = 0; + + if (FLAGSET(OpenType, SSH_FXF_CREAT | SSH_FXF_EXCL)) + { + Flags = SSH_FXF_CREATE_NEW; + } + else if (FLAGSET(OpenType, SSH_FXF_CREAT | SSH_FXF_TRUNC)) + { + Flags = SSH_FXF_CREATE_TRUNCATE; + } + else if (FLAGSET(OpenType, SSH_FXF_CREAT)) + { + Flags = SSH_FXF_OPEN_OR_CREATE; + } + else + { + Flags = SSH_FXF_OPEN_EXISTING; + } + + Flags |= + FLAGMASK(FLAGSET(OpenType, SSH_FXF_APPEND), SSH_FXF_ACCESS_APPEND_DATA) | + FLAGMASK(FLAGSET(OpenType, SSH_FXF_TEXT), SSH_FXF_ACCESS_TEXT_MODE); + + Packet.AddCardinal(Access); + Packet.AddCardinal(Flags); + } + + bool SendSize = + (Size >= 0) && + FLAGSET(OpenType, SSH_FXF_CREAT | SSH_FXF_TRUNC) && + // Particularly VanDyke VShell (4.0.3) does not support SSH_FILEXFER_ATTR_ALLOCATION_SIZE + // (it fails open request when the attribute is included). + // It's SFTP-6 attribute, so support structure should be available. + // It's actually not with VShell. But VShell supports the SSH_FILEXFER_ATTR_ALLOCATION_SIZE. + // All servers should support SSH_FILEXFER_ATTR_SIZE (SFTP < 6) + (!FSupport->Loaded || FLAGSET(FSupport->AttributeMask, Packet.AllocationSizeAttribute(FVersion))); + Packet.AddProperties(nullptr, nullptr, nullptr, nullptr, nullptr, + SendSize ? &Size : nullptr, false, FVersion, FUtfStrings); + + SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE); + + return Packet.GetFileHandle(); +} + +intptr_t TSFTPFileSystem::SFTPOpenRemote(void * AOpenParams, void * /*Param2*/) +{ + TOpenRemoteFileParams * OpenParams = NB_STATIC_DOWNCAST(TOpenRemoteFileParams, AOpenParams); + DebugAssert(OpenParams); + TFileOperationProgressType * OperationProgress = OpenParams->OperationProgress; + + SSH_FXF_TYPES OpenType = 0; + bool Success = false; + bool ConfirmOverwriting = false; + + do + { + try + { + ConfirmOverwriting = + !OpenParams->Confirmed && !OpenParams->Resume && + FTerminal->CheckRemoteFile(OpenParams->FileName, OpenParams->CopyParam, OpenParams->Params, OperationProgress); + OpenType = SSH_FXF_WRITE | SSH_FXF_CREAT; + // when we want to preserve overwritten files, we need to find out that + // they exist first... even if overwrite confirmation is disabled. + // but not when we already know we are not going to overwrite (but e.g. to append) + if ((ConfirmOverwriting || GetSessionData()->GetOverwrittenToRecycleBin()) && + (OpenParams->OverwriteMode == omOverwrite)) + { + OpenType |= SSH_FXF_EXCL; + } + if (!OpenParams->Resuming && (OpenParams->OverwriteMode == omOverwrite)) + { + OpenType |= SSH_FXF_TRUNC; + } + if ((FVersion >= 4) && OpenParams->OperationProgress->AsciiTransfer) + { + OpenType |= SSH_FXF_TEXT; + } + + OpenParams->RemoteFileHandle = SFTPOpenRemoteFile( + OpenParams->RemoteFileName, OpenType, OperationProgress->LocalSize); + + Success = true; + } + catch (Exception & E) + { + if (!OpenParams->Confirmed && (OpenType & SSH_FXF_EXCL) && FTerminal->GetActive()) + { + FTerminal->LogEvent(FORMAT(L"Cannot create new file \"%s\", checking if it exists already", OpenParams->RemoteFileName.c_str())); + + bool ThrowOriginal = false; + + // When exclusive opening of file fails, try to detect if file exists. + // When file does not exist, failure was probably caused by 'permission denied' + // or similar error. In this case throw original exception. + try + { + OperationProgress->Progress(); + UnicodeString RealFileName = LocalCanonify(OpenParams->RemoteFileName); + TRemoteFile * File = nullptr; + ReadFile(RealFileName, File); + std::unique_ptr FilePtr(File); + DebugAssert(FilePtr.get()); + OpenParams->DestFileSize = FilePtr->GetSize(); + if (OpenParams->FileParams != nullptr) + { + OpenParams->FileParams->DestTimestamp = File->GetModification(); + OpenParams->FileParams->DestSize = OpenParams->DestFileSize; + } + // file exists (otherwise exception was thrown) + } + catch (...) + { + if (!FTerminal->GetActive()) + { + throw; + } + else + { + ThrowOriginal = true; + } + } + + if (ThrowOriginal) + { + throw; + } + + // we may get here even if confirmation is disabled, + // when we have preserving of overwritten files enabled + if (ConfirmOverwriting) + { + OperationProgress->Progress(); + // confirmation duplicated in SFTPSource for resumable file transfers. + UnicodeString RemoteFileNameOnly = base::UnixExtractFileName(OpenParams->RemoteFileName); + SFTPConfirmOverwrite(OpenParams->FileName, RemoteFileNameOnly, + OpenParams->CopyParam, OpenParams->Params, OperationProgress, OpenParams->FileParams, + OpenParams->OverwriteMode); + if (RemoteFileNameOnly != base::UnixExtractFileName(OpenParams->RemoteFileName)) + { + OpenParams->RemoteFileName = + core::UnixExtractFilePath(OpenParams->RemoteFileName) + RemoteFileNameOnly; + } + OpenParams->Confirmed = true; + } + else + { + DebugAssert(GetSessionData()->GetOverwrittenToRecycleBin()); + } + + if ((OpenParams->OverwriteMode == omOverwrite) && + GetSessionData()->GetOverwrittenToRecycleBin() && + !FTerminal->GetSessionData()->GetRecycleBinPath().IsEmpty()) + { + OperationProgress->Progress(); + FTerminal->RecycleFile(OpenParams->RemoteFileName, nullptr); + } + } + else if (FTerminal->GetActive()) + { + // if file overwriting was confirmed, it means that the file already exists, + // if not, check now + if (!OpenParams->Confirmed) + { + bool ThrowOriginal = false; + + // When file does not exist, failure was probably caused by 'permission denied' + // or similar error. In this case throw original exception. + try + { + TRemoteFile * File = nullptr; + UnicodeString RealFileName = LocalCanonify(OpenParams->RemoteFileName); + ReadFile(RealFileName, File); + SAFE_DESTROY(File); + } + catch (...) + { + if (!FTerminal->GetActive()) + { + throw; + } + else + { + ThrowOriginal = true; + } + } + + if (ThrowOriginal) + { + throw; + } + } + + // now we know that the file exists + + if (FTerminal->FileOperationLoopQuery(E, OperationProgress, + FMTLOAD(SFTP_OVERWRITE_FILE_ERROR2, OpenParams->RemoteFileName.c_str()), + true, LoadStr(SFTP_OVERWRITE_DELETE_BUTTON))) + { + OperationProgress->Progress(); + intptr_t Params = dfNoRecursive; + FTerminal->RemoteDeleteFile(OpenParams->RemoteFileName, nullptr, &Params); + } + } + else + { + throw; + } + } + } + while (!Success); + + return 0; +} + +void TSFTPFileSystem::SFTPCloseRemote(const RawByteString & Handle, + const UnicodeString & AFileName, TFileOperationProgressType * OperationProgress, + bool TransferFinished, bool Request, TSFTPPacket * Packet) +{ + // Moving this out of SFTPSource() fixed external exception 0xC0000029 error + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(SFTP_CLOSE_FILE_ERROR, AFileName.c_str()), "", + [&]() + { + try + { + TSFTPPacket CloseRequest(FCodePage); + TSFTPPacket * P = (Packet == nullptr ? &CloseRequest : Packet); + + if (Request) + { + P->ChangeType(SSH_FXP_CLOSE); + P->AddString(Handle); + SendPacket(P); + ReserveResponse(P, Packet); + } + else + { + DebugAssert(Packet != nullptr); + ReceiveResponse(P, Packet, SSH_FXP_STATUS); + } + } + catch (...) + { + if (!FTerminal->GetActive() || TransferFinished) + { + throw; + } + } + }); +} + +void TSFTPFileSystem::SFTPDirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, uintptr_t LocalFileAttrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + UnicodeString DestDirectoryName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(::ExcludeTrailingBackslash(DirectoryName), false), + osLocal, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = core::UnixIncludeTrailingBackslash(TargetDir + DestDirectoryName); + + OperationProgress->SetFile(DirectoryName); + + bool CreateDir = false; + try + { + TryOpenDirectory(DestFullName); + } + catch (...) + { + if (FTerminal->GetActive()) + { + // opening directory failed, it probably does not exists, try to + // create it + CreateDir = true; + } + else + { + throw; + } + } + + if (CreateDir) + { + TRemoteProperties Properties; + if (CopyParam->GetPreserveRights()) + { + Properties.Valid = TValidProperties() << vpRights; + Properties.Rights = CopyParam->RemoteFileRights(LocalFileAttrs); + } + FTerminal->RemoteCreateDirectory(DestFullName, &Properties); + Flags |= tfNewDirectory; + } + + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + TSearchRecChecked SearchRec; + bool FindOK = false; + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = + ::FindFirstChecked(DirectoryName + L"*.*", FindAttrs, SearchRec) == 0; + }); + + try__finally + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + while (FindOK && !OperationProgress->Cancel) + { + UnicodeString FileName = DirectoryName + SearchRec.Name; + try + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + { + SFTPSourceRobust(FileName, nullptr, DestFullName, CopyParam, Params, OperationProgress, + Flags & ~tfFirstLevel); + } + } + catch (ESkipFile & E) + { + // If ESkipFile occurs, just log it and continue with next file + TSuspendFileOperationProgress Suspend(OperationProgress); + // here a message to user was displayed, which was not appropriate + // when user refused to overwrite the file in subdirectory. + // hopefully it won't be missing in other situations. + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = (::FindNextChecked(SearchRec) == 0); + }); + } + } + __finally + { + FindClose(SearchRec); + }; + + /* TODO : Delete also read-only directories. */ + /* TODO : Show error message on failure. */ + if (!OperationProgress->Cancel) + { + if (CopyParam->GetPreserveTime() && CopyParam->GetPreserveTimeDirs()) + { + TRemoteProperties Properties; + Properties.Valid << vpModification; + + FTerminal->TerminalOpenLocalFile( + ::ExcludeTrailingBackslash(DirectoryName), GENERIC_READ, nullptr, nullptr, nullptr, + &Properties.Modification, &Properties.LastAccess, nullptr); + + FTerminal->ChangeFileProperties(DestFullName, nullptr, &Properties); + } + + if (FLAGSET(Params, cpDelete)) + { + FTerminal->RemoveLocalDirectory(ApiPath(DirectoryName)); + } + else if (CopyParam->GetClearArchive() && FLAGSET(LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DirectoryName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(DirectoryName, LocalFileAttrs & ~faArchive) == 0); + }); + } + } +} + +void TSFTPFileSystem::CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + DebugAssert(AFilesToCopy && OperationProgress); + + UnicodeString FileName; + UnicodeString FullTargetDir = ::IncludeTrailingBackslash(TargetDir); + intptr_t Index = 0; + while (Index < AFilesToCopy->GetCount() && !OperationProgress->Cancel) + { + bool Success = false; + FileName = AFilesToCopy->GetString(Index); + const TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(Index)); + + DebugAssert(!FAvoidBusy); + FAvoidBusy = true; + + try__finally + { + SCOPE_EXIT + { + FAvoidBusy = false; + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + UnicodeString TargetDirectory = CreateTargetDirectory(File->GetFileName(), FullTargetDir, CopyParam); + try + { + SFTPSinkRobust(LocalCanonify(FileName), File, TargetDirectory, CopyParam, + Params, OperationProgress, tfFirstLevel); + Success = true; + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + catch (...) + { + TODO("remove the block?"); + throw; + } + } + __finally + { + FAvoidBusy = false; + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TSFTPFileSystem::SFTPSinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TFTPFileSystem + + TDownloadSessionAction Action(FTerminal->GetActionLog()); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + + do + { + bool ChildError = false; + try + { + SFTPSink(AFileName, AFile, TargetDir, CopyParam, Params, OperationProgress, + Flags, Action, ChildError); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + if (!ChildError) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + DebugAssert(AFile != nullptr); + if (!AFile->GetIsDirectory()) + { + // prevent overwrite and resume confirmations + Params |= cpNoConfirmation; + } + } + } + while (RobustLoop.Retry()); +} + +void TSFTPFileSystem::SFTPSink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action, bool & ChildError) +{ + + Action.SetFileName(AFileName); + + UnicodeString OnlyFileName = base::UnixExtractFileName(AFileName); + + TFileMasks::TParams MaskParams; + DebugAssert(AFile); + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + + UnicodeString BaseFileName = FTerminal->GetBaseFileName(AFileName); + if (!CopyParam->AllowTransfer(BaseFileName, osRemote, AFile->GetIsDirectory(), MaskParams)) + { + FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", AFileName.c_str())); + ThrowSkipFileNull(); + } + + if (CopyParam->SkipTransfer(AFileName, AFile->GetIsDirectory())) + { + OperationProgress->AddSkippedFileSize(AFile->GetSize()); + ThrowSkipFileNull(); + } + + FTerminal->LogFileDetails(AFileName, AFile->GetModification(), AFile->GetSize()); + + OperationProgress->SetFile(AFileName); + + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::UnixExtractFileName(AFile->GetFileName()), osRemote, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = TargetDir + DestFileName; + + if (AFile->GetIsDirectory()) + { + Action.Cancel(); + if (FTerminal->CanRecurseToDirectory(AFile)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_DIRECTORY_ERROR, DestFullName.c_str()), "", + [&]() + { + DWORD LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && (LocalFileAttrs & faDirectory) == 0) + { + ThrowExtException(); + } + }); + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CREATE_DIR_ERROR, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::ForceDirectories(ApiPath(DestFullName))); + }); + + TSinkFileParams SinkFileParams; + SinkFileParams.TargetDir = ::IncludeTrailingBackslash(DestFullName); + SinkFileParams.CopyParam = CopyParam; + SinkFileParams.Params = Params; + SinkFileParams.OperationProgress = OperationProgress; + SinkFileParams.Skipped = false; + SinkFileParams.Flags = Flags & ~tfFirstLevel; + + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TSFTPFileSystem::SFTPSinkFile, this), &SinkFileParams); + + if (CopyParam->GetPreserveTime() && CopyParam->GetPreserveTimeDirs()) + { + FTerminal->LogEvent(FORMAT(L"Preserving directory timestamp [%s]", + StandardTimestamp(AFile->GetModification()).c_str())); + int SetFileTimeError = ERROR_SUCCESS; + // FILE_FLAG_BACKUP_SEMANTICS is needed to "open" directory + HANDLE LocalFileHandle = FTerminal->TerminalCreateLocalFile(DestFullName, GENERIC_WRITE, + FILE_SHARE_WRITE, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS); + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + SetFileTimeError = ::GetLastError(); + } + else + { + FILETIME AcTime = DateTimeToFileTime(AFile->GetLastAccess(), FTerminal->GetSessionData()->GetDSTMode()); + FILETIME WrTime = DateTimeToFileTime(AFile->GetModification(), FTerminal->GetSessionData()->GetDSTMode()); +#ifndef __linux__ + if (!::SetFileTime(LocalFileHandle, nullptr, &AcTime, &WrTime)) + { + SetFileTimeError = ::GetLastError(); + } +#endif + ::CloseHandle(LocalFileHandle); + } + + if (SetFileTimeError != ERROR_SUCCESS) + { + FTerminal->LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s", + SysErrorMessageForError(SetFileTimeError).c_str())); + } + } + + // Do not delete directory if some of its files were skipped. + // Throw "skip file" for the directory to avoid attempt to deletion + // of any parent directory + if ((Params & cpDelete) && SinkFileParams.Skipped) + { + ThrowSkipFileNull(); + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", AFileName.c_str())); + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", AFileName.c_str())); + + UnicodeString DestPartialFullName; + bool ResumeAllowed; + int64_t ResumeOffset = 0; + + // Will we use ASCII of BINARY file transfer? + OperationProgress->SetAsciiTransfer( + CopyParam->UseAsciiTransfer(BaseFileName, osRemote, MaskParams)); + FTerminal->LogEvent(UnicodeString((OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary")) + + L" transfer mode selected."); + + // Suppose same data size to transfer as to write + // (not true with ASCII transfer) + OperationProgress->SetTransferSize(AFile->GetSize()); + OperationProgress->SetLocalSize(OperationProgress->TransferSize); + + // resume has no sense for temporary downloads + ResumeAllowed = ((Params & cpTemporary) == 0) && + !OperationProgress->AsciiTransfer && + CopyParam->AllowResume(OperationProgress->TransferSize); + // OperationProgress->SetResumeStatus(ResumeAllowed ? rsEnabled : rsDisabled); + + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_FILE_ERROR, DestFullName.c_str()), "", + [&]() + { + LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && (LocalFileAttrs & faDirectory)) + { + ThrowExtException(); + } + }); + + OperationProgress->TransferingFile = false; // not set with SFTP protocol + + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + TStream * FileStream = nullptr; + bool DeleteLocalFile = false; + RawByteString RemoteHandle; + UnicodeString LocalFileName = DestFullName; + TOverwriteMode OverwriteMode = omOverwrite; + + try__finally + { + bool ResumeTransfer = false; + SCOPE_EXIT + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + if (FileStream) + { + SAFE_DESTROY(FileStream); + } + if (DeleteLocalFile && (!ResumeAllowed || OperationProgress->LocallyUsed == 0) && + (OverwriteMode == omOverwrite)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, LocalFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(LocalFileName)); + }); + } + + // if the transfer was finished, the file is closed already + if (FTerminal->GetActive() && !RemoteHandle.IsEmpty()) + { + // do not wait for response + SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress, + true, true, nullptr); + } + }; + if (ResumeAllowed) + { + DestPartialFullName = DestFullName + FTerminal->GetConfiguration()->GetPartialExt(); + LocalFileName = DestPartialFullName; + + FTerminal->LogEvent("Checking existence of partially transfered file."); + if (::FileExists(ApiPath(DestPartialFullName))) + { + FTerminal->LogEvent("Partially transfered file exists."); + FTerminal->TerminalOpenLocalFile(DestPartialFullName, GENERIC_WRITE, + &LocalFileHandle, nullptr, nullptr, nullptr, nullptr, &ResumeOffset); + + bool PartialBiggerThanSource = (ResumeOffset > OperationProgress->TransferSize); + if (FLAGCLEAR(Params, cpNoConfirmation)) + { + ResumeTransfer = SFTPConfirmResume(DestFileName, + PartialBiggerThanSource, OperationProgress); + } + else + { + ResumeTransfer = !PartialBiggerThanSource; + if (!ResumeTransfer) + { + FTerminal->LogEvent("Partially transfered file is bigger that original file."); + } + } + + if (!ResumeTransfer) + { + ::CloseHandle(LocalFileHandle); + LocalFileHandle = INVALID_HANDLE_VALUE; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, DestPartialFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(DestPartialFullName)); + }); + } + else + { + FTerminal->LogEvent("Resuming file transfer."); + ::FileSeek(LocalFileHandle, ResumeOffset, 0); + OperationProgress->AddResumed(ResumeOffset); + } + } + + OperationProgress->Progress(); + } + + // first open source file, not to loose the destination file, + // if we cannot open the source one in the first place + FTerminal->LogEvent("Opening remote file."); + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(SFTP_OPEN_FILE_ERROR, AFileName.c_str()), "", + [&]() + { + SSH_FXF_TYPES OpenType = SSH_FXF_READ; + if ((FVersion >= 4) && OperationProgress->AsciiTransfer) + { + OpenType |= SSH_FXF_TEXT; + } + RemoteHandle = SFTPOpenRemoteFile(AFileName, OpenType); + OperationProgress->Progress(); + }); + + TDateTime Modification(0.0); + FILETIME AcTime; + ClearStruct(AcTime); + FILETIME WrTime; + ClearStruct(WrTime); + + TSFTPPacket RemoteFilePacket(SSH_FXP_FSTAT, FCodePage); + RemoteFilePacket.AddString(RemoteHandle); + SendCustomReadFile(&RemoteFilePacket, &RemoteFilePacket, + SSH_FILEXFER_ATTR_MODIFYTIME); + ReceiveResponse(&RemoteFilePacket, &RemoteFilePacket); + OperationProgress->Progress(); + + const TRemoteFile * File = AFile; + std::unique_ptr FilePtr; + try__finally + { + SCOPE_EXIT + { + if (AFile != File) + { + FilePtr.reset(); + } + }; + // ignore errors + if (RemoteFilePacket.GetType() == SSH_FXP_ATTRS) + { + // load file, avoid completion (resolving symlinks) as we do not need that + File = LoadFile(&RemoteFilePacket, nullptr, base::UnixExtractFileName(AFileName), + nullptr, false); + FilePtr.reset(File); + } + + Modification = File->GetModification(); + AcTime = ::DateTimeToFileTime(File->GetLastAccess(), + FTerminal->GetSessionData()->GetDSTMode()); + WrTime = ::DateTimeToFileTime(Modification, + FTerminal->GetSessionData()->GetDSTMode()); + } + __finally + { + if (AFile != File) + { +// delete AFile; + } + }; + + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && !ResumeTransfer) + { + int64_t DestFileSize = 0; + int64_t MTime = 0; + FTerminal->TerminalOpenLocalFile(DestFullName, GENERIC_WRITE, + &LocalFileHandle, nullptr, nullptr, &MTime, nullptr, &DestFileSize, false); + + FTerminal->LogEvent("Confirming overwriting of file."); + TOverwriteFileParams FileParams; + FileParams.SourceSize = OperationProgress->TransferSize; + FileParams.SourceTimestamp = AFile->GetModification(); + FileParams.DestTimestamp = ::UnixToDateTime(MTime, + GetSessionData()->GetDSTMode()); + FileParams.DestSize = DestFileSize; + UnicodeString PrevDestFileName = DestFileName; + SFTPConfirmOverwrite(AFileName, DestFileName, CopyParam, Params, OperationProgress, &FileParams, OverwriteMode); + if (PrevDestFileName != DestFileName) + { + DestFullName = TargetDir + DestFileName; + DestPartialFullName = DestFullName + FTerminal->GetConfiguration()->GetPartialExt(); + if (ResumeAllowed) + { + if (::FileExists(ApiPath(DestPartialFullName))) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, DestPartialFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(DestPartialFullName)); + }); + } + LocalFileName = DestPartialFullName; + } + else + { + LocalFileName = DestFullName; + } + } + + if (OverwriteMode == omOverwrite) + { + // is NULL when overwriting read-only file + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + LocalFileHandle = INVALID_HANDLE_VALUE; + } + } + else + { + // is NULL when overwriting read-only file, so following will + // probably fail anyway + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + FTerminal->TerminalOpenLocalFile(DestFullName, GENERIC_WRITE, + &LocalFileHandle, nullptr, nullptr, nullptr, nullptr, nullptr); + } + ResumeAllowed = false; + ::FileSeek(LocalFileHandle, DestFileSize, 0); + if (OverwriteMode == omAppend) + { + FTerminal->LogEvent("Appending to file."); + } + else + { + FTerminal->LogEvent("Resuming file transfer (append style)."); + DebugAssert(OverwriteMode == omResume); + OperationProgress->AddResumed(DestFileSize); + } + } + } + + Action.Destination(::ExpandUNCFileName(DestFullName)); + + // if not already opened (resume, append...), create new empty file + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + if (!FTerminal->TerminalCreateLocalFile(LocalFileName, OperationProgress, + FLAGSET(Params, cpResume), FLAGSET(Params, cpNoConfirmation), + &LocalFileHandle)) + { + ThrowSkipFileNull(); + } + } + DebugAssert(LocalFileHandle != INVALID_HANDLE_VALUE); + + DeleteLocalFile = true; + + FileStream = new TSafeHandleStream(LocalFileHandle); + + // at end of this block queue is discarded + { + TSFTPDownloadQueue Queue(this, FCodePage); + try__finally + { + SCOPE_EXIT + { + Queue.DisposeSafe(); + }; + TSFTPPacket DataPacket(FCodePage); + + uintptr_t BlSize = DownloadBlockSize(OperationProgress); + intptr_t QueueLen = static_cast(AFile->GetSize() / (BlSize != 0 ? BlSize : 1)) + 1; + if ((QueueLen > GetSessionData()->GetSFTPDownloadQueue()) || + (QueueLen < 0)) + { + QueueLen = GetSessionData()->GetSFTPDownloadQueue(); + } + if (QueueLen < 1) + { + QueueLen = 1; + } + Queue.Init(QueueLen, RemoteHandle, OperationProgress->TransferedSize, + OperationProgress); + + bool Eof = false; + bool PrevIncomplete = false; + int32_t GapFillCount = 0; + int32_t GapCount = 0; + uint32_t MissingLen = 0; + uint32_t DataLen = 0; + uintptr_t BlockSize = 0; + bool ConvertToken = false; + + while (!Eof) + { + if (MissingLen > 0) + { + Queue.InitFillGapRequest(OperationProgress->TransferedSize, MissingLen, + &DataPacket); + GapFillCount++; + SendPacketAndReceiveResponse(&DataPacket, &DataPacket, + SSH_FXP_DATA, asEOF); + } + else + { + Queue.ReceivePacket(&DataPacket, BlockSize); + } + + if (DataPacket.GetType() == SSH_FXP_STATUS) + { + // must be SSH_FX_EOF, any other status packet would raise exception + Eof = true; + // close file right away, before waiting for pending responses + SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress, + true, true, nullptr); + RemoteHandle.Clear(); // do not close file again in __finally block + } + + if (!Eof) + { + if ((MissingLen == 0) && PrevIncomplete) + { + // This can happen only if last request returned less bytes + // than expected, but exactly number of bytes missing to last + // known file size, but actually EOF was not reached. + // Can happen only when filesize has changed since directory + // listing and server returns less bytes than requested and + // file has some special file size. + FTerminal->LogEvent(FORMAT( + L"Received incomplete data packet before end of file, " + L"offset: %s, size: %d, requested: %d", + ::Int64ToStr(OperationProgress->TransferedSize).c_str(), static_cast(DataLen), + static_cast(BlockSize))); + FTerminal->TerminalError(nullptr, LoadStr(SFTP_INCOMPLETE_BEFORE_EOF)); + } + + // Buffer for one block of data + TFileBuffer BlockBuf; + + DataLen = DataPacket.GetCardinal(); + + PrevIncomplete = false; + if (MissingLen > 0) + { + DebugAssert(DataLen <= MissingLen); + MissingLen -= DataLen; + } + else if (DataLen < BlockSize) + { + if (OperationProgress->TransferedSize + static_cast(DataLen) != + OperationProgress->TransferSize) + { + // with native text transfer mode (SFTP>=4), do not bother about + // getting less than requested, read offset is ignored anyway + if ((FVersion < 4) || !OperationProgress->AsciiTransfer) + { + GapCount++; + MissingLen = static_cast(BlockSize - DataLen); + } + } + else + { + PrevIncomplete = true; + } + } + + DebugAssert(DataLen <= BlockSize); + BlockBuf.Insert(0, reinterpret_cast(DataPacket.GetNextData(DataLen)), DataLen); + DataPacket.DataConsumed(DataLen); + OperationProgress->AddTransfered(DataLen); + + if ((FVersion >= 6) && DataPacket.CanGetBool() && (MissingLen == 0)) + { + Eof = DataPacket.GetBool(); + } + + if (OperationProgress->AsciiTransfer) + { + DebugAssert(!ResumeTransfer && !ResumeAllowed); + + int64_t PrevBlockSize = BlockBuf.GetSize(); + BlockBuf.Convert(GetEOL(), FTerminal->GetConfiguration()->GetLocalEOLType(), 0, ConvertToken); + OperationProgress->SetLocalSize( + OperationProgress->LocalSize - PrevBlockSize + BlockBuf.GetSize()); + } + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(WRITE_ERROR, LocalFileName.c_str()), "", + [&]() + { + BlockBuf.WriteToStream(FileStream, BlockBuf.GetSize()); + }); + + OperationProgress->AddLocallyUsed(BlockBuf.GetSize()); + } + + if (OperationProgress->Cancel == csCancel) + { + Abort(); + } + } + + if (GapCount > 0) + { + FTerminal->LogEvent(FORMAT( + L"%d requests to fill %d data gaps were issued.", + GapFillCount, GapCount)); + } + } + __finally + { + Queue.DisposeSafe(); + }; + // queue is discarded here + } + + if (CopyParam->GetPreserveTime()) + { + FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]", + StandardTimestamp(Modification).c_str())); +#ifndef __linux__ + SetFileTime(LocalFileHandle, nullptr, &AcTime, &WrTime); +#endif + } + + ::CloseHandle(LocalFileHandle); + LocalFileHandle = INVALID_HANDLE_VALUE; + + if (ResumeAllowed) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(RENAME_AFTER_RESUME_ERROR, + base::ExtractFileName(DestPartialFullName, true).c_str(), DestFileName.c_str()), "", + [&]() + { + if (::FileExists(ApiPath(DestFullName))) + { + ::DeleteFileChecked(DestFullName); + } + THROWOSIFFALSE(::RenameFile(DestPartialFullName, DestFullName)); + }); + } + + DeleteLocalFile = false; + + if (LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + LocalFileAttrs = faArchive; + } + DWORD NewAttrs = CopyParam->LocalFileAttrs(*AFile->GetRights()); + if ((NewAttrs & LocalFileAttrs) != NewAttrs) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(DestFullName), LocalFileAttrs | NewAttrs) == 0); + }); + } + + } + __finally + { + if (LocalFileHandle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(LocalFileHandle); + } + if (FileStream) + { + SAFE_DESTROY(FileStream); + } + if (DeleteLocalFile && (!ResumeAllowed || OperationProgress->LocallyUsed == 0) && + (OverwriteMode == omOverwrite)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, LocalFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(LocalFileName)); + }); + } + + // if the transfer was finished, the file is closed already + if (FTerminal->GetActive() && !RemoteHandle.IsEmpty()) + { + // do not wait for response + SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress, + true, true, nullptr); + } + }; + + FTerminal->LogFileDone(OperationProgress); + } + + if (Params & cpDelete) + { + ChildError = true; + // If file is directory, do not delete it recursively, because it should be + // empty already. If not, it should not be deleted (some files were + // skipped or some new files were copied to it, while we were downloading) + intptr_t Params2 = dfNoRecursive; + FTerminal->RemoteDeleteFile(AFileName, AFile, &Params2); + ChildError = false; + } +} + +void TSFTPFileSystem::SFTPSinkFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param) +{ + TSinkFileParams * Params = NB_STATIC_DOWNCAST(TSinkFileParams, Param); + DebugAssert(Params->OperationProgress); + try + { + SFTPSinkRobust(AFileName, AFile, Params->TargetDir, Params->CopyParam, + Params->Params, Params->OperationProgress, Params->Flags); + } + catch (ESkipFile & E) + { + TFileOperationProgressType * OperationProgress = Params->OperationProgress; + + Params->Skipped = true; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + if (OperationProgress->Cancel) + { + Abort(); + } + } +} + +void TSFTPFileSystem::RegisterChecksumAlg(const UnicodeString & Alg, const UnicodeString & SftpAlg) +{ + FChecksumAlgs->Add(Alg); + FChecksumSftpAlgs->Add(SftpAlg); +} + +void TSFTPFileSystem::GetSupportedChecksumAlgs(TStrings * Algs) +{ + Algs->AddStrings(FChecksumAlgs.get()); +} + +void TSFTPFileSystem::LockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TSFTPFileSystem::UnlockFile(const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/) +{ + DebugFail(); +} + +void TSFTPFileSystem::UpdateFromMain(TCustomFileSystem * /*MainFileSystem*/) +{ + // noop +} + +NB_IMPLEMENT_CLASS(TSFTPPacket, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TSFTPQueuePacket, NB_GET_CLASS_INFO(TSFTPPacket), nullptr) + diff --git a/netbox/src/core/SftpFileSystem.h b/netbox/src/core/SftpFileSystem.h new file mode 100644 index 000000000..51f205347 --- /dev/null +++ b/netbox/src/core/SftpFileSystem.h @@ -0,0 +1,221 @@ +#pragma once + +#include + +typedef int32_t SSH_FX_TYPES; +typedef uint32_t SSH_FXP_TYPES; +typedef uint32_t SSH_FILEXFER_ATTR_TYPES; +typedef uint8_t SSH_FILEXFER_TYPES; +typedef uint32_t SSH_FXF_TYPES; +typedef uint32_t ACE4_TYPES; + +class TSFTPPacket; +struct TOverwriteFileParams; +struct TSFTPSupport; +class TSecureShell; + +//enum TSFTPOverwriteMode { omOverwrite, omAppend, omResume }; + +class TSFTPFileSystem : public TCustomFileSystem +{ +NB_DISABLE_COPY(TSFTPFileSystem) +friend class TSFTPPacket; +friend class TSFTPQueue; +friend class TSFTPAsynchronousQueue; +friend class TSFTPUploadQueue; +friend class TSFTPDownloadQueue; +friend class TSFTPLoadFilesPropertiesQueue; +friend class TSFTPCalculateFilesChecksumQueue; +friend class TSFTPBusy; +public: + explicit TSFTPFileSystem(TTerminal * ATermina); + virtual ~TSFTPFileSystem(); + + virtual void Init(void * Data /*TSecureShell* */); + virtual void FileTransferProgress(int64_t /*TransferSize*/, int64_t /*Bytes*/) {} + + virtual void Open(); + virtual void Close(); + virtual bool GetActive() const; + virtual void CollectUsage(); + virtual void Idle(); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const; + virtual void AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent); + virtual void ChangeDirectory(const UnicodeString & Directory); + virtual void CachedChangeDirectory(const UnicodeString & Directory); + virtual void AnnounceFileListOperation(); + virtual void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action); + virtual bool LoadFilesProperties(TStrings * AFileList); + virtual void CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum); + virtual void CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void RemoteCreateDirectory(const UnicodeString & ADirName); + virtual void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic); + virtual void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action); + virtual void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent); + virtual void DoStartup(); + virtual void HomeDirectory(); + virtual bool IsCapable(intptr_t Capability) const; + virtual void LookupUsersGroups(); + virtual void ReadCurrentDirectory(); + virtual void ReadDirectory(TRemoteFileList * FileList); + virtual void ReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile); + virtual void ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& AFile); + virtual void RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual void RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual TStrings * GetFixedPaths(); + virtual void SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable); + virtual const TSessionInfo & GetSessionInfo() const; + virtual const TFileSystemInfo & GetFileSystemInfo(bool Retrieve); + virtual bool TemporaryTransferFile(const UnicodeString & AFileName); + virtual bool GetStoredCredentialsTried() const; + virtual UnicodeString FSGetUserName() const; + virtual void GetSupportedChecksumAlgs(TStrings * Algs); + virtual void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UpdateFromMain(TCustomFileSystem * MainFileSystem); + +protected: + TSecureShell * FSecureShell; + TFileSystemInfo FFileSystemInfo; + bool FFileSystemInfoValid; + intptr_t FVersion; + UnicodeString FCurrentDirectory; + UnicodeString FDirectoryToChangeTo; + UnicodeString FHomeDirectory; + AnsiString FEOL; + TList * FPacketReservations; + std::vector FPacketNumbers; + SSH_FXP_TYPES FPreviousLoggedPacket; + int FNotLoggedPackets; + int FBusy; + void * FBusyToken; + bool FAvoidBusy; + TStrings * FExtensions; + TSFTPSupport * FSupport; + TAutoSwitch FUtfStrings; + bool FUtfDisablingAnnounced; + bool FSignedTS; + TStrings * FFixedPaths; + uint32_t FMaxPacketSize; + bool FSupportsStatVfsV2; + uintptr_t FCodePage; + bool FSupportsHardlink; + std::unique_ptr FChecksumAlgs; + std::unique_ptr FChecksumSftpAlgs; + + void SendCustomReadFile(TSFTPPacket * Packet, TSFTPPacket * Response, + uint32_t Flags); + void CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile, SSH_FXP_TYPES Type, TRemoteFile * ALinkedByFile = nullptr, + SSH_FX_TYPES AllowStatus = -1); + virtual UnicodeString GetCurrDirectory() const; + UnicodeString GetHomeDirectory(); + SSH_FX_TYPES GotStatusPacket(TSFTPPacket * Packet, SSH_FX_TYPES AllowStatus); + bool RemoteFileExists(const UnicodeString & FullPath, TRemoteFile ** AFile = nullptr); + TRemoteFile * LoadFile(TSFTPPacket * Packet, + TRemoteFile * ALinkedByFile, const UnicodeString & AFileName, + TRemoteFileList * TempFileList = nullptr, bool Complete = true); + void LoadFile(TRemoteFile * AFile, TSFTPPacket * Packet, + bool Complete = true); + UnicodeString LocalCanonify(const UnicodeString & APath) const; + UnicodeString Canonify(const UnicodeString & APath); + UnicodeString GetRealPath(const UnicodeString & APath); + UnicodeString GetRealPath(const UnicodeString & APath, const UnicodeString & ABaseDir); + void ReserveResponse(const TSFTPPacket * Packet, + TSFTPPacket * Response); + SSH_FX_TYPES ReceivePacket(TSFTPPacket * Packet, SSH_FXP_TYPES ExpectedType = -1, + SSH_FX_TYPES AllowStatus = -1, bool TryOnly = false); + bool PeekPacket(); + void RemoveReservation(intptr_t Reservation); + void SendPacket(const TSFTPPacket * Packet); + SSH_FX_TYPES ReceiveResponse(const TSFTPPacket * Packet, + TSFTPPacket * AResponse, SSH_FXP_TYPES ExpectedType = -1, SSH_FX_TYPES AllowStatus = -1, bool TryOnly = false); + SSH_FX_TYPES SendPacketAndReceiveResponse(const TSFTPPacket * Packet, + TSFTPPacket * Response, SSH_FXP_TYPES ExpectedType = -1, SSH_FX_TYPES AllowStatus = -1); + void UnreserveResponse(TSFTPPacket * Response); + void TryOpenDirectory(const UnicodeString & Directory); + bool SupportsExtension(const UnicodeString & Extension) const; + void ResetConnection(); + void DoCalculateFilesChecksum( + const UnicodeString & Alg, const UnicodeString & SftpAlg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum, + TFileOperationProgressType * OperationProgress, bool FirstLevel); + void RegisterChecksumAlg(const UnicodeString & Alg, const UnicodeString & SftpAlg); + void DoDeleteFile(const UnicodeString & AFileName, SSH_FXP_TYPES Type); + + void SFTPSourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void SFTPSource(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TOpenRemoteFileParams & OpenParams, + TOverwriteFileParams & FileParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action, bool & ChildError); + RawByteString SFTPOpenRemoteFile(const UnicodeString & AFileName, + SSH_FXF_TYPES OpenType, int64_t Size = -1); + intptr_t SFTPOpenRemote(void * AOpenParams, void * Param2); + void SFTPCloseRemote(const RawByteString & Handle, + const UnicodeString & AFileName, TFileOperationProgressType * OperationProgress, + bool TransferFinished, bool Request, TSFTPPacket * Packet); + void SFTPDirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, uintptr_t LocalFileAttrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void SFTPConfirmOverwrite(const UnicodeString & AFullFileName, UnicodeString & AFileName, + const TCopyParamType * CopyParam, intptr_t AParams, TFileOperationProgressType * OperationProgress, + const TOverwriteFileParams * FileParams, + OUT TOverwriteMode & Mode); + bool SFTPConfirmResume(const UnicodeString & DestFileName, bool PartialBiggerThanSource, + TFileOperationProgressType * OperationProgress); + void SFTPSinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void SFTPSink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action, bool & ChildError); + void SFTPSinkFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param); + char * GetEOL() const; + inline void BusyStart(); + inline void BusyEnd(); + inline uint32_t TransferBlockSize(uint32_t Overhead, + TFileOperationProgressType * OperationProgress, + uint32_t MinPacketSize = 0, + uint32_t MaxPacketSize = 0); + inline uint32_t UploadBlockSize(const RawByteString & Handle, + TFileOperationProgressType * OperationProgress); + inline uint32_t DownloadBlockSize( + TFileOperationProgressType * OperationProgress); + inline intptr_t PacketLength(uint8_t * LenBuf, SSH_FXP_TYPES ExpectedType); + void Progress(TFileOperationProgressType * OperationProgress); + +private: + const TSessionData * GetSessionData() const; +}; + diff --git a/netbox/src/core/Terminal.cpp b/netbox/src/core/Terminal.cpp new file mode 100644 index 000000000..677330738 --- /dev/null +++ b/netbox/src/core/Terminal.cpp @@ -0,0 +1,6844 @@ + +#include +#pragma hdrstop + +#include "Terminal.h" + +#include +#include +#include +#include + +#include "PuttyTools.h" +#include "Interface.h" +#include "RemoteFiles.h" +#include "SecureShell.h" +#include "ScpFileSystem.h" +#include "SftpFileSystem.h" +#ifndef NO_FILEZILLA +#include "FtpFileSystem.h" +#endif +#include "WebDAVFileSystem.h" +#include "TextsCore.h" +#include "HelpCore.h" +#include "CoreMain.h" +#include "Queue.h" +#include +#include + +#if !defined(AUTO_WINSOCK) && !defined(__linux__) +#include +#endif + +///* TODO : Better user interface (query to user) */ +void FileOperationLoopCustom(TTerminal * Terminal, + TFileOperationProgressType * OperationProgress, + bool AllowSkip, const UnicodeString & Message, + const UnicodeString & HelpKeyword, + const std::function & Operation) +{ + bool DoRepeat; + do + { + DoRepeat = false; + try + { + Operation(); + } + catch (EAbort &) + { + throw; + } + catch (ESkipFile &) + { + throw; + } + catch (EFatal &) + { + throw; + } + catch (EFileNotFoundError &) + { + throw; + } + catch (EOSError &) + { + throw; + } + catch (Exception & E) + { + Terminal->FileOperationLoopQuery( + E, OperationProgress, Message, AllowSkip, L"", HelpKeyword); + DoRepeat = true; + } + } + while (DoRepeat); +} + +class TLoopDetector : public TObject +{ +public: + TLoopDetector(); + void RecordVisitedDirectory(const UnicodeString & Directory); + bool IsUnvisitedDirectory(const UnicodeString & Directory); + +private: + std::unique_ptr FVisitedDirectories; +}; + +TLoopDetector::TLoopDetector() +{ + FVisitedDirectories.reset(CreateSortedStringList()); +} + +void TLoopDetector::RecordVisitedDirectory(const UnicodeString & Directory) +{ + UnicodeString VisitedDirectory = ::ExcludeTrailingBackslash(Directory); + FVisitedDirectories->Add(VisitedDirectory); +} + +bool TLoopDetector::IsUnvisitedDirectory(const UnicodeString & Directory) +{ + bool Result = (FVisitedDirectories->IndexOf(Directory) < 0); + + if (Result) + { + RecordVisitedDirectory(Directory); + } + + return Result; +} + +struct TMoveFileParams : public TObject +{ +NB_DECLARE_CLASS(TMoveFileParams) +public: + UnicodeString Target; + UnicodeString FileMask; +}; + +struct TFilesFindParams : public TObject +{ +NB_DECLARE_CLASS(TFilesFindParams) +public: + TFilesFindParams() : + OnFileFound(nullptr), + OnFindingFile(nullptr), + Cancel(false) + { + } + TFileMasks FileMask; + TFileFoundEvent OnFileFound; + TFindingFileEvent OnFindingFile; + bool Cancel; + TLoopDetector LoopDetector; + UnicodeString RealDirectory; +}; + +TCalculateSizeStats::TCalculateSizeStats() : + Files(0), + Directories(0), + SymLinks(0) +{ +// memset(this, 0, sizeof(*this)); +} + +TSynchronizeOptions::TSynchronizeOptions() : + Filter(0) +{ +// memset(this, 0, sizeof(*this)); +} + +TSynchronizeOptions::~TSynchronizeOptions() +{ + SAFE_DESTROY(Filter); +} + +bool TSynchronizeOptions::MatchesFilter(const UnicodeString & AFileName) +{ + bool Result = false; + if (Filter == nullptr) + { + Result = true; + } + else + { + intptr_t FoundIndex = 0; + Result = Filter->Find(AFileName, FoundIndex); + } + return Result; +} + +TSpaceAvailable::TSpaceAvailable() : + BytesOnDevice(0), + UnusedBytesOnDevice(0), + BytesAvailableToUser(0), + UnusedBytesAvailableToUser(0), + BytesPerAllocationUnit(0) +{ +// memset(this, 0, sizeof(*this)); +} + +TChecklistItem::TChecklistItem() : + Action(saNone), + IsDirectory(false), + ImageIndex(-1), + Checked(true), + RemoteFile(nullptr) +{ + Local.ModificationFmt = mfFull; + Local.Modification = 0; + Local.Size = 0; + Remote.ModificationFmt = mfFull; + Remote.Modification = 0; + Remote.Size = 0; + FLocalLastWriteTime.dwHighDateTime = 0; + FLocalLastWriteTime.dwLowDateTime = 0; +} + +TChecklistItem::~TChecklistItem() +{ + SAFE_DESTROY(RemoteFile); +} + +const UnicodeString TChecklistItem::GetFileName() const +{ + if (!Remote.FileName.IsEmpty()) + { + return Remote.FileName; + } + else + { + DebugAssert(!Local.FileName.IsEmpty()); + return Local.FileName; + } +} + +TSynchronizeChecklist::TSynchronizeChecklist() +{ +} + +TSynchronizeChecklist::~TSynchronizeChecklist() +{ + for (intptr_t Index = 0; Index < FList.GetCount(); ++Index) + { + TChecklistItem * Item = NB_STATIC_DOWNCAST(TChecklistItem, static_cast(FList.GetItem(Index))); + SAFE_DESTROY(Item); + } +} + +void TSynchronizeChecklist::Add(TChecklistItem * Item) +{ + FList.Add(Item); +} + +intptr_t TSynchronizeChecklist::Compare(const void * AItem1, const void * AItem2) +{ + const TChecklistItem * Item1 = NB_STATIC_DOWNCAST_CONST(TChecklistItem, AItem1); + const TChecklistItem * Item2 = NB_STATIC_DOWNCAST_CONST(TChecklistItem, AItem2); + + intptr_t Result; + if (!Item1->Local.Directory.IsEmpty()) + { + Result = ::AnsiCompareText(Item1->Local.Directory, Item2->Local.Directory); + } + else + { + DebugAssert(!Item1->Remote.Directory.IsEmpty()); + Result = ::AnsiCompareText(Item1->Remote.Directory, Item2->Remote.Directory); + } + + if (Result == 0) + { + Result = ::AnsiCompareText(Item1->GetFileName(), Item2->GetFileName()); + } + + return Result; +} + +void TSynchronizeChecklist::Sort() +{ + FList.Sort(Compare); +} + +intptr_t TSynchronizeChecklist::GetCount() const +{ + return FList.GetCount(); +} + +const TChecklistItem * TSynchronizeChecklist::GetItem(intptr_t Index) const +{ + return NB_STATIC_DOWNCAST(TChecklistItem, FList.GetItem(Index)); +} + +void TSynchronizeChecklist::Update(const TChecklistItem * Item, bool Check, TChecklistAction Action) +{ + // TSynchronizeChecklist owns non-const items so it can manipulate them freely, + // const_cast here is just an optimization + TChecklistItem * MutableItem = const_cast(Item); + DebugAssert(FList.IndexOf(MutableItem) >= 0); + MutableItem->Checked = Check; + MutableItem->Action = Action; +} + +TChecklistAction TSynchronizeChecklist::Reverse(TChecklistAction Action) +{ + switch (Action) + { + case saUploadNew: + return saDeleteLocal; + + case saDownloadNew: + return saDeleteRemote; + + case saUploadUpdate: + return saDownloadUpdate; + + case saDownloadUpdate: + return saUploadUpdate; + + case saDeleteRemote: + return saDownloadNew; + + case saDeleteLocal: + return saUploadNew; + + default: + case saNone: + DebugFail(); + return saNone; + } +} + + +class TTunnelThread : public TSimpleThread +{ +NB_DISABLE_COPY(TTunnelThread) +public: + explicit TTunnelThread(TSecureShell * SecureShell); + virtual ~TTunnelThread(); + + virtual void Init(); + virtual void Terminate(); + +protected: + virtual void Execute(); + +private: + TSecureShell * FSecureShell; + bool FTerminated; +}; + +TTunnelThread::TTunnelThread(TSecureShell * SecureShell) : + TSimpleThread(), + FSecureShell(SecureShell), + FTerminated(false) +{ +} + +void TTunnelThread::Init() +{ + TSimpleThread::Init(); + Start(); +} + +TTunnelThread::~TTunnelThread() +{ + // close before the class's virtual functions (Terminate particularly) are lost + Close(); +} + +void TTunnelThread::Terminate() +{ + FTerminated = true; +} + +void TTunnelThread::Execute() +{ + try + { + while (!FTerminated) + { + FSecureShell->Idle(250); + } + } + catch (...) + { + if (FSecureShell->GetActive()) + { + FSecureShell->Close(); + } + // do not pass exception out of thread's proc + } +} + + +class TTunnelUI : public TSessionUI +{ +NB_DISABLE_COPY(TTunnelUI) +public: + explicit TTunnelUI(TTerminal * Terminal); + virtual ~TTunnelUI() {} + virtual void Information(const UnicodeString & Str, bool Status); + virtual uintptr_t QueryUser(const UnicodeString & Query, + TStrings * MoreMessages, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType); + virtual uintptr_t QueryUserException(const UnicodeString & Query, + Exception * E, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType); + virtual bool PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & AInstructions, TStrings * Prompts, + TStrings * Results); + virtual void DisplayBanner(const UnicodeString & Banner); + virtual void FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpContext); + virtual void HandleExtendedException(Exception * E); + virtual void Closed(); + virtual void ProcessGUI(); + +private: + TTerminal * FTerminal; + uint32_t FTerminalThread; +}; + +TTunnelUI::TTunnelUI(TTerminal * Terminal) +{ + FTerminal = Terminal; + FTerminalThread = GetCurrentThreadId(); +} + +void TTunnelUI::Information(const UnicodeString & Str, bool Status) +{ + if (GetCurrentThreadId() == FTerminalThread) + { + FTerminal->Information(Str, Status); + } +} + +uintptr_t TTunnelUI::QueryUser(const UnicodeString & Query, + TStrings * MoreMessages, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType) +{ + uintptr_t Result; + if (GetCurrentThreadId() == FTerminalThread) + { + Result = FTerminal->QueryUser(Query, MoreMessages, Answers, Params, QueryType); + } + else + { + Result = AbortAnswer(Answers); + } + return Result; +} + +uintptr_t TTunnelUI::QueryUserException(const UnicodeString & Query, + Exception * E, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType) +{ + uintptr_t Result; + if (GetCurrentThreadId() == FTerminalThread) + { + Result = FTerminal->QueryUserException(Query, E, Answers, Params, QueryType); + } + else + { + Result = AbortAnswer(static_cast(Answers)); + } + return Result; +} + +bool TTunnelUI::PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & AInstructions, TStrings * Prompts, TStrings * Results) +{ + bool Result = false; + if (GetCurrentThreadId() == FTerminalThread) + { + UnicodeString Instructions = AInstructions; + if (IsAuthenticationPrompt(Kind)) + { + Instructions = LoadStr(TUNNEL_INSTRUCTION) + + (AInstructions.IsEmpty() ? L"" : L"\n") + + AInstructions; + } + + Result = FTerminal->PromptUser(Data, Kind, AName, Instructions, Prompts, Results); + } + return Result; +} + +void TTunnelUI::DisplayBanner(const UnicodeString & Banner) +{ + if (GetCurrentThreadId() == FTerminalThread) + { + FTerminal->DisplayBanner(Banner); + } +} + +void TTunnelUI::FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) +{ + throw ESshFatal(E, Msg, HelpKeyword); +} + +void TTunnelUI::HandleExtendedException(Exception * E) +{ + if (GetCurrentThreadId() == FTerminalThread) + { + FTerminal->HandleExtendedException(E); + } +} + +void TTunnelUI::Closed() +{ + // noop +} + +void TTunnelUI::ProcessGUI() +{ + // noop +} + +class TCallbackGuard : public TObject +{ +NB_DISABLE_COPY(TCallbackGuard) +public: + explicit TCallbackGuard(TTerminal * FTerminal); + inline ~TCallbackGuard(); + + void FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword); + inline void Verify(); + void Dismiss(); + +private: + ExtException * FFatalError; + TTerminal * FTerminal; + bool FGuarding; +}; + +TCallbackGuard::TCallbackGuard(TTerminal * Terminal) : + FFatalError(nullptr), + FTerminal(Terminal), + FGuarding(FTerminal->FCallbackGuard == nullptr) +{ + if (FGuarding) + { + FTerminal->FCallbackGuard = this; + } +} + +TCallbackGuard::~TCallbackGuard() +{ + if (FGuarding) + { + DebugAssert((FTerminal->FCallbackGuard == this) || (FTerminal->FCallbackGuard == nullptr)); + FTerminal->FCallbackGuard = nullptr; + } + + SAFE_DESTROY(FFatalError); +} + +void TCallbackGuard::FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) +{ + DebugAssert(FGuarding); + + // make sure we do not bother about getting back the silent abort exception + // we issued ourselves. this may happen when there is an exception handler + // that converts any exception to fatal one (such as in TTerminal::Open). + if (NB_STATIC_DOWNCAST(ECallbackGuardAbort, E) == nullptr) + { + SAFE_DESTROY(FFatalError); + FFatalError = new ExtException(E, Msg, HelpKeyword); + } + + // silently abort what we are doing. + // non-silent exception would be caught probably by default application + // exception handler, which may not do an appropriate action + // (particularly it will not resume broken transfer). + throw ECallbackGuardAbort(); +} + +void TCallbackGuard::Dismiss() +{ + DebugAssert(FFatalError == nullptr); + FGuarding = false; +} + +void TCallbackGuard::Verify() +{ + if (FGuarding) + { + FGuarding = false; + DebugAssert(FTerminal->FCallbackGuard == this); + FTerminal->FCallbackGuard = nullptr; + + if (FFatalError != nullptr) + { + throw ESshFatal(FFatalError, L""); + } + } +} + + +TRobustOperationLoop::TRobustOperationLoop(TTerminal * Terminal, TFileOperationProgressType * OperationProgress) : + FTerminal(Terminal), + FOperationProgress(OperationProgress), + FRetry(false) +{ +} + +bool TRobustOperationLoop::TryReopen(Exception & E) +{ + FRetry = FTerminal && + !FTerminal->GetActive() && + FTerminal->QueryReopen(&E, ropNoReadDirectory, FOperationProgress); + return FRetry; +} + +bool TRobustOperationLoop::ShouldRetry() const +{ + return FRetry; +} + +bool TRobustOperationLoop::Retry() +{ + bool Result = FRetry; + FRetry = false; + return Result; +} + +class TRetryOperationLoop +{ +public: + explicit TRetryOperationLoop(TTerminal * Terminal); + + void Error(Exception & E); + void Error(Exception & E, TSessionAction & Action); + void Error(Exception & E, const UnicodeString & Message); + void Error(Exception & E, TSessionAction & Action, const UnicodeString & Message); + bool Retry(); + +private: + TTerminal * FTerminal; + bool FRetry; + + void DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message); +}; + +TRetryOperationLoop::TRetryOperationLoop(TTerminal * Terminal) +{ + FTerminal = Terminal; + FRetry = false; +} + +void TRetryOperationLoop::DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message) +{ + // Note that the action may already be canceled when RollbackAction is called + uintptr_t Result; + try + { + Result = FTerminal->CommandError(&E, Message, qaRetry | qaSkip | qaAbort); + } + catch (Exception & E2) + { + if (Action != nullptr) + { + FTerminal->RollbackAction(*Action, nullptr, &E2); + } + throw; + } + + switch (Result) + { + case qaRetry: + FRetry = true; + if (Action != nullptr) + { + Action->Cancel(); + } + break; + + case qaAbort: + if (Action != nullptr) + { + FTerminal->RollbackAction(*Action, nullptr, &E); + } + Abort(); + break; + + case qaSkip: + if (Action != nullptr) + { + Action->Cancel(); + } + break; + + default: + DebugFail(); + break; + } +} + +void TRetryOperationLoop::Error(Exception & E) +{ + DoError(E, nullptr, UnicodeString()); +} + +void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action) +{ + DoError(E, &Action, UnicodeString()); +} + +void TRetryOperationLoop::Error(Exception & E, const UnicodeString & Message) +{ + DoError(E, nullptr, Message); +} + +void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action, const UnicodeString & Message) +{ + DoError(E, &Action, Message); +} + +bool TRetryOperationLoop::Retry() +{ + bool Result = FRetry; + FRetry = false; + return Result; +} + + +TTerminal::TTerminal() : + TObject(), + TSessionUI(), + FReadCurrentDirectoryPending(false), + FReadDirectoryPending(false), + FTunnelOpening(false), + FSessionData(nullptr), + FLog(nullptr), + FActionLog(nullptr), + FConfiguration(nullptr), + FExceptionOnFail(0), + FFiles(nullptr), + FInTransaction(0), + FSuspendTransaction(false), + FOnChangeDirectory(nullptr), + FOnReadDirectory(nullptr), + FOnStartReadDirectory(nullptr), + FOnReadDirectoryProgress(nullptr), + FOnDeleteLocalFile(nullptr), + FOnCreateLocalFile(nullptr), + FOnGetLocalFileAttributes(nullptr), + FOnSetLocalFileAttributes(nullptr), + FOnMoveLocalFile(nullptr), + FOnRemoveLocalDirectory(nullptr), + FOnCreateLocalDirectory(nullptr), + FOnInitializeLog(nullptr), + FUsersGroupsLookedup(false), + FOperationProgress(nullptr), + FUseBusyCursor(false), + FDirectoryCache(nullptr), + FDirectoryChangesCache(nullptr), + FFileSystem(nullptr), + FSecureShell(nullptr), + FFSProtocol(cfsUnknown), + FCommandSession(nullptr), + FAutoReadDirectory(false), + FReadingCurrentDirectory(false), + FClosedOnCompletion(nullptr), + FStatus(ssClosed), + FOpening(0), + FTunnelThread(nullptr), + FTunnel(nullptr), + FTunnelData(nullptr), + FTunnelLog(nullptr), + FTunnelUI(nullptr), + FTunnelLocalPortNumber(0), + FCallbackGuard(nullptr), + FEnableSecureShellUsage(false), + FCollectFileSystemUsage(false), + FRememberedPasswordTried(false), + FRememberedTunnelPasswordTried(false), + FNesting(0) +{ + FOldFiles = new TRemoteDirectory(this); +} + +TTerminal::~TTerminal() +{ + if (GetActive()) + { + Close(); + } + + if (FCallbackGuard != nullptr) + { + // see TTerminal::HandleExtendedException + FCallbackGuard->Dismiss(); + } + DebugAssert(FTunnel == nullptr); + + SAFE_DESTROY(FCommandSession); + + if (GetSessionData()->GetCacheDirectoryChanges() && GetSessionData()->GetPreserveDirectoryChanges() && + (FDirectoryChangesCache != nullptr)) + { + FConfiguration->SaveDirectoryChangesCache(GetSessionData()->GetSessionKey(), + FDirectoryChangesCache); + } + + SAFE_DESTROY_EX(TCustomFileSystem, FFileSystem); + SAFE_DESTROY_EX(TSessionLog, FLog); + SAFE_DESTROY_EX(TActionLog, FActionLog); + SAFE_DESTROY(FFiles); + SAFE_DESTROY_EX(TRemoteDirectoryCache, FDirectoryCache); + SAFE_DESTROY_EX(TRemoteDirectoryChangesCache, FDirectoryChangesCache); + SAFE_DESTROY(FSessionData); + SAFE_DESTROY(FOldFiles); +} + +void TTerminal::Init(TSessionData * SessionData, TConfiguration * Configuration) +{ + FConfiguration = Configuration; + FSessionData = new TSessionData(L""); + FSessionData->Assign(SessionData); + FLog = new TSessionLog(this, FSessionData, FConfiguration); + FActionLog = new TActionLog(this, FSessionData, FConfiguration); + FFiles = new TRemoteDirectory(this); + FExceptionOnFail = 0; + FInTransaction = 0; + FReadCurrentDirectoryPending = false; + FReadDirectoryPending = false; + FUsersGroupsLookedup = False; + FTunnelLocalPortNumber = 0; + FFileSystem = nullptr; + FSecureShell = nullptr; + FOnProgress = nullptr; + FOnFinished = nullptr; + FOnDeleteLocalFile = nullptr; + FOnCreateLocalFile = nullptr; + FOnGetLocalFileAttributes = nullptr; + FOnSetLocalFileAttributes = nullptr; + FOnMoveLocalFile = nullptr; + FOnRemoveLocalDirectory = nullptr; + FOnCreateLocalDirectory = nullptr; + FOnReadDirectoryProgress = nullptr; + FOnQueryUser = nullptr; + FOnPromptUser = nullptr; + FOnDisplayBanner = nullptr; + FOnShowExtendedException = nullptr; + FOnInformation = nullptr; + FOnClose = nullptr; + FOnFindingFile = nullptr; + + FUseBusyCursor = True; + FLockDirectory.Clear(); + FDirectoryCache = new TRemoteDirectoryCache(); + FDirectoryChangesCache = nullptr; + FFSProtocol = cfsUnknown; + FCommandSession = nullptr; + FAutoReadDirectory = true; + FReadingCurrentDirectory = false; + FStatus = ssClosed; + FTunnelThread = nullptr; + FTunnel = nullptr; + FTunnelData = nullptr; + FTunnelLog = nullptr; + FTunnelUI = nullptr; + FTunnelOpening = false; + FCallbackGuard = nullptr; + FEnableSecureShellUsage = false; + FCollectFileSystemUsage = false; + FSuspendTransaction = false; + FOperationProgress = nullptr; + FClosedOnCompletion = nullptr; +} + +void TTerminal::Idle() +{ + // Once we disconnect, do nothing, until reconnect handler + // "receives the information". + // Never go idle when called from within ::ProcessGUI() call + // as we may recurse for good, timeouting eventually. + if (GetActive() && (FNesting == 0)) + { + TAutoNestingCounter NestingCounter(FNesting); + + if (FConfiguration->GetActualLogProtocol() >= 1) + { + // LogEvent("Session upkeep"); + } + + DebugAssert(FFileSystem != nullptr); + FFileSystem->Idle(); + + if (GetCommandSessionOpened()) + { + try + { + FCommandSession->Idle(); + } + catch (Exception & E) + { + // If the secondary session is dropped, ignore the error and let + // it be reconnected when needed. + // BTW, non-fatal error can hardly happen here, that's why + // it is displayed, because it can be useful to know. + if (FCommandSession->GetActive()) + { + FCommandSession->HandleExtendedException(&E); + } + } + } + } +} + +RawByteString TTerminal::EncryptPassword(const UnicodeString & APassword) const +{ + return FConfiguration->EncryptPassword(APassword, GetSessionData()->GetSessionName()); +} + +UnicodeString TTerminal::DecryptPassword(const RawByteString & APassword) const +{ + UnicodeString Result; + try + { + Result = FConfiguration->DecryptPassword(APassword, GetSessionData()->GetSessionName()); + } + catch (EAbort &) + { + // silently ignore aborted prompts for master password and return empty password + } + return Result; +} + +void TTerminal::RecryptPasswords() +{ + FSessionData->RecryptPasswords(); + FRememberedPassword = EncryptPassword(DecryptPassword(FRememberedPassword)); + FRememberedTunnelPassword = EncryptPassword(DecryptPassword(FRememberedTunnelPassword)); +} + +UnicodeString TTerminal::ExpandFileName(const UnicodeString & APath, + const UnicodeString & BasePath) +{ + // replace this by AbsolutePath() + UnicodeString Result = core::UnixExcludeTrailingBackslash(APath); + if (!core::UnixIsAbsolutePath(Result) && !BasePath.IsEmpty()) + { + TODO("Handle more complicated cases like '../../xxx'"); + if (Result == PARENTDIRECTORY) + { + Result = core::UnixExcludeTrailingBackslash(core::UnixExtractFilePath( + core::UnixExcludeTrailingBackslash(BasePath))); + } + else + { + Result = core::UnixIncludeTrailingBackslash(BasePath) + APath; + } + } + return Result; +} + +bool TTerminal::GetActive() const +{ + return (this != nullptr) && (FFileSystem != nullptr) && FFileSystem->GetActive(); +} + +void TTerminal::Close() +{ + FFileSystem->Close(); + + // Cannot rely on CommandSessionOpened here as Status is set to ssClosed + // only after the OnClose is called + if ((FCommandSession != nullptr) && FCommandSession->GetActive()) + { + // prevent recursion + FCommandSession->SetOnClose(nullptr); + FCommandSession->Close(); + } +} + +void TTerminal::ResetConnection() +{ + // used to be called from Reopen(), why? + FTunnelError.Clear(); + + FRememberedPasswordTried = false; + // Particularly to prevent reusing a wrong client certificate passphrase + // from a previous login attempt + FRememberedPassword = UnicodeString(); + FRememberedTunnelPasswordTried = false; + FRememberedTunnelPassword = UnicodeString(); + + if (FDirectoryChangesCache != nullptr) + { + SAFE_DESTROY_EX(TRemoteDirectoryChangesCache, FDirectoryChangesCache); + } + + FFiles->SetDirectory(L""); + // note that we cannot clear contained files + // as they can still be referenced in the GUI atm +} + +UnicodeString TTerminal::FingerprintScan() +{ + GetSessionData()->SetFingerprintScan(true); + try + { + Open(); + // we should never get here + Abort(); + } + catch (...) + { + if (!FFingerprintScanned.IsEmpty()) + { + return FFingerprintScanned; + } + throw; + } + DebugFail(); + return UnicodeString(); +} + +void TTerminal::Open() +{ + TAutoNestingCounter OpeningCounter(FOpening); + ReflectSettings(); + bool Reopen = false; + do + { + Reopen = false; + DoInformation(L"", true, 1); + try + { + SCOPE_EXIT + { + DoInformation(L"", true, 0); + }; + { + InternalTryOpen(); + } + } + catch (EFatal & E) + { + Reopen = DoQueryReopen(&E); + if (Reopen) + { + SAFE_DESTROY(FFileSystem); + SAFE_DESTROY(FSecureShell); + SAFE_DESTROY(FTunnelData); + FStatus = ssClosed; + SAFE_DESTROY(FTunnel); + } + else + { + throw; + } + } + /*catch (EFatal &) + { + throw; + }*/ + catch (Exception & E) + { + LogEvent(FORMAT(L"Got error: \"%s\"", E.Message.c_str())); + // any exception while opening session is fatal + FatalError(&E, L""); + } + } + while (Reopen); + FSessionData->SetNumberOfRetries(0); +} + +void TTerminal::InternalTryOpen() +{ + try + { + ResetConnection(); + FStatus = ssOpening; + + { + SCOPE_EXIT + { + if (FSessionData->GetTunnel()) + { + FSessionData->RollbackTunnel(); + } + }; + InternalDoTryOpen(); + } + + if (GetSessionData()->GetCacheDirectoryChanges()) + { + DebugAssert(FDirectoryChangesCache == nullptr); + FDirectoryChangesCache = new TRemoteDirectoryChangesCache( + FConfiguration->GetCacheDirectoryChangesMaxSize()); + if (GetSessionData()->GetPreserveDirectoryChanges()) + { + FConfiguration->LoadDirectoryChangesCache(GetSessionData()->GetSessionKey(), + FDirectoryChangesCache); + } + } + + DoStartup(); + + if (FCollectFileSystemUsage) + { + FFileSystem->CollectUsage(); + FCollectFileSystemUsage = false; + } + + DoInformation(LoadStr(STATUS_READY), true); + FStatus = ssOpened; + } + catch (...) + { + // rollback + if (FDirectoryChangesCache != nullptr) + { + SAFE_DESTROY_EX(TRemoteDirectoryChangesCache, FDirectoryChangesCache); + } + if (GetSessionData()->GetFingerprintScan() && (FFileSystem != nullptr) && + DebugAlwaysTrue(GetSessionData()->GetFtps() != ftpsNone)) + { + FFingerprintScanned = FFileSystem->GetSessionInfo().CertificateFingerprint; + } + throw; + } +} + +void TTerminal::InternalDoTryOpen() +{ + if (FFileSystem == nullptr) + { + GetLog()->AddSystemInfo(); + DoInitializeLog(); + GetLog()->AddStartupInfo(); + } + + DebugAssert(FTunnel == nullptr); + if (FSessionData->GetTunnel()) + { + DoInformation(LoadStr(OPEN_TUNNEL), true); + LogEvent("Opening tunnel."); + OpenTunnel(); + GetLog()->AddSeparator(); + + FSessionData->ConfigureTunnel(FTunnelLocalPortNumber); + + DoInformation(LoadStr(USING_TUNNEL), false); + LogEvent(FORMAT(L"Connecting via tunnel interface %s:%d.", + FSessionData->GetHostNameExpanded().c_str(), FSessionData->GetPortNumber())); + } + else + { + DebugAssert(FTunnelLocalPortNumber == 0); + } + + if (FFileSystem == nullptr) + { + InitFileSystem(); + } + else + { + FFileSystem->Open(); + } +} + +void TTerminal::InitFileSystem() +{ + DebugAssert(FFileSystem == nullptr); + try + { + TFSProtocol FSProtocol = GetSessionData()->GetFSProtocol(); + if ((FSProtocol == fsFTP) && (GetSessionData()->GetFtps() == ftpsNone)) + { +#ifdef NO_FILEZILLA + LogEvent("FTP protocol is not supported by this build."); + FatalError(nullptr, LoadStr(FTP_UNSUPPORTED)); +#else + FFSProtocol = cfsFTP; + FFileSystem = new TFTPFileSystem(this); + FFileSystem->Init(nullptr); + FFileSystem->Open(); + GetLog()->AddSeparator(); + LogEvent("Using FTP protocol."); +#endif + } + else if ((FSProtocol == fsFTP) && (GetSessionData()->GetFtps() != ftpsNone)) + { +#if defined(NO_FILEZILLA) || defined(MPEXT_NO_SSLDLL) + LogEvent("FTPS protocol is not supported by this build."); + FatalError(nullptr, LoadStr(FZ_NOTSUPPORTED)); +#else + FFSProtocol = cfsFTPS; + FFileSystem = new TFTPFileSystem(this); + FFileSystem->Init(nullptr); + FFileSystem->Open(); + GetLog()->AddSeparator(); + LogEvent("Using FTPS protocol."); +#endif + } + else if (FSProtocol == fsWebDAV) + { + FFSProtocol = cfsWebDAV; + FFileSystem = new TWebDAVFileSystem(this); + FFileSystem->Init(nullptr); + FFileSystem->Open(); + GetLog()->AddSeparator(); + LogEvent("Using WebDAV protocol."); + } + else + { + DebugAssert(FSecureShell == nullptr); + { + SCOPE_EXIT + { + SAFE_DESTROY(FSecureShell); + }; + FSecureShell = new TSecureShell(this, FSessionData, GetLog(), FConfiguration); + try + { + // there will be only one channel in this session + FSecureShell->SetSimple(true); + FSecureShell->Open(); + } + catch (Exception & E) + { + DebugAssert(!FSecureShell->GetActive()); + if (FSessionData->GetFingerprintScan()) + { + FFingerprintScanned = FSecureShell->GetHostKeyFingerprint(); + } + if (!FSecureShell->GetActive() && !FTunnelError.IsEmpty()) + { + // the only case where we expect this to happen + UnicodeString ErrorMessage = LoadStr(UNEXPECTED_CLOSE_ERROR); + DebugAssert(E.Message == ErrorMessage); + FatalError(&E, FMTLOAD(TUNNEL_ERROR, FTunnelError.c_str())); + } + else + { + throw; + } + } + + GetLog()->AddSeparator(); + + if ((FSProtocol == fsSCPonly) || + (FSProtocol == fsSFTP && FSecureShell->SshFallbackCmd())) + { + FFSProtocol = cfsSCP; + FFileSystem= new TSCPFileSystem(this); + FFileSystem->Init(FSecureShell); + FSecureShell = nullptr; // ownership passed + LogEvent("Using SCP protocol."); + } + else + { + FFSProtocol = cfsSFTP; + FFileSystem = new TSFTPFileSystem(this); + FFileSystem->Init(FSecureShell); + FSecureShell = nullptr; // ownership passed + LogEvent("Using SFTP protocol."); + } + } + } + } + catch (EFatal &) + { + SAFE_DESTROY(FFileSystem); + throw; + } + catch (Exception & E) + { + // any exception while opening session is fatal + FatalError(&E, L""); + } +} + +bool TTerminal::IsListenerFree(uintptr_t PortNumber) const +{ +#ifndef __linux__ + SOCKET Socket = socket(AF_INET, SOCK_STREAM, 0); + bool Result = (Socket != INVALID_SOCKET); + if (Result) + { + SOCKADDR_IN Address; + + ClearStruct(Address); + Address.sin_family = AF_INET; + Address.sin_port = htons(static_cast(PortNumber)); + Address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + Result = (bind(Socket, reinterpret_cast(&Address), sizeof(Address)) == 0); + closesocket(Socket); + } + return Result; +#else + return true; +#endif +} + +void TTerminal::SetupTunnelLocalPortNumber() +{ + DebugAssert(FTunnelData == nullptr); + + FTunnelLocalPortNumber = FSessionData->GetTunnelLocalPortNumber(); + if (FTunnelLocalPortNumber == 0) + { + FTunnelLocalPortNumber = FConfiguration->GetTunnelLocalPortNumberLow(); + while (!IsListenerFree(FTunnelLocalPortNumber)) + { + FTunnelLocalPortNumber++; + if (FTunnelLocalPortNumber > FConfiguration->GetTunnelLocalPortNumberHigh()) + { + FTunnelLocalPortNumber = 0; + FatalError(nullptr, FMTLOAD(TUNNEL_NO_FREE_PORT, + FConfiguration->GetTunnelLocalPortNumberLow(), FConfiguration->GetTunnelLocalPortNumberHigh())); + } + } + LogEvent(FORMAT(L"Autoselected tunnel local port number %d", FTunnelLocalPortNumber)); + } +} + +void TTerminal::OpenTunnel() +{ + SetupTunnelLocalPortNumber(); + + try + { + FTunnelData = new TSessionData(L""); + FTunnelData->Assign(StoredSessions->GetDefaultSettings()); + FTunnelData->SetName(FMTLOAD(TUNNEL_SESSION_NAME, FSessionData->GetSessionName().c_str())); + FTunnelData->SetTunnel(false); + FTunnelData->SetHostName(FSessionData->GetTunnelHostName()); + FTunnelData->SetPortNumber(FSessionData->GetTunnelPortNumber()); + FTunnelData->SetUserName(FSessionData->GetTunnelUserName()); + FTunnelData->SetPassword(FSessionData->GetTunnelPassword()); + FTunnelData->SetPublicKeyFile(FSessionData->GetTunnelPublicKeyFile()); + FTunnelData->SetTunnelPortFwd(FORMAT(L"L%d\t%s:%d", + FTunnelLocalPortNumber, FSessionData->GetHostNameExpanded().c_str(), FSessionData->GetPortNumber())); + FTunnelData->SetHostKey(FSessionData->GetTunnelHostKey()); + FTunnelData->SetProxyMethod(FSessionData->GetProxyMethod()); + FTunnelData->SetProxyHost(FSessionData->GetProxyHost()); + FTunnelData->SetProxyPort(FSessionData->GetProxyPort()); + FTunnelData->SetProxyUsername(FSessionData->GetProxyUsername()); + FTunnelData->SetProxyPassword(FSessionData->GetProxyPassword()); + FTunnelData->SetProxyTelnetCommand(FSessionData->GetProxyTelnetCommand()); + FTunnelData->SetProxyLocalCommand(FSessionData->GetProxyLocalCommand()); + FTunnelData->SetProxyDNS(FSessionData->GetProxyDNS()); + FTunnelData->SetProxyLocalhost(FSessionData->GetProxyLocalhost()); + + FTunnelLog = new TSessionLog(this, FTunnelData, FConfiguration); + FTunnelLog->SetParent(FLog); + FTunnelLog->SetName(L"Tunnel"); + FTunnelLog->ReflectSettings(); + FTunnelUI = new TTunnelUI(this); + FTunnel = new TSecureShell(FTunnelUI, FTunnelData, FTunnelLog, FConfiguration); + + FTunnelOpening = true; + try__finally + { + SCOPE_EXIT + { + FTunnelOpening = false; + }; + FTunnel->Open(); + } + __finally + { + FTunnelOpening = false; + }; + + FTunnelThread = new TTunnelThread(FTunnel); + FTunnelThread->Init(); + } + catch (...) + { + CloseTunnel(); + throw; + } +} + +void TTerminal::CloseTunnel() +{ + SAFE_DESTROY_EX(TTunnelThread, FTunnelThread); + FTunnelError = FTunnel->GetLastTunnelError(); + SAFE_DESTROY_EX(TSecureShell, FTunnel); + SAFE_DESTROY_EX(TTunnelUI, FTunnelUI); + SAFE_DESTROY_EX(TSessionLog, FTunnelLog); + SAFE_DESTROY(FTunnelData); + + FTunnelLocalPortNumber = 0; +} + +void TTerminal::Closed() +{ + if (FTunnel != nullptr) + { + CloseTunnel(); + } + + if (GetOnClose()) + { + TCallbackGuard Guard(this); + GetOnClose()(this); + Guard.Verify(); + } + + FStatus = ssClosed; +} + +void TTerminal::ProcessGUI() +{ + // Do not process GUI here, as we are called directly from a GUI loop and may + // recurse for good. + // Alternatively we may check for (FOperationProgress == NULL) + if (FNesting == 0) + { + TAutoNestingCounter NestingCounter(FNesting); + ::ProcessGUI(); + } +} + +void TTerminal::Progress(TFileOperationProgressType * OperationProgress) +{ + if (FNesting == 0) + { + TAutoNestingCounter NestingCounter(FNesting); + OperationProgress->Progress(); + } +} + +void TTerminal::Reopen(intptr_t Params) +{ + TFSProtocol OrigFSProtocol = GetSessionData()->GetFSProtocol(); + UnicodeString PrevRemoteDirectory = GetSessionData()->GetRemoteDirectory(); + bool PrevReadCurrentDirectoryPending = FReadCurrentDirectoryPending; + bool PrevReadDirectoryPending = FReadDirectoryPending; + DebugAssert(!FSuspendTransaction); + bool PrevAutoReadDirectory = FAutoReadDirectory; + // here used to be a check for FExceptionOnFail being 0 + // but it can happen, e.g. when we are downloading file to execute it. + // however I'm not sure why we mind having exception-on-fail enabled here + Integer PrevExceptionOnFail = FExceptionOnFail; + { + SCOPE_EXIT + { + GetSessionData()->SetRemoteDirectory(PrevRemoteDirectory); + GetSessionData()->SetFSProtocol(OrigFSProtocol); + FAutoReadDirectory = PrevAutoReadDirectory; + FReadCurrentDirectoryPending = PrevReadCurrentDirectoryPending; + FReadDirectoryPending = PrevReadDirectoryPending; + FSuspendTransaction = false; + FExceptionOnFail = PrevExceptionOnFail; + }; + FReadCurrentDirectoryPending = false; + FReadDirectoryPending = false; + FSuspendTransaction = true; + FExceptionOnFail = 0; + // typically, we avoid reading directory, when there is operation ongoing, + // for file list which may reference files from current directory + if (FLAGSET(Params, ropNoReadDirectory)) + { + SetAutoReadDirectory(false); + } + + // only peek, we may not be connected at all atm, + // so make sure we do not try retrieving current directory from the server + // (particularly with FTP) + UnicodeString ACurrentDirectory = PeekCurrentDirectory(); + if (!ACurrentDirectory.IsEmpty()) + { + GetSessionData()->SetRemoteDirectory(ACurrentDirectory); + } + if (GetSessionData()->GetFSProtocol() == fsSFTP) + { + GetSessionData()->SetFSProtocol(FFSProtocol == cfsSCP ? fsSCPonly : fsSFTPonly); + } + + // Could be active before, if fatal error occured in the secondary terminal. + // But now, since we handle the secondary terminal's OnClose, + // by closing outselves, it should not happen anymore. + if (DebugAlwaysFalse(GetActive())) + { + Close(); + } + + Open(); + } + __finally + { + GetSessionData()->SetRemoteDirectory(PrevRemoteDirectory); + GetSessionData()->SetFSProtocol(OrigFSProtocol); + FAutoReadDirectory = PrevAutoReadDirectory; + FReadCurrentDirectoryPending = PrevReadCurrentDirectoryPending; + FReadDirectoryPending = PrevReadDirectoryPending; + FSuspendTransaction = false; + FExceptionOnFail = PrevExceptionOnFail; + }; +} + +bool TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & Instructions, + const UnicodeString & Prompt, + bool Echo, intptr_t MaxLen, UnicodeString & AResult) +{ + bool Result = false; + std::unique_ptr Prompts(new TStringList()); + std::unique_ptr Results(new TStringList()); + try__finally + { + Prompts->AddObject(Prompt, reinterpret_cast(FLAGMASK(Echo, pupEcho))); + Results->AddObject(AResult, reinterpret_cast(MaxLen)); + Result = PromptUser(Data, Kind, AName, Instructions, Prompts.get(), Results.get()); + AResult = Results->GetString(0); + } + __finally + { +// delete Prompts; +// delete Results; + }; + return Result; +} + +bool TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Results) +{ + // If PromptUser is overridden in descendant class, the overridden version + // is not called when accessed via TSessionIU interface. + // So this is workaround. + // Actually no longer needed as we do not override DoPromptUser + // anymore in TSecondaryTerminal. + return DoPromptUser(Data, Kind, AName, Instructions, Prompts, Results); +} + +TTerminal * TTerminal::GetPasswordSource() +{ + return this; +} + +bool TTerminal::DoPromptUser(TSessionData * /*Data*/, TPromptKind Kind, + const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, TStrings * Results) +{ + bool Result = false; + + bool PasswordOrPassphrasePrompt = ::IsPasswordOrPassphrasePrompt(Kind, Prompts); + if (PasswordOrPassphrasePrompt) + { + bool & PasswordTried = + FTunnelOpening ? FRememberedTunnelPasswordTried : FRememberedPasswordTried; + if (!PasswordTried) + { + // let's expect that the main session is already authenticated and its password + // is not written after, so no locking is necessary + // (no longer true, once the main session can be reconnected) + UnicodeString Password; + if (FTunnelOpening) + { + Password = GetPasswordSource()->GetRememberedTunnelPassword(); + } + else + { + Password = GetPasswordSource()->GetRememberedPassword(); + } + Results->SetString(0, Password); + if (!Results->GetString(0).IsEmpty()) + { + LogEvent("Using remembered password."); + Result = true; + } + PasswordTried = true; + } + } + + if (!Result) + { + if (PasswordOrPassphrasePrompt && !GetConfiguration()->GetRememberPassword()) + { + Prompts->SetObj(0, reinterpret_cast(reinterpret_cast(Prompts->GetObj(0)) | pupRemember)); + } + + if (GetOnPromptUser() != nullptr) + { + TCallbackGuard Guard(this); + GetOnPromptUser()(this, Kind, Name, Instructions, Prompts, Results, Result, nullptr); + Guard.Verify(); + } + + if (Result && PasswordOrPassphrasePrompt && + (GetConfiguration()->GetRememberPassword() || FLAGSET(reinterpret_cast(Prompts->GetObj(0)), pupRemember))) + { + RawByteString EncryptedPassword = EncryptPassword(Results->GetString(0)); + if (FTunnelOpening) + { + GetPasswordSource()->SetRememberedTunnelPassword(EncryptedPassword); + } + else + { + GetPasswordSource()->SetRememberedPassword(EncryptedPassword); + } + } + } + + return Result; +} + +uintptr_t TTerminal::QueryUser(const UnicodeString & Query, + TStrings * MoreMessages, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType) +{ + LogEvent(FORMAT(L"Asking user:\n%s (%s)", Query.c_str(), UnicodeString(MoreMessages ? MoreMessages->GetCommaText() : L"").c_str())); + uintptr_t Answer = AbortAnswer(Answers); + if (FOnQueryUser) + { + TCallbackGuard Guard(this); + FOnQueryUser(this, Query, MoreMessages, Answers, Params, Answer, QueryType, nullptr); + Guard.Verify(); + } + return Answer; +} + +uintptr_t TTerminal::QueryUserException(const UnicodeString & Query, + Exception * E, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType) +{ + uintptr_t Result = 0; + UnicodeString ExMessage; + if (DebugAlwaysTrue(ExceptionMessage(E, ExMessage) || !Query.IsEmpty())) + { + std::unique_ptr MoreMessages(new TStringList()); + try__finally + { + if (!ExMessage.IsEmpty() && !Query.IsEmpty()) + { + MoreMessages->Add(UnformatMessage(ExMessage)); + } + + ExtException * EE = NB_STATIC_DOWNCAST(ExtException, E); + if ((EE != nullptr) && (EE->GetMoreMessages() != nullptr)) + { + MoreMessages->AddStrings(EE->GetMoreMessages()); + } + + // We know MoreMessages not to be NULL here, + // AppendExceptionStackTraceAndForget should never return true + // (indicating it had to create the string list) + TStrings * MoreMessagesPtr = MoreMessages.release(); + DebugAlwaysFalse(AppendExceptionStackTraceAndForget(MoreMessagesPtr)); + MoreMessages.reset(MoreMessagesPtr); + + TQueryParams HelpKeywordOverrideParams; + if (Params != nullptr) + { + HelpKeywordOverrideParams.Assign(*Params); + } + HelpKeywordOverrideParams.HelpKeyword = + MergeHelpKeyword(HelpKeywordOverrideParams.HelpKeyword, GetExceptionHelpKeyword(E)); + + Result = QueryUser(!Query.IsEmpty() ? Query : ExMessage, + MoreMessages->GetCount() ? MoreMessages.get() : nullptr, + Answers, &HelpKeywordOverrideParams, QueryType); + } + __finally + { +// delete MoreMessages; + }; + } + return Result; +} + +void TTerminal::DisplayBanner(const UnicodeString & Banner) +{ + if (GetOnDisplayBanner() != nullptr) + { + if (FConfiguration->GetForceBanners() || + FConfiguration->ShowBanner(GetSessionData()->GetSessionKey(), Banner)) + { + bool NeverShowAgain = false; + intptr_t Options = + FLAGMASK(FConfiguration->GetForceBanners(), boDisableNeverShowAgain); + TCallbackGuard Guard(this); + GetOnDisplayBanner()(this, GetSessionData()->GetSessionName(), Banner, + NeverShowAgain, Options); + Guard.Verify(); + if (!FConfiguration->GetForceBanners() && NeverShowAgain) + { + FConfiguration->NeverShowBanner(GetSessionData()->GetSessionKey(), Banner); + } + } + } +} + +void TTerminal::HandleExtendedException(Exception * E) +{ + GetLog()->AddException(E); + if (GetOnShowExtendedException() != nullptr) + { + TCallbackGuard Guard(this); + // the event handler may destroy 'this' ... + GetOnShowExtendedException()(this, E, nullptr); + // .. hence guard is dismissed from destructor, to make following call no-op + Guard.Verify(); + } +} + +void TTerminal::ShowExtendedException(Exception * E) +{ + GetLog()->AddException(E); + if (GetOnShowExtendedException() != nullptr) + { + GetOnShowExtendedException()(this, E, nullptr); + } +} + +void TTerminal::DoInformation(const UnicodeString & Str, bool Status, + intptr_t Phase) +{ + if (GetOnInformation()) + { + TCallbackGuard Guard(this); + GetOnInformation()(this, Str, Status, Phase); + Guard.Verify(); + } +} + +void TTerminal::Information(const UnicodeString & Str, bool Status) +{ + DoInformation(Str, Status); +} + +void TTerminal::DoProgress(TFileOperationProgressType & ProgressData) +{ + if (GetOnProgress() != nullptr) + { + TCallbackGuard Guard(this); + GetOnProgress()(ProgressData); + Guard.Verify(); + } +} + +void TTerminal::DoFinished(TFileOperation Operation, TOperationSide Side, bool Temp, + const UnicodeString & AFileName, bool Success, TOnceDoneOperation & OnceDoneOperation) +{ + if (GetOnFinished() != nullptr) + { + TCallbackGuard Guard(this); + GetOnFinished()(Operation, Side, Temp, AFileName, Success, OnceDoneOperation); + Guard.Verify(); + } +} + +void TTerminal::SaveCapabilities(TFileSystemInfo & FileSystemInfo) +{ + for (intptr_t Index = 0; Index < fcCount; ++Index) + { + FileSystemInfo.IsCapable[Index] = GetIsCapable(static_cast(Index)); + } +} + +bool TTerminal::GetIsCapable(TFSCapability Capability) const +{ + DebugAssert(FFileSystem); + return FFileSystem->IsCapable(Capability); +} + +UnicodeString TTerminal::GetAbsolutePath(const UnicodeString & APath, bool Local) const +{ + return FFileSystem->GetAbsolutePath(APath, Local); +} + +void TTerminal::ReactOnCommand(intptr_t Cmd) +{ + bool ChangesDirectory = false; + bool ModifiesFiles = false; + + switch (static_cast(Cmd)) + { + case fsChangeDirectory: + case fsHomeDirectory: + ChangesDirectory = true; + break; + + case fsCopyToRemote: + case fsDeleteFile: + case fsRenameFile: + case fsMoveFile: + case fsCopyFile: + case fsCreateDirectory: + case fsChangeMode: + case fsChangeGroup: + case fsChangeOwner: + case fsChangeProperties: + case fsLock: + ModifiesFiles = true; + break; + + case fsAnyCommand: + ChangesDirectory = true; + ModifiesFiles = true; + break; + } + + if (ChangesDirectory) + { + if (!InTransaction()) + { + ReadCurrentDirectory(); + if (GetAutoReadDirectory()) + { + ReadDirectory(false); + } + } + else + { + FReadCurrentDirectoryPending = true; + if (GetAutoReadDirectory()) + { + FReadDirectoryPending = true; + } + } + } + else if (ModifiesFiles && GetAutoReadDirectory() && FConfiguration->GetAutoReadDirectoryAfterOp()) + { + if (!InTransaction()) + { + ReadDirectory(true); + } + else + { + FReadDirectoryPending = true; + } + } +} + +void TTerminal::TerminalError(const UnicodeString & Msg, const UnicodeString & HelpKeyword) +{ + TerminalError(nullptr, Msg, HelpKeyword); +} + +void TTerminal::TerminalError( + Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) +{ + throw ETerminal(E, Msg, HelpKeyword); +} + +bool TTerminal::DoQueryReopen(Exception * E) +{ + EFatal * Fatal = NB_STATIC_DOWNCAST(EFatal, E); + DebugAssert(Fatal != nullptr); + bool Result = false; + if ((Fatal != nullptr) && Fatal->GetReopenQueried()) + { + Result = false; + } + else + { + intptr_t NumberOfRetries = FSessionData->GetNumberOfRetries(); + if (FConfiguration->GetSessionReopenAutoMaximumNumberOfRetries() > 0 && NumberOfRetries >= FConfiguration->GetSessionReopenAutoMaximumNumberOfRetries()) + { + LogEvent(FORMAT(L"Reached maximum number of retries: %d", FConfiguration->GetSessionReopenAutoMaximumNumberOfRetries())); + } + else + { + LogEvent("Connection was lost, asking what to do."); + + NumberOfRetries++; + FSessionData->SetNumberOfRetries(NumberOfRetries); + + TQueryParams Params(qpAllowContinueOnError); + Params.Timeout = FConfiguration->GetSessionReopenAuto(); + Params.TimeoutAnswer = qaRetry; + TQueryButtonAlias Aliases[1]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(RECONNECT_BUTTON); + Aliases[0].Default = true; + Params.Aliases = Aliases; + Params.AliasesCount = _countof(Aliases); + Result = (QueryUserException(L"", E, qaRetry | qaAbort, &Params, qtError) == qaRetry); + } + + if (Fatal != nullptr) + { + Fatal->SetReopenQueried(true); + } + } + return Result; +} + +bool TTerminal::QueryReopen(Exception * E, intptr_t Params, + TFileOperationProgressType * OperationProgress) +{ + TSuspendFileOperationProgress Suspend(OperationProgress); + + bool Result = DoQueryReopen(E); + + if (Result) + { + TDateTime Start = Now(); + do + { + try + { + Reopen(Params); + FSessionData->SetNumberOfRetries(0); + } + catch (Exception & E) + { + if (!GetActive()) + { + Result = + ((FConfiguration->GetSessionReopenTimeout() == 0) || + (static_cast((double)(Now() - Start) * MSecsPerDay) < FConfiguration->GetSessionReopenTimeout())) && + DoQueryReopen(&E); + } + else + { + throw; + } + } + } + while (!GetActive() && Result); + } + + return Result; +} + +bool TTerminal::FileOperationLoopQuery(Exception & E, + TFileOperationProgressType * OperationProgress, const UnicodeString & Message, + bool AllowSkip, const UnicodeString & SpecialRetry, const UnicodeString & HelpKeyword) +{ + bool Result = false; + GetLog()->AddException(&E); + uintptr_t Answer; + bool SkipToAllPossible = AllowSkip && (OperationProgress != nullptr); + + if (SkipToAllPossible && OperationProgress->SkipToAll) + { + Answer = qaSkip; + } + else + { + uintptr_t Answers = qaRetry | qaAbort | + FLAGMASK(AllowSkip, qaSkip) | + FLAGMASK(SkipToAllPossible, qaAll) | + FLAGMASK(!SpecialRetry.IsEmpty(), qaYes); + TQueryParams Params(qpAllowContinueOnError | FLAGMASK(!AllowSkip, qpFatalAbort)); + Params.HelpKeyword = HelpKeyword; + TQueryButtonAlias Aliases[2]; + intptr_t AliasCount = 0; + + if (FLAGSET(Answers, qaAll)) + { + Aliases[AliasCount].Button = qaAll; + Aliases[AliasCount].Alias = LoadStr(SKIP_ALL_BUTTON); + AliasCount++; + } + if (FLAGSET(Answers, qaYes)) + { + Aliases[AliasCount].Button = qaYes; + Aliases[AliasCount].Alias = SpecialRetry; + AliasCount++; + } + + if (AliasCount > 0) + { + Params.Aliases = Aliases; + Params.AliasesCount = AliasCount; + } + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = QueryUserException(Message, &E, Answers, &Params, qtError); + } + + if (Answer == qaAll) + { + DebugAssert(OperationProgress != nullptr); + OperationProgress->SkipToAll = true; + Answer = qaSkip; + } + if (Answer == qaYes) + { + Result = true; + Answer = qaRetry; + } + } + + if (Answer != qaRetry) + { + if ((Answer == qaAbort) && (OperationProgress != nullptr)) + { + OperationProgress->Cancel = csCancel; + } + + if (AllowSkip) + { + ThrowSkipFile(&E, Message); + } + else + { + // this can happen only during file transfer with SCP + throw ExtException(&E, Message); + } + } + + return Result; +} + +intptr_t TTerminal::FileOperationLoop(TFileOperationEvent CallBackFunc, + TFileOperationProgressType * OperationProgress, bool AllowSkip, + const UnicodeString & Message, void * Param1, void * Param2) +{ + DebugAssert(CallBackFunc); + intptr_t Result = 0; + FileOperationLoopCustom(this, OperationProgress, AllowSkip, Message, "", + [&]() + { + Result = CallBackFunc(Param1, Param2); + }); + + return Result; +} + +UnicodeString TTerminal::TranslateLockedPath(const UnicodeString & APath, bool Lock) +{ + UnicodeString Result = APath; + if (GetSessionData()->GetLockInHome() && !Result.IsEmpty() && (Result[1] == L'/')) + { + if (Lock) + { + if (Result.SubString(1, FLockDirectory.Length()) == FLockDirectory) + { + Result.Delete(1, FLockDirectory.Length()); + if (Result.IsEmpty()) + { + Result = ROOTDIRECTORY; + } + } + } + else + { + Result = core::UnixExcludeTrailingBackslash(FLockDirectory + Result); + } + } + return Result; +} + +void TTerminal::ClearCaches() +{ + FDirectoryCache->Clear(); + if (FDirectoryChangesCache != nullptr) + { + FDirectoryChangesCache->Clear(); + } + if (FCommandSession != nullptr) + { + FCommandSession->ClearCaches(); + } +} + +void TTerminal::ClearCachedFileList(const UnicodeString & APath, + bool SubDirs) +{ + FDirectoryCache->ClearFileList(APath, SubDirs); +} + +void TTerminal::AddCachedFileList(TRemoteFileList * FileList) +{ + FDirectoryCache->AddFileList(FileList); +} + +bool TTerminal::DirectoryFileList(const UnicodeString & APath, + TRemoteFileList *& FileList, bool CanLoad) +{ + bool Result = false; + if (core::UnixSamePath(FFiles->GetDirectory(), APath)) + { + Result = (FileList == nullptr) || (FileList->GetTimestamp() < FFiles->GetTimestamp()); + if (Result) + { + if (FileList == nullptr) + { + FileList = new TRemoteFileList(); + } + FFiles->DuplicateTo(FileList); + } + } + else + { + if (((FileList == nullptr) && FDirectoryCache->HasFileList(APath)) || + ((FileList != nullptr) && FDirectoryCache->HasNewerFileList(APath, FileList->GetTimestamp()))) + { + bool Created = (FileList == nullptr); + if (Created) + { + FileList = new TRemoteFileList(); + } + + Result = FDirectoryCache->GetFileList(APath, FileList); + if (!Result && Created) + { + SAFE_DESTROY(FileList); + } + } + // do not attempt to load file list if there is cached version, + // only absence of cached version indicates that we consider + // the directory content obsolete + else if (CanLoad && !FDirectoryCache->HasFileList(APath)) + { + bool Created = (FileList == nullptr); + if (Created) + { + FileList = new TRemoteFileList(); + } + FileList->SetDirectory(APath); + + try + { + ReadDirectory(FileList); + Result = true; + } + catch (...) + { + if (Created) + { + SAFE_DESTROY(FileList); + } + throw; + } + } + } + + return Result; +} + +void TTerminal::TerminalSetCurrentDirectory(const UnicodeString & AValue) +{ + DebugAssert(FFileSystem); + UnicodeString Value = TranslateLockedPath(AValue, false); + if (Value != FFileSystem->GetCurrDirectory()) + { + RemoteChangeDirectory(Value); + } +} + +UnicodeString TTerminal::GetCurrDirectory() +{ + if (FFileSystem != nullptr) + { + // there's occasional crash when assigning FFileSystem->CurrentDirectory + // to FCurrentDirectory, splitting the assignment to two statements + // to locate the crash more closely + UnicodeString CurrentDirectory = FFileSystem->GetCurrDirectory(); + if (FCurrentDirectory != CurrentDirectory) + { + FCurrentDirectory = CurrentDirectory; + if (FCurrentDirectory.IsEmpty()) + { + ReadCurrentDirectory(); + } + } + } + + UnicodeString Result = TranslateLockedPath(FCurrentDirectory, true); + return Result; +} + +UnicodeString TTerminal::PeekCurrentDirectory() +{ + if (FFileSystem) + { + FCurrentDirectory = FFileSystem->GetCurrDirectory(); + } + + UnicodeString Result = TranslateLockedPath(FCurrentDirectory, true); + return Result; +} + +TRemoteTokenList * TTerminal::GetGroups() +{ + DebugAssert(FFileSystem); + LookupUsersGroups(); + return &FGroups; +} + +TRemoteTokenList * TTerminal::GetUsers() +{ + DebugAssert(FFileSystem); + LookupUsersGroups(); + return &FUsers; +} + +TRemoteTokenList * TTerminal::GetMembership() +{ + DebugAssert(FFileSystem); + LookupUsersGroups(); + return &FMembership; +} + +UnicodeString TTerminal::TerminalGetUserName() const +{ + // in future might also be implemented to detect username similar to GetUserGroups + DebugAssert(FFileSystem != nullptr); + UnicodeString Result = FFileSystem->FSGetUserName(); + // Is empty also when stored username was used + if (Result.IsEmpty()) + { + Result = GetSessionData()->GetUserNameExpanded(); + } + return Result; +} + +bool TTerminal::GetAreCachesEmpty() const +{ + return FDirectoryCache->GetIsEmpty() && + ((FDirectoryChangesCache == nullptr) || FDirectoryChangesCache->GetIsEmpty()); +} + +void TTerminal::DoInitializeLog() +{ + if (FOnInitializeLog) + { + TCallbackGuard Guard(this); + FOnInitializeLog(this); + Guard.Verify(); + } +} + +void TTerminal::DoChangeDirectory() +{ + if (FOnChangeDirectory) + { + TCallbackGuard Guard(this); + FOnChangeDirectory(this); + Guard.Verify(); + } +} + +void TTerminal::DoReadDirectory(bool ReloadOnly) +{ + if (FOnReadDirectory) + { + TCallbackGuard Guard(this); + FOnReadDirectory(this, ReloadOnly); + Guard.Verify(); + } +} + +void TTerminal::DoStartReadDirectory() +{ + if (FOnStartReadDirectory) + { + TCallbackGuard Guard(this); + FOnStartReadDirectory(this); + Guard.Verify(); + } +} + +void TTerminal::DoReadDirectoryProgress(intptr_t Progress, intptr_t ResolvedLinks, bool & Cancel) +{ + if (FReadingCurrentDirectory && (FOnReadDirectoryProgress != nullptr)) + { + TCallbackGuard Guard(this); + FOnReadDirectoryProgress(this, Progress, ResolvedLinks, Cancel); + Guard.Verify(); + } + if (FOnFindingFile != nullptr) + { + TCallbackGuard Guard(this); + FOnFindingFile(this, L"", Cancel); + Guard.Verify(); + } +} + +bool TTerminal::InTransaction() const +{ + return (FInTransaction > 0) && !FSuspendTransaction; +} + +void TTerminal::BeginTransaction() +{ + if (FInTransaction == 0) + { + FReadCurrentDirectoryPending = false; + FReadDirectoryPending = false; + } + FInTransaction++; + + if (FCommandSession != nullptr) + { + FCommandSession->BeginTransaction(); + } +} + +void TTerminal::EndTransaction() +{ + DoEndTransaction(false); +} + +void TTerminal::DoEndTransaction(bool Inform) +{ + if (FInTransaction == 0) + TerminalError(L"Can't end transaction, not in transaction"); + DebugAssert(FInTransaction > 0); + FInTransaction--; + + // it connection was closed due to fatal error during transaction, do nothing + if (GetActive()) + { + if (FInTransaction == 0) + { + try__finally + { + SCOPE_EXIT + { + FReadCurrentDirectoryPending = false; + FReadDirectoryPending = false; + }; + if (FReadCurrentDirectoryPending) + { + ReadCurrentDirectory(); + } + + if (FReadDirectoryPending) + { + if (Inform) + { + DoInformation(LoadStr(STATUS_OPEN_DIRECTORY), true); + } + ReadDirectory(!FReadCurrentDirectoryPending); + } + } + __finally + { + FReadCurrentDirectoryPending = false; + FReadDirectoryPending = false; + }; + } + } + + if (FCommandSession != nullptr) + { + FCommandSession->EndTransaction(); + } +} + +void TTerminal::SetExceptionOnFail(bool Value) +{ + if (Value) + { + FExceptionOnFail++; + } + else + { + if (FExceptionOnFail == 0) + throw Exception(L"ExceptionOnFail is already zero."); + FExceptionOnFail--; + } + + if (FCommandSession != nullptr) + { + FCommandSession->FExceptionOnFail = FExceptionOnFail; + } +} + +bool TTerminal::GetExceptionOnFail() const +{ + return static_cast(FExceptionOnFail > 0); +} + +void TTerminal::FatalAbort() +{ + FatalError(nullptr, L""); +} + +void TTerminal::FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword) +{ + bool SecureShellActive = (FSecureShell != nullptr) && FSecureShell->GetActive(); + if (GetActive() || SecureShellActive) + { + // We log this instead of exception handler, because Close() would + // probably cause exception handler to loose pointer to TShellLog() + LogEvent("Attempt to close connection due to fatal exception:"); + GetLog()->Add(llException, Msg); + GetLog()->AddException(E); + + if (GetActive()) + { + Close(); + } + + // this may happen if failure of authentication of SSH, owned by terminal yet + // (because the protocol was not decided yet), is detected by us (not by putty). + // e.g. not verified host key + if (SecureShellActive) + { + FSecureShell->Close(); + } + } + + if (FCallbackGuard != nullptr) + { + FCallbackGuard->FatalError(E, Msg, HelpKeyword); + } + else + { + throw ESshFatal(E, Msg, HelpKeyword); + } +} + +void TTerminal::CommandError(Exception * E, const UnicodeString & Msg) +{ + CommandError(E, Msg, 0); +} + +uintptr_t TTerminal::CommandError(Exception * E, const UnicodeString & Msg, + uintptr_t Answers, const UnicodeString & HelpKeyword) +{ + // may not be, particularly when TTerminal::Reopen is being called + // from within OnShowExtendedException handler + DebugAssert(FCallbackGuard == nullptr); + uintptr_t Result = 0; + if (E && (NB_STATIC_DOWNCAST(EFatal, E) != nullptr)) + { + FatalError(E, Msg, HelpKeyword); + } + else if (E && (NB_STATIC_DOWNCAST(EAbort, E) != nullptr)) + { + // resent EAbort exception + Abort(); + } + else if (GetExceptionOnFail()) + { + throw ECommand(E, Msg, HelpKeyword); + } + else if (!Answers) + { + ECommand ECmd(E, Msg, HelpKeyword); + try__finally + { + HandleExtendedException(&ECmd); + } + __finally + { +// delete ECmd; + }; + } + else + { + // small hack to enable "skip to all" for TRetryOperationLoop + bool CanSkip = FLAGSET(Answers, qaSkip) && (GetOperationProgress() != nullptr); + if (CanSkip && GetOperationProgress()->SkipToAll) + { + Result = qaSkip; + } + else + { + TQueryParams Params(qpAllowContinueOnError, HelpKeyword); + TQueryButtonAlias Aliases[1]; + if (CanSkip) + { + Aliases[0].Button = qaAll; + Aliases[0].Alias = LoadStr(SKIP_ALL_BUTTON); + Params.Aliases = Aliases; + Params.AliasesCount = _countof(Aliases); + Answers |= qaAll; + } + Result = QueryUserException(Msg, E, Answers, &Params, qtError); + if (Result == qaAll) + { + DebugAssert(GetOperationProgress() != nullptr); + GetOperationProgress()->SkipToAll = true; + Result = qaSkip; + } + } + } + return Result; +} + +bool TTerminal::HandleException(Exception * E) + { + if (GetExceptionOnFail()) + { + return false; + } + else + { + GetLog()->AddException(E); + return true; + } +} + +void TTerminal::CloseOnCompletion(TOnceDoneOperation Operation, const UnicodeString & Message) +{ +// Configuration->Usage->Inc(L"ClosesOnCompletion"); + LogEvent("Closing session after completed operation (as requested by user)"); + Close(); + throw ESshTerminate(nullptr, + Message.IsEmpty() ? UnicodeString(LoadStr(CLOSED_ON_COMPLETION)) : Message, + Operation); +} + +TBatchOverwrite TTerminal::EffectiveBatchOverwrite( + const UnicodeString & ASourceFullFileName, const TCopyParamType * CopyParam, intptr_t Params, TFileOperationProgressType * OperationProgress, bool Special) +{ + TBatchOverwrite Result; + if (Special && + (FLAGSET(Params, cpResume) || CopyParam->ResumeTransfer(ASourceFullFileName))) + { + Result = boResume; + } + else if (FLAGSET(Params, cpAppend)) + { + Result = boAppend; + } + else if (CopyParam->GetNewerOnly() && + (((OperationProgress->Side == osLocal) && GetIsCapable(fcNewerOnlyUpload)) || + (OperationProgress->Side != osLocal))) + { + // no way to change batch overwrite mode when CopyParam->NewerOnly is on + Result = boOlder; + } + else if (FLAGSET(Params, cpNoConfirmation) || !FConfiguration->GetConfirmOverwriting()) + { + // no way to change batch overwrite mode when overwrite confirmations are off + DebugAssert(OperationProgress->BatchOverwrite == boNo); + Result = boAll; + } + else + { + Result = OperationProgress->BatchOverwrite; + if (!Special && + ((Result == boOlder) || (Result == boAlternateResume) || (Result == boResume))) + { + Result = boNo; + } + } + return Result; +} + +bool TTerminal::CheckRemoteFile( + const UnicodeString & AFileName, const TCopyParamType * CopyParam, intptr_t Params, TFileOperationProgressType * OperationProgress) +{ + return (EffectiveBatchOverwrite(AFileName, CopyParam, Params, OperationProgress, true) != boAll); +} + +uintptr_t TTerminal::ConfirmFileOverwrite(const UnicodeString & ASourceFullFileName, const UnicodeString & ATargetFileName, + const TOverwriteFileParams * FileParams, uintptr_t Answers, TQueryParams * QueryParams, + TOperationSide Side, const TCopyParamType * CopyParam, intptr_t Params, TFileOperationProgressType * OperationProgress, + const UnicodeString & AMessage) +{ + UnicodeString Message = AMessage; + uintptr_t Result = 0; + // duplicated in TSFTPFileSystem::SFTPConfirmOverwrite + bool CanAlternateResume = + (FileParams != nullptr) && + (FileParams->DestSize < FileParams->SourceSize) && + !OperationProgress->AsciiTransfer; + TBatchOverwrite BatchOverwrite = EffectiveBatchOverwrite(ASourceFullFileName, CopyParam, Params, OperationProgress, true); + bool Applicable = true; + switch (BatchOverwrite) + { + case boOlder: + Applicable = (FileParams != nullptr); + break; + + case boAlternateResume: + Applicable = CanAlternateResume; + break; + + case boResume: + Applicable = CanAlternateResume; + break; + } + + if (!Applicable) + { + TBatchOverwrite EffBatchOverwrite = EffectiveBatchOverwrite(ASourceFullFileName, CopyParam, Params, OperationProgress, false); + DebugAssert(BatchOverwrite != EffBatchOverwrite); + BatchOverwrite = EffBatchOverwrite; + } + + if (BatchOverwrite == boNo) + { + if (Message.IsEmpty()) + { + // Side refers to destination side here + Message = FMTLOAD((Side == osLocal ? LOCAL_FILE_OVERWRITE2 : + REMOTE_FILE_OVERWRITE2), ATargetFileName.c_str(), ATargetFileName.c_str()); + } + if (FileParams != nullptr) + { + Message = FMTLOAD(FILE_OVERWRITE_DETAILS, Message.c_str(), + ::Int64ToStr(FileParams->SourceSize).c_str(), + core::UserModificationStr(FileParams->SourceTimestamp, FileParams->SourcePrecision).c_str(), + ::Int64ToStr(FileParams->DestSize).c_str(), + core::UserModificationStr(FileParams->DestTimestamp, FileParams->DestPrecision).c_str()); + } + if (DebugAlwaysTrue(QueryParams->HelpKeyword.IsEmpty())) + { + QueryParams->HelpKeyword = HELP_OVERWRITE; + } + Result = QueryUser(Message, nullptr, Answers, QueryParams); + switch (Result) + { + case qaNeverAskAgain: + FConfiguration->SetConfirmOverwriting(false); + Result = qaYes; + break; + + case qaYesToAll: + BatchOverwrite = boAll; + break; + + case qaAll: + BatchOverwrite = boOlder; + break; + + case qaNoToAll: + BatchOverwrite = boNone; + break; + } + + // we user has not selected another batch overwrite mode, + // keep the current one. note that we may get here even + // when batch overwrite was selected already, but it could not be applied + // to current transfer (see condition above) + if (BatchOverwrite != boNo) + { + GetOperationProgress()->BatchOverwrite = BatchOverwrite; + } + } + + if (BatchOverwrite != boNo) + { + switch (BatchOverwrite) + { + case boAll: + Result = qaYes; + break; + + case boNone: + Result = qaNo; + break; + + case boOlder: + if (FileParams == nullptr) + { + Result = qaNo; + } + else + { + TModificationFmt Precision = core::LessDateTimePrecision(FileParams->SourcePrecision, FileParams->DestPrecision); + TDateTime ReducedSourceTimestamp = + core::ReduceDateTimePrecision(FileParams->SourceTimestamp, Precision); + TDateTime ReducedDestTimestamp = + core::ReduceDateTimePrecision(FileParams->DestTimestamp, Precision); + + Result = + CompareFileTime(ReducedSourceTimestamp, ReducedDestTimestamp) > 0 ? + qaYes : qaNo; + + LogEvent(FORMAT(L"Source file timestamp is [%s], destination timestamp is [%s], will%s overwrite", + StandardTimestamp(ReducedSourceTimestamp).c_str(), + StandardTimestamp(ReducedDestTimestamp).c_str(), + UnicodeString(Result == qaYes ? L"" : L" not").c_str())); + } + break; + + case boAlternateResume: + DebugAssert(CanAlternateResume); + Result = qaSkip; // ugh + break; + + case boAppend: + Result = qaRetry; + break; + + case boResume: + Result = qaRetry; + break; + } + } + + return Result; +} + +void TTerminal::FileModified(const TRemoteFile * AFile, + const UnicodeString & AFileName, bool ClearDirectoryChange) +{ + UnicodeString ParentDirectory; + UnicodeString Directory; + + if (GetSessionData()->GetCacheDirectories() || GetSessionData()->GetCacheDirectoryChanges()) + { + if ((AFile != nullptr) && (AFile->GetDirectory() != nullptr)) + { + if (AFile->GetIsDirectory()) + { + Directory = AFile->GetDirectory()->GetFullDirectory() + AFile->GetFileName(); + } + ParentDirectory = AFile->GetDirectory()->GetDirectory(); + } + else if (!AFileName.IsEmpty()) + { + ParentDirectory = core::UnixExtractFilePath(AFileName); + if (ParentDirectory.IsEmpty()) + { + ParentDirectory = GetCurrDirectory(); + } + + // this case for scripting + if ((AFile != nullptr) && AFile->GetIsDirectory()) + { + Directory = core::UnixIncludeTrailingBackslash(ParentDirectory) + + base::UnixExtractFileName(AFile->GetFileName()); + } + } + } + + if (GetSessionData()->GetCacheDirectories()) + { + if (!Directory.IsEmpty()) + { + DirectoryModified(Directory, true); + } + if (!ParentDirectory.IsEmpty()) + { + DirectoryModified(ParentDirectory, false); + } + } + + if (GetSessionData()->GetCacheDirectoryChanges() && ClearDirectoryChange) + { + if (!Directory.IsEmpty()) + { + FDirectoryChangesCache->ClearDirectoryChange(Directory); + FDirectoryChangesCache->ClearDirectoryChangeTarget(Directory); + } + } +} + +void TTerminal::DirectoryModified(const UnicodeString & APath, bool SubDirs) +{ + if (APath.IsEmpty()) + { + ClearCachedFileList(GetCurrDirectory(), SubDirs); + } + else + { + ClearCachedFileList(APath, SubDirs); + } +} + +void TTerminal::DirectoryLoaded(TRemoteFileList * FileList) +{ + AddCachedFileList(FileList); +} + +void TTerminal::ReloadDirectory() +{ + if (GetSessionData()->GetCacheDirectories()) + { + DirectoryModified(GetCurrDirectory(), false); + } + if (GetSessionData()->GetCacheDirectoryChanges()) + { + DebugAssert(FDirectoryChangesCache != nullptr); + FDirectoryChangesCache->ClearDirectoryChange(GetCurrDirectory()); + } + + ReadCurrentDirectory(); + FReadCurrentDirectoryPending = false; + ReadDirectory(true); + FReadDirectoryPending = false; +} + +void TTerminal::RefreshDirectory() +{ + if (GetSessionData()->GetCacheDirectories()) + { + LogEvent("Not refreshing directory, caching is off."); + } + else if (FDirectoryCache->HasNewerFileList(GetCurrDirectory(), FFiles->GetTimestamp())) + { + // Second parameter was added to allow (rather force) using the cache. + // Before, the directory was reloaded always, it seems useless, + // has it any reason? + ReadDirectory(true, true); + FReadDirectoryPending = false; + } +} + +void TTerminal::EnsureNonExistence(const UnicodeString & AFileName, bool IsDirectory) +{ + // if filename doesn't contain path, we check for existence of file + if ((core::UnixExtractFileDir(AFileName).IsEmpty()) && + core::UnixSamePath(GetCurrDirectory(), FFiles->GetDirectory())) + { + TRemoteFile * File = FFiles->FindFile(AFileName); + if (File) + { + if (File->GetIsDirectory() && IsDirectory) + { + throw ECommand(nullptr, FMTLOAD(RENAME_CREATE_DIR_EXISTS, AFileName.c_str())); + } + else if (!File->GetIsDirectory() && !IsDirectory) + { + throw ECommand(nullptr, FMTLOAD(RENAME_CREATE_FILE_EXISTS, AFileName.c_str())); + } + } + } +} + +void TTerminal::LogEvent(const UnicodeString & Str) +{ + if (GetLog()->GetLogging()) + { + GetLog()->Add(llMessage, Str); + } +} + +void TTerminal::RollbackAction(TSessionAction & Action, + TFileOperationProgressType * OperationProgress, Exception * E) +{ + // ESkipFile without "cancel" is file skip, + // and we do not want to record skipped actions. + // But ESkipFile with "cancel" is abort and we want to record that. + // Note that TSCPFileSystem modifies the logic of RollbackAction little bit. + if ((NB_STATIC_DOWNCAST(ESkipFile, E) != nullptr) && + ((OperationProgress == nullptr) || + (OperationProgress->Cancel == csContinue))) + { + Action.Cancel(); + } + else + { + Action.Rollback(E); + } +} + +void TTerminal::DoStartup() +{ + LogEvent("Doing startup conversation with host."); + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + DoEndTransaction(true); + }; + DoInformation(LoadStr(STATUS_STARTUP), true); + + // Make sure that directory would be loaded at last + FReadCurrentDirectoryPending = true; + FReadDirectoryPending = GetAutoReadDirectory(); + + FFileSystem->DoStartup(); + + LookupUsersGroups(); + + if (!GetSessionData()->GetRemoteDirectory().IsEmpty()) + { + RemoteChangeDirectory(GetSessionData()->GetRemoteDirectory()); + } + } + __finally + { + DoEndTransaction(true); + }; + LogEvent("Startup conversation with host finished."); +} + +void TTerminal::ReadCurrentDirectory() +{ + DebugAssert(FFileSystem); + try + { + // reset flag is case we are called externally (like from console dialog) + FReadCurrentDirectoryPending = false; + + LogEvent("Getting current directory name."); + UnicodeString OldDirectory = FFileSystem->GetCurrDirectory(); + + FFileSystem->ReadCurrentDirectory(); + ReactOnCommand(fsCurrentDirectory); + + if (GetSessionData()->GetCacheDirectoryChanges()) + { + DebugAssert(FDirectoryChangesCache != nullptr); + UnicodeString CurrentDirectory = GetCurrDirectory(); + if (!CurrentDirectory.IsEmpty() && !FLastDirectoryChange.IsEmpty() && (CurrentDirectory != OldDirectory)) + { + FDirectoryChangesCache->AddDirectoryChange(OldDirectory, + FLastDirectoryChange, CurrentDirectory); + } + // not to broke the cache, if the next directory change would not + // be initialized by ChangeDirectory(), which sets it + // (HomeDirectory() particularly) + FLastDirectoryChange.Clear(); + } + + if (OldDirectory.IsEmpty()) + { + FLockDirectory = (GetSessionData()->GetLockInHome() ? + FFileSystem->GetCurrDirectory() : UnicodeString(L"")); + } + // if (OldDirectory != FFileSystem->GetCurrDirectory()) + { + DoChangeDirectory(); + } + } + catch (Exception & E) + { + CommandError(&E, LoadStr(READ_CURRENT_DIR_ERROR)); + } +} + +void TTerminal::ReadDirectory(bool ReloadOnly, bool ForceCache) +{ + bool LoadedFromCache = false; + + if (GetSessionData()->GetCacheDirectories() && FDirectoryCache->HasFileList(GetCurrDirectory())) + { + if (ReloadOnly && !ForceCache) + { + LogEvent("Cached directory not reloaded."); + } + else + { + DoStartReadDirectory(); + try__finally + { + SCOPE_EXIT + { + DoReadDirectory(ReloadOnly); + }; + LoadedFromCache = FDirectoryCache->GetFileList(GetCurrDirectory(), FFiles); + } + __finally + { + DoReadDirectory(ReloadOnly); + }; + + if (LoadedFromCache) + { + LogEvent("Directory content loaded from cache."); + } + else + { + LogEvent("Cached Directory content has been removed."); + } + } + } + + if (!LoadedFromCache) + { + DoStartReadDirectory(); + FReadingCurrentDirectory = true; + bool Cancel = false; // dummy + DoReadDirectoryProgress(0, 0, Cancel); + + try + { + TRemoteDirectory * Files = new TRemoteDirectory(this, FFiles); + try__finally + { + SCOPE_EXIT + { + DoReadDirectoryProgress(-1, 0, Cancel); + FReadingCurrentDirectory = false; + FOldFiles->Reset(); + FOldFiles->AddFiles(FFiles); + FFiles = Files; + DoReadDirectory(ReloadOnly); + // delete only after loading new files to dir view, + // not to destroy the file objects that the view holds + // (can be issue in multi threaded environment, such as when the + // terminal is reconnecting in the terminal thread) + if (GetActive()) + { + if (GetSessionData()->GetCacheDirectories()) + { + DirectoryLoaded(FFiles); + } + } + }; + Files->SetDirectory(GetCurrDirectory()); + CustomReadDirectory(Files); + } + __finally + { + DoReadDirectoryProgress(-1, 0, Cancel); + FReadingCurrentDirectory = false; + FOldFiles->Reset(); + FOldFiles->AddFiles(FFiles); + FFiles = Files; + try__finally + { + DoReadDirectory(ReloadOnly); + } + __finally + { + // delete only after loading new files to dir view, + // not to destroy the file objects that the view holds + // (can be issue in multi threaded environment, such as when the + // terminal is reconnecting in the terminal thread) +// delete OldFiles; + }; + if (GetActive()) + { + if (GetSessionData()->GetCacheDirectories()) + { + DirectoryLoaded(FFiles); + } + } + }; + } + catch (Exception & E) + { + CommandError(&E, FMTLOAD(LIST_DIR_ERROR, FFiles->GetDirectory().c_str())); + } + } +} + +UnicodeString TTerminal::GetRemoteFileInfo(TRemoteFile * File) +{ + return + FORMAT(L"%s;%c;%lld;%s;%d;%s;%s;%s;%d", + File->GetFileName().c_str(), File->GetType(), File->GetSize(), StandardTimestamp(File->GetModification()).c_str(), int(File->GetModificationFmt()), + File->GetFileOwner().GetLogText().c_str(), File->GetFileGroup().GetLogText().c_str(), File->GetRights()->GetText().c_str(), + File->GetAttr()); +} + +void TTerminal::LogRemoteFile(TRemoteFile * AFile) +{ + // optimization + if (GetLog()->GetLogging() && AFile) + { + LogEvent(GetRemoteFileInfo(AFile)); + } +} + +UnicodeString TTerminal::FormatFileDetailsForLog(const UnicodeString & AFileName, const TDateTime & Modification, int64_t Size) +{ + UnicodeString Result; + // optimization + if (GetLog()->GetLogging()) + { + Result = FORMAT(L"'%s' [%s] [%s]", AFileName.c_str(), UnicodeString(Modification != TDateTime() ? StandardTimestamp(Modification) : UnicodeString(L"n/a")).c_str(), ::Int64ToStr(Size).c_str()); + } + return Result; +} + +void TTerminal::LogFileDetails(const UnicodeString & AFileName, const TDateTime & AModification, int64_t Size) +{ + // optimization + if (GetLog()->GetLogging()) + { + LogEvent(FORMAT(L"File: %s", FormatFileDetailsForLog(AFileName, AModification, Size).c_str())); + } +} + +void TTerminal::LogFileDone(TFileOperationProgressType * OperationProgress) +{ + // optimization + if (GetLog()->GetLogging()) + { + LogEvent(FORMAT(L"Transfer done: '%s' [%s]", OperationProgress->FullFileName.c_str(), Int64ToStr(OperationProgress->TransferedSize).c_str())); + } +} + +void TTerminal::CustomReadDirectory(TRemoteFileList * AFileList) +{ + DebugAssert(AFileList); + DebugAssert(FFileSystem); + + TRobustOperationLoop RobustLoop(this, FOperationProgress); + + do + { + try + { + FFileSystem->ReadDirectory(AFileList); + } + catch (Exception & E) + { + // Do not retry for initial listing of directory, + // we instead retry whole connection attempt, + // what would be done anyway (but Open is not ready for recursion). + if ((FOpening > 0) || + !RobustLoop.TryReopen(E)) + { + throw; + } + } + } + while (RobustLoop.Retry()); + + if (GetLog()->GetLogging()) + { + for (intptr_t Index = 0; Index < AFileList->GetCount(); ++Index) + { + LogRemoteFile(AFileList->GetFile(Index)); + } + } + + ReactOnCommand(fsListDirectory); +} + +TRemoteFileList * TTerminal::ReadDirectoryListing(const UnicodeString & Directory, const TFileMasks & Mask) +{ + TRemoteFileList * FileList = nullptr; + TRetryOperationLoop RetryLoop(this); + do + { + FileList = nullptr; + TLsSessionAction Action(GetActionLog(), GetAbsolutePath(Directory, true)); + + try + { + FileList = DoReadDirectoryListing(Directory, false); + if (FileList != nullptr) + { + intptr_t Index = 0; + while (Index < FileList->GetCount()) + { + TRemoteFile * File = FileList->GetFile(Index); + TFileMasks::TParams Params; + Params.Size = File->GetSize(); + Params.Modification = File->GetModification(); + // Have to use UnicodeString(), instead of L"", as with that + // overload with (UnicodeString, bool, bool, TParams*) wins + if (!Mask.Matches(File->GetFileName(), false, UnicodeString(), &Params)) + { + FileList->Delete(Index); + } + else + { + ++Index; + } + } + + Action.FileList(FileList); + } + } + catch (Exception & E) + { + RetryLoop.Error(E, Action); + } + } + while (RetryLoop.Retry()); + return FileList; +} + +TRemoteFile * TTerminal::ReadFileListing(const UnicodeString & APath) +{ + TRemoteFile * File = nullptr; + TRetryOperationLoop RetryLoop(this); + do + { + File = nullptr; + TStatSessionAction Action(GetActionLog(), GetAbsolutePath(APath, true)); + try + { + // reset caches + AnnounceFileListOperation(); + ReadFile(APath, File); + Action.File(File); + } + catch (Exception & E) + { + RetryLoop.Error(E, Action); + } + } + while (RetryLoop.Retry()); + return File; +} + +TRemoteFileList * TTerminal::CustomReadDirectoryListing(const UnicodeString & Directory, bool UseCache) +{ + TRemoteFileList * FileList = nullptr; + TRetryOperationLoop RetryLoop(this); + do + { + try + { + FileList = DoReadDirectoryListing(Directory, UseCache); + } + catch (Exception & E) + { + RetryLoop.Error(E); + } + } + while (RetryLoop.Retry()); + return FileList; +} + +TRemoteFileList * TTerminal::DoReadDirectoryListing(const UnicodeString & ADirectory, bool UseCache) +{ + std::unique_ptr FileList(new TRemoteFileList()); + try__catch + { + bool Cache = UseCache && GetSessionData()->GetCacheDirectories(); + bool LoadedFromCache = Cache && FDirectoryCache->HasFileList(ADirectory); + if (LoadedFromCache) + { + LoadedFromCache = FDirectoryCache->GetFileList(ADirectory, FileList.get()); + } + + if (!LoadedFromCache) + { + FileList->SetDirectory(ADirectory); + + SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + SetExceptionOnFail(false); + }; + ReadDirectory(FileList.get()); + } + __finally + { + SetExceptionOnFail(false); + }; + + if (Cache) + { + AddCachedFileList(FileList.get()); + } + } + } + /*catch(...) + { + delete FileList; + throw; + }*/ + return FileList.release(); +} + +void TTerminal::ProcessDirectory(const UnicodeString & ADirName, + TProcessFileEvent CallBackFunc, void * Param, bool UseCache, bool IgnoreErrors) +{ + std::unique_ptr FileList; + if (IgnoreErrors) + { + SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + SetExceptionOnFail(false); + }; + try + { + FileList.reset(CustomReadDirectoryListing(ADirName, UseCache)); + } + catch (...) + { + if (!GetActive()) + { + throw; + } + } + } + __finally + { + SetExceptionOnFail(false); + }; + } + else + { + FileList.reset(CustomReadDirectoryListing(ADirName, UseCache)); + } + + // skip if directory listing fails and user selects "skip" + if (FileList.get()) + { + try__finally + { + UnicodeString Directory = core::UnixIncludeTrailingBackslash(ADirName); + + for (intptr_t Index = 0; Index < FileList->GetCount(); ++Index) + { + TRemoteFile * File = FileList->GetFile(Index); + if (!File->GetIsParentDirectory() && !File->GetIsThisDirectory()) + { + CallBackFunc(Directory + File->GetFileName(), File, Param); + // We should catch EScpSkipFile here as we do in ProcessFiles. + // Now we have to handle EScpSkipFile in every callback implementation. + } + } + } + __finally + { +// delete FileList; + }; + } +} + +void TTerminal::ReadDirectory(TRemoteFileList * AFileList) +{ + try + { + CustomReadDirectory(AFileList); + } + catch (Exception & E) + { + CommandError(&E, FMTLOAD(LIST_DIR_ERROR, AFileList->GetDirectory().c_str())); + } +} + +void TTerminal::ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& File) +{ + DebugAssert(FFileSystem); + try + { + LogEvent(FORMAT(L"Reading symlink \"%s\".", SymlinkFile->GetFileName().c_str())); + FFileSystem->ReadSymlink(SymlinkFile, File); + ReactOnCommand(fsReadSymlink); + } + catch (Exception & E) + { + CommandError(&E, FMTLOAD(READ_SYMLINK_ERROR, SymlinkFile->GetFileName().c_str())); + } +} + +void TTerminal::ReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile) +{ + DebugAssert(FFileSystem); + AFile = nullptr; + try + { + LogEvent(FORMAT(L"Listing file \"%s\".", AFileName.c_str())); + FFileSystem->ReadFile(AFileName, AFile); + ReactOnCommand(fsListFile); + LogRemoteFile(AFile); + } + catch (Exception & E) + { + if (AFile) + { + SAFE_DESTROY(AFile); + } + AFile = nullptr; + CommandError(&E, FMTLOAD(CANT_GET_ATTRS, AFileName.c_str())); + } +} + +bool TTerminal::FileExists(const UnicodeString & AFileName, TRemoteFile ** AFile) +{ + bool Result; + TRemoteFile * File = nullptr; + try + { + SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + SetExceptionOnFail(false); + }; + ReadFile(core::UnixExcludeTrailingBackslash(AFileName), File); + } + __finally + { + SetExceptionOnFail(false); + }; + + if (AFile != nullptr) + { + *AFile = File; + } + else + { + SAFE_DESTROY(File); + } + Result = true; + } + catch (...) + { + if (GetActive()) + { + Result = false; + } + else + { + throw; + } + } + return Result; +} + +void TTerminal::AnnounceFileListOperation() +{ + FFileSystem->AnnounceFileListOperation(); +} + +bool TTerminal::ProcessFiles(const TStrings * AFileList, + TFileOperation Operation, TProcessFileEvent ProcessFile, void * Param, + TOperationSide Side, bool Ex) +{ + DebugAssert(FFileSystem); + DebugAssert(AFileList); + + bool Result = false; + TOnceDoneOperation OnceDoneOperation = odoIdle; + + try + { + TFileOperationProgressType Progress(MAKE_CALLBACK(TTerminal::DoProgress, this), MAKE_CALLBACK(TTerminal::DoFinished, this)); + Progress.Start(Operation, Side, AFileList->GetCount()); + + TFileOperationProgressType * OperationProgress(&Progress); + + FOperationProgress = &Progress; //-V506 + try__finally + { + SCOPE_EXIT + { + FOperationProgress = nullptr; + Progress.Stop(); + }; + if (Side == osRemote) + { + BeginTransaction(); + } + + try__finally + { + SCOPE_EXIT + { + if (Side == osRemote) + { + EndTransaction(); + } + }; + intptr_t Index = 0; + UnicodeString FileName; + while ((Index < AFileList->GetCount()) && (Progress.Cancel == csContinue)) + { + FileName = AFileList->GetString(Index); + try + { + bool Success = false; + try__finally + { + SCOPE_EXIT + { + Progress.Finish(FileName, Success, OnceDoneOperation); + }; + if (!Ex) + { + TRemoteFile * RemoteFile = NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(Index)); + ProcessFile(FileName, RemoteFile, Param); + } + else + { + // not used anymore +#if 0 + TProcessFileEventEx ProcessFileEx = (TProcessFileEventEx)ProcessFile; + ProcessFileEx(FileName, (TRemoteFile *)FileList->GetObjject(Index), Param, Index); +#endif + } + Success = true; + } + __finally + { + Progress.Finish(FileName, Success, OnceDoneOperation); + }; + } + catch (ESkipFile & E) + { + DEBUG_PRINTF("before HandleException"); + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!HandleException(&E)) + { + throw; + } + } + ++Index; + } + } + __finally + { + if (Side == osRemote) + { + EndTransaction(); + } + }; + + if (Progress.Cancel == csContinue) + { + Result = true; + } + } + __finally + { + FOperationProgress = nullptr; + Progress.Stop(); + }; + } + catch (...) + { + OnceDoneOperation = odoIdle; + // this was missing here. was it by purpose? + // without it any error message is lost + throw; + } + + if (OnceDoneOperation != odoIdle) + { + CloseOnCompletion(OnceDoneOperation); + } + + return Result; +} + +// not used anymore +#if 0 +bool TTerminal::ProcessFilesEx(TStrings * FileList, TFileOperation Operation, + TProcessFileEventEx ProcessFile, void * Param, TOperationSide Side) +{ + return ProcessFiles(FileList, Operation, TProcessFileEvent(ProcessFile), + Param, Side, true); +} +#endif + +TStrings * TTerminal::GetFixedPaths() +{ + DebugAssert(FFileSystem != nullptr); + return FFileSystem->GetFixedPaths(); +} + +bool TTerminal::GetResolvingSymlinks() const +{ + return GetSessionData()->GetResolveSymlinks() && GetIsCapable(fcResolveSymlink); +} + +TUsableCopyParamAttrs TTerminal::UsableCopyParamAttrs(intptr_t Params) +{ + TUsableCopyParamAttrs Result; + Result.General = + FLAGMASK(!GetIsCapable(fcTextMode), cpaNoTransferMode) | + FLAGMASK(!GetIsCapable(fcModeChanging), cpaNoRights) | + FLAGMASK(!GetIsCapable(fcModeChanging), cpaNoPreserveReadOnly) | + FLAGMASK(FLAGSET(Params, cpDelete), cpaNoClearArchive) | + FLAGMASK(!GetIsCapable(fcIgnorePermErrors), cpaNoIgnorePermErrors) | + // the following three are never supported for download, + // so when they are not suppored for upload too, + // set them in General flags, so that they do not get enabled on + // Synchronize dialog. + FLAGMASK(!GetIsCapable(fcModeChangingUpload), cpaNoRights) | + FLAGMASK(!GetIsCapable(fcRemoveCtrlZUpload), cpaNoRemoveCtrlZ) | + FLAGMASK(!GetIsCapable(fcRemoveBOMUpload), cpaNoRemoveBOM) | + FLAGMASK(!GetIsCapable(fcPreservingTimestampDirs), cpaNoPreserveTimeDirs) | + FLAGMASK(!GetIsCapable(fcResumeSupport), cpaNoResumeSupport); + Result.Download = Result.General | cpaNoClearArchive | + cpaNoIgnorePermErrors | + // May be already set in General flags, but it's unconditional here + cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM; + Result.Upload = Result.General | cpaNoPreserveReadOnly | + FLAGMASK(!GetIsCapable(fcPreservingTimestampUpload), cpaNoPreserveTime); + return Result; +} + +bool TTerminal::IsRecycledFile(const UnicodeString & AFileName) +{ + bool Result = !GetSessionData()->GetRecycleBinPath().IsEmpty(); + if (Result) + { + UnicodeString Path = core::UnixExtractFilePath(AFileName); + if (Path.IsEmpty()) + { + Path = GetCurrDirectory(); + } + Result = core::UnixSamePath(Path, GetSessionData()->GetRecycleBinPath()); + } + return Result; +} + +void TTerminal::RecycleFile(const UnicodeString & AFileName, + const TRemoteFile * AFile) +{ + UnicodeString FileName = AFileName; + if (FileName.IsEmpty()) + { + DebugAssert(AFile != nullptr); + if (AFile) + FileName = AFile->GetFileName(); + } + + if (!IsRecycledFile(FileName)) + { + LogEvent(FORMAT(L"Moving file \"%s\" to remote recycle bin '%s'.", + FileName.c_str(), GetSessionData()->GetRecycleBinPath().c_str())); + + TMoveFileParams Params; + Params.Target = GetSessionData()->GetRecycleBinPath(); +#if defined(__BORLANDC__) + Params.FileMask = FORMAT("*-%s.*", (FormatDateTime(L"yyyymmdd-hhnnss", Now()))); +#else + uint16_t Y, M, D, H, N, S, MS; + TDateTime DateTime = Now(); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, N, S, MS); + UnicodeString dt = FORMAT(L"%04d%02d%02d-%02d%02d%02d", Y, M, D, H, N, S); + // Params.FileMask = FORMAT(L"*-%s.*", FormatDateTime(L"yyyymmdd-hhnnss", Now()).c_str()); + Params.FileMask = FORMAT(L"*-%s.*", dt.c_str()); +#endif + TerminalMoveFile(FileName, AFile, &Params); + } +} + +bool TTerminal::TryStartOperationWithFile( + const UnicodeString & AFileName, TFileOperation Operation1, TFileOperation Operation2) +{ + bool Result = true; + if ((GetOperationProgress() != nullptr) && + ((GetOperationProgress()->Operation == Operation1) || + ((Operation2 != foNone) && (GetOperationProgress()->Operation == Operation2)))) + { + if (GetOperationProgress()->Cancel != csContinue) + { + Result = false; + } + else + { + GetOperationProgress()->SetFile(AFileName); + } + } + return Result; +} + +void TTerminal::StartOperationWithFile( + const UnicodeString & AFileName, TFileOperation Operation1, TFileOperation Operation2) +{ + if (!TryStartOperationWithFile(AFileName, Operation1, Operation2)) + { + Abort(); + } +} + +void TTerminal::RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * AParams) +{ + UnicodeString FileName = AFileName; + if (AFileName.IsEmpty() && AFile) + { + FileName = AFile->GetFileName(); + } + StartOperationWithFile(FileName, foDelete); + intptr_t Params = (AParams != nullptr) ? *(static_cast(AParams)) : 0; + bool Recycle = + FLAGCLEAR(Params, dfForceDelete) && + (GetSessionData()->GetDeleteToRecycleBin() != FLAGSET(Params, dfAlternative)) && + !GetSessionData()->GetRecycleBinPath().IsEmpty(); + if (Recycle && !IsRecycledFile(FileName)) + { + RecycleFile(FileName, AFile); + } + else + { + LogEvent(FORMAT(L"Deleting file \"%s\".", FileName.c_str())); + FileModified(AFile, FileName, true); + DoDeleteFile(FileName, AFile, Params); + ReactOnCommand(fsDeleteFile); + } +} + +void TTerminal::DoDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params) +{ + TRetryOperationLoop RetryLoop(this); + do + { + TRmSessionAction Action(GetActionLog(), GetAbsolutePath(AFileName, true)); + try + { + DebugAssert(FFileSystem); + // 'File' parameter: SFTPFileSystem needs to know if file is file or directory + FFileSystem->RemoteDeleteFile(AFileName, AFile, Params, Action); + } + catch (Exception & E) + { + RetryLoop.Error(E, Action, FMTLOAD(DELETE_FILE_ERROR, AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +bool TTerminal::RemoteDeleteFiles(TStrings * AFilesToDelete, intptr_t Params) +{ + TValueRestorer UseBusyCursorRestorer(FUseBusyCursor); + FUseBusyCursor = false; + + TODO("avoid resolving symlinks while reading subdirectories."); + // Resolving does not work anyway for relative symlinks in subdirectories + // (at least for SFTP). + return ProcessFiles(AFilesToDelete, foDelete, MAKE_CALLBACK(TTerminal::RemoteDeleteFile, this), &Params); +} + +void TTerminal::DeleteLocalFile(const UnicodeString & AFileName, + const TRemoteFile * /*AFile*/, void * Params) +{ + StartOperationWithFile(AFileName, foDelete); + if (GetOnDeleteLocalFile() == nullptr) + { + RecursiveDeleteFileChecked(AFileName, false); + } + else + { + GetOnDeleteLocalFile()(AFileName, FLAGSET(*(static_cast(Params)), dfAlternative)); + } +} + +bool TTerminal::DeleteLocalFiles(TStrings * AFileList, intptr_t Params) +{ + return ProcessFiles(AFileList, foDelete, MAKE_CALLBACK(TTerminal::DeleteLocalFile, this), &Params, osLocal); +} + +void TTerminal::CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * AParams) +{ + TCustomCommandParams * Params = NB_STATIC_DOWNCAST(TCustomCommandParams, AParams); + UnicodeString LocalFileName = AFileName; + if (AFileName.IsEmpty() && AFile) + { + LocalFileName = AFile->GetFileName(); + } + StartOperationWithFile(LocalFileName, foCustomCommand); + LogEvent(FORMAT(L"Executing custom command \"%s\" (%d) on file \"%s\".", + Params->Command.c_str(), Params->Params, LocalFileName.c_str())); + FileModified(AFile, LocalFileName); + DoCustomCommandOnFile(LocalFileName, AFile, Params->Command, Params->Params, + Params->OutputEvent); + ReactOnCommand(fsAnyCommand); +} + +void TTerminal::DoCustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, + TCaptureOutputEvent OutputEvent) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + if (GetIsCapable(fcAnyCommand)) + { + DebugAssert(FFileSystem); + DebugAssert(fcShellAnyCommand); + FFileSystem->CustomCommandOnFile(AFileName, AFile, Command, Params, OutputEvent); + } + else + { + DebugAssert(GetCommandSessionOpened()); + DebugAssert(FCommandSession->GetFSProtocol() == cfsSCP); + LogEvent("Executing custom command on command session."); + + if (FCommandSession->GetCurrDirectory() != GetCurrDirectory()) + { + FCommandSession->TerminalSetCurrentDirectory(GetCurrDirectory()); + // We are likely in transaction, so ReadCurrentDirectory won't get called + // until transaction ends. But we need to know CurrentDirectory to + // expand !/ pattern. + // Doing this only, when current directory of the main and secondary shell differs, + // what would be the case before the first file in transaction. + // Otherwise we would be reading pwd before every time as the + // CustomCommandOnFile on its own sets FReadCurrentDirectoryPending + if (FCommandSession->FReadCurrentDirectoryPending) + { + FCommandSession->ReadCurrentDirectory(); + } + } + FCommandSession->FFileSystem->CustomCommandOnFile(AFileName, AFile, Command, + Params, OutputEvent); + } + } + catch (Exception & E) + { + RetryLoop.Error(E, FMTLOAD(CUSTOM_COMMAND_ERROR, Command.c_str(), AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::CustomCommandOnFiles(const UnicodeString & Command, + intptr_t Params, TStrings * AFiles, TCaptureOutputEvent OutputEvent) +{ + if (!TRemoteCustomCommand().IsFileListCommand(Command)) + { + TCustomCommandParams AParams; + AParams.Command = Command; + AParams.Params = Params; + AParams.OutputEvent = OutputEvent; + ProcessFiles(AFiles, foCustomCommand, MAKE_CALLBACK(TTerminal::CustomCommandOnFile, this), &AParams); + } + else + { + UnicodeString FileList; + for (intptr_t Index = 0; Index < AFiles->GetCount(); ++Index) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFiles->GetObj(Index)); + bool Dir = File->GetIsDirectory() && CanRecurseToDirectory(File); + + if (!Dir || FLAGSET(Params, ccApplyToDirectories)) + { + if (!FileList.IsEmpty()) + { + FileList += L" "; + } + + FileList += L"\"" + ShellDelimitStr(AFiles->GetString(Index), L'"') + L"\""; + } + } + + TCustomCommandData Data(this); + UnicodeString Cmd = + TRemoteCustomCommand(Data, GetCurrDirectory(), L"", FileList). + Complete(Command, true); + DoAnyCommand(Cmd, OutputEvent, nullptr); + } +} + +void TTerminal::ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*const TRemoteProperties*/ void * Properties) +{ + TRemoteProperties * RProperties = NB_STATIC_DOWNCAST(TRemoteProperties, Properties); + DebugAssert(RProperties && !RProperties->Valid.Empty()); + UnicodeString LocalFileName = AFileName; + if (AFileName.IsEmpty() && AFile) + { + LocalFileName = AFile->GetFileName(); + } + StartOperationWithFile(LocalFileName, foSetProperties); + if (GetLog()->GetLogging()) + { + LogEvent(FORMAT(L"Changing properties of \"%s\" (%s)", + LocalFileName.c_str(), BooleanToEngStr(RProperties->Recursive).c_str())); + if (RProperties->Valid.Contains(vpRights)) + { + LogEvent(FORMAT(L" - mode: \"%s\"", RProperties->Rights.GetModeStr().c_str())); + } + if (RProperties->Valid.Contains(vpGroup)) + { + LogEvent(FORMAT(L" - group: %s", RProperties->Group.GetLogText().c_str())); + } + if (RProperties->Valid.Contains(vpOwner)) + { + LogEvent(FORMAT(L" - owner: %s", RProperties->Owner.GetLogText().c_str())); + } + if (RProperties->Valid.Contains(vpModification)) + { + uint16_t Y, M, D, H, N, S, MS; + TDateTime DateTime = ::UnixToDateTime(RProperties->Modification, GetSessionData()->GetDSTMode()); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, N, S, MS); + UnicodeString dt = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d ", D, M, Y, H, N, S); + LogEvent(FORMAT(L" - modification: \"%s\"", +// FormatDateTime(L"dddddd tt", +// ::UnixToDateTime(RProperties->Modification, GetSessionData()->GetDSTMode())).c_str())); + dt.c_str())); + } + if (RProperties->Valid.Contains(vpLastAccess)) + { + uint16_t Y, M, D, H, N, S, MS; + TDateTime DateTime = ::UnixToDateTime(RProperties->LastAccess, GetSessionData()->GetDSTMode()); + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, N, S, MS); + UnicodeString dt = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d ", D, M, Y, H, N, S); + LogEvent(FORMAT(L" - last access: \"%s\"", +// FormatDateTime(L"dddddd tt", +// ::UnixToDateTime(RProperties->LastAccess, GetSessionData()->GetDSTMode())).c_str())); + dt.c_str())); + } + } + FileModified(AFile, LocalFileName); + DoChangeFileProperties(LocalFileName, AFile, RProperties); + ReactOnCommand(fsChangeProperties); +} + +void TTerminal::DoChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties) +{ + TRetryOperationLoop RetryLoop(this); + do + { + TChmodSessionAction Action(GetActionLog(), GetAbsolutePath(AFileName, true)); + try + { + DebugAssert(FFileSystem); + FFileSystem->ChangeFileProperties(AFileName, AFile, Properties, Action); + } + catch (Exception & E) + { + RetryLoop.Error(E, Action, FMTLOAD(CHANGE_PROPERTIES_ERROR, AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::ChangeFilesProperties(TStrings * AFileList, + const TRemoteProperties * Properties) +{ + TValueRestorer UseBusyCursorRestorer(FUseBusyCursor); + FUseBusyCursor = false; + + AnnounceFileListOperation(); + ProcessFiles(AFileList, foSetProperties, MAKE_CALLBACK(TTerminal::ChangeFileProperties, this), const_cast(static_cast(Properties))); +} + +bool TTerminal::LoadFilesProperties(TStrings * AFileList) +{ + // see comment in TSFTPFileSystem::IsCapable + bool Result = + GetIsCapable(fcLoadingAdditionalProperties) && + FFileSystem->LoadFilesProperties(AFileList); + if (Result && GetSessionData()->GetCacheDirectories() && + (AFileList->GetCount() > 0) && + (NB_STATIC_DOWNCAST(TRemoteFile, AFileList->GetObj(0))->GetDirectory() == FFiles)) + { + AddCachedFileList(FFiles); + } + return Result; +} + +void TTerminal::CalculateFileSize(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TCalculateSizeParams*/ void * Param) +{ + DebugAssert(Param); + DebugAssert(AFile); + TCalculateSizeParams * AParams = NB_STATIC_DOWNCAST(TCalculateSizeParams, Param); + UnicodeString LocalFileName = AFileName; + if (AFileName.IsEmpty()) + { + LocalFileName = AFile->GetFileName(); + } + + if (!TryStartOperationWithFile(LocalFileName, foCalculateSize)) + { + AParams->Result = false; + Abort(); + } + + bool AllowTransfer = (AParams->CopyParam == nullptr); + if (!AllowTransfer) + { + TFileMasks::TParams MaskParams; + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + + UnicodeString BaseFileName = + GetBaseFileName(core::UnixExcludeTrailingBackslash(AFile->GetFullFileName())); + AllowTransfer = AParams->CopyParam->AllowTransfer( + BaseFileName, osRemote, AFile->GetIsDirectory(), MaskParams); + } + + if (AllowTransfer) + { + if (AFile->GetIsDirectory()) + { + if (CanRecurseToDirectory(AFile)) + { + if (!AParams->AllowDirs) + { + AParams->Result = false; + } + else + { + LogEvent(FORMAT(L"Getting size of directory \"%s\"", LocalFileName.c_str())); + // pass in full path so we get it back in file list for AllowTransfer() exclusion + DoCalculateDirectorySize(AFile->GetFullFileName(), AFile, AParams); + } + } + + if (AParams->Stats != nullptr) + { + AParams->Stats->Directories++; + } + } + else + { + AParams->Size += AFile->GetSize(); + + if (AParams->Stats != nullptr) + { + AParams->Stats->Files++; + } + } + + if ((AParams->Stats != nullptr) && AFile->GetIsSymLink()) + { + AParams->Stats->SymLinks++; + } + } +} + +void TTerminal::DoCalculateDirectorySize(const UnicodeString & AFileName, + const TRemoteFile * AFile, TCalculateSizeParams * Params) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + ProcessDirectory(AFileName, MAKE_CALLBACK(TTerminal::CalculateFileSize, this), Params); + } + catch (Exception & E) + { + if (!GetActive() || ((Params->Params & csIgnoreErrors) == 0)) + { + RetryLoop.Error(E, FMTLOAD(CALCULATE_SIZE_ERROR, AFileName.c_str())); + } + } + } + while (RetryLoop.Retry()); +} + +bool TTerminal::CalculateFilesSize(const TStrings * AFileList, + int64_t & Size, intptr_t Params, const TCopyParamType * CopyParam, + bool AllowDirs, TCalculateSizeStats * Stats) +{ + // With FTP protocol, we may use DSIZ command from + // draft-peterson-streamlined-ftp-command-extensions-10 + // Implemented by Serv-U FTP. + + TValueRestorer UseBusyCursorRestorer(FUseBusyCursor); + FUseBusyCursor = false; + + TCalculateSizeParams Param; + Param.Size = 0; + Param.Params = Params; + Param.CopyParam = CopyParam; + Param.Stats = Stats; + Param.AllowDirs = AllowDirs; + Param.Result = true; + ProcessFiles(AFileList, foCalculateSize, MAKE_CALLBACK(TTerminal::CalculateFileSize, this), &Param); + Size = Param.Size; + return Param.Result; +} + +void TTerminal::CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum) +{ + FFileSystem->CalculateFilesChecksum(Alg, AFileList, Checksums, OnCalculatedChecksum); +} + +void TTerminal::TerminalRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + LogEvent(FORMAT(L"Renaming file \"%s\" to \"%s\".", AFileName.c_str(), ANewName.c_str())); + DoRenameFile(AFileName, ANewName, false); + ReactOnCommand(fsRenameFile); +} + +void TTerminal::TerminalRenameFile(const TRemoteFile * AFile, + const UnicodeString & ANewName, bool CheckExistence) +{ + DebugAssert(AFile && AFile->GetDirectory() == FFiles); + bool Proceed = true; + // if filename doesn't contain path, we check for existence of file + if ((AFile->GetFileName() != ANewName) && CheckExistence && + FConfiguration->GetConfirmOverwriting() && + core::UnixSamePath(GetCurrDirectory(), FFiles->GetDirectory())) + { + TRemoteFile * DuplicateFile = FFiles->FindFile(ANewName); + if (DuplicateFile) + { + UnicodeString QuestionFmt; + if (DuplicateFile->GetIsDirectory()) + { + QuestionFmt = LoadStr(DIRECTORY_OVERWRITE); + } + else + { + QuestionFmt = LoadStr(PROMPT_FILE_OVERWRITE); + } + TQueryParams Params(qpNeverAskAgainCheck); + UnicodeString Question = MainInstructions(FORMAT(QuestionFmt.c_str(), ANewName.c_str())); + intptr_t Result = QueryUser(Question, nullptr, + qaYes | qaNo, &Params); + if (Result == qaNeverAskAgain) + { + Proceed = true; + FConfiguration->SetConfirmOverwriting(false); + } + else + { + Proceed = (Result == qaYes); + } + } + } + + if (Proceed) + { + FileModified(AFile, AFile->GetFileName()); + TerminalRenameFile(AFile->GetFileName(), ANewName); + } +} + +void TTerminal::DoRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName, bool Move) +{ + TRetryOperationLoop RetryLoop(this); + do + { + TMvSessionAction Action(GetActionLog(), GetAbsolutePath(AFileName, true), GetAbsolutePath(ANewName, true)); + try + { + DebugAssert(FFileSystem); + FFileSystem->RemoteRenameFile(AFileName, ANewName); + } + catch (Exception & E) + { + UnicodeString Message = FMTLOAD(Move ? MOVE_FILE_ERROR : RENAME_FILE_ERROR, AFileName.c_str(), ANewName.c_str()); + RetryLoop.Error(E, Action, Message); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::TerminalMoveFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*const TMoveFileParams*/ void * Param) +{ + StartOperationWithFile(AFileName, foRemoteMove, foDelete); + DebugAssert(Param != nullptr); + const TMoveFileParams & Params = *NB_STATIC_DOWNCAST_CONST(TMoveFileParams, Param); + UnicodeString NewName = core::UnixIncludeTrailingBackslash(Params.Target) + + MaskFileName(base::UnixExtractFileName(AFileName), Params.FileMask); + LogEvent(FORMAT(L"Moving file \"%s\" to \"%s\".", AFileName.c_str(), NewName.c_str())); + FileModified(AFile, AFileName); + DoRenameFile(AFileName, NewName, true); + ReactOnCommand(fsMoveFile); +} + +bool TTerminal::MoveFiles(TStrings * AFileList, const UnicodeString & Target, + const UnicodeString & FileMask) +{ + TMoveFileParams Params; + Params.Target = Target; + Params.FileMask = FileMask; + DirectoryModified(Target, true); + bool Result = false; + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + if (GetActive()) + { + UnicodeString WithTrailing = core::UnixIncludeTrailingBackslash(this->GetCurrDirectory()); + bool PossiblyMoved = false; + // check if we was moving current directory. + // this is just optimization to avoid checking existence of current + // directory after each move operation. + UnicodeString CurrentDirectory = this->GetCurrDirectory(); + for (intptr_t Index = 0; !PossiblyMoved && (Index < AFileList->GetCount()); ++Index) + { + const TRemoteFile * File = + NB_STATIC_DOWNCAST_CONST(TRemoteFile, AFileList->GetObj(Index)); + // File can be nullptr, and filename may not be full path, + // but currently this is the only way we can move (at least in GUI) + // current directory + const UnicodeString & Str = AFileList->GetString(Index); + if ((File != nullptr) && + File->GetIsDirectory() && + ((CurrentDirectory.SubString(1, Str.Length()) == Str) && + ((Str.Length() == CurrentDirectory.Length()) || + (CurrentDirectory[Str.Length() + 1] == L'/')))) + { + PossiblyMoved = true; + } + } + + if (PossiblyMoved && !this->FileExists(CurrentDirectory)) + { + UnicodeString NearestExisting = CurrentDirectory; + do + { + NearestExisting = core::UnixExtractFileDir(NearestExisting); + } + while (!core::IsUnixRootPath(NearestExisting) && !this->FileExists(NearestExisting)); + + RemoteChangeDirectory(NearestExisting); + } + } + EndTransaction(); + }; + Result = ProcessFiles(AFileList, foRemoteMove, MAKE_CALLBACK(TTerminal::TerminalMoveFile, this), &Params); + } + __finally + { + if (GetActive()) + { + UnicodeString WithTrailing = core::UnixIncludeTrailingBackslash(this->GetCurrDirectory()); + bool PossiblyMoved = false; + // check if we was moving current directory. + // this is just optimization to avoid checking existence of current + // directory after each move operation. + UnicodeString CurrentDirectory = this->GetCurrDirectory(); + for (intptr_t Index = 0; !PossiblyMoved && (Index < AFileList->GetCount()); ++Index) + { + const TRemoteFile * File = + NB_STATIC_DOWNCAST_CONST(TRemoteFile, AFileList->GetObj(Index)); + // File can be nullptr, and filename may not be full path, + // but currently this is the only way we can move (at least in GUI) + // current directory + const UnicodeString & Str = AFileList->GetString(Index); + if ((File != nullptr) && + File->GetIsDirectory() && + ((CurrentDirectory.SubString(1, Str.Length()) == Str) && + ((Str.Length() == CurrentDirectory.Length()) || + (CurrentDirectory[Str.Length() + 1] == L'/')))) + { + PossiblyMoved = true; + } + } + + if (PossiblyMoved && !FileExists(CurrentDirectory)) + { + UnicodeString NearestExisting = CurrentDirectory; + do + { + NearestExisting = core::UnixExtractFileDir(NearestExisting); + } + while (!core::IsUnixRootPath(NearestExisting) && !FileExists(NearestExisting)); + + RemoteChangeDirectory(NearestExisting); + } + } + EndTransaction(); + }; + return Result; +} + +void TTerminal::DoCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + DebugAssert(FFileSystem); + if (GetIsCapable(fcRemoteCopy)) + { + FFileSystem->RemoteCopyFile(AFileName, ANewName); + } + else + { + DebugAssert(GetCommandSessionOpened()); + DebugAssert(FCommandSession->GetFSProtocol() == cfsSCP); + LogEvent("Copying file on command session."); + FCommandSession->TerminalSetCurrentDirectory(GetCurrDirectory()); + FCommandSession->FFileSystem->RemoteCopyFile(AFileName, ANewName); + } + } + catch (Exception & E) + { + RetryLoop.Error(E, FMTLOAD(COPY_FILE_ERROR, AFileName.c_str(), ANewName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::TerminalCopyFile(const UnicodeString & AFileName, + const TRemoteFile * /*File*/, /*const TMoveFileParams*/ void * Param) +{ + StartOperationWithFile(AFileName, foRemoteCopy); + DebugAssert(Param != nullptr); + const TMoveFileParams & Params = *NB_STATIC_DOWNCAST_CONST(TMoveFileParams, Param); + UnicodeString NewName = core::UnixIncludeTrailingBackslash(Params.Target) + + MaskFileName(base::UnixExtractFileName(AFileName), Params.FileMask); + LogEvent(FORMAT(L"Copying file \"%s\" to \"%s\".", AFileName.c_str(), NewName.c_str())); + DoCopyFile(AFileName, NewName); + ReactOnCommand(fsCopyFile); +} + +bool TTerminal::CopyFiles(const TStrings * AFileList, const UnicodeString & Target, + const UnicodeString & FileMask) +{ + TMoveFileParams Params; + Params.Target = Target; + Params.FileMask = FileMask; + DirectoryModified(Target, true); + return ProcessFiles(AFileList, foRemoteCopy, MAKE_CALLBACK(TTerminal::TerminalCopyFile, this), &Params); +} + +void TTerminal::RemoteCreateDirectory(const UnicodeString & ADirName, + const TRemoteProperties * Properties) +{ + DebugAssert(FFileSystem); + EnsureNonExistence(ADirName, true); + FileModified(nullptr, ADirName); + + LogEvent(FORMAT(L"Creating directory \"%s\".", ADirName.c_str())); + DoCreateDirectory(ADirName); + + if ((Properties != nullptr) && !Properties->Valid.Empty()) + { + DoChangeFileProperties(ADirName, nullptr, Properties); + } + + ReactOnCommand(fsCreateDirectory); +} + +void TTerminal::DoCreateDirectory(const UnicodeString & ADirName) +{ + TRetryOperationLoop RetryLoop(this); + do + { + TMkdirSessionAction Action(GetActionLog(), GetAbsolutePath(ADirName, true)); + try + { + DebugAssert(FFileSystem); + FFileSystem->RemoteCreateDirectory(ADirName); + } + catch (Exception & E) + { + RetryLoop.Error(E, Action, FMTLOAD(CREATE_DIR_ERROR, ADirName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::CreateLink(const UnicodeString & AFileName, + const UnicodeString & PointTo, bool Symbolic, bool IsDirectory) +{ + DebugAssert(FFileSystem); + EnsureNonExistence(AFileName, IsDirectory); + if (GetSessionData()->GetCacheDirectories()) + { + DirectoryModified(GetCurrDirectory(), false); + } + + LogEvent(FORMAT(L"Creating link \"%s\" to \"%s\" (symbolic: %s).", + AFileName.c_str(), PointTo.c_str(), BooleanToEngStr(Symbolic).c_str())); + DoCreateLink(AFileName, PointTo, Symbolic); + ReactOnCommand(fsCreateDirectory); +} + +void TTerminal::DoCreateLink(const UnicodeString & AFileName, + const UnicodeString & PointTo, bool Symbolic) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + DebugAssert(FFileSystem); + FFileSystem->CreateLink(AFileName, PointTo, Symbolic); + } + catch (Exception & E) + { + RetryLoop.Error(E, FMTLOAD(CREATE_LINK_ERROR, AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::HomeDirectory() +{ + DebugAssert(FFileSystem); + try + { + LogEvent("Changing directory to home directory."); + FFileSystem->HomeDirectory(); + ReactOnCommand(fsHomeDirectory); + } + catch (Exception & E) + { + CommandError(&E, LoadStr(CHANGE_HOMEDIR_ERROR)); + } +} + +void TTerminal::RemoteChangeDirectory(const UnicodeString & Directory) +{ + UnicodeString DirectoryNormalized = core::ToUnixPath(Directory); + DebugAssert(FFileSystem); + try + { + UnicodeString CachedDirectory; + DebugAssert(!GetSessionData()->GetCacheDirectoryChanges() || (FDirectoryChangesCache != nullptr)); + // never use directory change cache during startup, this ensures, we never + // end-up initially in non-existing directory + if ((GetStatus() == ssOpened) && + GetSessionData()->GetCacheDirectoryChanges() && + FDirectoryChangesCache->GetDirectoryChange(PeekCurrentDirectory(), + DirectoryNormalized, CachedDirectory)) + { + LogEvent(FORMAT(L"Cached directory change via \"%s\" to \"%s\".", + DirectoryNormalized.c_str(), CachedDirectory.c_str())); + FFileSystem->CachedChangeDirectory(CachedDirectory); + } + else + { + LogEvent(FORMAT(L"Changing directory to \"%s\".", DirectoryNormalized.c_str())); + FFileSystem->ChangeDirectory(DirectoryNormalized); + } + FLastDirectoryChange = DirectoryNormalized; + ReactOnCommand(fsChangeDirectory); + } + catch (Exception & E) + { + CommandError(&E, FMTLOAD(CHANGE_DIR_ERROR, DirectoryNormalized.c_str())); + } +} + +void TTerminal::LookupUsersGroups() +{ + if (!FUsersGroupsLookedup && GetSessionData()->GetLookupUserGroups() && + GetIsCapable(fcUserGroupListing)) + { + DebugAssert(FFileSystem); + + try + { + FUsersGroupsLookedup = true; + LogEvent("Looking up groups and users."); + FFileSystem->LookupUsersGroups(); + ReactOnCommand(fsLookupUsersGroups); + + if (GetLog()->GetLogging()) + { + FGroups.Log(this, L"groups"); + FMembership.Log(this, L"membership"); + FUsers.Log(this, L"users"); + } + } + catch (Exception & E) + { + if (!GetActive() || (GetSessionData()->GetLookupUserGroups() == asOn)) + { + CommandError(&E, LoadStr(LOOKUP_GROUPS_ERROR)); + } + } + } +} + +bool TTerminal::AllowedAnyCommand(const UnicodeString & Command) const +{ + return !Command.Trim().IsEmpty(); +} + +bool TTerminal::GetCommandSessionOpened() const +{ + // consider secondary terminal open in "ready" state only + // so we never do keepalives on it until it is completely initialized + return (FCommandSession != nullptr) && + (FCommandSession->GetStatus() == ssOpened); +} + +TTerminal * TTerminal::GetCommandSession() +{ + if ((FCommandSession != nullptr) && !FCommandSession->GetActive()) + { + SAFE_DESTROY(FCommandSession); + } + + if (FCommandSession == nullptr) + { + // transaction cannot be started yet to allow proper matching transaction + // levels between main and command session + DebugAssert(FInTransaction == 0); + + try__catch + { + std::unique_ptr CommandSession(new TSecondaryTerminal(this)); + CommandSession->Init(GetSessionData(), FConfiguration, L"Shell"); + + CommandSession->SetAutoReadDirectory(false); + + TSessionData * CommandSessionData = CommandSession->FSessionData; + CommandSessionData->SetRemoteDirectory(GetCurrDirectory()); + CommandSessionData->SetFSProtocol(fsSCPonly); + CommandSessionData->SetClearAliases(false); + CommandSessionData->SetUnsetNationalVars(false); + CommandSessionData->SetLookupUserGroups(asOn); + + CommandSession->FExceptionOnFail = FExceptionOnFail; + + CommandSession->SetOnQueryUser(GetOnQueryUser()); + CommandSession->SetOnPromptUser(GetOnPromptUser()); + CommandSession->SetOnShowExtendedException(GetOnShowExtendedException()); + CommandSession->SetOnProgress(GetOnProgress()); + CommandSession->SetOnFinished(GetOnFinished()); + CommandSession->SetOnInformation(GetOnInformation()); + CommandSession->SetOnClose(MAKE_CALLBACK(TTerminal::CommandSessionClose, this)); + // do not copy OnDisplayBanner to avoid it being displayed + FCommandSession = CommandSession.release(); + } + /*catch(...) + { + SAFE_DESTROY(FCommandSession); + throw; + }*/ + } + + return FCommandSession; +} + +void TTerminal::CommandSessionClose(TObject * /*Sender*/) +{ + // Keep the states in sync. + // This is particularly to invoke ours OnClose, + // So that it is triggered before Reopen is called + Close(); +} + +#pragma warning(push) +#pragma warning(disable: 4512) // assignment operator could not be generated + +class TOutputProxy : public TObject +{ +public: + TOutputProxy(TCallSessionAction & Action, TCaptureOutputEvent OutputEvent) : + FAction(Action), + FOutputEvent(OutputEvent) + { + } + + void Output(const UnicodeString & Str, TCaptureOutputType OutputType) + { + // FAction.AddOutput(Str, StdError); + switch (OutputType) + { + case cotOutput: + FAction.AddOutput(Str, false); + break; + case cotError: + FAction.AddOutput(Str, true); + break; + case cotExitCode: + FAction.ExitCode((int)::StrToInt64(Str)); + break; + } + + if (FOutputEvent != nullptr) + { + FOutputEvent(Str, OutputType); + } + } + +private: + TCallSessionAction & FAction; + TCaptureOutputEvent FOutputEvent; +}; + +#pragma warning(pop) + +void TTerminal::AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent) +{ + TCallSessionAction Action(GetActionLog(), Command, GetCurrDirectory()); + TOutputProxy ProxyOutputEvent(Action, OutputEvent); + DoAnyCommand(Command, MAKE_CALLBACK(TOutputProxy::Output, &ProxyOutputEvent), &Action); +} + +void TTerminal::DoAnyCommand(const UnicodeString & ACommand, + TCaptureOutputEvent OutputEvent, TCallSessionAction * Action) +{ + DebugAssert(FFileSystem); + try + { + DirectoryModified(GetCurrDirectory(), false); + if (GetIsCapable(fcAnyCommand)) + { + LogEvent("Executing user defined command."); + FFileSystem->AnyCommand(ACommand, OutputEvent); + } + else + { + DebugAssert(GetCommandSessionOpened()); + DebugAssert(FCommandSession->GetFSProtocol() == cfsSCP); + LogEvent("Executing user defined command on command session."); + + FCommandSession->TerminalSetCurrentDirectory(GetCurrDirectory()); + FCommandSession->FFileSystem->AnyCommand(ACommand, OutputEvent); + + FCommandSession->GetFileSystem()->ReadCurrentDirectory(); + + // synchronize pwd (by purpose we lose transaction optimization here) + RemoteChangeDirectory(FCommandSession->GetCurrDirectory()); + } + ReactOnCommand(fsAnyCommand); + } + catch (Exception & E) + { + if (Action != nullptr) + { + RollbackAction(*Action, nullptr, &E); + } + if (GetExceptionOnFail() || (NB_STATIC_DOWNCAST(EFatal, &E) != nullptr)) + { + throw; + } + else + { + HandleExtendedException(&E); + } + } +} + +bool TTerminal::DoCreateLocalFile(const UnicodeString & AFileName, + TFileOperationProgressType * OperationProgress, + bool Resume, + bool NoConfirmation, + OUT HANDLE * AHandle) +{ + DebugAssert(OperationProgress); + DebugAssert(AHandle); + bool Result = true; + bool Done; + DWORD DesiredAccess = GENERIC_WRITE; + DWORD ShareMode = FILE_SHARE_READ; + DWORD CreationDisposition = Resume ? OPEN_ALWAYS : CREATE_ALWAYS; + DWORD FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; + do + { + *AHandle = TerminalCreateLocalFile(ApiPath(AFileName).c_str(), DesiredAccess, ShareMode, + CreationDisposition, FlagsAndAttributes); + Done = (*AHandle != INVALID_HANDLE_VALUE); + if (!Done) + { + // save the error, otherwise it gets overwritten by call to FileExists + int LastError = ::GetLastError(); + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + if (::FileExists(ApiPath(AFileName)) && + (((LocalFileAttrs = GetLocalFileAttributes(ApiPath(AFileName))) & (faReadOnly | faHidden)) != 0)) + { + if (FLAGSET(LocalFileAttrs, faReadOnly)) + { + if (OperationProgress->BatchOverwrite == boNone) + { + Result = false; + } + else if ((OperationProgress->BatchOverwrite != boAll) && !NoConfirmation) + { + uintptr_t Answer; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = QueryUser( + MainInstructions(FMTLOAD(READ_ONLY_OVERWRITE, AFileName.c_str())), nullptr, + qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll, 0); + } + + switch (Answer) + { + case qaYesToAll: + OperationProgress->BatchOverwrite = boAll; + break; + case qaCancel: + OperationProgress->Cancel = csCancel; // continue on next case + Result = false; + break; + case qaNoToAll: + OperationProgress->BatchOverwrite = boNone; + Result = false; + break; + case qaNo: + Result = false; + break; + } + } + } + else + { + DebugAssert(FLAGSET(LocalFileAttrs, faHidden)); + Result = true; + } + + if (Result) + { + FlagsAndAttributes |= + FLAGMASK(FLAGSET(LocalFileAttrs, faHidden), FILE_ATTRIBUTE_HIDDEN) | + FLAGMASK(FLAGSET(LocalFileAttrs, faReadOnly), FILE_ATTRIBUTE_READONLY); + + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, AFileName.c_str()), "", + [&]() + { + if (!this->SetLocalFileAttributes(ApiPath(AFileName), LocalFileAttrs & ~(faReadOnly | faHidden))) + { + ::RaiseLastOSError(); + } + }); + } + else + { + Done = true; + } + } + else + { + ::RaiseLastOSError(LastError); + } + } + } + while (!Done); + + return Result; +} + +bool TTerminal::TerminalCreateLocalFile(const UnicodeString & ATargetFileName, + TFileOperationProgressType * OperationProgress, + bool Resume, + bool NoConfirmation, + OUT HANDLE * AHandle) +{ + DebugAssert(OperationProgress); + DebugAssert(AHandle); + bool Result = true; + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CREATE_FILE_ERROR, ATargetFileName.c_str()), "", + [&]() + { + Result = DoCreateLocalFile(ATargetFileName, OperationProgress, Resume, NoConfirmation, + AHandle); + }); + + return Result; +} + +void TTerminal::TerminalOpenLocalFile(const UnicodeString & ATargetFileName, + DWORD Access, + OUT HANDLE * AHandle, OUT uintptr_t * AAttrs, OUT int64_t * ACTime, + OUT int64_t * AMTime, OUT int64_t * AATime, OUT int64_t * ASize, + bool TryWriteReadOnly) +{ + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + TFileOperationProgressType * OperationProgress = GetOperationProgress(); + + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(FILE_NOT_EXISTS, ATargetFileName.c_str()), "", + [&]() + { + UnicodeString FileNameApi = ApiPath(ATargetFileName); + LocalFileAttrs = this->GetLocalFileAttributes(FileNameApi); + if (LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + ::RaiseLastOSError(); + } + }); + + if (FLAGCLEAR(LocalFileAttrs, faDirectory) || (AHandle == INVALID_HANDLE_VALUE)) + { + bool NoHandle = false; + if (!TryWriteReadOnly && (Access == GENERIC_WRITE) && + ((LocalFileAttrs & faReadOnly) != 0)) + { + Access = GENERIC_READ; + NoHandle = true; + } + + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(OPENFILE_ERROR, ATargetFileName.c_str()), "", + [&]() + { + DWORD Flags = FLAGMASK(FLAGSET(LocalFileAttrs, faDirectory), FILE_FLAG_BACKUP_SEMANTICS); + LocalFileHandle = this->TerminalCreateLocalFile(ApiPath(ATargetFileName).c_str(), Access, + Access == GENERIC_READ ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ, + OPEN_EXISTING, Flags); + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + ::RaiseLastOSError(); + } + }); + + try + { + if (AATime || AMTime || ACTime) + { + FILETIME ATime; + FILETIME MTime; + FILETIME CTime; + + // Get last file access and modification time + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CANT_GET_ATTRS, ATargetFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::GetFileTime(LocalFileHandle, &CTime, &ATime, &MTime)); + }); + if (ACTime) + { + *ACTime = ::ConvertTimestampToUnixSafe(CTime, GetSessionData()->GetDSTMode()); + } + if (AATime) + { + *AATime = ::ConvertTimestampToUnixSafe(ATime, GetSessionData()->GetDSTMode()); + } + if (AMTime) + { + *AMTime = ::ConvertTimestampToUnix(MTime, GetSessionData()->GetDSTMode()); + } + } + + if (ASize) + { + // Get file size + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CANT_GET_ATTRS, ATargetFileName.c_str()), "", + [&]() + { + uint32_t LSize; + DWORD HSize; + LSize = ::GetFileSize(LocalFileHandle, &HSize); + if ((LSize == static_cast(-1)) && (::GetLastError() != NO_ERROR)) + { + ::RaiseLastOSError(); + } + *ASize = (static_cast(HSize) << 32) + LSize; + }); + } + + if ((AHandle == nullptr) || NoHandle) + { + ::CloseHandle(LocalFileHandle); + LocalFileHandle = INVALID_HANDLE_VALUE; + } + } + catch (...) + { + ::CloseHandle(LocalFileHandle); + throw; + } + } + + if (AAttrs) + { + *AAttrs = LocalFileAttrs; + } + if (AHandle) + { + *AHandle = LocalFileHandle; + } +} + +bool TTerminal::AllowLocalFileTransfer(const UnicodeString & AFileName, + const TCopyParamType * CopyParam, TFileOperationProgressType * OperationProgress) +{ + bool Result = true; + // optimization + if (GetLog()->GetLogging() || !CopyParam->AllowAnyTransfer()) + { + WIN32_FIND_DATA FindData = {}; + HANDLE LocalFileHandle = INVALID_HANDLE_VALUE; + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(FILE_NOT_EXISTS, AFileName.c_str()), "", + [&]() + { + LocalFileHandle = ::FindFirstFile(ApiPath(::ExcludeTrailingBackslash(AFileName)).c_str(), &FindData); + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + ::RaiseLastOSError(); + } + }); + ::FindClose(LocalFileHandle); + bool Directory = FLAGSET(FindData.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY); + TFileMasks::TParams Params; + // SearchRec.Size in C++B2010 is int64_t, + // so we should be able to use it instead of FindData.nFileSize* + Params.Size = + (static_cast(FindData.nFileSizeHigh) << 32) + + FindData.nFileSizeLow; + Params.Modification = ::FileTimeToDateTime(FindData.ftLastWriteTime); + UnicodeString BaseFileName = GetBaseFileName(AFileName); + if (!CopyParam->AllowTransfer(BaseFileName, osLocal, Directory, Params)) + { + LogEvent(FORMAT(L"File \"%s\" excluded from transfer", AFileName.c_str())); + Result = false; + } + else if (CopyParam->SkipTransfer(AFileName, Directory)) + { + OperationProgress->AddSkippedFileSize(Params.Size); + Result = false; + } + + if (Result) + { + LogFileDetails(AFileName, Params.Modification, Params.Size); + } + } + return Result; +} + +void TTerminal::MakeLocalFileList(const UnicodeString & AFileName, + const TSearchRec & Rec, void * Param) +{ + TMakeLocalFileListParams & Params = *NB_STATIC_DOWNCAST(TMakeLocalFileListParams, Param); + + bool Directory = FLAGSET(Rec.Attr, faDirectory); + if (Directory && Params.Recursive) + { + ProcessLocalDirectory(AFileName, MAKE_CALLBACK(TTerminal::MakeLocalFileList, this), &Params); + } + + if (!Directory || Params.IncludeDirs) + { + Params.FileList->Add(AFileName); + if (Params.FileTimes != nullptr) + { + TODO("Add TSearchRec::TimeStamp"); + // Params.FileTimes->push_back(const_cast(Rec).TimeStamp); + } + } +} + +void TTerminal::CalculateLocalFileSize(const UnicodeString & AFileName, + const TSearchRec & Rec, /*int64_t*/ void * Params) +{ + TCalculateSizeParams * AParams = NB_STATIC_DOWNCAST(TCalculateSizeParams, Params); + + bool Dir = FLAGSET(Rec.Attr, faDirectory); + + bool AllowTransfer = (AParams->CopyParam == nullptr); + // SearchRec.Size in C++B2010 is int64_t, + // so we should be able to use it instead of FindData.nFileSize* + int64_t Size = + (static_cast(Rec.FindData.nFileSizeHigh) << 32) + + Rec.FindData.nFileSizeLow; + if (!AllowTransfer) + { + TFileMasks::TParams MaskParams; + MaskParams.Size = Size; + MaskParams.Modification = ::FileTimeToDateTime(Rec.FindData.ftLastWriteTime); + + UnicodeString BaseFileName = GetBaseFileName(AFileName); + AllowTransfer = AParams->CopyParam->AllowTransfer(BaseFileName, osLocal, Dir, MaskParams); + } + + if (AllowTransfer) + { + if (!Dir) + { + AParams->Size += Size; + } + else + { + ProcessLocalDirectory(AFileName, MAKE_CALLBACK(TTerminal::CalculateLocalFileSize, this), Params); + } + } + + StartOperationWithFile(AFileName, foCalculateSize); +} + +bool TTerminal::CalculateLocalFilesSize(const TStrings * AFileList, + const TCopyParamType * CopyParam, bool AllowDirs, + OUT int64_t & Size) +{ + bool Result = true; + TFileOperationProgressType OperationProgress(MAKE_CALLBACK(TTerminal::DoProgress, this), MAKE_CALLBACK(TTerminal::DoFinished, this)); + TOnceDoneOperation OnceDoneOperation = odoIdle; + OperationProgress.Start(foCalculateSize, osLocal, AFileList->GetCount()); + try__finally + { + SCOPE_EXIT + { + FOperationProgress = nullptr; + OperationProgress.Stop(); + }; + TCalculateSizeParams Params; + Params.Size = 0; + Params.Params = 0; + Params.CopyParam = CopyParam; + + DebugAssert(!FOperationProgress); + FOperationProgress = &OperationProgress; //-V506 + for (intptr_t Index = 0; Result && (Index < AFileList->GetCount()); ++Index) + { + UnicodeString FileName = AFileList->GetString(Index); + TSearchRec Rec; + if (FileSearchRec(FileName, Rec)) + { + if (FLAGSET(Rec.Attr, faDirectory) && !AllowDirs) + { + Result = false; + } + else + { + CalculateLocalFileSize(FileName, Rec, &Params); + OperationProgress.Finish(FileName, true, OnceDoneOperation); + } + } + } + + Size = Params.Size; + } + __finally + { + FOperationProgress = nullptr; + OperationProgress.Stop(); + }; + + if (OnceDoneOperation != odoIdle) + { + CloseOnCompletion(OnceDoneOperation); + } + return Result; +} + +struct TSynchronizeFileData : public TObject +{ +NB_DECLARE_CLASS(TSynchronizeFileData) +public: + bool Modified; + bool New; + bool IsDirectory; + TChecklistItem::TFileInfo Info; + TChecklistItem::TFileInfo MatchingRemoteFile; + TRemoteFile * MatchingRemoteFileFile; + intptr_t MatchingRemoteFileImageIndex; + FILETIME LocalLastWriteTime; +}; + +const intptr_t sfFirstLevel = 0x01; +struct TSynchronizeData : public TObject +{ +NB_DECLARE_CLASS(TSynchronizeData) +public: + UnicodeString LocalDirectory; + UnicodeString RemoteDirectory; + TTerminal::TSynchronizeMode Mode; + intptr_t Params; + TSynchronizeDirectoryEvent OnSynchronizeDirectory; + TSynchronizeOptions * Options; + intptr_t Flags; + TStringList * LocalFileList; + const TCopyParamType * CopyParam; + TSynchronizeChecklist * Checklist; + + void DeleteLocalFileList() + { + if (LocalFileList != nullptr) + { + for (intptr_t Index = 0; Index < LocalFileList->GetCount(); ++Index) + { + TSynchronizeFileData * FileData = NB_STATIC_DOWNCAST(TSynchronizeFileData, + LocalFileList->GetObj(Index)); + SAFE_DESTROY(FileData); + } + SAFE_DESTROY(LocalFileList); + } + } +}; + +TSynchronizeChecklist * TTerminal::SynchronizeCollect(const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, TSynchronizeMode Mode, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory, + TSynchronizeOptions * Options) +{ + TValueRestorer UseBusyCursorRestorer(FUseBusyCursor); + FUseBusyCursor = false; + + std::unique_ptr Checklist(new TSynchronizeChecklist()); + try__catch + { + DoSynchronizeCollectDirectory(LocalDirectory, RemoteDirectory, Mode, + CopyParam, Params, OnSynchronizeDirectory, Options, sfFirstLevel, + Checklist.get()); + Checklist->Sort(); + } + /*catch(...) + { + delete Checklist; + throw; + }*/ + return Checklist.release(); +} + +static void AddFlagName(UnicodeString & ParamsStr, intptr_t & Params, intptr_t Param, const UnicodeString & Name) +{ + if (FLAGSET(Params, Param)) + { + AddToList(ParamsStr, Name, L", "); + } + Params &= ~Param; +} + +UnicodeString TTerminal::SynchronizeModeStr(TSynchronizeMode Mode) +{ + UnicodeString ModeStr; + switch (Mode) + { + case smRemote: + ModeStr = L"Remote"; + break; + case smLocal: + ModeStr = L"Local"; + break; + case smBoth: + ModeStr = L"Both"; + break; + default: + ModeStr = L"Unknown"; + break; + } + return ModeStr; +} + +UnicodeString TTerminal::SynchronizeParamsStr(intptr_t Params) +{ + UnicodeString ParamsStr; + AddFlagName(ParamsStr, Params, spDelete, L"Delete"); + AddFlagName(ParamsStr, Params, spNoConfirmation, L"NoConfirmation"); + AddFlagName(ParamsStr, Params, spExistingOnly, L"ExistingOnly"); + AddFlagName(ParamsStr, Params, spNoRecurse, L"NoRecurse"); + AddFlagName(ParamsStr, Params, spUseCache, L"UseCache"); + AddFlagName(ParamsStr, Params, spDelayProgress, L"DelayProgress"); + AddFlagName(ParamsStr, Params, spPreviewChanges, L"*PreviewChanges"); // GUI only + AddFlagName(ParamsStr, Params, spSubDirs, L"SubDirs"); + AddFlagName(ParamsStr, Params, spTimestamp, L"Timestamp"); + AddFlagName(ParamsStr, Params, spNotByTime, L"NotByTime"); + AddFlagName(ParamsStr, Params, spBySize, L"BySize"); + AddFlagName(ParamsStr, Params, spSelectedOnly, L"*SelectedOnly"); // GUI only + AddFlagName(ParamsStr, Params, spMirror, L"Mirror"); + if (Params > 0) + { + AddToList(ParamsStr, FORMAT(L"0x%x", static_cast(Params)), L", "); + } + return ParamsStr; +} + +void TTerminal::DoSynchronizeCollectDirectory(const UnicodeString & LocalDirectory, + const UnicodeString & ARemoteDirectory, TSynchronizeMode Mode, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory, TSynchronizeOptions * Options, + intptr_t Flags, TSynchronizeChecklist * Checklist) +{ + TFileOperationProgressType * OperationProgress = GetOperationProgress(); + TSynchronizeData Data; + + Data.LocalDirectory = ::IncludeTrailingBackslash(LocalDirectory); + Data.RemoteDirectory = core::UnixIncludeTrailingBackslash(ARemoteDirectory); + Data.Mode = Mode; + Data.Params = Params; + Data.OnSynchronizeDirectory = OnSynchronizeDirectory; + Data.LocalFileList = nullptr; + Data.CopyParam = CopyParam; + Data.Options = Options; + Data.Flags = Flags; + Data.Checklist = Checklist; + + LogEvent(FORMAT(L"Collecting synchronization list for local directory '%s' and remote directory '%s', " + L"mode = %s, params = 0x%x (%s)", LocalDirectory.c_str(), ARemoteDirectory.c_str(), + SynchronizeModeStr(Mode).c_str(), int(Params), SynchronizeParamsStr(Params).c_str())); + + if (FLAGCLEAR(Params, spDelayProgress)) + { + DoSynchronizeProgress(Data, true); + } + + try__finally + { + SCOPE_EXIT + { + Data.DeleteLocalFileList(); + }; + bool Found = false; + TSearchRecChecked SearchRec; + Data.LocalFileList = CreateSortedStringList(); + + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, LocalDirectory.c_str()), "", + [&]() + { + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + Found = ::FindFirstChecked(Data.LocalDirectory + L"*.*", FindAttrs, SearchRec) == 0; + }); + + if (Found) + { + try__finally + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + UnicodeString FileName; + while (Found) + { + FileName = SearchRec.Name; + // add dirs for recursive mode or when we are interested in newly + // added subdirs + // SearchRec.Size in C++B2010 is int64_t, + // so we should be able to use it instead of FindData.nFileSize* + int64_t Size = + (static_cast(SearchRec.FindData.nFileSizeHigh) << 32) + + SearchRec.FindData.nFileSizeLow; + TDateTime Modification = ::FileTimeToDateTime(SearchRec.FindData.ftLastWriteTime); + TFileMasks::TParams MaskParams; + MaskParams.Size = Size; + MaskParams.Modification = Modification; + UnicodeString RemoteFileName = + ChangeFileName(CopyParam, FileName, osLocal, false); + UnicodeString FullLocalFileName = Data.LocalDirectory + FileName; + UnicodeString BaseFileName = GetBaseFileName(FullLocalFileName); + if ((FileName != THISDIRECTORY) && (FileName != PARENTDIRECTORY) && + CopyParam->AllowTransfer(BaseFileName, osLocal, + FLAGSET(SearchRec.Attr, faDirectory), MaskParams) && + !FFileSystem->TemporaryTransferFile(FileName) && + (FLAGCLEAR(Flags, sfFirstLevel) || + (Options == nullptr) || + Options->MatchesFilter(FileName) || + Options->MatchesFilter(RemoteFileName))) + { + TSynchronizeFileData * FileData = new TSynchronizeFileData; + + FileData->IsDirectory = FLAGSET(SearchRec.Attr, faDirectory); + FileData->Info.FileName = FileName; + FileData->Info.Directory = Data.LocalDirectory; + FileData->Info.Modification = Modification; + FileData->Info.ModificationFmt = mfFull; + FileData->Info.Size = Size; + FileData->LocalLastWriteTime = SearchRec.FindData.ftLastWriteTime; + FileData->New = true; + FileData->Modified = false; + Data.LocalFileList->AddObject(FileName, FileData); + LogEvent(FORMAT(L"Local file %s included to synchronization", + FormatFileDetailsForLog(FullLocalFileName, Modification, Size).c_str())); + } + else + { + LogEvent(FORMAT(L"Local file %s excluded from synchronization", + FormatFileDetailsForLog(FullLocalFileName, Modification, Size).c_str())); + } + + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, LocalDirectory.c_str()), "", + [&]() + { + Found = (::FindNextChecked(SearchRec) == 0); + }); + } + } + __finally + { + FindClose(SearchRec); + }; + + // can we expect that ProcessDirectory would take so little time + // that we can postpone showing progress window until anything actually happens? + bool Cached = FLAGSET(Params, spUseCache) && GetSessionData()->GetCacheDirectories() && + FDirectoryCache->HasFileList(ARemoteDirectory); + + if (!Cached && FLAGSET(Params, spDelayProgress)) + { + DoSynchronizeProgress(Data, true); + } + + ProcessDirectory(ARemoteDirectory, MAKE_CALLBACK(TTerminal::SynchronizeCollectFile, this), &Data, + FLAGSET(Params, spUseCache)); + + TSynchronizeFileData * FileData; + for (intptr_t Index = 0; Index < Data.LocalFileList->GetCount(); ++Index) + { + FileData = NB_STATIC_DOWNCAST(TSynchronizeFileData, + Data.LocalFileList->GetObj(Index)); + // add local file either if we are going to upload it + // (i.e. if it is updated or we want to upload even new files) + // or if we are going to delete it (i.e. all "new"=obsolete files) + bool Modified = (FileData->Modified && ((Mode == smBoth) || (Mode == smRemote))); + bool New = (FileData->New && + ((Mode == smLocal) || + (((Mode == smBoth) || (Mode == smRemote)) && FLAGCLEAR(Params, spTimestamp)))); + + if (New) + { + LogEvent(FORMAT(L"Local file %s is new", + FormatFileDetailsForLog(UnicodeString(FileData->Info.Directory) + UnicodeString(FileData->Info.FileName), + FileData->Info.Modification, FileData->Info.Size).c_str())); + } + + if (Modified || New) + { + std::unique_ptr ChecklistItem(new TChecklistItem()); + try__finally + { + ChecklistItem->IsDirectory = FileData->IsDirectory; + + ChecklistItem->Local = FileData->Info; + ChecklistItem->FLocalLastWriteTime = FileData->LocalLastWriteTime; + + if (Modified) + { + DebugAssert(!FileData->MatchingRemoteFile.Directory.IsEmpty()); + ChecklistItem->Remote = FileData->MatchingRemoteFile; + ChecklistItem->ImageIndex = FileData->MatchingRemoteFileImageIndex; + ChecklistItem->RemoteFile = FileData->MatchingRemoteFileFile; + } + else + { + ChecklistItem->Remote.Directory = Data.RemoteDirectory; + } + + if ((Mode == smBoth) || (Mode == smRemote)) + { + ChecklistItem->Action = + (Modified ? saUploadUpdate : saUploadNew); + ChecklistItem->Checked = + (Modified || FLAGCLEAR(Params, spExistingOnly)) && + (!ChecklistItem->IsDirectory || FLAGCLEAR(Params, spNoRecurse) || + FLAGSET(Params, spSubDirs)); + } + else if ((Mode == smLocal) && FLAGCLEAR(Params, spTimestamp)) + { + ChecklistItem->Action = saDeleteLocal; + ChecklistItem->Checked = + FLAGSET(Params, spDelete) && + (!ChecklistItem->IsDirectory || FLAGCLEAR(Params, spNoRecurse) || + FLAGSET(Params, spSubDirs)); + } + + if (ChecklistItem->Action != saNone) + { + Data.Checklist->Add(ChecklistItem.get()); + ChecklistItem.release(); + } + } + __finally + { +// delete ChecklistItem; + }; + } + else + { + if (FileData->Modified) + { + SAFE_DESTROY(FileData->MatchingRemoteFileFile); + } + } + } + } + } + __finally + { + Data.DeleteLocalFileList(); +#if 0 + if (Data.LocalFileList != NULL) + { + for (int Index = 0; Index < Data.LocalFileList->Count; Index++) + { + TSynchronizeFileData * FileData = reinterpret_cast + (Data.LocalFileList->Objects[Index]); + delete FileData; + } + delete Data.LocalFileList; + } +#endif + }; +} + +void TTerminal::SynchronizeCollectFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TSynchronizeData*/ void * Param) +{ + TFileOperationProgressType * OperationProgress = GetOperationProgress(); + try + { + DoSynchronizeCollectFile(AFileName, AFile, Param); + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!HandleException(&E)) + { + throw; + } + } +} + +void TTerminal::DoSynchronizeCollectFile(const UnicodeString & /*AFileName*/, + const TRemoteFile * AFile, /*TSynchronizeData*/ void * Param) +{ + TSynchronizeData * Data = NB_STATIC_DOWNCAST(TSynchronizeData, Param); + + TFileMasks::TParams MaskParams; + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + UnicodeString LocalFileName = + ChangeFileName(Data->CopyParam, AFile->GetFileName(), osRemote, false); + UnicodeString FullRemoteFileName = + core::UnixExcludeTrailingBackslash(AFile->GetFullFileName()); + UnicodeString BaseFileName = GetBaseFileName(FullRemoteFileName); + if (Data->CopyParam->AllowTransfer( + BaseFileName, osRemote, + AFile->GetIsDirectory(), MaskParams) && + !FFileSystem->TemporaryTransferFile(AFile->GetFileName()) && + (FLAGCLEAR(Data->Flags, sfFirstLevel) || + (Data->Options == nullptr) || + Data->Options->MatchesFilter(AFile->GetFileName()) || + Data->Options->MatchesFilter(LocalFileName))) + { + std::unique_ptr ChecklistItem(new TChecklistItem()); + try__finally + { + ChecklistItem->IsDirectory = AFile->GetIsDirectory(); + ChecklistItem->ImageIndex = AFile->GetIconIndex(); + + ChecklistItem->Remote.FileName = AFile->GetFileName(); + ChecklistItem->Remote.Directory = Data->RemoteDirectory; + ChecklistItem->Remote.Modification = AFile->GetModification(); + ChecklistItem->Remote.ModificationFmt = AFile->GetModificationFmt(); + ChecklistItem->Remote.Size = AFile->GetSize(); + + bool Modified = false; + bool New = false; + if (AFile->GetIsDirectory() && !CanRecurseToDirectory(AFile)) + { + LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", AFile->GetFileName().c_str())); + } + else + { + intptr_t LocalIndex = Data->LocalFileList->IndexOf(LocalFileName.c_str()); + New = (LocalIndex < 0); + if (!New) + { + TSynchronizeFileData * LocalData = + NB_STATIC_DOWNCAST(TSynchronizeFileData, Data->LocalFileList->GetObj(LocalIndex)); + + LocalData->New = false; + + if (AFile->GetIsDirectory() != LocalData->IsDirectory) + { + LogEvent(FORMAT(L"%s is directory on one side, but file on the another", + AFile->GetFileName().c_str())); + } + else if (!AFile->GetIsDirectory()) + { + ChecklistItem->Local = LocalData->Info; + + ChecklistItem->Local.Modification = + core::ReduceDateTimePrecision(ChecklistItem->Local.Modification, AFile->GetModificationFmt()); + + bool LocalModified = false; + // for spTimestamp+spBySize require that the file sizes are the same + // before comparing file time + intptr_t TimeCompare; + if (FLAGCLEAR(Data->Params, spNotByTime) && + (FLAGCLEAR(Data->Params, spTimestamp) || + FLAGCLEAR(Data->Params, spBySize) || + (ChecklistItem->Local.Size == ChecklistItem->Remote.Size))) + { + TimeCompare = CompareFileTime(ChecklistItem->Local.Modification, + ChecklistItem->Remote.Modification); + } + else + { + TimeCompare = 0; + } + if (TimeCompare < 0) + { + if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) || + (Data->Mode == smBoth) || (Data->Mode == smLocal)) + { + Modified = true; + } + else + { + LocalModified = true; + } + } + else if (TimeCompare > 0) + { + if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) || + (Data->Mode == smBoth) || (Data->Mode == smRemote)) + { + LocalModified = true; + } + else + { + Modified = true; + } + } + else if (FLAGSET(Data->Params, spBySize) && + (ChecklistItem->Local.Size != ChecklistItem->Remote.Size) && + FLAGCLEAR(Data->Params, spTimestamp)) + { + Modified = true; + LocalModified = true; + } + + if (LocalModified) + { + LocalData->Modified = true; + LocalData->MatchingRemoteFile = ChecklistItem->Remote; + LocalData->MatchingRemoteFileImageIndex = ChecklistItem->ImageIndex; + // we need this for custom commands over checklist only, + // not for sync itself + LocalData->MatchingRemoteFileFile = AFile->Duplicate(); + LogEvent(FORMAT(L"Local file %s is modified comparing to remote file %s", + FormatFileDetailsForLog(UnicodeString(LocalData->Info.Directory) + UnicodeString(LocalData->Info.FileName), + LocalData->Info.Modification, LocalData->Info.Size).c_str(), + FormatFileDetailsForLog(FullRemoteFileName, + AFile->GetModification(), AFile->GetSize()).c_str())); + } + + if (Modified) + { + LogEvent(FORMAT(L"Remote file %s is modified comparing to local file %s", + FormatFileDetailsForLog(FullRemoteFileName, + AFile->GetModification(), + AFile->GetSize()).c_str(), + FormatFileDetailsForLog(UnicodeString(LocalData->Info.Directory) + UnicodeString(LocalData->Info.FileName), + LocalData->Info.Modification, LocalData->Info.Size).c_str())); + } + } + else if (FLAGCLEAR(Data->Params, spNoRecurse)) + { + DoSynchronizeCollectDirectory( + Data->LocalDirectory + LocalData->Info.FileName, + Data->RemoteDirectory + AFile->GetFileName(), + Data->Mode, Data->CopyParam, Data->Params, Data->OnSynchronizeDirectory, + Data->Options, (Data->Flags & ~sfFirstLevel), + Data->Checklist); + } + } + else + { + ChecklistItem->Local.Directory = Data->LocalDirectory; + LogEvent(FORMAT(L"Remote file %s is new", + FormatFileDetailsForLog(FullRemoteFileName, AFile->GetModification(), AFile->GetSize()).c_str())); + } + } + + if (New || Modified) + { + DebugAssert(!New || !Modified); + + // download the file if it changed or is new and we want to have it locally + if ((Data->Mode == smBoth) || (Data->Mode == smLocal)) + { + if (FLAGCLEAR(Data->Params, spTimestamp) || Modified) + { + ChecklistItem->Action = + (Modified ? saDownloadUpdate : saDownloadNew); + ChecklistItem->Checked = + (Modified || FLAGCLEAR(Data->Params, spExistingOnly)) && + (!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) || + FLAGSET(Data->Params, spSubDirs)); + } + } + else if ((Data->Mode == smRemote) && New) + { + if (FLAGCLEAR(Data->Params, spTimestamp)) + { + ChecklistItem->Action = saDeleteRemote; + ChecklistItem->Checked = + FLAGSET(Data->Params, spDelete) && + (!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) || + FLAGSET(Data->Params, spSubDirs)); + } + } + + if (ChecklistItem->Action != saNone) + { + ChecklistItem->RemoteFile = AFile->Duplicate(); + Data->Checklist->Add(ChecklistItem.get()); + ChecklistItem.release(); + } + } + } + __finally + { +// delete ChecklistItem; + }; + } + else + { + LogEvent(FORMAT(L"Remote file %s excluded from synchronization", + FormatFileDetailsForLog(FullRemoteFileName, AFile->GetModification(), AFile->GetSize()).c_str())); + } +} + +void TTerminal::SynchronizeApply(TSynchronizeChecklist * Checklist, + const UnicodeString & /*LocalDirectory*/, const UnicodeString & /*RemoteDirectory*/, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory) +{ + TSynchronizeData Data; + + Data.OnSynchronizeDirectory = OnSynchronizeDirectory; + + intptr_t CopyParams = + FLAGMASK(FLAGSET(Params, spNoConfirmation), cpNoConfirmation); + + TCopyParamType SyncCopyParam = *CopyParam; + // when synchronizing by time, we force preserving time, + // otherwise it does not make any sense + if (FLAGCLEAR(Params, spNotByTime)) + { + SyncCopyParam.SetPreserveTime(true); + } + + std::unique_ptr DownloadList(new TStringList()); + std::unique_ptr DeleteRemoteList(new TStringList()); + std::unique_ptr UploadList(new TStringList()); + std::unique_ptr DeleteLocalList(new TStringList()); + + BeginTransaction(); + + try__finally + { + SCOPE_EXIT + { + EndTransaction(); + }; + intptr_t IIndex = 0; + while (IIndex < Checklist->GetCount()) + { + const TChecklistItem * ChecklistItem; + + DownloadList->Clear(); + DeleteRemoteList->Clear(); + UploadList->Clear(); + DeleteLocalList->Clear(); + + ChecklistItem = Checklist->GetItem(IIndex); + + UnicodeString CurrentLocalDirectory = ChecklistItem->Local.Directory; + UnicodeString CurrentRemoteDirectory = ChecklistItem->Remote.Directory; + + LogEvent(FORMAT(L"Synchronizing local directory '%s' with remote directory '%s', " + L"params = 0x%x (%s)", CurrentLocalDirectory.c_str(), CurrentRemoteDirectory.c_str(), + int(Params), SynchronizeParamsStr(Params).c_str())); + + intptr_t Count = 0; + + while ((IIndex < Checklist->GetCount()) && + (Checklist->GetItem(IIndex)->Local.Directory == CurrentLocalDirectory) && + (Checklist->GetItem(IIndex)->Remote.Directory == CurrentRemoteDirectory)) + { + ChecklistItem = Checklist->GetItem(IIndex); + if (ChecklistItem->Checked) + { + Count++; + + if (FLAGSET(Params, spTimestamp)) + { + switch (ChecklistItem->Action) + { + case saDownloadUpdate: + DownloadList->AddObject( + core::UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) + + ChecklistItem->Remote.FileName, + const_cast(ChecklistItem)); + break; + + case saUploadUpdate: + UploadList->AddObject( + ::IncludeTrailingBackslash(ChecklistItem->Local.Directory) + + ChecklistItem->Local.FileName, + const_cast(ChecklistItem)); + break; + + default: + DebugFail(); + break; + } + } + else + { + switch (ChecklistItem->Action) + { + case saDownloadNew: + case saDownloadUpdate: + DownloadList->AddObject( + core::UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) + + ChecklistItem->Remote.FileName, + ChecklistItem->RemoteFile); + break; + + case saDeleteRemote: + DeleteRemoteList->AddObject( + core::UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) + + ChecklistItem->Remote.FileName, + ChecklistItem->RemoteFile); + break; + + case saUploadNew: + case saUploadUpdate: + UploadList->Add( + ::IncludeTrailingBackslash(ChecklistItem->Local.Directory) + + ChecklistItem->Local.FileName); + break; + + case saDeleteLocal: + DeleteLocalList->Add( + ::IncludeTrailingBackslash(ChecklistItem->Local.Directory) + + ChecklistItem->Local.FileName); + break; + + default: + DebugFail(); + break; + } + } + } + ++IIndex; + } + + // prevent showing/updating of progress dialog if there's nothing to do + if (Count > 0) + { + Data.LocalDirectory = ::IncludeTrailingBackslash(CurrentLocalDirectory); + Data.RemoteDirectory = core::UnixIncludeTrailingBackslash(CurrentRemoteDirectory); + DoSynchronizeProgress(Data, false); + + if (FLAGSET(Params, spTimestamp)) + { + if (DownloadList->GetCount() > 0) + { + ProcessFiles(DownloadList.get(), foSetProperties, + MAKE_CALLBACK(TTerminal::SynchronizeLocalTimestamp, this), nullptr, osLocal); + } + + if (UploadList->GetCount() > 0) + { + ProcessFiles(UploadList.get(), foSetProperties, + MAKE_CALLBACK(TTerminal::SynchronizeRemoteTimestamp, this)); + } + } + else + { + if ((DownloadList->GetCount() > 0) && + !CopyToLocal(DownloadList.get(), Data.LocalDirectory, &SyncCopyParam, CopyParams)) + { + Abort(); + } + + if ((DeleteRemoteList->GetCount() > 0) && + !RemoteDeleteFiles(DeleteRemoteList.get())) + { + Abort(); + } + + if ((UploadList->GetCount() > 0) && + !CopyToRemote(UploadList.get(), Data.RemoteDirectory, &SyncCopyParam, CopyParams)) + { + Abort(); + } + + if ((DeleteLocalList->GetCount() > 0) && + !DeleteLocalFiles(DeleteLocalList.get())) + { + Abort(); + } + } + } + } + } + __finally + { +#if 0 + delete DownloadList; + delete DeleteRemoteList; + delete UploadList; + delete DeleteLocalList; +#endif + + EndTransaction(); + }; +} + +void TTerminal::DoSynchronizeProgress(const TSynchronizeData & Data, + bool Collect) +{ + if (Data.OnSynchronizeDirectory) + { + bool Continue = true; + Data.OnSynchronizeDirectory(Data.LocalDirectory, Data.RemoteDirectory, + Continue, Collect); + + if (!Continue) + { + Abort(); + } + } +} + +void TTerminal::SynchronizeLocalTimestamp(const UnicodeString & /*FileName*/, + const TRemoteFile * AFile, void * /*Param*/) +{ + TFileOperationProgressType * OperationProgress = GetOperationProgress(); + + const TChecklistItem * ChecklistItem = + reinterpret_cast(AFile); + + UnicodeString LocalFile = + ::IncludeTrailingBackslash(ChecklistItem->Local.Directory) + + ChecklistItem->Local.FileName; + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, LocalFile.c_str()), "", + [&]() + { + SetLocalFileTime(LocalFile, ChecklistItem->Remote.Modification); + }); +} + +void TTerminal::SynchronizeRemoteTimestamp(const UnicodeString & /*AFileName*/, + const TRemoteFile * AFile, void * /*Param*/) +{ + const TChecklistItem * ChecklistItem = + reinterpret_cast(AFile); + + TRemoteProperties Properties; + Properties.Valid << vpModification; + Properties.Modification = ::ConvertTimestampToUnix(ChecklistItem->FLocalLastWriteTime, + GetSessionData()->GetDSTMode()); + + ChangeFileProperties( + core::UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) + ChecklistItem->Remote.FileName, + nullptr, &Properties); +} + +void TTerminal::FileFind(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TFilesFindParams*/ void * Param) +{ + // see DoFilesFind + FOnFindingFile = nullptr; + + DebugAssert(Param); + DebugAssert(AFile); + TFilesFindParams * AParams = NB_STATIC_DOWNCAST(TFilesFindParams, Param); + + if (!AParams->Cancel) + { + UnicodeString LocalFileName = AFileName; + if (AFileName.IsEmpty()) + { + LocalFileName = AFile->GetFileName(); + } + + TFileMasks::TParams MaskParams; + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + + UnicodeString FullFileName = core::UnixExcludeTrailingBackslash(AFile->GetFullFileName()); + bool ImplicitMatch = false; + // Do not use recursive include match + if (AParams->FileMask.Matches(FullFileName, false, + AFile->GetIsDirectory(), &MaskParams, false, ImplicitMatch)) + { + if (!ImplicitMatch) + { + AParams->OnFileFound(this, LocalFileName, AFile, AParams->Cancel); + } + + if (AFile->GetIsDirectory()) + { + UnicodeString RealDirectory; + if (!AFile->GetIsSymLink() || AFile->GetLinkTo().IsEmpty()) + { + RealDirectory = core::UnixIncludeTrailingBackslash(AParams->RealDirectory) + AFile->GetFileName(); + } + else + { + RealDirectory = core::AbsolutePath(AParams->RealDirectory, AFile->GetLinkTo()); + } + + if (!AParams->LoopDetector.IsUnvisitedDirectory(RealDirectory)) + { + LogEvent(FORMAT(L"Already searched \"%s\" directory, link loop detected", FullFileName.c_str())); + } + else + { + DoFilesFind(FullFileName, *AParams, RealDirectory); + } + } + } + } +} + +void TTerminal::DoFilesFind(const UnicodeString & Directory, TFilesFindParams & Params, const UnicodeString & RealDirectory) +{ + LogEvent(FORMAT(L"Searching directory \"%s\" (real path \"%s\")", Directory.c_str(), RealDirectory.c_str())); + Params.OnFindingFile(this, Directory, Params.Cancel); + if (!Params.Cancel) + { + DebugAssert(FOnFindingFile == nullptr); + // ideally we should set the handler only around actually reading + // of the directory listing, so we at least reset the handler in + // FileFind + FOnFindingFile = Params.OnFindingFile; + UnicodeString PrevRealDirectory = Params.RealDirectory; + try__finally + { + SCOPE_EXIT + { + Params.RealDirectory = PrevRealDirectory; + FOnFindingFile = nullptr; + }; + Params.RealDirectory = RealDirectory; + ProcessDirectory(Directory, MAKE_CALLBACK(TTerminal::FileFind, this), &Params, false, true); + } + __finally + { + Params.RealDirectory = PrevRealDirectory; + FOnFindingFile = nullptr; + }; + } +} + +void TTerminal::FilesFind(const UnicodeString & Directory, const TFileMasks & FileMask, + TFileFoundEvent OnFileFound, TFindingFileEvent OnFindingFile) +{ + TFilesFindParams Params; + Params.FileMask = FileMask; + Params.OnFileFound = OnFileFound; + Params.OnFindingFile = OnFindingFile; + Params.Cancel = false; + + Params.LoopDetector.RecordVisitedDirectory(Directory); + + DoFilesFind(Directory, Params, Directory); +} + +void TTerminal::SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable) +{ + DebugAssert(GetIsCapable(fcCheckingSpaceAvailable)); + + try + { + FFileSystem->SpaceAvailable(APath, ASpaceAvailable); + } + catch (Exception & E) + { + CommandError(&E, FMTLOAD(SPACE_AVAILABLE_ERROR, APath.c_str())); + } +} + +void TTerminal::LockFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * /*Param*/) +{ + StartOperationWithFile(AFileName, foLock); + + LogEvent(FORMAT(L"Locking file \"%s\".", AFileName.c_str())); + FileModified(AFile, AFileName, true); + + DoLockFile(AFileName, AFile); + ReactOnCommand(fsLock); +} + +void TTerminal::DoLockFile(const UnicodeString & AFileName, const TRemoteFile * AFile) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + FFileSystem->LockFile(AFileName, AFile); + } + catch (Exception & E) + { + RetryLoop.Error(E, FMTLOAD(LOCK_FILE_ERROR, AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::UnlockFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * /*Param*/) +{ + StartOperationWithFile(AFileName, foUnlock); + + LogEvent(FORMAT(L"Unlocking file \"%s\".", AFileName.c_str())); + FileModified(AFile, AFileName, true); + + DoUnlockFile(AFileName, AFile); + ReactOnCommand(fsLock); +} + +void TTerminal::DoUnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile) +{ + TRetryOperationLoop RetryLoop(this); + do + { + try + { + FFileSystem->UnlockFile(AFileName, AFile); + } + catch (Exception & E) + { + RetryLoop.Error(E, FMTLOAD(UNLOCK_FILE_ERROR, AFileName.c_str())); + } + } + while (RetryLoop.Retry()); +} + +void TTerminal::LockFiles(TStrings * AFileList) +{ + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + EndTransaction(); + }; + { + ProcessFiles(AFileList, foLock, MAKE_CALLBACK(TTerminal::LockFile, this), nullptr); + } + } + __finally + { + EndTransaction(); + }; +} + +void TTerminal::UnlockFiles(TStrings * AFileList) +{ + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + EndTransaction(); + }; + { + ProcessFiles(AFileList, foUnlock, MAKE_CALLBACK(TTerminal::UnlockFile, this), nullptr); + } + } + __finally + { + EndTransaction(); + }; +} + +const TSessionInfo & TTerminal::GetSessionInfo() const +{ + return FFileSystem->GetSessionInfo(); +} + +const TFileSystemInfo & TTerminal::GetFileSystemInfo(bool Retrieve) +{ + return FFileSystem->GetFileSystemInfo(Retrieve); +} + +void TTerminal::GetSupportedChecksumAlgs(TStrings * Algs) +{ + FFileSystem->GetSupportedChecksumAlgs(Algs); +} + +UnicodeString TTerminal::GetPassword() const +{ + UnicodeString Result; + // FRememberedPassword is empty also when stored password was used + if (FRememberedPassword.IsEmpty()) + { + Result = GetSessionData()->GetPassword(); + } + else + { + Result = GetRememberedPassword(); + } + return Result; +} + +UnicodeString TTerminal::GetRememberedPassword() const +{ + return DecryptPassword(FRememberedPassword); +} + +UnicodeString TTerminal::GetRememberedTunnelPassword() const +{ + return DecryptPassword(FRememberedTunnelPassword); +} + +bool TTerminal::GetStoredCredentialsTried() const +{ + bool Result; + if (FFileSystem != nullptr) + { + Result = FFileSystem->GetStoredCredentialsTried(); + } + else if (FSecureShell != nullptr) + { + Result = FSecureShell->GetStoredCredentialsTried(); + } + else + { + DebugAssert(FTunnelOpening); + Result = false; + } + return Result; +} + +bool TTerminal::CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params) +{ + DebugAssert(FFileSystem); + DebugAssert(AFilesToCopy); + + bool Result = false; + TOnceDoneOperation OnceDoneOperation = odoIdle; + + TFileOperationProgressType OperationProgress(MAKE_CALLBACK(TTerminal::DoProgress, this), MAKE_CALLBACK(TTerminal::DoFinished, this)); + try + { + int64_t Size = 0; + // dirty trick: when moving, do not pass copy param to avoid exclude mask + bool CalculatedSize = + CalculateLocalFilesSize( + AFilesToCopy, + (FLAGCLEAR(Params, cpDelete) ? CopyParam : nullptr), + CopyParam->GetCalculateSize(), + Size); + + OperationProgress.Start((Params & cpDelete) ? foMove : foCopy, osLocal, + AFilesToCopy->GetCount(), (Params & cpTemporary) > 0, TargetDir, CopyParam->GetCPSLimit()); + + FOperationProgress = &OperationProgress; //-V506 + //bool CollectingUsage = false; + { + SCOPE_EXIT + { + OperationProgress.Stop(); + FOperationProgress = nullptr; + }; + if (CalculatedSize) + { +#if 0 + if (Configuration->Usage->Collect) + { + int CounterSize = TUsage::CalculateCounterSize(Size); + Configuration->Usage->Inc(L"Uploads"); + Configuration->Usage->Inc(L"UploadedBytes", CounterSize); + Configuration->Usage->SetMax(L"MaxUploadSize", CounterSize); + CollectingUsage = true; + } +#endif + OperationProgress.SetTotalSize(Size); + } + + UnicodeString UnlockedTargetDir = TranslateLockedPath(TargetDir, false); + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + if (GetActive()) + { + ReactOnCommand(fsCopyToRemote); + } + EndTransaction(); + }; + if (GetLog()->GetLogging()) + { + LogEvent(FORMAT(L"Copying %d files/directories to remote directory " + L"%s\"", AFilesToCopy->GetCount(), TargetDir.c_str())); + LogEvent(CopyParam->GetLogStr()); + } + + FFileSystem->CopyToRemote(AFilesToCopy, UnlockedTargetDir, + CopyParam, Params, &OperationProgress, OnceDoneOperation); + } + __finally + { + if (GetActive()) + { + ReactOnCommand(fsCopyToRemote); + } + EndTransaction(); + }; + + if (OperationProgress.Cancel == csContinue) + { + Result = true; + } + } + __finally + { + /*if (GetCollectingUsage()) + { + int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed()); + Configuration->Usage->Inc(L"UploadTime", CounterTime); + Configuration->Usage->SetMax(L"MaxUploadTime", CounterTime); + }*/ + OperationProgress.Stop(); + FOperationProgress = nullptr; + }; + } + catch (Exception & E) + { + if (OperationProgress.Cancel != csCancel) + { + CommandError(&E, MainInstructions(LoadStr(TOREMOTE_COPY_ERROR))); + } + OnceDoneOperation = odoIdle; + } + + if (OnceDoneOperation != odoIdle) + { + CloseOnCompletion(OnceDoneOperation); + } + + return Result; +} + +bool TTerminal::CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params) +{ + DebugAssert(FFileSystem); + + // see scp.c: sink(), tolocal() + + bool Result = false; + bool OwnsFileList = (AFilesToCopy == nullptr); + std::unique_ptr FilesToCopy; + TOnceDoneOperation OnceDoneOperation = odoIdle; + + try__finally + { + if (OwnsFileList) + { + FilesToCopy.reset(new TStringList()); + FilesToCopy->Assign(GetFiles()->GetSelectedFiles()); + AFilesToCopy = FilesToCopy.get(); + } + + BeginTransaction(); + try__finally + { + SCOPE_EXIT + { + // If session is still active (no fatal error) we reload directory + // by calling EndTransaction + EndTransaction(); + }; + int64_t TotalSize = 0; + bool TotalSizeKnown = false; + TFileOperationProgressType OperationProgress(MAKE_CALLBACK(TTerminal::DoProgress, this), MAKE_CALLBACK(TTerminal::DoFinished, this)); + + SetExceptionOnFail(true); + try__finally + { + SCOPE_EXIT + { + SetExceptionOnFail(false); + }; + // dirty trick: when moving, do not pass copy param to avoid exclude mask + if (CalculateFilesSize( + AFilesToCopy, TotalSize, csIgnoreErrors, + (FLAGCLEAR(Params, cpDelete) ? CopyParam : nullptr), + CopyParam->GetCalculateSize(), nullptr)) + { + TotalSizeKnown = true; + } + } + __finally + { + SetExceptionOnFail(false); + }; + + OperationProgress.Start(((Params & cpDelete) != 0 ? foMove : foCopy), osRemote, + AFilesToCopy->GetCount(), (Params & cpTemporary) != 0, TargetDir, CopyParam->GetCPSLimit()); + + FOperationProgress = &OperationProgress; //-V506 + //bool CollectingUsage = false; + try__finally + { + SCOPE_EXIT + { +#if 0 + if (CollectingUsage) + { + int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed()); + Configuration->Usage->Inc(L"DownloadTime", CounterTime); + Configuration->Usage->SetMax(L"MaxDownloadTime", CounterTime); + } +#endif + FOperationProgress = nullptr; + OperationProgress.Stop(); + }; + if (TotalSizeKnown) + { +#if 0 + if (Configuration->Usage->Collect) + { + int CounterTotalSize = TUsage::CalculateCounterSize(TotalSize); + Configuration->Usage->Inc(L"Downloads"); + Configuration->Usage->Inc(L"DownloadedBytes", CounterTotalSize); + Configuration->Usage->SetMax(L"MaxDownloadSize", CounterTotalSize); + CollectingUsage = true; + } +#endif + OperationProgress.SetTotalSize(TotalSize); + } + + try + { + try__finally + { + SCOPE_EXIT + { + if (GetActive()) + { + ReactOnCommand(fsCopyToLocal); + } + }; + if (GetLog()->GetLogging()) + { + LogEvent(FORMAT(L"Copying %d files/directories to local directory " + L"\"%s\"", AFilesToCopy->GetCount(), TargetDir.c_str())); + LogEvent(CopyParam->GetLogStr()); + } + + FFileSystem->CopyToLocal(AFilesToCopy, TargetDir, CopyParam, Params, + &OperationProgress, OnceDoneOperation); + } + __finally + { + if (GetActive()) + { + ReactOnCommand(fsCopyToLocal); + } + }; + } + catch (Exception & E) + { + if (OperationProgress.Cancel != csCancel) + { + CommandError(&E, MainInstructions(LoadStr(TOLOCAL_COPY_ERROR))); + } + OnceDoneOperation = odoIdle; + } + + if (OperationProgress.Cancel == csContinue) + { + Result = true; + } + } + __finally + { +#if 0 + if (CollectingUsage) + { + int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed()); + Configuration->Usage->Inc(L"DownloadTime", CounterTime); + Configuration->Usage->SetMax(L"MaxDownloadTime", CounterTime); + } +#endif + FOperationProgress = nullptr; + OperationProgress.Stop(); + }; + } + __finally + { + // If session is still active (no fatal error) we reload directory + // by calling EndTransaction + EndTransaction(); + }; + } + __finally + { +// if (OwnsFileList) delete FilesToCopy; + }; + + if (OnceDoneOperation != odoIdle) + { + CloseOnCompletion(OnceDoneOperation); + } + + return Result; +} + +void TTerminal::SetLocalFileTime(const UnicodeString & LocalFileName, + const TDateTime & Modification) +{ + FILETIME WrTime = ::DateTimeToFileTime(Modification, + GetSessionData()->GetDSTMode()); + SetLocalFileTime(LocalFileName, nullptr, &WrTime); +} + +void TTerminal::SetLocalFileTime(const UnicodeString & LocalFileName, + FILETIME * AcTime, FILETIME * WrTime) +{ +#ifndef __linux__ + TFileOperationProgressType * OperationProgress = GetOperationProgress(); + FileOperationLoopCustom(this, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, LocalFileName.c_str()), "", + [&]() + { + HANDLE LocalFileHandle; + this->TerminalOpenLocalFile(LocalFileName, GENERIC_WRITE, + &LocalFileHandle, nullptr, nullptr, nullptr, nullptr, nullptr); + bool Result = ::SetFileTime(LocalFileHandle, nullptr, AcTime, WrTime) > 0; + ::CloseHandle(LocalFileHandle); + if (!Result) + { + Abort(); + } + }); +#endif +} + +HANDLE TTerminal::TerminalCreateLocalFile(const UnicodeString & LocalFileName, DWORD DesiredAccess, + DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes) +{ + if (GetOnCreateLocalFile()) + { + return GetOnCreateLocalFile()(ApiPath(LocalFileName), DesiredAccess, ShareMode, CreationDisposition, FlagsAndAttributes); + } + else + { + return ::CreateFile(ApiPath(LocalFileName).c_str(), DesiredAccess, ShareMode, nullptr, CreationDisposition, FlagsAndAttributes, 0); + } +} + +DWORD TTerminal::GetLocalFileAttributes(const UnicodeString & LocalFileName) +{ + if (GetOnGetLocalFileAttributes()) + { + return GetOnGetLocalFileAttributes()(ApiPath(LocalFileName)); + } + else + { + return ::FileGetAttrFix(LocalFileName); + } +} + +BOOL TTerminal::SetLocalFileAttributes(const UnicodeString & LocalFileName, DWORD FileAttributes) +{ + if (GetOnSetLocalFileAttributes()) + { + return GetOnSetLocalFileAttributes()(ApiPath(LocalFileName), FileAttributes); + } + else + { + return ::FileSetAttr(LocalFileName, FileAttributes) != 0; + } +} + +BOOL TTerminal::MoveLocalFile(const UnicodeString & LocalFileName, const UnicodeString & NewLocalFileName, DWORD Flags) +{ + if (GetOnMoveLocalFile()) + { + return GetOnMoveLocalFile()(LocalFileName, NewLocalFileName, Flags); + } + else + { + return ::MoveFileEx(ApiPath(LocalFileName).c_str(), ApiPath(NewLocalFileName).c_str(), Flags) != 0; + } +} + +BOOL TTerminal::RemoveLocalDirectory(const UnicodeString & LocalDirName) +{ + if (GetOnRemoveLocalDirectory()) + { + return GetOnRemoveLocalDirectory()(LocalDirName); + } + else + { + return ::RemoveDir(LocalDirName); + } +} + +BOOL TTerminal::CreateLocalDirectory(const UnicodeString & LocalDirName, LPSECURITY_ATTRIBUTES SecurityAttributes) +{ + if (GetOnCreateLocalDirectory()) + { + return GetOnCreateLocalDirectory()(LocalDirName, SecurityAttributes); + } + else + { + return ::CreateDir(LocalDirName, SecurityAttributes); + } +} + +void TTerminal::ReflectSettings() +{ + DebugAssert(FLog != nullptr); + FLog->ReflectSettings(); + DebugAssert(FActionLog != nullptr); + FActionLog->ReflectSettings(); + // also FTunnelLog ? +} + +void TTerminal::CollectUsage() +{ + switch (GetSessionData()->GetFSProtocol()) + { + case fsSCPonly: +// Configuration->Usage->Inc(L"OpenedSessionsSCP"); + break; + + case fsSFTP: + case fsSFTPonly: +// Configuration->Usage->Inc(L"OpenedSessionsSFTP"); + break; + + case fsFTP: + if (GetSessionData()->GetFtps() == ftpsNone) + { +// Configuration->Usage->Inc(L"OpenedSessionsFTP"); + } + else + { +// Configuration->Usage->Inc(L"OpenedSessionsFTPS"); + } + break; + + case fsWebDAV: + if (GetSessionData()->GetFtps() == ftpsNone) + { +// Configuration->Usage->Inc(L"OpenedSessionsWebDAV"); + } + else + { +// Configuration->Usage->Inc(L"OpenedSessionsWebDAVS"); + } + break; + } + + if (GetConfiguration()->GetLogging() && GetConfiguration()->GetLogToFile()) + { +// Configuration->Usage->Inc(L"OpenedSessionsLogToFile2"); + } + + if (GetConfiguration()->GetLogActions()) + { +// Configuration->Usage->Inc(L"OpenedSessionsXmlLog"); + } + + std::unique_ptr FactoryDefaults(new TSessionData(L"")); + if (!GetSessionData()->IsSame(FactoryDefaults.get(), true, nullptr)) + { +// Configuration->Usage->Inc(L"OpenedSessionsAdvanced"); + } + + if (GetSessionData()->GetProxyMethod() != ::pmNone) + { +// Configuration->Usage->Inc(L"OpenedSessionsProxy"); + } + if (GetSessionData()->GetFtpProxyLogonType() > 0) + { +// Configuration->Usage->Inc(L"OpenedSessionsFtpProxy"); + } + + FCollectFileSystemUsage = true; +} + +bool TTerminal::CheckForEsc() +{ + if (FOnCheckForEsc) + return FOnCheckForEsc(); + else + return (FOperationProgress && FOperationProgress->Cancel == csCancel); +} + +static UnicodeString FormatCertificateData(const UnicodeString & Fingerprint, int Failures) +{ + return FORMAT(L"%s;%2.2X", Fingerprint.c_str(), Failures); +} + +bool TTerminal::VerifyCertificate( + const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey, + const UnicodeString & Fingerprint, + const UnicodeString & CertificateSubject, int Failures) +{ + bool Result = false; + + UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures); + + std::unique_ptr Storage(GetConfiguration()->CreateConfigStorage()); + Storage->SetAccessMode(smRead); + + if (Storage->OpenSubKey(CertificateStorageKey, false)) + { + if (Storage->ValueExists(SiteKey)) + { + UnicodeString CachedCertificateData = Storage->ReadString(SiteKey, L""); + if (CertificateData == CachedCertificateData) + { + LogEvent(FORMAT(L"Certificate for \"%s\" matches cached fingerprint and failures", CertificateSubject.c_str())); + Result = true; + } + } + else if (Storage->ValueExists(Fingerprint)) + { + LogEvent(FORMAT(L"Certificate for \"%s\" matches legacy cached fingerprint", CertificateSubject.c_str())); + Result = true; + } + } + + if (!Result) + { + UnicodeString Buf = GetSessionData()->GetHostKey(); + while (!Result && !Buf.IsEmpty()) + { + UnicodeString ExpectedKey = CutToChar(Buf, L';', false); + if (ExpectedKey == L"*") + { + UnicodeString Message = LoadStr(ANY_CERTIFICATE); + Information(Message, true); + GetLog()->Add(llException, Message); + Result = true; + } + else if (ExpectedKey == Fingerprint) + { + LogEvent(FORMAT(L"Certificate for \"%s\" matches configured fingerprint", CertificateSubject.c_str())); + Result = true; + } + } + } + + return Result; +} + +void TTerminal::CacheCertificate(const UnicodeString & CertificateStorageKey, + const UnicodeString & SiteKey, const UnicodeString & Fingerprint, int Failures) +{ + UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures); + + std::unique_ptr Storage(GetConfiguration()->CreateConfigStorage()); + Storage->SetAccessMode(smReadWrite); + + if (Storage->OpenSubKey(CertificateStorageKey, true)) + { + Storage->WriteString(SiteKey, CertificateData); + } +} + +void TTerminal::CollectTlsUsage(const UnicodeString & TlsVersionStr) +{ + // see SSL_get_version() in OpenSSL ssl_lib.c + if (TlsVersionStr == L"TLSv1.2") + { +// Configuration->Usage->Inc(L"OpenedSessionsTLS12"); + } + else if (TlsVersionStr == L"TLSv1.1") + { +// Configuration->Usage->Inc(L"OpenedSessionsTLS11"); + } + else if (TlsVersionStr == L"TLSv1") + { +// Configuration->Usage->Inc(L"OpenedSessionsTLS10"); + } + else if (TlsVersionStr == L"SSLv3") + { +// Configuration->Usage->Inc(L"OpenedSessionsSSL30"); + } + else if (TlsVersionStr == L"SSLv2") + { +// Configuration->Usage->Inc(L"OpenedSessionsSSL20"); + } + else + { +// DebugFail(); + } +} + +bool TTerminal::LoadTlsCertificate(X509 *& Certificate, EVP_PKEY *& PrivateKey) +{ + bool Result = !GetSessionData()->GetTlsCertificateFile().IsEmpty(); + if (Result) + { + UnicodeString Passphrase = GetSessionData()->GetPassphrase(); + + // Inspired by neon's ne_ssl_clicert_read + bool Retry; + + do + { + Retry = false; + + bool WrongPassphrase = false; + ParseCertificate(GetSessionData()->GetTlsCertificateFile(), Passphrase, Certificate, PrivateKey, WrongPassphrase); + if (WrongPassphrase) + { + if (Passphrase.IsEmpty()) + { + LogEvent(L"Certificate is encrypted, need passphrase"); + Information(LoadStr(CLIENT_CERTIFICATE_LOADING), false); + } + else + { + Information(LoadStr(CERTIFICATE_DECODE_ERROR_INFO), false); + } + + Passphrase = L""; + if (PromptUser( + GetSessionData(), pkPassphrase, + LoadStr(CERTIFICATE_PASSPHRASE_TITLE), L"", + LoadStr(CERTIFICATE_PASSPHRASE_PROMPT), false, 0, Passphrase)) + { + Retry = true; + } + else + { + Result = false; + } + } + } + while (Retry); + } + return Result; +} + +UnicodeString TTerminal::GetBaseFileName(const UnicodeString & AFileName) +{ + UnicodeString FileName = AFileName; + + if (FSessionData->GetTrimVMSVersions()) + { + intptr_t P = FileName.LastDelimiter(L";"); + if (P > 0) + { + FileName.SetLength(P - 1); + } + } + return FileName; +} + +UnicodeString TTerminal::ChangeFileName(const TCopyParamType * CopyParam, + const UnicodeString & AFileName, TOperationSide Side, bool FirstLevel) +{ + UnicodeString FileName = GetBaseFileName(AFileName); + FileName = CopyParam->ChangeFileName(FileName, Side, FirstLevel); + return FileName; +} + +bool TTerminal::CanRecurseToDirectory(const TRemoteFile * AFile) const +{ + return !AFile->GetIsSymLink() || FSessionData->GetFollowDirectorySymlinks(); +} + +TSecondaryTerminal::TSecondaryTerminal(TTerminal * MainTerminal) : + TTerminal(), + FMainTerminal(MainTerminal) +{ +} + +void TSecondaryTerminal::Init( + TSessionData * ASessionData, TConfiguration * AConfiguration, const UnicodeString & Name) +{ + TTerminal::Init(ASessionData, AConfiguration); + DebugAssert(FMainTerminal != nullptr); + GetLog()->SetParent(FMainTerminal->GetLog()); + GetLog()->SetName(Name); + GetActionLog()->SetEnabled(false); + GetSessionData()->NonPersistant(); + if (!FMainTerminal->TerminalGetUserName().IsEmpty()) + { + GetSessionData()->SetUserName(FMainTerminal->TerminalGetUserName()); + } +} + +void TSecondaryTerminal::UpdateFromMain() +{ + if ((FFileSystem != nullptr) && (FMainTerminal->FFileSystem != nullptr)) + { + FFileSystem->UpdateFromMain(FMainTerminal->FFileSystem); + } +} + +void TSecondaryTerminal::DirectoryLoaded(TRemoteFileList * FileList) +{ + FMainTerminal->DirectoryLoaded(FileList); + DebugAssert(FileList != nullptr); +} + +void TSecondaryTerminal::DirectoryModified(const UnicodeString & APath, + bool SubDirs) +{ + // clear cache of main terminal + FMainTerminal->DirectoryModified(APath, SubDirs); +} + +TTerminal * TSecondaryTerminal::GetPasswordSource() +{ + return FMainTerminal; +} + +TTerminalList::TTerminalList(TConfiguration * AConfiguration) : + TObjectList(), + FConfiguration(AConfiguration) +{ + DebugAssert(FConfiguration); +} + +TTerminalList::~TTerminalList() +{ + DebugAssert(GetCount() == 0); +} + +TTerminal * TTerminalList::CreateTerminal(TSessionData * Data) +{ + TTerminal * Result = new TTerminal(); + Result->Init(Data, FConfiguration); + return Result; +} + +TTerminal * TTerminalList::NewTerminal(TSessionData * Data) +{ + TTerminal * Result = CreateTerminal(Data); + Add(Result); + return Result; +} + +void TTerminalList::FreeTerminal(TTerminal * Terminal) +{ + DebugAssert(IndexOf(Terminal) >= 0); + Remove(Terminal); +} + +void TTerminalList::FreeAndNullTerminal(TTerminal *& Terminal) +{ + TTerminal * T = Terminal; + Terminal = nullptr; + FreeTerminal(T); +} + +TTerminal * TTerminalList::GetTerminal(intptr_t Index) +{ + return NB_STATIC_DOWNCAST(TTerminal, GetObj(Index)); +} + +void TTerminalList::Idle() +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + TTerminal * Terminal = GetTerminal(Index); + if (Terminal->GetStatus() == ssOpened) + { + Terminal->Idle(); + } + } +} + +void TTerminalList::RecryptPasswords() +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + GetTerminal(Index)->RecryptPasswords(); + } +} + +UnicodeString GetSessionUrl(const TTerminal * Terminal, bool WithUserName) +{ + UnicodeString Result; + const TSessionInfo & SessionInfo = Terminal->GetSessionInfo(); + const TSessionData * SessionData = Terminal->GetSessionData(); + UnicodeString Protocol = SessionInfo.ProtocolBaseName; + UnicodeString HostName = SessionData->GetHostNameExpanded(); + UnicodeString UserName = SessionData->GetUserNameExpanded(); + intptr_t Port = Terminal->GetSessionData()->GetPortNumber(); + if (WithUserName && !UserName.IsEmpty()) + { + Result = FORMAT(L"%s://%s:@%s:%d", Protocol.Lower().c_str(), UserName.c_str(), HostName.c_str(), Port); + } + else + { + Result = FORMAT(L"%s://%s:%d", Protocol.Lower().c_str(), HostName.c_str(), Port); + } + return Result; +} + +NB_IMPLEMENT_CLASS(TTerminal, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TChecklistItem, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TSynchronizeData, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TCalculateSizeParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TMakeLocalFileListParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TFilesFindParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TCustomCommandParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TMoveFileParams, NB_GET_CLASS_INFO(TObject), nullptr) +NB_IMPLEMENT_CLASS(TSynchronizeFileData, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/core/Terminal.h b/netbox/src/core/Terminal.h new file mode 100644 index 000000000..2690cf4b0 --- /dev/null +++ b/netbox/src/core/Terminal.h @@ -0,0 +1,886 @@ +#pragma once + +#include +#include +#include + +#include "SessionInfo.h" +#include "Interface.h" +#include "FileOperationProgress.h" +#include "FileMasks.h" + +class TCopyParamType; +class TFileOperationProgressType; +class TRemoteDirectory; +class TRemoteFile; +class TCustomFileSystem; +class TTunnelThread; +class TSecureShell; +struct TCalculateSizeParams; +struct TOverwriteFileParams; +struct TSynchronizeData; +struct TSynchronizeOptions; +class TSynchronizeChecklist; +struct TCalculateSizeStats; +struct TFileSystemInfo; +struct TSpaceAvailable; +struct TFilesFindParams; +class TTunnelUI; +class TCallbackGuard; + +DEFINE_CALLBACK_TYPE8(TQueryUserEvent, void, + TObject * /*Sender*/, const UnicodeString & /*Query*/, TStrings * /*MoreMessages*/ , + uintptr_t /*Answers*/, + const TQueryParams * /*Params*/, uintptr_t & /*Answer*/, + TQueryType /*QueryType*/, void * /*Arg*/); +DEFINE_CALLBACK_TYPE8(TPromptUserEvent, void, + TTerminal * /*Terminal*/, TPromptKind /*Kind*/, const UnicodeString & /*Name*/, + const UnicodeString & /*Instructions*/, + TStrings * /*Prompts*/, TStrings * /*Results*/, bool & /*Result*/, void * /*Arg*/); +DEFINE_CALLBACK_TYPE5(TDisplayBannerEvent, void, + TTerminal * /*Terminal*/, const UnicodeString & /*SessionName*/, + const UnicodeString & /*Banner*/, + bool & /*NeverShowAgain*/, intptr_t /*Options*/); +DEFINE_CALLBACK_TYPE3(TExtendedExceptionEvent, void, + TTerminal * /*Terminal*/, Exception * /*E*/, void * /*Arg*/); +DEFINE_CALLBACK_TYPE2(TReadDirectoryEvent, void, TObject * /*Sender*/, + Boolean /*ReloadOnly*/); +DEFINE_CALLBACK_TYPE4(TReadDirectoryProgressEvent, void, + TObject * /*Sender*/, intptr_t /*Progress*/, intptr_t /*ResolvedLinks*/, + bool & /*Cancel*/); +DEFINE_CALLBACK_TYPE3(TProcessFileEvent, void, + const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/, + void * /*Param*/); +DEFINE_CALLBACK_TYPE4(TProcessFileEventEx, void, + const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/, + void * /*Param*/, intptr_t /*Index*/); +DEFINE_CALLBACK_TYPE2(TFileOperationEvent, intptr_t, + void * /*Param1*/, void * /*Param2*/); +DEFINE_CALLBACK_TYPE4(TSynchronizeDirectoryEvent, void, + const UnicodeString & /*LocalDirectory*/, const UnicodeString & /*RemoteDirectory*/, + bool & /*Continue*/, bool /*Collect*/); +DEFINE_CALLBACK_TYPE2(TDeleteLocalFileEvent, void, + const UnicodeString & /*FileName*/, bool /*Alternative*/); +DEFINE_CALLBACK_TYPE3(TDirectoryModifiedEvent, int, + TTerminal * /*Terminal*/, const UnicodeString & /*Directory*/, bool /*SubDirs*/); +DEFINE_CALLBACK_TYPE4(TInformationEvent, void, + TTerminal * /*Terminal*/, const UnicodeString & /*Str*/, bool /*Status*/, + intptr_t /*Phase*/); +DEFINE_CALLBACK_TYPE5(TCreateLocalFileEvent, HANDLE, + const UnicodeString & /*FileName*/, DWORD /*DesiredAccess*/, + DWORD /*ShareMode*/, DWORD /*CreationDisposition*/, DWORD /*FlagsAndAttributes*/); +DEFINE_CALLBACK_TYPE1(TGetLocalFileAttributesEvent, DWORD, + const UnicodeString & /*FileName*/); +DEFINE_CALLBACK_TYPE2(TSetLocalFileAttributesEvent, BOOL, + const UnicodeString & /*FileName*/, DWORD /*FileAttributes*/); +DEFINE_CALLBACK_TYPE3(TMoveLocalFileEvent, BOOL, + const UnicodeString & /*FileName*/, const UnicodeString & /*NewFileName*/, + DWORD /*Flags*/); +DEFINE_CALLBACK_TYPE1(TRemoveLocalDirectoryEvent, BOOL, + const UnicodeString & /*LocalDirName*/); +DEFINE_CALLBACK_TYPE2(TCreateLocalDirectoryEvent, BOOL, + const UnicodeString & /*LocalDirName*/, LPSECURITY_ATTRIBUTES /*SecurityAttributes*/); +DEFINE_CALLBACK_TYPE0(TCheckForEscEvent, bool); + +inline void ThrowSkipFile(Exception * Exception, const UnicodeString & Message) +{ + throw ESkipFile(Exception, Message); +} +inline void ThrowSkipFileNull() { ThrowSkipFile(nullptr, L""); } + +void FileOperationLoopCustom(TTerminal * Terminal, + TFileOperationProgressType * OperationProgress, + bool AllowSkip, const UnicodeString & Message, + const UnicodeString & HelpKeyword, + const std::function & Operation); + +enum TCurrentFSProtocol +{ + cfsUnknown, + cfsSCP, + cfsSFTP, + cfsFTP, + cfsFTPS, + cfsWebDAV +}; + +const int cpDelete = 0x01; +const int cpTemporary = 0x04; +const int cpNoConfirmation = 0x08; +const int cpNewerOnly = 0x10; +const int cpAppend = 0x20; +const int cpResume = 0x40; + +const int ccApplyToDirectories = 0x01; +const int ccRecursive = 0x02; +const int ccUser = 0x100; + +const int csIgnoreErrors = 0x01; + +const int ropNoReadDirectory = 0x02; + +const int boDisableNeverShowAgain = 0x01; + +class TTerminal : public TObject, public TSessionUI +{ +NB_DISABLE_COPY(TTerminal) +NB_DECLARE_CLASS(TTerminal) +public: + // TScript::SynchronizeProc relies on the order + enum TSynchronizeMode + { + smRemote, + smLocal, + smBoth, + }; + + static const int spDelete = 0x01; // cannot be combined with spTimestamp + static const int spNoConfirmation = 0x02; // has no effect for spTimestamp + static const int spExistingOnly = 0x04; // is implicit for spTimestamp + static const int spNoRecurse = 0x08; + static const int spUseCache = 0x10; // cannot be combined with spTimestamp + static const int spDelayProgress = 0x20; // cannot be combined with spTimestamp + static const int spPreviewChanges = 0x40; // not used by core + static const int spSubDirs = 0x80; // cannot be combined with spTimestamp + static const int spTimestamp = 0x100; + static const int spNotByTime = 0x200; // cannot be combined with spTimestamp and smBoth + static const int spBySize = 0x400; // cannot be combined with smBoth, has opposite meaning for spTimestamp + static const int spSelectedOnly = 0x800; // not used by core + static const int spMirror = 0x1000; + static const int spDefault = TTerminal::spNoConfirmation | TTerminal::spPreviewChanges; + +// for TranslateLockedPath() +friend class TRemoteFile; +// for ReactOnCommand() +friend class TSCPFileSystem; +friend class TSFTPFileSystem; +friend class TFTPFileSystem; +friend class TWebDAVFileSystem; +friend class TTunnelUI; +friend class TCallbackGuard; +friend class TSecondaryTerminal; +friend class TRetryOperationLoop; + +private: + TSessionData * FSessionData; + TSessionLog * FLog; + TActionLog * FActionLog; + TConfiguration * FConfiguration; + UnicodeString FCurrentDirectory; + UnicodeString FLockDirectory; + Integer FExceptionOnFail; + TRemoteDirectory * FFiles; + intptr_t FInTransaction; + bool FSuspendTransaction; + TNotifyEvent FOnChangeDirectory; + TReadDirectoryEvent FOnReadDirectory; + TNotifyEvent FOnStartReadDirectory; + TReadDirectoryProgressEvent FOnReadDirectoryProgress; + TDeleteLocalFileEvent FOnDeleteLocalFile; + TCreateLocalFileEvent FOnCreateLocalFile; + TGetLocalFileAttributesEvent FOnGetLocalFileAttributes; + TSetLocalFileAttributesEvent FOnSetLocalFileAttributes; + TMoveLocalFileEvent FOnMoveLocalFile; + TRemoveLocalDirectoryEvent FOnRemoveLocalDirectory; + TCreateLocalDirectoryEvent FOnCreateLocalDirectory; + TNotifyEvent FOnInitializeLog; + TRemoteTokenList FMembership; + TRemoteTokenList FGroups; + TRemoteTokenList FUsers; + bool FUsersGroupsLookedup; + TFileOperationProgressEvent FOnProgress; + TFileOperationFinishedEvent FOnFinished; + TFileOperationProgressType * FOperationProgress; + bool FUseBusyCursor; + TRemoteDirectoryCache * FDirectoryCache; + TRemoteDirectoryChangesCache * FDirectoryChangesCache; + TSecureShell * FSecureShell; + UnicodeString FLastDirectoryChange; + TCurrentFSProtocol FFSProtocol; + TTerminal * FCommandSession; + bool FAutoReadDirectory; + bool FReadingCurrentDirectory; + bool * FClosedOnCompletion; + TSessionStatus FStatus; + int FOpening; + RawByteString FRememberedPassword; + RawByteString FRememberedTunnelPassword; + TTunnelThread * FTunnelThread; + TSecureShell * FTunnel; + TSessionData * FTunnelData; + TSessionLog * FTunnelLog; + TTunnelUI * FTunnelUI; + intptr_t FTunnelLocalPortNumber; + UnicodeString FTunnelError; + TQueryUserEvent FOnQueryUser; + TPromptUserEvent FOnPromptUser; + TDisplayBannerEvent FOnDisplayBanner; + TExtendedExceptionEvent FOnShowExtendedException; + TInformationEvent FOnInformation; + TNotifyEvent FOnClose; + TCheckForEscEvent FOnCheckForEsc; + TCallbackGuard * FCallbackGuard; + TFindingFileEvent FOnFindingFile; + bool FEnableSecureShellUsage; + bool FCollectFileSystemUsage; + bool FRememberedPasswordTried; + bool FRememberedTunnelPasswordTried; + int FNesting; + UnicodeString FFingerprintScanned; + TRemoteDirectory * FOldFiles; + +public: + void CommandError(Exception * E, const UnicodeString & Msg); + uintptr_t CommandError(Exception * E, const UnicodeString & Msg, + uintptr_t Answers, const UnicodeString & HelpKeyword = L""); + UnicodeString GetCurrDirectory(); + bool GetExceptionOnFail() const; + const TRemoteTokenList * GetGroups() const { return const_cast(this)->GetGroups(); } + TRemoteTokenList * GetGroups(); + const TRemoteTokenList * GetUsers() const { return const_cast(this)->GetUsers(); } + TRemoteTokenList * GetUsers(); + const TRemoteTokenList * GetMembership() const { return const_cast(this)->GetMembership(); } + TRemoteTokenList * GetMembership(); + void TerminalSetCurrentDirectory(const UnicodeString & AValue); + void SetExceptionOnFail(bool Value); + void ReactOnCommand(intptr_t /*TFSCommand*/ Cmd); + UnicodeString TerminalGetUserName() const; + bool GetAreCachesEmpty() const; + void ClearCachedFileList(const UnicodeString & APath, bool SubDirs); + void AddCachedFileList(TRemoteFileList * FileList); + bool GetCommandSessionOpened() const; + TTerminal * GetCommandSession(); + bool GetResolvingSymlinks() const; + bool GetActive() const; + UnicodeString GetPassword() const; + UnicodeString GetRememberedPassword() const; + UnicodeString GetRememberedTunnelPassword() const; + bool GetStoredCredentialsTried() const; + inline bool InTransaction() const; + void SaveCapabilities(TFileSystemInfo & FileSystemInfo); + static UnicodeString SynchronizeModeStr(TSynchronizeMode Mode); + static UnicodeString SynchronizeParamsStr(intptr_t Params); + +protected: + bool FReadCurrentDirectoryPending; + bool FReadDirectoryPending; + bool FTunnelOpening; + TCustomFileSystem * FFileSystem; + +public: + void DoStartReadDirectory(); + void DoReadDirectoryProgress(intptr_t Progress, intptr_t ResolvedLinks, bool & Cancel); + void DoReadDirectory(bool ReloadOnly); + void DoCreateDirectory(const UnicodeString & ADirName); + void DoDeleteFile(const UnicodeString & AFileName, const TRemoteFile * AFile, + intptr_t Params); + void DoCustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent); + void DoRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName, bool Move); + void DoCopyFile(const UnicodeString & AFileName, const UnicodeString & ANewName); + void DoChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties); + void DoChangeDirectory(); + void DoInitializeLog(); + void EnsureNonExistence(const UnicodeString & AFileName, bool IsDirectory); + void LookupUsersGroups(); + void FileModified(const TRemoteFile * AFile, + const UnicodeString & AFileName, bool ClearDirectoryChange = false); + intptr_t FileOperationLoop(TFileOperationEvent CallBackFunc, + TFileOperationProgressType * OperationProgress, bool AllowSkip, + const UnicodeString & Message, void * Param1 = nullptr, void * Param2 = nullptr); + bool GetIsCapable(TFSCapability Capability) const; + bool ProcessFiles(const TStrings * AFileList, TFileOperation Operation, + TProcessFileEvent ProcessFile, void * Param = nullptr, TOperationSide Side = osRemote, + bool Ex = false); + bool ProcessFilesEx(TStrings * FileList, TFileOperation Operation, + TProcessFileEventEx ProcessFile, void * Param = nullptr, TOperationSide Side = osRemote); + void ProcessDirectory(const UnicodeString & ADirName, + TProcessFileEvent CallBackFunc, void * Param = nullptr, bool UseCache = false, + bool IgnoreErrors = false); + void AnnounceFileListOperation(); + UnicodeString TranslateLockedPath(const UnicodeString & APath, bool Lock); + void ReadDirectory(TRemoteFileList * AFileList); + void CustomReadDirectory(TRemoteFileList * AFileList); + void DoCreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic); + bool TerminalCreateLocalFile(const UnicodeString & AFileName, + TFileOperationProgressType * OperationProgress, + bool Resume, + bool NoConfirmation, + OUT HANDLE * AHandle); + HANDLE TerminalCreateLocalFile(const UnicodeString & LocalFileName, DWORD DesiredAccess, + DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes); + void TerminalOpenLocalFile(const UnicodeString & AFileName, DWORD Access, + OUT OPTIONAL HANDLE * AHandle, OUT OPTIONAL uintptr_t * AAttrs, OUT OPTIONAL int64_t * ACTime, OUT OPTIONAL int64_t * AMTime, + OUT OPTIONAL int64_t * AATime, OUT OPTIONAL int64_t * ASize, bool TryWriteReadOnly = true); + bool AllowLocalFileTransfer(const UnicodeString & AFileName, + const TCopyParamType * CopyParam, TFileOperationProgressType * OperationProgress); + bool HandleException(Exception * E); + void CalculateFileSize(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TCalculateSizeParams*/ void * Size); + void DoCalculateDirectorySize(const UnicodeString & AFileName, + const TRemoteFile * AFile, TCalculateSizeParams * Params); + void CalculateLocalFileSize(const UnicodeString & AFileName, + const TSearchRec & Rec, /*int64_t*/ void * Params); + bool CalculateLocalFilesSize(const TStrings * AFileList, + const TCopyParamType * CopyParam, bool AllowDirs, + OUT int64_t & Size); + TBatchOverwrite EffectiveBatchOverwrite( + const UnicodeString & AFileName, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, bool Special); + bool CheckRemoteFile( + const UnicodeString & AFileName, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress); + uintptr_t ConfirmFileOverwrite( + const UnicodeString & ASourceFullFileName, const UnicodeString & ATargetFileName, + const TOverwriteFileParams * FileParams, uintptr_t Answers, TQueryParams * QueryParams, + TOperationSide Side, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, const UnicodeString & AMessage = L""); + void DoSynchronizeCollectDirectory(const UnicodeString & ALocalDirectory, + const UnicodeString & ARemoteDirectory, TSynchronizeMode Mode, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory, + TSynchronizeOptions * Options, intptr_t Level, TSynchronizeChecklist * Checklist); + void DoSynchronizeCollectFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TSynchronizeData*/ void * Param); + void SynchronizeCollectFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*TSynchronizeData*/ void * Param); + void SynchronizeRemoteTimestamp(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param); + void SynchronizeLocalTimestamp(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param); + void DoSynchronizeProgress(const TSynchronizeData & Data, bool Collect); + void DeleteLocalFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param); + void RecycleFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + TStrings * GetFixedPaths(); + void DoStartup(); + virtual bool DoQueryReopen(Exception * E); + virtual void FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + void ResetConnection(); + virtual bool DoPromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & Name, const UnicodeString & Instructions, TStrings * Prompts, + TStrings * Response); + void OpenTunnel(); + void CloseTunnel(); + void DoInformation(const UnicodeString & Str, bool Status, intptr_t Phase = -1); + bool PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & Instructions, const UnicodeString & Prompt, bool Echo, + intptr_t MaxLen, OUT UnicodeString & AResult); + void FileFind(const UnicodeString & AFileName, const TRemoteFile * AFile, void * Param); + void DoFilesFind(const UnicodeString & Directory, TFilesFindParams & Params, const UnicodeString & RealDirectory); + bool DoCreateLocalFile(const UnicodeString & AFileName, + TFileOperationProgressType * OperationProgress, + bool Resume, + bool NoConfirmation, + OUT HANDLE * AHandle); + void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile, void * AParam); + void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile, void * AParam); + void DoLockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + void DoUnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + + virtual void Information(const UnicodeString & Str, bool Status); + virtual uintptr_t QueryUser(const UnicodeString & Query, + TStrings * MoreMessages, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType = qtConfirmation); + virtual uintptr_t QueryUserException(const UnicodeString & Query, + Exception * E, uintptr_t Answers, const TQueryParams * Params, + TQueryType QueryType = qtConfirmation); + virtual bool PromptUser(TSessionData * Data, TPromptKind Kind, + const UnicodeString & AName, const UnicodeString & Instructions, TStrings * Prompts, TStrings * Results); + virtual void DisplayBanner(const UnicodeString & Banner); + virtual void Closed(); + virtual void ProcessGUI(); + void Progress(TFileOperationProgressType * OperationProgress); + virtual void HandleExtendedException(Exception * E); + bool IsListenerFree(uintptr_t PortNumber) const; + void DoProgress(TFileOperationProgressType & ProgressData); + void DoFinished(TFileOperation Operation, TOperationSide Side, bool Temp, + const UnicodeString & AFileName, bool Success, TOnceDoneOperation & OnceDoneOperation); + void RollbackAction(TSessionAction & Action, + TFileOperationProgressType * OperationProgress, Exception * E = nullptr); + void DoAnyCommand(const UnicodeString & ACommand, TCaptureOutputEvent OutputEvent, + TCallSessionAction * Action); + TRemoteFileList * DoReadDirectoryListing(const UnicodeString & ADirectory, bool UseCache); + RawByteString EncryptPassword(const UnicodeString & APassword) const; + UnicodeString DecryptPassword(const RawByteString & APassword) const; + UnicodeString GetRemoteFileInfo(TRemoteFile * AFile); + void LogRemoteFile(TRemoteFile * AFile); + UnicodeString FormatFileDetailsForLog(const UnicodeString & AFileName, const TDateTime & AModification, int64_t Size); + void LogFileDetails(const UnicodeString & AFileName, const TDateTime & Modification, int64_t Size); + void LogFileDone(TFileOperationProgressType * OperationProgress); + virtual const TTerminal * GetPasswordSource() const { return this; } + virtual TTerminal * GetPasswordSource(); + void DoEndTransaction(bool Inform); + bool VerifyCertificate( + const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey, + const UnicodeString & Fingerprint, + const UnicodeString & CertificateSubject, int Failures); + void CacheCertificate(const UnicodeString & CertificateStorageKey, + const UnicodeString & SiteKey, const UnicodeString & Fingerprint, int Failures); + void CollectTlsUsage(const UnicodeString & TlsVersionStr); + bool LoadTlsCertificate(X509 *& Certificate, EVP_PKEY *& PrivateKey); + bool TryStartOperationWithFile( + const UnicodeString & AFileName, TFileOperation Operation1, TFileOperation Operation2 = foNone); + void StartOperationWithFile( + const UnicodeString & AFileName, TFileOperation Operation1, TFileOperation Operation2 = foNone); + void CommandSessionClose(TObject * Sender); + bool CanRecurseToDirectory(const TRemoteFile * AFile) const; + + // __property TFileOperationProgressType * OperationProgress = { read=FOperationProgress }; + const TFileOperationProgressType * GetOperationProgress() const { return FOperationProgress; } + TFileOperationProgressType * GetOperationProgress() { return FOperationProgress; } + void SetOperationProgress(TFileOperationProgressType * OperationProgress) { FOperationProgress = OperationProgress; } + + +public: + explicit TTerminal(); + void Init(TSessionData * SessionData, TConfiguration * Configuration); + virtual ~TTerminal(); + void Open(); + void Close(); + UnicodeString FingerprintScan(); + void Reopen(intptr_t Params); + virtual void DirectoryModified(const UnicodeString & APath, bool SubDirs); + virtual void DirectoryLoaded(TRemoteFileList * FileList); + void ShowExtendedException(Exception * E); + void Idle(); + void RecryptPasswords(); + bool AllowedAnyCommand(const UnicodeString & Command) const; + void AnyCommand(const UnicodeString & Command, TCaptureOutputEvent OutputEvent); + void CloseOnCompletion(TOnceDoneOperation Operation = odoDisconnect, const UnicodeString & Message = L""); + UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const; + void BeginTransaction(); + void ReadCurrentDirectory(); + void ReadDirectory(bool ReloadOnly, bool ForceCache = false); + TRemoteFileList * ReadDirectoryListing(const UnicodeString & Directory, const TFileMasks & Mask); + TRemoteFileList * CustomReadDirectoryListing(const UnicodeString & Directory, bool UseCache); + TRemoteFile * ReadFileListing(const UnicodeString & APath); + void ReadFile(const UnicodeString & AFileName, TRemoteFile *& AFile); + bool FileExists(const UnicodeString & AFileName, TRemoteFile ** AFile = nullptr); + void ReadSymlink(TRemoteFile * SymlinkFile, TRemoteFile *& File); + bool CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params); + bool CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params); + void RemoteCreateDirectory(const UnicodeString & ADirName, + const TRemoteProperties * Properties = nullptr); + void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic, bool IsDirectory); + void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile = nullptr, void * Params = nullptr); + bool RemoteDeleteFiles(TStrings * AFilesToDelete, intptr_t Params = 0); + bool DeleteLocalFiles(TStrings * AFileList, intptr_t Params = 0); + bool IsRecycledFile(const UnicodeString & AFileName); + void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * AParams); + void CustomCommandOnFiles(const UnicodeString & Command, intptr_t Params, + TStrings * AFiles, TCaptureOutputEvent OutputEvent); + void RemoteChangeDirectory(const UnicodeString & Directory); + void EndTransaction(); + void HomeDirectory(); + void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, /*const TRemoteProperties*/ void * Properties); + void ChangeFilesProperties(TStrings * AFileList, + const TRemoteProperties * Properties); + bool LoadFilesProperties(TStrings * AFileList); + void TerminalError(const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + void TerminalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword = L""); + void ReloadDirectory(); + void RefreshDirectory(); + void TerminalRenameFile(const UnicodeString & AFileName, const UnicodeString & ANewName); + void TerminalRenameFile(const TRemoteFile * AFile, const UnicodeString & ANewName, bool CheckExistence); + void TerminalMoveFile(const UnicodeString & AFileName, const TRemoteFile * AFile, + /*const TMoveFileParams*/ void * Param); + bool MoveFiles(TStrings * AFileList, const UnicodeString & Target, + const UnicodeString & FileMask); + void TerminalCopyFile(const UnicodeString & AFileName, const TRemoteFile * AFile, + /*const TMoveFileParams*/ void * Param); + bool CopyFiles(const TStrings * AFileList, const UnicodeString & Target, + const UnicodeString & FileMask); + bool CalculateFilesSize(const TStrings * AFileList, int64_t & Size, + intptr_t Params, const TCopyParamType * CopyParam, bool AllowDirs, + TCalculateSizeStats * Stats = nullptr); + void CalculateFilesChecksum(const UnicodeString & Alg, TStrings * AFileList, + TStrings * Checksums, TCalculatedChecksumEvent OnCalculatedChecksum); + void ClearCaches(); + TSynchronizeChecklist * SynchronizeCollect(const UnicodeString & LocalDirectory, + const UnicodeString & RemoteDirectory, TSynchronizeMode Mode, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory, TSynchronizeOptions * Options); + void SynchronizeApply(TSynchronizeChecklist * Checklist, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory, + const TCopyParamType * CopyParam, intptr_t Params, + TSynchronizeDirectoryEvent OnSynchronizeDirectory); + void FilesFind(const UnicodeString & Directory, const TFileMasks & FileMask, + TFileFoundEvent OnFileFound, TFindingFileEvent OnFindingFile); + void SpaceAvailable(const UnicodeString & APath, TSpaceAvailable & ASpaceAvailable); + void LockFiles(TStrings * AFileList); + void UnlockFiles(TStrings * AFileList); + bool DirectoryFileList(const UnicodeString & APath, + TRemoteFileList *& FileList, bool CanLoad); + void MakeLocalFileList(const UnicodeString & AFileName, + const TSearchRec & Rec, void * Param); + bool FileOperationLoopQuery(Exception & E, + TFileOperationProgressType * OperationProgress, const UnicodeString & Message, + bool AllowSkip, const UnicodeString & SpecialRetry = UnicodeString(), const UnicodeString & HelpKeyword = UnicodeString()); + TUsableCopyParamAttrs UsableCopyParamAttrs(intptr_t Params); + bool QueryReopen(Exception * E, intptr_t Params, + TFileOperationProgressType * OperationProgress); + UnicodeString PeekCurrentDirectory(); + void FatalAbort(); + void ReflectSettings(); + void CollectUsage(); + + const TSessionInfo & GetSessionInfo() const; + const TFileSystemInfo & GetFileSystemInfo(bool Retrieve = false); + void inline LogEvent(const UnicodeString & Str); + void GetSupportedChecksumAlgs(TStrings * Algs); + UnicodeString ChangeFileName(const TCopyParamType * CopyParam, + const UnicodeString & AFileName, TOperationSide Side, bool FirstLevel); + UnicodeString GetBaseFileName(const UnicodeString & AFileName); + + static UnicodeString ExpandFileName(const UnicodeString & APath, + const UnicodeString & BasePath); + +#if 0 + __property TSessionData * SessionData = { read = FSessionData }; + __property TSessionLog * Log = { read = FLog }; + __property TActionLog * ActionLog = { read = FActionLog }; + __property TConfiguration * Configuration = { read = FConfiguration }; + __property bool Active = { read = GetActive }; + __property TSessionStatus Status = { read = FStatus }; + __property UnicodeString CurrentDirectory = { read = GetCurrentDirectory, write = SetCurrentDirectory }; + __property bool ExceptionOnFail = { read = GetExceptionOnFail, write = SetExceptionOnFail }; + __property TRemoteDirectory * Files = { read = FFiles }; + __property TNotifyEvent OnChangeDirectory = { read = FOnChangeDirectory, write = FOnChangeDirectory }; + __property TReadDirectoryEvent OnReadDirectory = { read = FOnReadDirectory, write = FOnReadDirectory }; + __property TNotifyEvent OnStartReadDirectory = { read = FOnStartReadDirectory, write = FOnStartReadDirectory }; + __property TReadDirectoryProgressEvent OnReadDirectoryProgress = { read = FOnReadDirectoryProgress, write = FOnReadDirectoryProgress }; + __property TDeleteLocalFileEvent OnDeleteLocalFile = { read = FOnDeleteLocalFile, write = FOnDeleteLocalFile }; + __property TNotifyEvent OnInitializeLog = { read = FOnInitializeLog, write = FOnInitializeLog }; + __property const TRemoteTokenList * Groups = { read = GetGroups }; + __property const TRemoteTokenList * Users = { read = GetUsers }; + __property const TRemoteTokenList * Membership = { read = GetMembership }; + __property TFileOperationProgressEvent OnProgress = { read=FOnProgress, write=FOnProgress }; + __property TFileOperationFinished OnFinished = { read=FOnFinished, write=FOnFinished }; + __property TCurrentFSProtocol FSProtocol = { read = FFSProtocol }; + __property bool UseBusyCursor = { read = FUseBusyCursor, write = FUseBusyCursor }; + __property UnicodeString UserName = { read=GetUserName }; + __property bool IsCapable[TFSCapability Capability] = { read = GetIsCapable }; + __property bool AreCachesEmpty = { read = GetAreCachesEmpty }; + __property bool CommandSessionOpened = { read = GetCommandSessionOpened }; + __property TTerminal * CommandSession = { read = GetCommandSession }; + __property bool AutoReadDirectory = { read = FAutoReadDirectory, write = FAutoReadDirectory }; + __property TStrings * FixedPaths = { read = GetFixedPaths }; + __property bool ResolvingSymlinks = { read = GetResolvingSymlinks }; + __property UnicodeString Password = { read = GetPassword }; + __property UnicodeString RememberedPassword = { read = GetRememberedPassword }; + __property UnicodeString RememberedTunnelPassword = { read = GetRememberedTunnelPassword }; + __property bool StoredCredentialsTried = { read = GetStoredCredentialsTried }; + __property TQueryUserEvent OnQueryUser = { read = FOnQueryUser, write = FOnQueryUser }; + __property TPromptUserEvent OnPromptUser = { read = FOnPromptUser, write = FOnPromptUser }; + __property TDisplayBannerEvent OnDisplayBanner = { read = FOnDisplayBanner, write = FOnDisplayBanner }; + __property TExtendedExceptionEvent OnShowExtendedException = { read = FOnShowExtendedException, write = FOnShowExtendedException }; + __property TInformationEvent OnInformation = { read = FOnInformation, write = FOnInformation }; + __property TNotifyEvent OnClose = { read = FOnClose, write = FOnClose }; + __property int TunnelLocalPortNumber = { read = FTunnelLocalPortNumber }; +#endif + + void SetMasks(const UnicodeString & Value); + + void SetLocalFileTime(const UnicodeString & LocalFileName, + const TDateTime & Modification); + void SetLocalFileTime(const UnicodeString & LocalFileName, + FILETIME * AcTime, FILETIME * WrTime); + DWORD GetLocalFileAttributes(const UnicodeString & LocalFileName); + BOOL SetLocalFileAttributes(const UnicodeString & LocalFileName, DWORD FileAttributes); + BOOL MoveLocalFile(const UnicodeString & LocalFileName, const UnicodeString & NewLocalFileName, DWORD Flags); + BOOL RemoveLocalDirectory(const UnicodeString & LocalDirName); + BOOL CreateLocalDirectory(const UnicodeString & LocalDirName, LPSECURITY_ATTRIBUTES SecurityAttributes); + + TSessionData * GetSessionData() const { return FSessionData; } + TSessionData * GetSessionData() { return FSessionData; } + TSessionLog * GetLog() const { return FLog; } + TSessionLog * GetLog() { return FLog; } + TActionLog * GetActionLog() const { return FActionLog; } + const TConfiguration * GetConfiguration() const { return FConfiguration; } + TConfiguration * GetConfiguration() { return FConfiguration; } + TSessionStatus GetStatus() const { return FStatus; } + TRemoteDirectory * GetFiles() const { return FFiles; } + TNotifyEvent & GetOnChangeDirectory() { return FOnChangeDirectory; } + void SetOnChangeDirectory(TNotifyEvent Value) { FOnChangeDirectory = Value; } + TReadDirectoryEvent & GetOnReadDirectory() { return FOnReadDirectory; } + void SetOnReadDirectory(TReadDirectoryEvent Value) { FOnReadDirectory = Value; } + TNotifyEvent & GetOnStartReadDirectory() { return FOnStartReadDirectory; } + void SetOnStartReadDirectory(TNotifyEvent Value) { FOnStartReadDirectory = Value; } + TReadDirectoryProgressEvent & GetOnReadDirectoryProgress() { return FOnReadDirectoryProgress; } + void SetOnReadDirectoryProgress(TReadDirectoryProgressEvent Value) { FOnReadDirectoryProgress = Value; } + TDeleteLocalFileEvent & GetOnDeleteLocalFile() { return FOnDeleteLocalFile; } + void SetOnDeleteLocalFile(TDeleteLocalFileEvent Value) { FOnDeleteLocalFile = Value; } + TNotifyEvent & GetOnInitializeLog() { return FOnInitializeLog; } + void SetOnInitializeLog(TNotifyEvent Value) { FOnInitializeLog = Value; } + TCreateLocalFileEvent & GetOnCreateLocalFile() { return FOnCreateLocalFile; } + void SetOnCreateLocalFile(TCreateLocalFileEvent Value) { FOnCreateLocalFile = Value; } + TGetLocalFileAttributesEvent & GetOnGetLocalFileAttributes() { return FOnGetLocalFileAttributes; } + void SetOnGetLocalFileAttributes(TGetLocalFileAttributesEvent Value) { FOnGetLocalFileAttributes = Value; } + TSetLocalFileAttributesEvent & GetOnSetLocalFileAttributes() { return FOnSetLocalFileAttributes; } + void SetOnSetLocalFileAttributes(TSetLocalFileAttributesEvent Value) { FOnSetLocalFileAttributes = Value; } + TMoveLocalFileEvent & GetOnMoveLocalFile() { return FOnMoveLocalFile; } + void SetOnMoveLocalFile(TMoveLocalFileEvent Value) { FOnMoveLocalFile = Value; } + TRemoveLocalDirectoryEvent & GetOnRemoveLocalDirectory() { return FOnRemoveLocalDirectory; } + void SetOnRemoveLocalDirectory(TRemoveLocalDirectoryEvent Value) { FOnRemoveLocalDirectory = Value; } + TCreateLocalDirectoryEvent & GetOnCreateLocalDirectory() { return FOnCreateLocalDirectory; } + void SetOnCreateLocalDirectory(TCreateLocalDirectoryEvent Value) { FOnCreateLocalDirectory = Value; } + TFileOperationProgressEvent & GetOnProgress() { return FOnProgress; } + void SetOnProgress(TFileOperationProgressEvent Value) { FOnProgress = Value; } + TFileOperationFinishedEvent & GetOnFinished() { return FOnFinished; } + void SetOnFinished(TFileOperationFinishedEvent Value) { FOnFinished = Value; } + TCurrentFSProtocol GetFSProtocol() const { return FFSProtocol; } + bool GetUseBusyCursor() const { return FUseBusyCursor; } + void SetUseBusyCursor(bool Value) { FUseBusyCursor = Value; } + bool GetAutoReadDirectory() const { return FAutoReadDirectory; } + void SetAutoReadDirectory(bool Value) { FAutoReadDirectory = Value; } + TQueryUserEvent & GetOnQueryUser() { return FOnQueryUser; } + void SetOnQueryUser(TQueryUserEvent Value) { FOnQueryUser = Value; } + TPromptUserEvent & GetOnPromptUser() { return FOnPromptUser; } + void SetOnPromptUser(TPromptUserEvent Value) { FOnPromptUser = Value; } + TDisplayBannerEvent & GetOnDisplayBanner() { return FOnDisplayBanner; } + void SetOnDisplayBanner(TDisplayBannerEvent Value) { FOnDisplayBanner = Value; } + TExtendedExceptionEvent & GetOnShowExtendedException() { return FOnShowExtendedException; } + void SetOnShowExtendedException(TExtendedExceptionEvent Value) { FOnShowExtendedException = Value; } + TInformationEvent & GetOnInformation() { return FOnInformation; } + void SetOnInformation(TInformationEvent Value) { FOnInformation = Value; } + TCheckForEscEvent & GetOnCheckForEsc() { return FOnCheckForEsc; } + void SetOnCheckForEsc(TCheckForEscEvent Value) { FOnCheckForEsc = Value; } + TNotifyEvent & GetOnClose() { return FOnClose; } + void SetOnClose(TNotifyEvent Value) { FOnClose = Value; } + intptr_t GetTunnelLocalPortNumber() const { return FTunnelLocalPortNumber; } + void SetRememberedPassword(const UnicodeString & Value) { FRememberedPassword = Value; } + void SetRememberedTunnelPassword(const UnicodeString & Value) { FRememberedTunnelPassword = Value; } + void SetTunnelPassword(const UnicodeString & Value) { FRememberedTunnelPassword = Value; } + TCustomFileSystem * GetFileSystem() const { return FFileSystem; } + TCustomFileSystem * GetFileSystem() { return FFileSystem; } + + bool CheckForEsc(); + void SetupTunnelLocalPortNumber(); + +private: + void InternalTryOpen(); + void InternalDoTryOpen(); + void InitFileSystem(); +}; + +class TSecondaryTerminal : public TTerminal +{ +NB_DISABLE_COPY(TSecondaryTerminal) +public: + explicit TSecondaryTerminal(TTerminal * MainTerminal); + virtual ~TSecondaryTerminal() {} + void Init(TSessionData * SessionData, TConfiguration * Configuration, + const UnicodeString & Name); + + void UpdateFromMain(); + + //__property TTerminal * MainTerminal = { read = FMainTerminal }; + TTerminal * GetMainTerminal() const { return FMainTerminal; } + +protected: + virtual void DirectoryLoaded(TRemoteFileList * FileList); + virtual void DirectoryModified(const UnicodeString & APath, + bool SubDirs); + virtual const TTerminal * GetPasswordSource() const { return FMainTerminal; } + virtual TTerminal * GetPasswordSource(); + +private: + TTerminal * FMainTerminal; +}; + +class TTerminalList : public TObjectList +{ +NB_DISABLE_COPY(TTerminalList) +public: + explicit TTerminalList(TConfiguration * AConfiguration); + virtual ~TTerminalList(); + + virtual TTerminal * NewTerminal(TSessionData * Data); + virtual void FreeTerminal(TTerminal * Terminal); + void FreeAndNullTerminal(TTerminal *& Terminal); + virtual void Idle(); + void RecryptPasswords(); + + // __property TTerminal * Terminals[int Index] = { read=GetTerminal }; + +protected: + virtual TTerminal * CreateTerminal(TSessionData * Data); + +private: + TConfiguration * FConfiguration; + +public: + TTerminal * GetTerminal(intptr_t Index); +}; + +struct TCustomCommandParams : public TObject +{ +NB_DECLARE_CLASS(TCustomCommandParams) +public: + UnicodeString Command; + intptr_t Params; + TCaptureOutputEvent OutputEvent; +}; + +struct TCalculateSizeStats : public TObject +{ + TCalculateSizeStats(); + + intptr_t Files; + intptr_t Directories; + intptr_t SymLinks; +}; + +struct TCalculateSizeParams : public TObject +{ +NB_DECLARE_CLASS(TCalculateSizeParams) +public: + int64_t Size; + intptr_t Params; + const TCopyParamType * CopyParam; + TCalculateSizeStats * Stats; + bool AllowDirs; + bool Result; +}; + +typedef std::vector TDateTimes; + +struct TMakeLocalFileListParams : public TObject +{ +NB_DECLARE_CLASS(TMakeLocalFileListParams) +public: + TStrings * FileList; + TDateTimes * FileTimes; + bool IncludeDirs; + bool Recursive; +}; + +struct TSynchronizeOptions : public TObject +{ +NB_DISABLE_COPY(TSynchronizeOptions) +public: + TSynchronizeOptions(); + ~TSynchronizeOptions(); + + TStringList * Filter; + + bool FilterFind(const UnicodeString & AFileName); + bool MatchesFilter(const UnicodeString & AFileName); +}; + +enum TChecklistAction +{ + saNone, + saUploadNew, + saDownloadNew, + saUploadUpdate, + saDownloadUpdate, + saDeleteRemote, + saDeleteLocal, +}; + +//--------------------------------------------------------------------------- +class TChecklistItem : public TObject +{ +friend class TTerminal; +NB_DECLARE_CLASS(TChecklistItem) +NB_DISABLE_COPY(TChecklistItem) +public: + struct TFileInfo : public TObject + { + UnicodeString FileName; + UnicodeString Directory; + TDateTime Modification; + TModificationFmt ModificationFmt; + int64_t Size; + }; + + TChecklistAction Action; + bool IsDirectory; + TFileInfo Local; + TFileInfo Remote; + intptr_t ImageIndex; + bool Checked; + TRemoteFile * RemoteFile; + + const UnicodeString GetFileName() const; + + ~TChecklistItem(); + +private: + FILETIME FLocalLastWriteTime; + + TChecklistItem(); +}; + +class TSynchronizeChecklist : public TObject +{ +friend class TTerminal; +NB_DISABLE_COPY(TSynchronizeChecklist) +public: + static const intptr_t ActionCount = saDeleteLocal; + + ~TSynchronizeChecklist(); + + void Update(const TChecklistItem * Item, bool Check, TChecklistAction Action); + + static TChecklistAction Reverse(TChecklistAction Action); + +/* + __property int Count = { read = GetCount }; + __property const TItem * Item[int Index] = { read = GetItem }; +*/ +protected: + TSynchronizeChecklist(); + + void Sort(); + void Add(TChecklistItem * Item); + +public: + void SetMasks(const UnicodeString & Value); + + intptr_t GetCount() const; + const TChecklistItem * GetItem(intptr_t Index) const; + +private: + TList FList; + + static intptr_t Compare(const void * Item1, const void * Item2); +}; + +struct TSpaceAvailable : public TObject +{ + TSpaceAvailable(); + + int64_t BytesOnDevice; + int64_t UnusedBytesOnDevice; + int64_t BytesAvailableToUser; + int64_t UnusedBytesAvailableToUser; + uintptr_t BytesPerAllocationUnit; +}; + +class TRobustOperationLoop : public TObject +{ +NB_DISABLE_COPY(TRobustOperationLoop) +public: + TRobustOperationLoop(TTerminal * Terminal, TFileOperationProgressType * OperationProgress); + bool TryReopen(Exception & E); + bool ShouldRetry() const; + bool Retry(); + +private: + TTerminal * FTerminal; + TFileOperationProgressType * FOperationProgress; + bool FRetry; +}; + +UnicodeString GetSessionUrl(const TTerminal * Terminal, bool WithUserName = false); + diff --git a/netbox/src/core/WebDAVFileSystem.cpp b/netbox/src/core/WebDAVFileSystem.cpp new file mode 100644 index 000000000..e16eb7ab7 --- /dev/null +++ b/netbox/src/core/WebDAVFileSystem.cpp @@ -0,0 +1,2927 @@ + +#include +#pragma hdrstop + +#include +#include +#include +#ifndef __linux__ +#include +#include +#endif +#include + +#ifndef NE_LFS +#define NE_LFS +#endif +#ifndef WINSCP +#define WINSCP +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "WebDAVFileSystem.h" + +#include "Interface.h" +#include "Common.h" +#include "Exceptions.h" +#include "Terminal.h" +#include "TextsCore.h" +#include "SecureShell.h" +#include "HelpCore.h" +#include "CoreMain.h" +#ifndef __linux__ +#include "Security.h" +#endif +#include +#include + +struct TWebDAVCertificateData +{ + UnicodeString Subject; + UnicodeString Issuer; + + TDateTime ValidFrom; + TDateTime ValidUntil; + + UnicodeString Fingerprint; + AnsiString AsciiCert; + + int Failures; +}; + +#ifdef __linux__ +#define _open_osfhandle(name, flags) ((int)name) +#endif + +#define SESSION_FS_KEY "filesystem" +static const char HttpsCertificateStorageKey[] = "HttpsCertificates"; +static const UnicodeString CONST_WEBDAV_PROTOCOL_BASE_NAME = L"WebDAV"; +static const int HttpUnauthorized = 401; + +#define DAV_PROP_NAMESPACE "DAV:" +#define MODDAV_PROP_NAMESPACE "http://apache.org/dav/props/" +#define PROP_CONTENT_LENGTH "getcontentlength" +#define PROP_LAST_MODIFIED "getlastmodified" +#define PROP_CREATIONDATE "creationdate" +#define PROP_RESOURCE_TYPE "resourcetype" +#define PROP_HIDDEN "ishidden" +#define PROP_QUOTA_AVAILABLE "quota-available-bytes" +#define PROP_QUOTA_USED "quota-used-bytes" +#define PROP_EXECUTABLE "executable" +#define PROP_OWNER "owner" + +static std::unique_ptr DebugSection(new TCriticalSection); +//static std::set FileSystems; + +#ifndef __linux__ +extern "C" +{ + +void ne_debug(void * Context, int Channel, const char * Format, ...) +{ + bool DoLog; + + if (FLAGSET(Channel, NE_DBG_SOCKET) || + FLAGSET(Channel, NE_DBG_HTTP) || + FLAGSET(Channel, NE_DBG_HTTPAUTH) || + FLAGSET(Channel, NE_DBG_SSL)) + { + DoLog = true; + } + else if (FLAGSET(Channel, NE_DBG_XML) || + FLAGSET(Channel, NE_DBG_WINSCP_HTTP_DETAIL)) + { + DoLog = (GetConfiguration()->GetActualLogProtocol() >= 1); + } + else if (FLAGSET(Channel, NE_DBG_LOCKS) || + FLAGSET(Channel, NE_DBG_XMLPARSE) || + FLAGSET(Channel, NE_DBG_HTTPBODY)) + { + DoLog = (GetConfiguration()->GetActualLogProtocol() >= 2); + } + else + { + DoLog = false; + DebugFail(); + } + + #ifndef _DEBUG + if (DoLog) + #endif + { + va_list Args; + va_start(Args, Format); + UTF8String UTFMessage; + UTFMessage.vprintf(Format, Args); + va_end(Args); + + UnicodeString Message(UTFMessage); + + if (DoLog) + { + // Note that this gets called for THttp sessions too. + // It does no harm atm. + TWebDAVFileSystem * FileSystem = nullptr; + if (Context != nullptr) + { + ne_session * Session = static_cast(Context); + + FileSystem = + static_cast(ne_get_session_private(Session, SESSION_FS_KEY)); + } + else + { + TGuard Guard(*DebugSection.get()); + + TODO("implement"); + /*if (FileSystems.size() == 1) + { + FileSystem = *FileSystems.begin(); + }*/ + } + + if (FileSystem != nullptr) + { + FileSystem->NeonDebug(Message); + } + } + } +} + +} // extern "C" +#endif + +// ne_path_escape returns 7-bit string, so it does not really matter if we use +// AnsiString or UTF8String here, though UTF8String might be more safe +static AnsiString PathEscape(const char * Path) +{ + char * EscapedPath = ne_path_escape(Path); + AnsiString Result = EscapedPath; + ne_free(EscapedPath); + return Result; +} + +static UTF8String PathUnescape(const char * Path) +{ + char * UnescapedPath = ne_path_unescape(Path); + UTF8String Result(UnescapedPath, UnescapedPath ? strlen(UnescapedPath) : 0); + ne_free(UnescapedPath); + return Result; +} + +#define AbsolutePathToNeon(P) PathEscape(StrToNeon(P)).c_str() +#define PathToNeonStatic(THIS, P) AbsolutePathToNeon((THIS)->GetAbsolutePath(P, false)) +#define PathToNeon(P) PathToNeonStatic(this, P) + + +static bool NeonInitialized = false; +static bool NeonSspiInitialized = false; + +void NeonInitialize() +{ + // Even if this fails, we do not want to interrupt WinSCP starting for that. + // Anyway, it can hardly fail. + // Though it fails on Wine on Debian VM, because of ne_sspi_init(): + // sspi: QuerySecurityPackageInfo [failed] [80090305]. + // sspi: Unable to get negotiate maximum packet size + int NeonResult = ne_sock_init(); + if (NeonResult == 0) + { + NeonInitialized = true; + NeonSspiInitialized = true; + } + else if (NeonResult == -2) + { + NeonInitialized = true; + NeonSspiInitialized = false; + } + else + { + NeonInitialized = false; + NeonSspiInitialized = false; + } +} + +void NeonFinalize() +{ + if (NeonInitialized) + { + ne_sock_exit(); + NeonInitialized = false; + } +} + +UnicodeString NeonVersion() +{ + UnicodeString Str = StrFromNeon(ne_version_string()); + CutToChar(Str, L' ', true); // "neon" + UnicodeString Result = CutToChar(Str, L':', true); + return Result; +} + +UnicodeString ExpatVersion() +{ + return FORMAT(L"%d.%d.%d", XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION); +} + + +TWebDAVFileSystem::TWebDAVFileSystem(TTerminal * ATerminal) : + TCustomFileSystem(ATerminal), + FPortNumber(0), + FIgnoreAuthenticationFailure(iafNo), + FActive(false), + FHasTrailingSlash(false), + FCancelled(false), + FStoredPasswordTried(false), + FUploading(false), + FDownloading(false), + FNeonSession(nullptr), + FNeonLockStore(nullptr), + FInitialHandshake(false), + FAuthenticationRequested(false), + FCapabilities(0), + FAuthenticationRetry(false) +{ +} + +void TWebDAVFileSystem::Init(void *) +{ + FFileSystemInfo.ProtocolBaseName = CONST_WEBDAV_PROTOCOL_BASE_NAME; + FFileSystemInfo.ProtocolName = FFileSystemInfo.ProtocolBaseName; +} + +void TWebDAVFileSystem::FileTransferProgress(int64_t TransferSize, int64_t Bytes) +{ + +} + +TWebDAVFileSystem::~TWebDAVFileSystem() +{ + UnregisterFromDebug(); + + { + TGuard Guard(FNeonLockStoreSection); + if (FNeonLockStore != nullptr) + { + ne_lockstore_destroy(FNeonLockStore); + FNeonLockStore = nullptr; + } + } + +} + +void TWebDAVFileSystem::Open() +{ + + if (!NeonInitialized) + { + throw Exception(LoadStr(NEON_INIT_FAILED)); + } + + if (!NeonSspiInitialized) + { + FTerminal->LogEvent(L"Warning: SSPI initialization failed."); + } + + RegisterForDebug(); + + FCurrentDirectory.Clear(); + FHasTrailingSlash = false; + FStoredPasswordTried = false; + FTlsVersionStr.Clear(); + FCapabilities = 0; + + TSessionData * Data = FTerminal->GetSessionData(); + + FSessionInfo.LoginTime = Now(); + + UnicodeString HostName = Data->GetHostNameExpanded(); + size_t Port = Data->GetPortNumber(); + UnicodeString ProtocolName = (FTerminal->GetSessionData()->GetFtps() == ftpsNone) ? WebDAVProtocol : WebDAVSProtocol; + UnicodeString Path = Data->GetRemoteDirectory(); + // PathToNeon is not used as we cannot call AbsolutePath here + UnicodeString EscapedPath = StrFromNeon(PathEscape(StrToNeon(Path)).c_str()); + UnicodeString Url = FORMAT(L"%s://%s:%d%s", ProtocolName.c_str(), HostName.c_str(), Port, EscapedPath.c_str()); + + FTerminal->Information(LoadStr(STATUS_CONNECT), true); + FActive = false; + try + { + OpenUrl(Url); + } + catch (Exception & E) + { + CloseNeonSession(); + FTerminal->Closed(); + FTerminal->FatalError(&E, LoadStr(CONNECTION_FAILED)); + } + FActive = true; +} + +UnicodeString TWebDAVFileSystem::ParsePathFromUrl(const UnicodeString & Url) +{ + UnicodeString Result; + ne_uri ParsedUri; + if (ne_uri_parse(StrToNeon(Url), &ParsedUri) == 0) + { + Result = StrFromNeon(PathUnescape(ParsedUri.path)); + ne_uri_free(&ParsedUri); + } + return Result; +} + +void TWebDAVFileSystem::OpenUrl(const UnicodeString & Url) +{ + UnicodeString CorrectedUrl; + NeonClientOpenSessionInternal(CorrectedUrl, Url); + + if (CorrectedUrl.IsEmpty()) + { + CorrectedUrl = Url; + } + UnicodeString ParsedPath = ParsePathFromUrl(CorrectedUrl); + if (!ParsedPath.IsEmpty()) + { + // this is most likely pointless as it get overwritten by + // call to ChangeDirectory() from TTerminal::DoStartup + FCurrentDirectory = ParsedPath; + } +} + +void TWebDAVFileSystem::NeonClientOpenSessionInternal(UnicodeString & CorrectedUrl, UnicodeString Url) +{ + std::unique_ptr AttemptedUrls(CreateSortedStringList()); + AttemptedUrls->Add(Url); + while (true) + { + CorrectedUrl = L""; + NeonOpen(CorrectedUrl, Url); + // No error and no corrected URL? We're done here. + if (CorrectedUrl.IsEmpty()) + { + break; + } + CloseNeonSession(); + CheckRedirectLoop(CorrectedUrl, AttemptedUrls.get()); + // Our caller will want to know what our final corrected URL was. + Url = CorrectedUrl; + } + + CorrectedUrl = Url; +} + +void TWebDAVFileSystem::NeonOpen(UnicodeString & CorrectedUrl, const UnicodeString & Url) +{ + ne_uri uri; + NeonParseUrl(Url, uri); + + FHostName = StrFromNeon(uri.host); + FPortNumber = uri.port; + + FSessionInfo.CSCipher = UnicodeString(); + FSessionInfo.SCCipher = UnicodeString(); + bool Ssl = IsTlsUri(uri); + FSessionInfo.SecurityProtocolName = Ssl ? LoadStr(FTPS_IMPLICIT) : UnicodeString(); + + if (Ssl != (FTerminal->GetSessionData()->GetFtps() != ftpsNone)) + { + FTerminal->LogEvent(FORMAT(L"Warning: %s", LoadStr(UNENCRYPTED_REDIRECT).c_str())); + } + + TSessionData * Data = FTerminal->GetSessionData(); + + DebugAssert(FNeonSession == nullptr); + FNeonSession = + CreateNeonSession( + uri, Data->GetProxyMethod(), Data->GetProxyHost(), static_cast(Data->GetProxyPort()), + Data->GetProxyUsername(), Data->GetProxyPassword()); + + UTF8String Path(uri.path); + ne_uri_free(&uri); + ne_set_session_private(FNeonSession, SESSION_FS_KEY, this); + + // Other flags: + // NE_DBG_FLUSH - used only in native implementation of ne_debug + // NE_DBG_HTTPPLAIN - log credentials in HTTP authentication + + ne_debug_mask = + NE_DBG_SOCKET | + NE_DBG_HTTP | + NE_DBG_XML | // detail + NE_DBG_HTTPAUTH | + NE_DBG_LOCKS | // very details + NE_DBG_XMLPARSE | // very details + NE_DBG_HTTPBODY | // very details + NE_DBG_SSL | + FLAGMASK(GetConfiguration()->GetLogSensitive(), NE_DBG_HTTPPLAIN); + + ne_set_read_timeout(FNeonSession, static_cast(Data->GetTimeout())); + + ne_set_connect_timeout(FNeonSession, static_cast(Data->GetTimeout())); + + NeonAddAuthentiation(Ssl); + + if (Ssl) + { + SetNeonTlsInit(FNeonSession, InitSslSession); + + // When the CA certificate or server certificate has + // verification problems, neon will call our verify function before + // outright rejection of the connection. + ne_ssl_set_verify(FNeonSession, NeonServerSSLCallback, this); + + ne_ssl_trust_default_ca(FNeonSession); + + ne_ssl_provide_clicert(FNeonSession, NeonProvideClientCert, this); + } + + ne_set_notifier(FNeonSession, NeonNotifier, this); + ne_hook_create_request(FNeonSession, NeonCreateRequest, this); + ne_hook_pre_send(FNeonSession, NeonPreSend, this); + ne_hook_post_send(FNeonSession, NeonPostSend, this); + ne_hook_post_headers(FNeonSession, NeonPostHeaders, this); + + TAutoFlag Flag(FInitialHandshake); + ExchangeCapabilities(Path.c_str(), CorrectedUrl); +} + +void TWebDAVFileSystem::NeonAddAuthentiation(bool UseNegotiate) +{ + unsigned int NeonAuthTypes = NE_AUTH_BASIC | NE_AUTH_DIGEST; + if (UseNegotiate) + { + NeonAuthTypes |= NE_AUTH_NEGOTIATE; + } + ne_add_server_auth(FNeonSession, NeonAuthTypes, NeonRequestAuth, this); +} + +UnicodeString TWebDAVFileSystem::GetRedirectUrl() +{ + UnicodeString Result = GetNeonRedirectUrl(FNeonSession); + FTerminal->LogEvent(FORMAT(L"Redirected to \"%s\".", Result.c_str())); + return Result; +} + +#ifdef __linux__ +static const struct options_map { + const char *name; + unsigned int cap; +} options_map[] = { + { "1", NE_CAP_DAV_CLASS1 }, + { "2", NE_CAP_DAV_CLASS2 }, + { "3", NE_CAP_DAV_CLASS3 }, + { "", NE_CAP_MODDAV_EXEC }, + { "access-control", NE_CAP_DAV_ACL }, + { "version-control", NE_CAP_VER_CONTROL }, + { "checkout-in-place", NE_CAP_CO_IN_PLACE }, + { "version-history", NE_CAP_VER_HISTORY }, + { "workspace", NE_CAP_WORKSPACE }, + { "update", NE_CAP_UPDATE }, + { "label", NE_CAP_LABEL }, + { "working-resource", NE_CAP_WORK_RESOURCE }, + { "merge", NE_CAP_MERGE }, + { "baseline", NE_CAP_BASELINE }, + { "version-controlled-collection", NE_CAP_VC_COLLECTION }, + { "extended-mkcol", NE_CAP_EXT_MKCOL } +}; + +const char * ne_capability_name(unsigned int cap) +{ + unsigned n; + for (n = 0; n < sizeof(options_map)/sizeof(options_map[0]); n++) + { + if (options_map[n].cap == cap) + { + return options_map[n].name; + } + } + return NULL; +} +#endif + +void TWebDAVFileSystem::ExchangeCapabilities(const char * Path, UnicodeString & CorrectedUrl) +{ + ClearNeonError(); + + int NeonStatus; + FAuthenticationRetry = false; + do + { + NeonStatus = ne_options2(FNeonSession, Path, &FCapabilities); + } + while ((NeonStatus == NE_AUTH) && FAuthenticationRetry); + + if (NeonStatus == NE_REDIRECT) + { + CorrectedUrl = GetRedirectUrl(); + } + else if (NeonStatus == NE_OK) + { + if (FCapabilities > 0) + { + UnicodeString Str; + uintptr_t Capability = 0x01; + uintptr_t Capabilities = FCapabilities; + while (Capabilities > 0) + { + if (FLAGSET(Capabilities, Capability)) + { + AddToList(Str, StrFromNeon(ne_capability_name(Capability)), L", "); + Capabilities -= Capability; + } + Capability <<= 1; + } + FTerminal->LogEvent(FORMAT(L"Server capabilities: %s", Str.c_str())); + FFileSystemInfo.AdditionalInfo += + LoadStr(WEBDAV_EXTENSION_INFO) + sLineBreak + + L" " + Str + sLineBreak; + } + } + else + { + CheckStatus(NeonStatus); + } + + FTerminal->SaveCapabilities(FFileSystemInfo); +} + +void TWebDAVFileSystem::CloseNeonSession() +{ + if (FNeonSession != nullptr) + { + DestroyNeonSession(FNeonSession); + FNeonSession = nullptr; + } +} + +void TWebDAVFileSystem::Close() +{ + DebugAssert(FActive); + CloseNeonSession(); + FTerminal->Closed(); + FActive = false; + UnregisterFromDebug(); +} + +void TWebDAVFileSystem::RegisterForDebug() +{ + TGuard Guard(*DebugSection.get()); +// FileSystems.insert(this); +} + +void TWebDAVFileSystem::UnregisterFromDebug() +{ + TGuard Guard(*DebugSection.get()); +// FileSystems.erase(this); +} + +bool TWebDAVFileSystem::GetActive() const +{ + return FActive; +} + +void TWebDAVFileSystem::CollectUsage() +{ + if (!FTlsVersionStr.IsEmpty()) + { + FTerminal->CollectTlsUsage(FTlsVersionStr); + } + + if (!FTerminal->GetSessionData()->GetTlsCertificateFile().IsEmpty()) + { +// GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsWebDAVSCertificate"); + } + + UnicodeString RemoteSystem = FFileSystemInfo.RemoteSystem; + if (ContainsText(RemoteSystem, L"Microsoft-IIS")) + { +// FTerminal->GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsWebDAVIIS"); + } + else if (ContainsText(RemoteSystem, L"IT Hit WebDAV Server")) + { +// FTerminal->GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsWebDAVITHit"); + } + // e.g. brickftp.com + else if (ContainsText(RemoteSystem, L"nginx")) + { +// FTerminal->GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsWebDAVNginx"); + } + else + { + // We also know OpenDrive, Yandex, iFiles (iOS), Swapper (iOS), SafeSync +// FTerminal->GetConfiguration()->GetUsage()->Inc(L"OpenedSessionsWebDAVOther"); + } +} + +const TSessionInfo & TWebDAVFileSystem::GetSessionInfo() const +{ + return FSessionInfo; +} + +const TFileSystemInfo & TWebDAVFileSystem::GetFileSystemInfo(bool /*Retrieve*/) +{ + return FFileSystemInfo; +} + +bool TWebDAVFileSystem::TemporaryTransferFile(const UnicodeString & /*AFileName*/) +{ + return false; +} + +bool TWebDAVFileSystem::GetStoredCredentialsTried() const +{ + return FStoredPasswordTried; +} + +UnicodeString TWebDAVFileSystem::FSGetUserName() const +{ + return FUserName; +} + +void TWebDAVFileSystem::Idle() +{ + TODO("Keep session alive"); + // noop +} + +UnicodeString TWebDAVFileSystem::GetAbsolutePath(const UnicodeString & APath, bool /*Local*/) const +{ + bool AddTrailingBackslash; + + if (APath == L"/") + { + // does not really matter as path "/" is still "/" when absolute, + // no slash needed + AddTrailingBackslash = FHasTrailingSlash; + } + else + { + AddTrailingBackslash = (APath[APath.Length()] == L'/'); + } + + UnicodeString Result = core::AbsolutePath(GetCurrDirectory(), APath); + // We must preserve trailing slash, because particularly for mod_dav, + // it really matters if the slash in there or not + if (AddTrailingBackslash) + { + Result = core::UnixIncludeTrailingBackslash(Result); + } + + return Result; +} + +UnicodeString TWebDAVFileSystem::GetAbsolutePath(const UnicodeString & APath, bool Local) +{ + return static_cast(this)->GetAbsolutePath(APath, Local); +} + +bool TWebDAVFileSystem::IsCapable(intptr_t Capability) const +{ + DebugAssert(FTerminal); + switch (Capability) + { + case fcRename: + case fcRemoteMove: + case fcMoveToQueue: + case fcPreservingTimestampUpload: + case fcCheckingSpaceAvailable: + // Only to make double-click on file edit/open the file, + // instead of trying to open it as directory + case fcResolveSymlink: + return true; + + case fcUserGroupListing: + case fcModeChanging: + case fcModeChangingUpload: + case fcGroupChanging: + case fcOwnerChanging: + case fcAnyCommand: + case fcShellAnyCommand: + case fcHardLink: + case fcSymbolicLink: + case fcTextMode: + case fcNativeTextMode: + case fcNewerOnlyUpload: + case fcTimestampChanging: + case fcLoadingAdditionalProperties: + case fcIgnorePermErrors: + case fcCalculatingChecksum: + case fcSecondaryShell: + case fcGroupOwnerChangingByID: + case fcRemoveCtrlZUpload: + case fcRemoveBOMUpload: + case fcRemoteCopy: + case fcPreservingTimestampDirs: + case fcResumeSupport: + return false; + + case fcLocking: + return FLAGSET(FCapabilities, NE_CAP_DAV_CLASS2); + + default: + DebugFail(); + return false; + } +} + +UnicodeString TWebDAVFileSystem::GetCurrDirectory() const +{ + return FCurrentDirectory; +} + +void TWebDAVFileSystem::DoStartup() +{ + FTerminal->SetExceptionOnFail(true); + // retrieve initialize working directory to save it as home directory + ReadCurrentDirectory(); + FTerminal->SetExceptionOnFail(false); +} + +void TWebDAVFileSystem::ClearNeonError() +{ + FCancelled = false; + FAuthenticationRequested = false; + ne_set_error(FNeonSession, ""); +} + +UnicodeString TWebDAVFileSystem::GetNeonError() const +{ + return ::GetNeonError(FNeonSession); +} + +void TWebDAVFileSystem::CheckStatus(int NeonStatus) +{ + if ((NeonStatus == NE_ERROR) && FCancelled) + { + FCancelled = false; + Abort(); + } + else + { + CheckNeonStatus(FNeonSession, NeonStatus, FHostName); + } +} + +void TWebDAVFileSystem::LookupUsersGroups() +{ + DebugFail(); +} + +void TWebDAVFileSystem::ReadCurrentDirectory() +{ + if (FCachedDirectoryChange.IsEmpty()) + { + FCurrentDirectory = FCurrentDirectory.IsEmpty() ? UnicodeString(ROOTDIRECTORY) : FCurrentDirectory; + } + else + { + FCurrentDirectory = FCachedDirectoryChange; + FCachedDirectoryChange = L""; + } +} + +void TWebDAVFileSystem::HomeDirectory() +{ + ChangeDirectory(L"/"); +} + +UnicodeString TWebDAVFileSystem::DirectoryPath(UnicodeString Path) +{ + if (FHasTrailingSlash) + { + Path = core::UnixIncludeTrailingBackslash(Path); + } + return Path; +} + +UnicodeString TWebDAVFileSystem::FilePath(const TRemoteFile * AFile) +{ + UnicodeString Result = AFile->GetFullFileName(); + if (AFile->GetIsDirectory()) + { + Result = DirectoryPath(Result); + } + return Result; +} + +void TWebDAVFileSystem::TryOpenDirectory(const UnicodeString & ADirectory) +{ + UnicodeString Directory = DirectoryPath(ADirectory); + FTerminal->LogEvent(FORMAT(L"Trying to open directory \"%s\".", Directory.c_str())); + TRemoteFile * File; + ReadFile(Directory, File); + delete File; +} + +void TWebDAVFileSystem::AnnounceFileListOperation() +{ + // noop +} + +void TWebDAVFileSystem::ChangeDirectory(const UnicodeString & ADirectory) +{ + UnicodeString Path = GetAbsolutePath(ADirectory, false); + + // to verify existence of directory try to open it + TryOpenDirectory(Path); + + // if open dir did not fail, directory exists -> success. + FCachedDirectoryChange = Path; +} + +void TWebDAVFileSystem::CachedChangeDirectory(const UnicodeString & Directory) +{ + FCachedDirectoryChange = core::UnixExcludeTrailingBackslash(Directory); +} + +struct TReadFileData +{ + TWebDAVFileSystem * FileSystem; + TRemoteFile * File; + TRemoteFileList * FileList; +}; + +int TWebDAVFileSystem::ReadDirectoryInternal( + const UnicodeString & Path, TRemoteFileList * FileList) +{ + TReadFileData Data; + Data.FileSystem = this; + Data.File = nullptr; + Data.FileList = FileList; + ClearNeonError(); + ne_propfind_handler * PropFindHandler = ne_propfind_create(FNeonSession, PathToNeon(Path), NE_DEPTH_ONE); +#ifndef __linux__ + void * DiscoveryContext = ne_lock_register_discovery(PropFindHandler); +#endif + int Result; + try__finally + { + SCOPE_EXIT + { +#ifndef __linux__ + ne_lock_discovery_free(DiscoveryContext); +#endif + ne_propfind_destroy(PropFindHandler); + }; + Result = ne_propfind_allprop(PropFindHandler, NeonPropsResult, &Data); + } + __finally + { +#ifndef __linux__ + ne_lock_discovery_free(DiscoveryContext); +#endif + ne_propfind_destroy(PropFindHandler); + }; + return Result; +} + +bool TWebDAVFileSystem::IsValidRedirect(int NeonStatus, UnicodeString & Path) +{ + bool Result = (NeonStatus == NE_REDIRECT); + if (Result) + { + // What PathToNeon does + UnicodeString OriginalPath = GetAbsolutePath(Path, false); + // Handle one-step redirect + // (for more steps we would have to implement loop detection). + // This is mainly to handle "folder" => "folder/" redirects of Apache/mod_dav. + UnicodeString RedirectUrl = GetRedirectUrl(); + // We should test if the redirect is not for another server, + // though not sure how to do this reliably (domain aliases, IP vs. domain, etc.) + UnicodeString RedirectPath = ParsePathFromUrl(RedirectUrl); + Result = + !RedirectPath.IsEmpty() && + (RedirectPath != OriginalPath); + + if (Result) + { + Path = RedirectPath; + } + } + + return Result; +} + +void TWebDAVFileSystem::ReadDirectory(TRemoteFileList * FileList) +{ + UnicodeString Path = DirectoryPath(FileList->GetDirectory()); + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + + int NeonStatus = ReadDirectoryInternal(Path, FileList); + if (IsValidRedirect(NeonStatus, Path)) + { + NeonStatus = ReadDirectoryInternal(Path, FileList); + } + CheckStatus(NeonStatus); +} + +void TWebDAVFileSystem::ReadSymlink(TRemoteFile * /*SymlinkFile*/, + TRemoteFile *& /*File*/) +{ + // we never set SymLink flag, so we should never get here + DebugFail(); +} + +void TWebDAVFileSystem::ReadFile(const UnicodeString & AFileName, + TRemoteFile *& File) +{ + CustomReadFile(AFileName, File, nullptr); +} + +void TWebDAVFileSystem::NeonPropsResult( + void * UserData, const ne_uri * Uri, const ne_prop_result_set * Results) +{ + UnicodeString Path = StrFromNeon(PathUnescape(Uri->path).c_str()); + + TReadFileData & Data = *static_cast(UserData); + if (Data.FileList != nullptr) + { + UnicodeString FileListPath = Data.FileSystem->GetAbsolutePath(Data.FileList->GetDirectory(), false); + if (core::UnixSamePath(Path, FileListPath)) + { + Path = core::UnixIncludeTrailingBackslash(core::UnixIncludeTrailingBackslash(Path) + L".."); + } + std::unique_ptr File(new TRemoteFile(nullptr)); + File->SetTerminal(Data.FileSystem->FTerminal); + Data.FileSystem->ParsePropResultSet(File.get(), Path, Results); + Data.FileList->AddFile(File.release()); + } + else + { + Data.FileSystem->ParsePropResultSet(Data.File, Path, Results); + } +} + +const char * TWebDAVFileSystem::GetProp( + const ne_prop_result_set * Results, const char * Name, const char * NameSpace) +{ + ne_propname Prop; + Prop.nspace = (NameSpace == nullptr) ? DAV_PROP_NAMESPACE : NameSpace; + Prop.name = Name; + return ne_propset_value(Results, &Prop); +} + +void TWebDAVFileSystem::ParsePropResultSet(TRemoteFile * AFile, + const UnicodeString & APath, const ne_prop_result_set * Results) +{ + AFile->SetFullFileName(core::UnixExcludeTrailingBackslash(APath)); + // Some servers do not use DAV:collection tag, but indicate the folder by trailing slash only. + // It seems that all servers actually use the trailing slash, including IIS, mod_Dav, IT Hit, OpenDrive, etc. + bool Collection = (AFile->GetFullFileName() != APath); + AFile->SetFileName(base::UnixExtractFileName(AFile->GetFullFileName())); + const char * ContentLength = GetProp(Results, PROP_CONTENT_LENGTH); + // some servers, for example iFiles, do not provide "getcontentlength" for folders + if (ContentLength != nullptr) + { + AFile->SetSize(StrToInt64Def(ContentLength, 0)); + } + const char * LastModified = GetProp(Results, PROP_LAST_MODIFIED); + const char * CreationDate = GetProp(Results, PROP_CREATIONDATE); + const char * Modified = LastModified ? LastModified : CreationDate; + if (DebugAlwaysTrue(Modified != nullptr)) + { + char WeekDay[4] = { L'\0' }; + int Year = 0; + char MonthStr[4] = { L'\0' }; + int Day = 0; + int Hour = 0; + int Min = 0; + int Sec = 0; + #define RFC1123_FORMAT "%3s, %02d %3s %4d %02d:%02d:%02d GMT" + int Filled = + sscanf(Modified, RFC1123_FORMAT, WeekDay, &Day, MonthStr, &Year, &Hour, &Min, &Sec); + // we need at least a complete date + if (Filled >= 4) + { + int Month = ParseShortEngMonthName(MonthStr); + if (Month >= 1) + { + TDateTime Modification = + EncodeDateVerbose((unsigned short)Year, (unsigned short)Month, (unsigned short)Day) + + EncodeTimeVerbose((unsigned short)Hour, (unsigned short)Min, (unsigned short)Sec, 0); + AFile->SetModification(ConvertTimestampFromUTC(Modification)); + AFile->SetModificationFmt(mfFull); + } + } + } + + // optimization + if (!Collection) + { + // This is possibly redundant code as all servers we know (see a comment above) + // indicate the folder by trailing slash too + const char * ResourceType = GetProp(Results, PROP_RESOURCE_TYPE); + if (ResourceType != nullptr) + { + // property has XML value + UnicodeString AResourceType = ResourceType; + // this is very poor parsing + if (ContainsText(ResourceType, L"SetType(Collection ? FILETYPE_DIRECTORY : FILETYPE_DEFAULT); + // this is MS extension (draft-hopmann-collection-props-00) + const char * IsHidden = GetProp(Results, PROP_HIDDEN); + if (IsHidden != nullptr) + { + AFile->SetIsHidden(StrToIntDef(IsHidden, 0) != 0); + } + + const char * Owner = GetProp(Results, PROP_OWNER); + if (Owner != nullptr) + { + AFile->GetFileOwner().SetName(Owner); + } + + const UnicodeString RightsDelimiter(L", "); + UnicodeString HumanRights; + + // Proprietary property of mod_dav + // http://www.webdav.org/mod_dav/#imp + const char * Executable = GetProp(Results, PROP_EXECUTABLE, MODDAV_PROP_NAMESPACE); + if (Executable != nullptr) + { + if (strcmp(Executable, "T") == 0) + { + UnicodeString ExecutableRights; + // The "gear" character is supported since Windows 8 + if (IsWin8()) + { + ExecutableRights = L"\u2699"; + } + else + { + ExecutableRights = LoadStr(EXECUTABLE); + } + AddToList(HumanRights, ExecutableRights, RightsDelimiter); + } + } + + struct ne_lock * Lock = static_cast(ne_propset_private(Results)); + if ((Lock != nullptr) && (Lock->token != nullptr)) + { + UnicodeString Owner2; + if (Lock->owner != nullptr) + { + Owner2 = StrFromNeon(Lock->owner).Trim(); + } + UnicodeString LockRights; + if (IsWin8()) + { + // The "lock" character is supported since Windows 8 +// LockRights = L"\uD83D\uDD12" + Owner2; + } + else + { + LockRights = LoadStr(LOCKED); + if (!Owner2.IsEmpty()) + { + LockRights = FORMAT(L"%s (%s)", LockRights.c_str(), Owner2.c_str()); + } + } + + AddToList(HumanRights, LockRights, RightsDelimiter); + } + + AFile->SetHumanRights(HumanRights); +} + +int TWebDAVFileSystem::CustomReadFileInternal(const UnicodeString & AFileName, + TRemoteFile *& AFile, TRemoteFile * ALinkedByFile) +{ + std::unique_ptr File(new TRemoteFile(ALinkedByFile)); + TReadFileData Data; + Data.FileSystem = this; + Data.File = File.get(); + Data.FileList = nullptr; + ClearNeonError(); + int Result = + ne_simple_propfind(FNeonSession, PathToNeon(AFileName), NE_DEPTH_ZERO, nullptr, + NeonPropsResult, &Data); + if (Result == NE_OK) + { + AFile = File.release(); + } + return Result; +} + +void TWebDAVFileSystem::CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& AFile, TRemoteFile * ALinkedByFile) +{ + UnicodeString FileName = AFileName; + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + + int NeonStatus = CustomReadFileInternal(AFileName, AFile, ALinkedByFile); + if (IsValidRedirect(NeonStatus, FileName)) + { + NeonStatus = CustomReadFileInternal(FileName, AFile, ALinkedByFile); + } + CheckStatus(NeonStatus); +} + +void TWebDAVFileSystem::RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action) +{ + Action.Recursive(); + ClearNeonError(); + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + RawByteString Path = PathToNeon(FilePath(AFile)); + // WebDAV does not allow non-recursive delete: + // RFC 4918, section 9.6.1: + // "A client MUST NOT submit a Depth header with a DELETE on a collection with any value but infinity." + // We should check that folder is empty when called with FLAGSET(Params, dfNoRecursive) + CheckStatus(ne_delete(FNeonSession, Path.c_str())); + // The lock is removed with the file, but if a file with the same name gets created, + // we would try to use obsoleted lock token with it, what the server would reject + // (mod_dav returns "412 Precondition Failed") + DiscardLock(Path); +} + +int TWebDAVFileSystem::RenameFileInternal(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + // 0 = no overwrite + return ne_move(FNeonSession, 0, PathToNeon(AFileName), PathToNeon(ANewName)); +} + +void TWebDAVFileSystem::RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName) +{ + ClearNeonError(); + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + + UnicodeString Path = AFileName; + int NeonStatus = RenameFileInternal(Path, ANewName); + if (IsValidRedirect(NeonStatus, Path)) + { + NeonStatus = RenameFileInternal(Path, ANewName); + } + CheckStatus(NeonStatus); + // See a comment in DeleteFile + DiscardLock(PathToNeon(Path)); +} + +void TWebDAVFileSystem::RemoteCopyFile(const UnicodeString & /*AFileName*/, + const UnicodeString & /*ANewName*/) +{ + DebugFail(); + ThrowNotImplemented(1012); +} + +void TWebDAVFileSystem::RemoteCreateDirectory(const UnicodeString & ADirName) +{ + ClearNeonError(); + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + CheckStatus(ne_mkcol(FNeonSession, PathToNeon(ADirName))); +} + +void TWebDAVFileSystem::CreateLink(const UnicodeString & /*AFileName*/, + const UnicodeString & /*PointTo*/, bool /*Symbolic*/) +{ + DebugFail(); + ThrowNotImplemented(1014); +} + +void TWebDAVFileSystem::ChangeFileProperties(const UnicodeString & /*AFileName*/, + const TRemoteFile * /*AFile*/, const TRemoteProperties * /*Properties*/, + TChmodSessionAction & /*Action*/) +{ + DebugFail(); + ThrowNotImplemented(1006); +} + +bool TWebDAVFileSystem::LoadFilesProperties(TStrings * /*FileList*/) +{ + DebugFail(); + return false; +} + +void TWebDAVFileSystem::CalculateFilesChecksum(const UnicodeString & /*Alg*/, + TStrings * /*FileList*/, TStrings * /*Checksums*/, + TCalculatedChecksumEvent /*OnCalculatedChecksum*/) +{ + DebugFail(); +} + +void TWebDAVFileSystem::ConfirmOverwrite( + const UnicodeString & ASourceFullFileName, UnicodeString & ATargetFileName, + TFileOperationProgressType * OperationProgress, + const TOverwriteFileParams * FileParams, const TCopyParamType * CopyParam, + intptr_t Params, + OUT TOverwriteMode & OverwriteMode, + OUT uintptr_t & Answer) +{ + // all = "yes to newer" + int Answers = qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll | qaAll; + TQueryButtonAlias Aliases[3]; + Aliases[0].Button = qaAll; + Aliases[0].Alias = LoadStr(YES_TO_NEWER_BUTTON); + Aliases[0].GroupWith = qaYes; + Aliases[0].GrouppedShiftState = ssCtrl; + Aliases[1].Button = qaYesToAll; + Aliases[1].GroupWith = qaYes; + Aliases[1].GrouppedShiftState = ssShift; + Aliases[2].Button = qaNoToAll; + Aliases[2].GroupWith = qaNo; + Aliases[2].GrouppedShiftState = ssShift; + TQueryParams QueryParams(qpNeverAskAgainCheck); + QueryParams.Aliases = Aliases; + QueryParams.AliasesCount = _countof(Aliases); + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + Answer = + FTerminal->ConfirmFileOverwrite( + ASourceFullFileName, ATargetFileName, FileParams, Answers, &QueryParams, + (OperationProgress->Side == osLocal) ? osRemote : osLocal, + CopyParam, Params, OperationProgress); + } + + switch (Answer) + { + case qaYes: + // noop + break; + + case qaNo: + ThrowSkipFileNull(); + + default: + DebugFail(); + case qaCancel: + if (!OperationProgress->Cancel) + { + OperationProgress->Cancel = csCancel; + } + Abort(); + break; + } +} + +void TWebDAVFileSystem::CustomCommandOnFile(const UnicodeString & /*AFileName*/, + const TRemoteFile * /*AFile*/, const UnicodeString & /*Command*/, intptr_t /*Params*/, TCaptureOutputEvent /*OutputEvent*/) +{ + DebugFail(); +} + +void TWebDAVFileSystem::AnyCommand(const UnicodeString & /*Command*/, + TCaptureOutputEvent /*OutputEvent*/) +{ + DebugFail(); + ThrowNotImplemented(1008); +} + +TStrings * TWebDAVFileSystem::GetFixedPaths() +{ + return nullptr; +} + +void TWebDAVFileSystem::NeonQuotaResult( + void * UserData, const ne_uri * /*Uri*/, const ne_prop_result_set * Results) +{ + TSpaceAvailable & SpaceAvailable = *static_cast(UserData); + + const char * Value = GetProp(Results, PROP_QUOTA_AVAILABLE); + if (Value != nullptr) + { + SpaceAvailable.UnusedBytesAvailableToUser = StrToInt64(StrFromNeon(Value)); + + const char * Value2 = GetProp(Results, PROP_QUOTA_USED); + if (Value2 != nullptr) + { + SpaceAvailable.BytesAvailableToUser = + StrToInt64(StrFromNeon(Value2)) + SpaceAvailable.UnusedBytesAvailableToUser; + } + } +} + +void TWebDAVFileSystem::SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable) +{ + // RFC4331: http://tools.ietf.org/html/rfc4331 + + // This is known to be supported by: + + // OpenDrive: for a root drive only (and contrary to the spec, it sends the properties + // unconditionally, even when not explicitly requested) + // Server: Apache/2.2.17 (Fedora) + // X-Powered-By: PHP/5.5.7 + // X-DAV-Powered-By: OpenDrive + // WWW-Authenticate: Basic realm="PHP WebDAV" + + // IT Hit WebDAV Server: + // Server: Microsoft-HTTPAPI/1.0 + // X-Engine: IT Hit WebDAV Server .Net v3.8.1877.0 (Evaluation License) + + // Yandex disk: + // WWW-Authenticate: Basic realm="Yandex.Disk" + // Server: MochiWeb/1.0 + + UnicodeString Path = DirectoryPath(APath); + + ne_propname QuotaProps[3]; + ::memset(QuotaProps, 0, sizeof(QuotaProps)); + QuotaProps[0].nspace = DAV_PROP_NAMESPACE; + QuotaProps[0].name = PROP_QUOTA_AVAILABLE; + QuotaProps[1].nspace = DAV_PROP_NAMESPACE; + QuotaProps[1].name = PROP_QUOTA_USED; + QuotaProps[2].nspace = nullptr; + QuotaProps[2].name = nullptr; + + TOperationVisualizer Visualizer(FTerminal->GetUseBusyCursor()); + + CheckStatus( + ne_simple_propfind(FNeonSession, PathToNeon(Path), NE_DEPTH_ZERO, QuotaProps, + NeonQuotaResult, &ASpaceAvailable)); +} + +void TWebDAVFileSystem::CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & ATargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + DebugAssert((AFilesToCopy != nullptr) && (OperationProgress != nullptr)); + + Params &= ~cpAppend; + UnicodeString FileName, FileNameOnly; + UnicodeString TargetDir = GetAbsolutePath(ATargetDir, false); + UnicodeString FullTargetDir = core::UnixIncludeTrailingBackslash(TargetDir); + intptr_t Index = 0; + while ((Index < AFilesToCopy->GetCount()) && !OperationProgress->Cancel) + { + TRemoteFile * File = NB_STATIC_DOWNCAST(TRemoteFile, AFilesToCopy->GetObj(Index)); + bool Success = false; + FileName = AFilesToCopy->GetString(Index); + UnicodeString RealFileName = File ? File->GetFileName() : FileName; + FileNameOnly = base::ExtractFileName(RealFileName, false); + + try__finally + { + SCOPE_EXIT + { + OperationProgress->Finish(RealFileName, Success, OnceDoneOperation); + }; + try + { + if (FTerminal->GetSessionData()->GetCacheDirectories()) + { + FTerminal->DirectoryModified(TargetDir, false); + + if (::DirectoryExists(ApiPath(::ExtractFilePath(FileName)))) + { + FTerminal->DirectoryModified(FullTargetDir + FileNameOnly, true); + } + } + SourceRobust(FileName, File, FullTargetDir, CopyParam, Params, OperationProgress, + tfFirstLevel); + Success = true; + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + __finally + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TWebDAVFileSystem::SourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TSFTPFileSystem + + TUploadSessionAction Action(FTerminal->GetActionLog()); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + + do + { + bool ChildError = false; + try + { + Source(AFileName, AFile, TargetDir, CopyParam, Params, OperationProgress, + Flags, Action, ChildError); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + if (!ChildError) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + // prevent overwrite confirmations + // (should not be set for directories!) + Params |= cpNoConfirmation; + } + } + while (RobustLoop.Retry()); +} + +void TWebDAVFileSystem::Source(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action, bool & ChildError) +{ + UnicodeString RealFileName = AFile ? AFile->GetFileName() : AFileName; + Action.SetFileName(::ExpandUNCFileName(RealFileName)); + + OperationProgress->SetFile(RealFileName, false); + + if (!FTerminal->AllowLocalFileTransfer(RealFileName, CopyParam, OperationProgress)) + { + ThrowSkipFileNull(); + } + + HANDLE File; + int64_t MTime; + int64_t Size; + uintptr_t LocalFileAttrs = 0; + + FTerminal->TerminalOpenLocalFile(RealFileName, GENERIC_READ, &File, + &LocalFileAttrs, nullptr, nullptr, &MTime, &Size); + + bool Dir = FLAGSET(LocalFileAttrs, faDirectory); + + int FD = -1; + try__finally + { + SCOPE_EXIT + { + if (FD >= 0) + { + // _close calls CloseHandle internally (even doc states, we should not call CloseHandle), + // but it crashes code guard + _close(FD); + } + else if (File != nullptr) + { + CloseHandle(File); + } + }; + OperationProgress->SetFileInProgress(); + + if (Dir) + { + Action.Cancel(); + DirectorySource(::IncludeTrailingBackslash(RealFileName), TargetDir, + LocalFileAttrs, CopyParam, Params, OperationProgress, Flags); + } + else + { + UnicodeString DestFileName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(RealFileName, false), osLocal, + FLAGSET(Flags, tfFirstLevel)); + + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", RealFileName.c_str())); + + OperationProgress->SetLocalSize(Size); + + // Suppose same data size to transfer as to read + // (not true with ASCII transfer) + OperationProgress->SetTransferSize(OperationProgress->LocalSize); + OperationProgress->TransferingFile = false; + + UnicodeString DestFullName = TargetDir + DestFileName; + + std::unique_ptr RemoteFile; + try + { + TValueRestorer IgnoreAuthenticationFailureRestorer(FIgnoreAuthenticationFailure); + FIgnoreAuthenticationFailure = iafWaiting; + + // this should not throw + TRemoteFile * RemoteFilePtr = nullptr; + CustomReadFileInternal(DestFullName, RemoteFilePtr, nullptr); + RemoteFile.reset(RemoteFilePtr); + } + catch (...) + { + if (!FTerminal->GetActive()) + { + throw; + } + } + + TDateTime Modification = ::UnixToDateTime(MTime, FTerminal->GetSessionData()->GetDSTMode()); + + if (RemoteFile.get() != nullptr) + { + TOverwriteFileParams FileParams; + + FileParams.SourceSize = Size; + FileParams.SourceTimestamp = Modification; + FileParams.DestSize = RemoteFile->GetSize(); + FileParams.DestTimestamp = RemoteFile->GetModification(); + RemoteFile.reset(); + + TOverwriteMode OverwriteMode = omOverwrite; + uintptr_t Answer = 0; + ConfirmOverwrite(AFileName, DestFileName, OperationProgress, + &FileParams, CopyParam, Params, + OverwriteMode, Answer); + } + + DestFullName = TargetDir + DestFileName; + // only now, we know the final destination + // (not really true as we do not support changing file name on overwrite dialog) + Action.Destination(DestFullName); + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(TRANSFER_ERROR, RealFileName.c_str()), "", + [&]() + { + SetFilePointer(File, 0, nullptr, FILE_BEGIN); + + FD = _open_osfhandle((intptr_t)File, O_BINARY); + if (FD < 0) + { + ThrowSkipFileNull(); + } + + TAutoFlag UploadingFlag(FUploading); + + ClearNeonError(); + CheckStatus(ne_put(FNeonSession, PathToNeon(DestFullName), FD)); + }); + + if (CopyParam->GetPreserveTime()) + { + FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]", + StandardTimestamp(Modification).c_str())); + + TTouchSessionAction TouchAction(FTerminal->GetActionLog(), DestFullName, Modification); + try + { + TDateTime ModificationUTC = ConvertTimestampToUTC(Modification); +#if 0 + TFormatSettings FormatSettings = GetEngFormatSettings(); + UnicodeString LastModified = + FormatDateTime(L"ddd, d mmm yyyy hh:nn:ss 'GMT'", ModificationUTC, FormatSettings); +#endif + uint16_t Y, M, D, H, NN, S, MS; + TDateTime DateTime = ModificationUTC; + DateTime.DecodeDate(Y, M, D); + DateTime.DecodeTime(H, NN, S, MS); + UnicodeString LastModified = FORMAT(L"%04d, %d %02d %04d %02d:%02d%02d 'GMT'", D, D, M, Y, H, NN, D); + + UTF8String NeonLastModified(LastModified); + // second element is "NULL-terminating" + ne_proppatch_operation Operations[2]; + ::memset(Operations, 0, sizeof(Operations)); + ne_propname LastModifiedProp; + LastModifiedProp.nspace = DAV_PROP_NAMESPACE; + LastModifiedProp.name = PROP_LAST_MODIFIED; + Operations[0].name = &LastModifiedProp; + Operations[0].type = ne_propset; + Operations[0].value = NeonLastModified.c_str(); + int Status = ne_proppatch(FNeonSession, PathToNeon(DestFullName), Operations); + if (Status == NE_ERROR) + { + FTerminal->LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s", + GetNeonError().c_str())); + // Ignore errors as major WebDAV servers (like IIS), do not support + // changing getlastmodified. + // The only server we found that supports this is TradeMicro SafeSync. + // But it announces itself as "Server: Apache", + // so it's not reliable to autodetect the support. + TouchAction.Cancel(); + } + else + { + CheckStatus(Status); + } + } + catch (Exception & E) + { + TouchAction.Rollback(&E); + ChildError = true; + throw; + } + } + + FTerminal->LogFileDone(OperationProgress); + } + } + __finally + { + if (FD >= 0) + { + // _close calls CloseHandle internally (even doc states, we should not call CloseHandle), + // but it crashes code guard + _close(FD); + } + else if (File != nullptr) + { + CloseHandle(File); + } + }; + + // TODO : Delete also read-only files. + if (FLAGSET(Params, cpDelete)) + { + if (!Dir) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, RealFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(RealFileName)); + }); + } + } + else if (CopyParam->GetClearArchive() && FLAGSET(LocalFileAttrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, RealFileName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(RealFileName), LocalFileAttrs & ~faArchive) == 0); + }); + } +} + +void TWebDAVFileSystem::DirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, uintptr_t Attrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + UnicodeString DestDirectoryName = + FTerminal->ChangeFileName( + CopyParam, base::ExtractFileName(::ExcludeTrailingBackslash(DirectoryName), false), + osLocal, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = core::UnixIncludeTrailingBackslash(TargetDir + DestDirectoryName); + // create DestFullName if it does not exist + if (!FTerminal->FileExists(DestFullName)) + { + TRemoteProperties Properties; + if (CopyParam->GetPreserveRights()) + { + Properties.Valid = TValidProperties() << vpRights; + Properties.Rights = CopyParam->RemoteFileRights(Attrs); + } + FTerminal->RemoteCreateDirectory(DestFullName, &Properties); + } + + OperationProgress->SetFile(DirectoryName); + + DWORD FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive; + TSearchRecChecked SearchRec; + bool FindOK = false; + + UnicodeString FindPath = ApiPath(DirectoryName) + L"*.*"; + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = + (FindFirstChecked(FindPath.c_str(), FindAttrs, SearchRec) == 0); + }); + + try__finally + { + SCOPE_EXIT + { + ::FindClose(SearchRec); + }; + while (FindOK && !OperationProgress->Cancel) + { + UnicodeString FileName = DirectoryName + SearchRec.Name; + try + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + { + SourceRobust(FileName, nullptr, DestFullName, CopyParam, Params, OperationProgress, + Flags & ~(tfFirstLevel)); + } + } + catch (ESkipFile & E) + { + // If ESkipFile occurs, just log it and continue with next file + TSuspendFileOperationProgress Suspend(OperationProgress); + // here a message to user was displayed, which was not appropriate + // when user refused to overwrite the file in subdirectory. + // hopefully it won't be missing in other situations. + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(LIST_DIR_ERROR, DirectoryName.c_str()), "", + [&]() + { + FindOK = FindNextChecked(SearchRec) == 0; + }); + } + } + __finally + { + ::FindClose(SearchRec); + }; + // TODO : Delete also read-only directories. + // TODO : Show error message on failure. + if (!OperationProgress->Cancel) + { + if (FLAGSET(Params, cpDelete)) + { + FTerminal->RemoveLocalDirectory(ApiPath(DirectoryName)); + } + else if (CopyParam->GetClearArchive() && FLAGSET(Attrs, faArchive)) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DirectoryName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(DirectoryName), Attrs & ~faArchive) == 0); + }); + } + } +} + +void TWebDAVFileSystem::CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation) +{ + Params &= ~cpAppend; + UnicodeString FullTargetDir = ::IncludeTrailingBackslash(TargetDir); + + intptr_t Index = 0; + while (Index < AFilesToCopy->GetCount() && !OperationProgress->Cancel) + { + UnicodeString FileName = AFilesToCopy->GetString(Index); + const TRemoteFile * File = NB_STATIC_DOWNCAST_CONST(TRemoteFile, AFilesToCopy->GetObj(Index)); + bool Success = false; + try__finally + { + SCOPE_EXIT + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + UnicodeString AbsoluteFilePath = GetAbsolutePath(FileName, false); + UnicodeString TargetDirectory = CreateTargetDirectory(File->GetFileName(), FullTargetDir, CopyParam); + try + { + SinkRobust(AbsoluteFilePath, File, TargetDirectory, CopyParam, Params, + OperationProgress, tfFirstLevel); + Success = true; + } + catch (ESkipFile & E) + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + } + __finally + { + OperationProgress->Finish(FileName, Success, OnceDoneOperation); + }; + ++Index; + } +} + +void TWebDAVFileSystem::SinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags) +{ + // the same in TSFTPFileSystem + + TDownloadSessionAction Action(FTerminal->GetActionLog()); + TRobustOperationLoop RobustLoop(FTerminal, OperationProgress); + + do + { + bool ChildError = false; + try + { + Sink(AFileName, AFile, TargetDir, CopyParam, Params, OperationProgress, + Flags, Action, ChildError); + } + catch (Exception & E) + { + if (!RobustLoop.TryReopen(E)) + { + if (!ChildError) + { + FTerminal->RollbackAction(Action, OperationProgress, &E); + } + throw; + } + } + + if (RobustLoop.ShouldRetry()) + { + OperationProgress->RollbackTransfer(); + Action.Restart(); + DebugAssert(AFile != nullptr); + if (!AFile->GetIsDirectory()) + { + // prevent overwrite confirmations + Params |= cpNoConfirmation; + } + } + } + while (RobustLoop.Retry()); +} + +void TWebDAVFileSystem::NeonCreateRequest( + ne_request * Request, void * UserData, const char * /*Method*/, const char * /*Uri*/) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + ne_set_request_private(Request, SESSION_FS_KEY, FileSystem); + ne_add_response_body_reader(Request, NeonBodyAccepter, NeonBodyReader, Request); + FileSystem->FNtlmAuthenticationFailed = false; +} + +void TWebDAVFileSystem::NeonPreSend( + ne_request * Request, void * UserData, ne_buffer * Header) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + + FileSystem->FAuthorizationProtocol = L""; + UnicodeString HeaderBuf(StrFromNeon(UTF8String(Header->data, Header->used))); + const UnicodeString AuthorizationHeaderName(L"Authorization:"); + intptr_t P = HeaderBuf.Pos(AuthorizationHeaderName); + if (P > 0) + { + P += AuthorizationHeaderName.Length(); + intptr_t P2 = PosEx(L"\n", HeaderBuf, P); + if (DebugAlwaysTrue(P2 > 0)) + { + UnicodeString AuthorizationHeader = HeaderBuf.SubString(P, P2 - P).Trim(); + FileSystem->FAuthorizationProtocol = CutToChar(AuthorizationHeader, L' ', false); + } + } + + if (FileSystem->FDownloading) + { + // Needed by IIS server to make it download source code, not code output, + // and mainly to even allow downloading file with unregistered extensions. + // Without it files like .001 return 404 (Not found) HTTP code. + // http://msdn.microsoft.com/en-us/library/cc250098.aspx + // http://msdn.microsoft.com/en-us/library/cc250216.aspx + // http://lists.manyfish.co.uk/pipermail/neon/2012-April/001452.html + // It's also supported by Oracle server: + // https://docs.oracle.com/cd/E19146-01/821-1828/gczya/index.html + // We do not know yet of any server that fails when the header is used, + // so it's added unconditionally. + ne_buffer_zappend(Header, "Translate: f\r\n"); + } +#ifndef __linux__ + if (FileSystem->FTerminal->GetLog()->GetLogging()) + { + const char * Buffer; + size_t Size; + if (ne_get_request_body_buffer(Request, &Buffer, &Size)) + { + // all neon request types that use ne_add_request_header + // use XML content-type, so it's text-based + DebugAssert(ContainsStr(AnsiString(HeaderBuf), AnsiString("Content-Type: " NE_XML_MEDIA_TYPE))); + FileSystem->FTerminal->GetLog()->Add(llInput, UnicodeString(UTF8String(Buffer, Size))); + } + } + + if (FileSystem->FUploading) + { + ne_set_request_body_provider_pre(Request, + FileSystem->NeonUploadBodyProvider, FileSystem); + } +#endif + FileSystem->FResponse = L""; +} + +int TWebDAVFileSystem::NeonPostSend(ne_request * /*Req*/, void * UserData, + const ne_status * /*Status*/) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + if (!FileSystem->FResponse.IsEmpty()) + { + FileSystem->FTerminal->GetLog()->Add(llOutput, FileSystem->FResponse); + } + return NE_OK; +} + +bool TWebDAVFileSystem::IsNtlmAuthentication() const +{ + return + SameText(FAuthorizationProtocol, L"NTLM") || + SameText(FAuthorizationProtocol, L"Negotiate"); +} + +void TWebDAVFileSystem::HttpAuthenticationFailed() +{ + // NTLM/GSSAPI failed + if (IsNtlmAuthentication()) + { + if (FNtlmAuthenticationFailed) + { + // Next time do not try Negotiate (NTLM/GSSAPI), + // otherwise we end up in an endless loop. + // If the server returns all other challenges in the response, removing the Negotiate + // protocol will itself ensure that other protocols are tried (we haven't seen this behaviour). + // IIS will return only Negotiate response if the request was Negotiate, so there's no fallback. + // We have to retry with a fresh request. That's what FAuthenticationRetry does. + FTerminal->LogEvent(FORMAT(L"%s challenge failed, will try different challenge", FAuthorizationProtocol.c_str())); +#ifndef __linux__ + ne_remove_server_auth(FNeonSession); +#endif + NeonAddAuthentiation(false); + FAuthenticationRetry = true; + } + else + { + // The first 401 is expected, the server is using it to send WWW-Authenticate header with data. + FNtlmAuthenticationFailed = true; + } + } +} + +void TWebDAVFileSystem::NeonPostHeaders(ne_request * /*Req*/, void * UserData, const ne_status * Status) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + if (Status->code == HttpUnauthorized) + { + FileSystem->HttpAuthenticationFailed(); + } +} + +ssize_t TWebDAVFileSystem::NeonUploadBodyProvider(void * UserData, char * /*Buffer*/, size_t /*BufLen*/) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + ssize_t Result; + if (FileSystem->CancelTransfer()) + { + Result = -1; + } + else + { + Result = 1; + } + return Result; +} + +static void AddHeaderValueToList(UnicodeString & List, ne_request * Request, const char * Name) +{ + const char * Value = ne_get_response_header(Request, Name); + if (Value != nullptr) + { + AddToList(List, StrFromNeon(Value), L"; "); + } +} + +int TWebDAVFileSystem::NeonBodyAccepter(void * UserData, ne_request * Request, const ne_status * Status) +{ + DebugAssert(UserData == Request); + TWebDAVFileSystem * FileSystem = + static_cast(ne_get_request_private(Request, SESSION_FS_KEY)); + + bool AuthenticationFailureCode = (Status->code == HttpUnauthorized); + bool PasswordAuthenticationFailed = AuthenticationFailureCode && FileSystem->FAuthenticationRequested; + bool AuthenticationFailed = PasswordAuthenticationFailed || (AuthenticationFailureCode && FileSystem->IsNtlmAuthentication()); + bool AuthenticationNeeded = AuthenticationFailureCode && !AuthenticationFailed; + + if (FileSystem->FInitialHandshake) + { + UnicodeString Line; + if (AuthenticationNeeded) + { + Line = LoadStr(STATUS_AUTHENTICATE); + } + else if (AuthenticationFailed) + { + Line = LoadStr(FTP_ACCESS_DENIED); + } + else if (Status->klass == 2) + { + Line = LoadStr(STATUS_AUTHENTICATED); + } + + if (!Line.IsEmpty()) + { + FileSystem->FTerminal->Information(Line, true); + } + + UnicodeString RemoteSystem; + // Used by IT Hit WebDAV Server: + // Server: Microsoft-HTTPAPI/1.0 + // X-Engine: IT Hit WebDAV Server .Net v3.8.1877.0 (Evaluation License) + AddHeaderValueToList(RemoteSystem, Request, "X-Engine"); + // Used by OpenDrive: + // Server: Apache/2.2.17 (Fedora) + // X-Powered-By: PHP/5.5.7 + // X-DAV-Powered-By: OpenDrive + AddHeaderValueToList(RemoteSystem, Request, "X-DAV-Powered-By"); + // Used by IIS: + // Server: Microsoft-IIS/8.5 + AddHeaderValueToList(RemoteSystem, Request, "Server"); + // Not really useful. + // Can be e.g. "PleskLin" + AddHeaderValueToList(RemoteSystem, Request, "X-Powered-By"); + FileSystem->FFileSystemInfo.RemoteSystem = RemoteSystem; + } + + // When we explicitly fail authentication of request + // with FIgnoreAuthenticationFailure flag (after it failed with password), + // neon resets its internal password store and tries the next request + // without calling our authentication hook first + // (note AuthenticationFailed vs. AuthenticationNeeded) + // what likely fails, but we do not want to reset out password + // (as it was not even tried yet for this request). + if (PasswordAuthenticationFailed) + { + if (FileSystem->FIgnoreAuthenticationFailure == iafNo) + { + FileSystem->FPassword = RawByteString(); + } + else + { + FileSystem->FIgnoreAuthenticationFailure = iafPasswordFailed; + } + } + + return ne_accept_2xx(UserData, Request, Status); +} + +bool TWebDAVFileSystem::CancelTransfer() +{ + bool Result = false; + if ((FUploading || FDownloading) && + (FTerminal->GetOperationProgress() != nullptr) && + (FTerminal->GetOperationProgress()->Cancel != csContinue)) + { + FCancelled = true; + Result = true; + } + return Result; +} + +#ifdef __linux__ +static int media_type_is_xml(const ne_content_type *ctype) +{ + size_t stlen; + return + (ne_strcasecmp(ctype->type, "text") == 0 + && ne_strcasecmp(ctype->subtype, "xml") == 0) + || (ne_strcasecmp(ctype->type, "application") == 0 + && ne_strcasecmp(ctype->subtype, "xml") == 0) + || ((stlen = strlen(ctype->subtype)) > 4 + && ne_strcasecmp(ctype->subtype + stlen - 4, "+xml") == 0); +} +#endif + +int TWebDAVFileSystem::NeonBodyReader(void * UserData, const char * Buf, size_t Len) +{ + ne_request * Request = static_cast(UserData); + TWebDAVFileSystem * FileSystem = + static_cast(ne_get_request_private(Request, SESSION_FS_KEY)); + + if (FileSystem->FTerminal->GetLog()->GetLogging()) + { + ne_content_type ContentType; + if (ne_get_content_type(Request, &ContentType) == 0) + { + // The main point of the content-type check was to exclude + // GET responses (with file contents). + // But this won't work when downloading text files that have text + // content type on their own, hence the additional not-downloading test. + if (!FileSystem->FDownloading && + ((ne_strcasecmp(ContentType.type, "text") == 0) || + media_type_is_xml(&ContentType))) + { + UnicodeString Content = UnicodeString(UTF8String(Buf, Len)).Trim(); + FileSystem->FResponse += Content; + } + ne_free(ContentType.value); + } + } + + int Result = FileSystem->CancelTransfer() ? 1 : 0; + return Result; +} + +void TWebDAVFileSystem::Sink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t AParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action, bool & ChildError) +{ + UnicodeString FileNameOnly = base::UnixExtractFileName(AFileName); + + Action.SetFileName(AFileName); + + DebugAssert(AFile); + TFileMasks::TParams MaskParams; + MaskParams.Size = AFile->GetSize(); + MaskParams.Modification = AFile->GetModification(); + + UnicodeString BaseFileName = FTerminal->GetBaseFileName(AFileName); + if (!CopyParam->AllowTransfer(BaseFileName, osRemote, AFile->GetIsDirectory(), MaskParams)) + { + FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", AFileName.c_str())); + ThrowSkipFileNull(); + } + + if (CopyParam->SkipTransfer(AFileName, AFile->GetIsDirectory())) + { + OperationProgress->AddSkippedFileSize(AFile->GetSize()); + ThrowSkipFileNull(); + } + + FTerminal->LogFileDetails(AFileName, TDateTime(), AFile->GetSize()); + + OperationProgress->SetFile(FileNameOnly); + + UnicodeString DestFileName = + CopyParam->ChangeFileName( + base::UnixExtractFileName(AFile->GetFileName()), osRemote, FLAGSET(Flags, tfFirstLevel)); + UnicodeString DestFullName = TargetDir + DestFileName; + + if (AFile->GetIsDirectory()) + { + Action.Cancel(); + if (DebugAlwaysTrue(FTerminal->CanRecurseToDirectory(AFile))) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_DIRECTORY_ERROR, DestFullName.c_str()), "", + [&]() + { + DWORD LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if (FLAGCLEAR(LocalFileAttrs, faDirectory)) + { + ThrowExtException(); + } + }); + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CREATE_DIR_ERROR, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::ForceDirectories(ApiPath(DestFullName))); + }); + + TSinkFileParams SinkFileParams; + SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName); + SinkFileParams.CopyParam = CopyParam; + SinkFileParams.Params = AParams; + SinkFileParams.OperationProgress = OperationProgress; + SinkFileParams.Skipped = false; + SinkFileParams.Flags = Flags & ~tfFirstLevel; + + FTerminal->ProcessDirectory(AFileName, MAKE_CALLBACK(TWebDAVFileSystem::SinkFile, this), &SinkFileParams); + + // Do not delete directory if some of its files were skip. + // Throw "skip file" for the directory to avoid attempt to deletion + // of any parent directory + if (FLAGSET(AParams, cpDelete) && SinkFileParams.Skipped) + { + ThrowSkipFileNull(); + } + } + else + { + // file is symlink to directory, currently do nothing, but it should be + // reported to user + } + } + else + { + FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", AFileName.c_str())); + if (::FileExists(ApiPath(DestFullName))) + { + int64_t Size = 0; + int64_t MTime = 0; + FTerminal->TerminalOpenLocalFile(DestFullName, GENERIC_READ, nullptr, + nullptr, nullptr, &MTime, nullptr, &Size); + TOverwriteFileParams FileParams; + + FileParams.SourceSize = AFile->GetSize(); + FileParams.SourceTimestamp = AFile->GetModification(); + FileParams.DestSize = Size; + FileParams.DestTimestamp = ::UnixToDateTime(MTime, + FTerminal->GetSessionData()->GetDSTMode()); + + TOverwriteMode OverwriteMode = omOverwrite; + uintptr_t Answer = 0; + ConfirmOverwrite(AFileName, DestFileName, OperationProgress, + &FileParams, CopyParam, AParams, + OverwriteMode, Answer); + } + + // Suppose same data size to transfer as to write + OperationProgress->SetTransferSize(AFile->GetSize()); + OperationProgress->SetLocalSize(OperationProgress->TransferSize); + + DWORD LocalFileAttrs = INVALID_FILE_ATTRIBUTES; + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(NOT_FILE_ERROR, DestFullName.c_str()), "", + [&]() + { + LocalFileAttrs = FTerminal->GetLocalFileAttributes(ApiPath(DestFullName)); + if ((LocalFileAttrs != INVALID_FILE_ATTRIBUTES) && FLAGSET(LocalFileAttrs, faDirectory)) + { + ThrowExtException(); + } + }); + + OperationProgress->TransferingFile = false; // not set with WebDAV protocol + + UnicodeString FilePath = core::UnixExtractFilePath(AFileName); + if (FilePath.IsEmpty()) + { + FilePath = L"/"; + } + + Action.Destination(::ExpandUNCFileName(DestFullName)); + + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(TRANSFER_ERROR, AFileName.c_str()), "", + [&]() + { + HANDLE LocalFileHandle = FTerminal->TerminalCreateLocalFile(DestFullName, + GENERIC_WRITE, 0, CREATE_ALWAYS, 0); +// if (!FTerminal->CreateLocalFile(DestFullName, OperationProgress, +// &LocalHandle, FLAGSET(Params, cpNoConfirmation))) + if (LocalFileHandle == INVALID_HANDLE_VALUE) + { + ThrowSkipFileNull(); + } + + bool DeleteLocalFile = true; + + int FD = -1; + try__finally + { + SCOPE_EXIT + { + if (FD >= 0) + { + // _close calls CloseHandle internally (even doc states, we should not call CloseHandle), + // but it crashes code guard + _close(FD); + } + else + { + CloseHandle(LocalFileHandle); + } + + if (DeleteLocalFile) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(DestFullName)); + }); + } + }; + + FD = _open_osfhandle((intptr_t)LocalFileHandle, O_BINARY); + if (FD < 0) + { + ThrowSkipFileNull(); + } + + TAutoFlag DownloadingFlag(FDownloading); + + ClearNeonError(); + CheckStatus(ne_get(FNeonSession, PathToNeon(AFileName), FD)); + DeleteLocalFile = false; +#ifndef __linux__ + if (CopyParam->GetPreserveTime()) + { + TDateTime Modification = AFile->GetModification(); + FILETIME WrTime = DateTimeToFileTime(Modification, FTerminal->GetSessionData()->GetDSTMode()); + FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]", + StandardTimestamp(Modification).c_str())); + SetFileTime(LocalFileHandle, nullptr, nullptr, &WrTime); + } +#endif + } + __finally + { + if (FD >= 0) + { + // _close calls CloseHandle internally (even doc states, we should not call CloseHandle), + // but it crashes code guard + _close(FD); + } + else + { + CloseHandle(LocalFileHandle); + } + + if (DeleteLocalFile) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CORE_DELETE_LOCAL_FILE_ERROR, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(::RemoveFile(DestFullName)); + }); + } + }; + }); + + if (LocalFileAttrs == INVALID_FILE_ATTRIBUTES) + { + LocalFileAttrs = faArchive; + } + DWORD NewAttrs = CopyParam->LocalFileAttrs(*AFile->GetRights()); + if ((NewAttrs & LocalFileAttrs) != NewAttrs) + { + FileOperationLoopCustom(FTerminal, OperationProgress, True, FMTLOAD(CANT_SET_ATTRS, DestFullName.c_str()), "", + [&]() + { + THROWOSIFFALSE(FTerminal->SetLocalFileAttributes(ApiPath(DestFullName), LocalFileAttrs | NewAttrs) == 0); + }); + } + + FTerminal->LogFileDone(OperationProgress); + } + + if (FLAGSET(AParams, cpDelete)) + { + ChildError = true; + // If file is directory, do not delete it recursively, because it should be + // empty already. If not, it should not be deleted (some files were + // skipped or some new files were copied to it, while we were downloading) + intptr_t Params = dfNoRecursive; + FTerminal->RemoteDeleteFile(AFileName, AFile, &Params); + ChildError = false; + } +} + +void TWebDAVFileSystem::SinkFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, void * Param) +{ + TSinkFileParams * Params = NB_STATIC_DOWNCAST(TSinkFileParams, Param); + DebugAssert(Params->OperationProgress); + try + { + SinkRobust(AFileName, AFile, Params->TargetDir, Params->CopyParam, + Params->Params, Params->OperationProgress, Params->Flags); + } + catch (ESkipFile & E) + { + TFileOperationProgressType * OperationProgress = Params->OperationProgress; + + Params->Skipped = true; + + { + TSuspendFileOperationProgress Suspend(OperationProgress); + if (!FTerminal->HandleException(&E)) + { + throw; + } + } + + if (OperationProgress->Cancel) + { + Abort(); + } + } +} + +bool TWebDAVFileSystem::VerifyCertificate(const TWebDAVCertificateData & Data) +{ + FSessionInfo.CertificateFingerprint = Data.Fingerprint; + + bool Result; + if (FTerminal->GetSessionData()->GetFingerprintScan()) + { + Result = false; + } + else + { + FTerminal->LogEvent( + FORMAT(L"Verifying certificate for \"%s\" with fingerprint %s and %2.2X failures", + Data.Subject.c_str(), Data.Fingerprint.c_str(), Data.Failures)); + + int Failures = Data.Failures; + + UnicodeString SiteKey = TSessionData::FormatSiteKey(FHostName, FPortNumber); + Result = + FTerminal->VerifyCertificate(HttpsCertificateStorageKey, SiteKey, Data.Fingerprint, Data.Subject, Failures); + + if (!Result) + { + if (NeonWindowsValidateCertificate(Failures, Data.AsciiCert)) + { + FTerminal->LogEvent(L"Certificate verified against Windows certificate store"); + // There can be also other flags, not just the NE_SSL_UNTRUSTED. + Result = (Failures == 0); + } + } + + UnicodeString Summary; + if (Failures == 0) + { + Summary = LoadStr(CERT_OK); + } + else + { + Summary = NeonCertificateFailuresErrorStr(Failures, FHostName); + } + + UnicodeString ValidityTimeFormat = L"ddddd tt"; + FSessionInfo.Certificate = + FMTLOAD(CERT_TEXT, + UnicodeString(Data.Issuer + L"\n").c_str(), + UnicodeString(Data.Subject + L"\n").c_str(), + FormatDateTime(ValidityTimeFormat, Data.ValidFrom).c_str(), + FormatDateTime(ValidityTimeFormat, Data.ValidUntil).c_str(), + Data.Fingerprint.c_str(), + Summary.c_str()); + + if (!Result) + { + TClipboardHandler ClipboardHandler; + ClipboardHandler.Text = Data.Fingerprint; + + TQueryButtonAlias Aliases[1]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON); + Aliases[0].OnClick = MAKE_CALLBACK(TClipboardHandler::Copy, &ClipboardHandler); + + TQueryParams Params; + Params.HelpKeyword = HELP_VERIFY_CERTIFICATE; + Params.NoBatchAnswers = qaYes | qaRetry; + Params.Aliases = Aliases; + Params.AliasesCount = _countof(Aliases); + uintptr_t Answer = FTerminal->QueryUser( + FMTLOAD(VERIFY_CERT_PROMPT3, FSessionInfo.Certificate.c_str()), + nullptr, qaYes | qaNo | qaCancel | qaRetry, &Params, qtWarning); + switch (Answer) + { + case qaYes: + FTerminal->CacheCertificate(HttpsCertificateStorageKey, SiteKey, Data.Fingerprint, Failures); + Result = true; + break; + + case qaNo: + Result = true; + break; + + default: + DebugFail(); + case qaCancel: +// FTerminal->GetConfiguration()->GetUsage()->Inc(L"HostNotVerified"); + Result = false; + break; + } + + if (Result) + { + FTerminal->GetConfiguration()->RememberLastFingerprint( + FTerminal->GetSessionData()->GetSiteKey(), TlsFingerprintType, FSessionInfo.CertificateFingerprint); + } + } + + if (Result) + { + CollectTLSSessionInfo(); + } + } + + return Result; +} + +void TWebDAVFileSystem::CollectTLSSessionInfo() +{ +#ifndef __linux__ + // See also TFTPFileSystem::Open(). + // Have to cache the value as the connection (the neon HTTP session, not "our" session) + // can be closed as the time we need it in CollectUsage(). + FTlsVersionStr = StrFromNeon(ne_ssl_get_version(FNeonSession)); + AddToList(FSessionInfo.SecurityProtocolName, FTlsVersionStr, L", "); + + UnicodeString Cipher = StrFromNeon(ne_ssl_get_cipher(FNeonSession)); + FSessionInfo.CSCipher = Cipher; + FSessionInfo.SCCipher = Cipher; + + // see CAsyncSslSocketLayer::PrintSessionInfo() + FTerminal->LogEvent(FORMAT(L"Using %s, cipher %s", FTlsVersionStr.c_str(), Cipher.c_str())); +#endif +} + +// A neon-session callback to validate the SSL certificate when the CA +// is unknown (e.g. a self-signed cert), or there are other SSL +// certificate problems. +int TWebDAVFileSystem::NeonServerSSLCallback(void * UserData, int Failures, const ne_ssl_certificate * Certificate) +{ + TWebDAVCertificateData Data; + + char Fingerprint[NE_SSL_DIGESTLEN] = {0}; + if (ne_ssl_cert_digest(Certificate, Fingerprint) != 0) + { + strcpy(Fingerprint, ""); + } + Data.Fingerprint = StrFromNeon(Fingerprint); + Data.AsciiCert = NeonExportCertificate(Certificate); + + char * Subject = ne_ssl_readable_dname(ne_ssl_cert_subject(Certificate)); + Data.Subject = StrFromNeon(Subject); + ne_free(Subject); + char * Issuer = ne_ssl_readable_dname(ne_ssl_cert_issuer(Certificate)); + Data.Issuer = StrFromNeon(Issuer); + ne_free(Issuer); + + Data.Failures = Failures; + + time_t ValidFrom; + time_t ValidUntil; + ne_ssl_cert_validity_time(Certificate, &ValidFrom, &ValidUntil); + Data.ValidFrom = UnixToDateTime(ValidFrom, dstmWin); + Data.ValidUntil = UnixToDateTime(ValidUntil, dstmWin); + + TWebDAVFileSystem * FileSystem = static_cast(UserData); + + return FileSystem->VerifyCertificate(Data) ? NE_OK : NE_ERROR; +} + +void TWebDAVFileSystem::NeonProvideClientCert(void * UserData, ne_session * Sess, + const ne_ssl_dname * const * /*DNames*/, int /*DNCount*/) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + + FileSystem->FTerminal->LogEvent(LoadStr(NEED_CLIENT_CERTIFICATE)); +#ifndef __linux__ + X509 * Certificate; + EVP_PKEY * PrivateKey; + if (FileSystem->FTerminal->LoadTlsCertificate(Certificate, PrivateKey)) + { + ne_ssl_client_cert * NeonCertificate = ne_ssl_clicert_create(Certificate, PrivateKey); + ne_ssl_set_clicert(Sess, NeonCertificate); + ne_ssl_clicert_free(NeonCertificate); + } +#endif +} + +int TWebDAVFileSystem::NeonRequestAuth( + void * UserData, const char * Realm, int Attempt, char * UserName, char * Password) +{ + DebugUsedParam(Realm); + DebugUsedParam(Attempt); + TWebDAVFileSystem * FileSystem = static_cast(UserData); + + TTerminal * Terminal = FileSystem->FTerminal; + TSessionData * SessionData = Terminal->GetSessionData(); + + bool Result = true; + + // will ask for username only once + if (FileSystem->FUserName.IsEmpty()) + { + if (!SessionData->SessionGetUserName().IsEmpty()) + { + FileSystem->FUserName = SessionData->GetUserNameExpanded(); + } + else + { + if (!Terminal->PromptUser(SessionData, pkUserName, LoadStr(USERNAME_TITLE), L"", + LoadStr(USERNAME_PROMPT2), true, NE_ABUFSIZ, FileSystem->FUserName)) + { + // note that we never get here actually + Result = false; + } + } + } + + UnicodeString APassword; + if (Result) + { + // Some servers (Gallery2 on https://g2.pixi.me/w/webdav/) + // return authentication error (401) on PROPFIND request for + // non-existing files. + // When we already tried password before, do not try anymore. + // When we did not try password before (possible only when + // server does not require authentication for any previous request, + // such as when read access is not authenticated), try it now, + // but use special flag for the try, because when it fails + // we still want to try password for future requests (such as PUT). + + if (!FileSystem->FPassword.IsEmpty()) + { + if (FileSystem->FIgnoreAuthenticationFailure == iafPasswordFailed) + { + // Fail PROPFIND /nonexising request... + Result = false; + } + else + { + APassword = Terminal->DecryptPassword(FileSystem->FPassword); + } + } + else + { + if (!SessionData->GetPassword().IsEmpty() && !FileSystem->FStoredPasswordTried) + { + APassword = SessionData->GetPassword(); + FileSystem->FStoredPasswordTried = true; + } + else + { + // Asking for password (or using configured password) the first time, + // and asking for password. + // Note that we never get false here actually + Result = + Terminal->PromptUser( + SessionData, pkPassword, LoadStr(PASSWORD_TITLE), L"", + LoadStr(PASSWORD_PROMPT), false, NE_ABUFSIZ, APassword); + } + + if (Result) + { + // While neon remembers the password on its own, + // we need to keep a copy in case neon store gets reset by + // 401 response to PROPFIND /nonexisting on G2, see above. + // Possibly we can do this for G2 servers only. + FileSystem->FPassword = Terminal->EncryptPassword(APassword); + } + } + } + + if (Result) + { + strncpy(UserName, StrToNeon(FileSystem->FUserName), NE_ABUFSIZ); + strncpy(Password, StrToNeon(APassword), NE_ABUFSIZ); + } + + FileSystem->FAuthenticationRequested = true; + + return Result ? 0 : -1; +} + +void TWebDAVFileSystem::NeonNotifier(void * UserData, ne_session_status Status, const ne_session_status_info * StatusInfo) +{ + TWebDAVFileSystem * FileSystem = static_cast(UserData); + TFileOperationProgressType * OperationProgress = FileSystem->FTerminal->GetOperationProgress(); + + // We particularly have to filter out response to "put" request, + // handling that would reset the upload progress back to low number (response is small). + if (((FileSystem->FUploading && (Status == ne_status_sending)) || + (FileSystem->FDownloading && (Status == ne_status_recving))) && + DebugAlwaysTrue(OperationProgress != nullptr)) + { + int64_t Progress = StatusInfo->sr.progress; + int64_t Diff = Progress - OperationProgress->TransferedSize; + + if (Diff > 0) + { + OperationProgress->ThrottleToCPSLimit(static_cast(Diff)); + } + + int64_t Total = StatusInfo->sr.total; + + // Total size unknown + if (Total < 0) + { + if (Diff >= 0) + { + OperationProgress->AddTransfered(Diff); + } + else + { + // Session total has been reset. A new stream started + OperationProgress->AddTransfered(Progress); + } + } + else + { + OperationProgress->SetTransferSize(Total); + OperationProgress->AddTransfered(Diff); + } + } +} + +void TWebDAVFileSystem::NeonDebug(const UnicodeString & Message) +{ + FTerminal->LogEvent(Message); +} + +void TWebDAVFileSystem::InitSslSession(ssl_st * Ssl, ne_session * Session) +{ + TWebDAVFileSystem * FileSystem = + static_cast(ne_get_session_private(Session, SESSION_FS_KEY)); + FileSystem->InitSslSessionImpl(Ssl); +} + +void TWebDAVFileSystem::InitSslSessionImpl(ssl_st * Ssl) +{ + // See also CAsyncSslSocketLayer::InitSSLConnection + TSessionData * Data = FTerminal->GetSessionData(); + #define MASK_TLS_VERSION(VERSION, FLAG) ((Data->GetMinTlsVersion() > VERSION) || (Data->GetMaxTlsVersion() < VERSION) ? FLAG : 0) + int Options = + MASK_TLS_VERSION(ssl2, SSL_OP_NO_SSLv2) | + MASK_TLS_VERSION(ssl3, SSL_OP_NO_SSLv3) | + MASK_TLS_VERSION(tls10, SSL_OP_NO_TLSv1) | + MASK_TLS_VERSION(tls11, SSL_OP_NO_TLSv1_1) | + MASK_TLS_VERSION(tls12, SSL_OP_NO_TLSv1_2); + // SSL_ctrl() with SSL_CTRL_OPTIONS adds flags (not sets) + SSL_ctrl(Ssl, SSL_CTRL_OPTIONS, Options, nullptr); +} +//--------------------------------------------------------------------------- +void TWebDAVFileSystem::GetSupportedChecksumAlgs(TStrings * /*Algs*/) +{ + // NOOP +} + +void TWebDAVFileSystem::LockFile(const UnicodeString & /*AFileName*/, const TRemoteFile * AFile) +{ + ClearNeonError(); + struct ne_lock * Lock = ne_lock_create(); + try__finally + { + SCOPE_EXIT + { + if (Lock != nullptr) + { + ne_lock_destroy(Lock); + } + }; + Lock->uri.path = ne_strdup(PathToNeon(FilePath(AFile))); + Lock->depth = NE_DEPTH_INFINITE; + Lock->timeout = NE_TIMEOUT_INFINITE; + Lock->owner = ne_strdup(StrToNeon(FTerminal->TerminalGetUserName())); + CheckStatus(ne_lock(FNeonSession, Lock)); + + { + TGuard Guard(FNeonLockStoreSection); + + RequireLockStore(); + + ne_lockstore_add(FNeonLockStore, Lock); + } + // ownership passed + Lock = nullptr; + } + __finally + { + if (Lock != nullptr) + { + ne_lock_destroy(Lock); + } + }; +} + +void TWebDAVFileSystem::RequireLockStore() +{ + // Create store only when needed, + // to limit the use of cross-thread code in UpdateFromMain + if (FNeonLockStore == nullptr) + { + FNeonLockStore = ne_lockstore_create(); + ne_lockstore_register(FNeonLockStore, FNeonSession); + } +} + +void TWebDAVFileSystem::LockResult(void * UserData, const struct ne_lock * Lock, + const ne_uri * /*Uri*/, const ne_status * /*Status*/) +{ + // Is NULL on failure (Status is not NULL then) + if (Lock != nullptr) + { + RawByteString & LockToken = *static_cast(UserData); + LockToken = Lock->token; + } +} + +struct ne_lock * TWebDAVFileSystem::FindLock(const RawByteString & Path) +{ + ne_uri Uri = {0}; + Uri.path = const_cast(Path.c_str()); + return ne_lockstore_findbyuri(FNeonLockStore, &Uri); +} + +void TWebDAVFileSystem::DiscardLock(const RawByteString & Path) +{ + TGuard Guard(FNeonLockStoreSection); + if (FNeonLockStore != nullptr) + { + struct ne_lock * Lock = FindLock(Path); + if (Lock != nullptr) + { + ne_lockstore_remove(FNeonLockStore, Lock); + } + } +} + +void TWebDAVFileSystem::UnlockFile(const UnicodeString & FileName, const TRemoteFile * File) +{ + ClearNeonError(); + struct ne_lock * Lock = ne_lock_create(); + try__finally + { + SCOPE_EXIT + { + ne_lock_destroy(Lock); + }; + RawByteString Path = PathToNeon(FilePath(File)); + RawByteString LockToken; + + struct ne_lock * Lock2 = nullptr; + + { + TGuard Guard(FNeonLockStoreSection); + if (FNeonLockStore != nullptr) + { + Lock2 = FindLock(Path); + } + } + + // we are not aware of the file being locked, + // though it can be locked from another (previous and already closed) + // session, so query the server. + if (Lock2 == nullptr) + { + CheckStatus(ne_lock_discover(FNeonSession, Path.c_str(), LockResult, &LockToken)); + } + + if ((Lock2 == nullptr) && (LockToken.IsEmpty())) + { + throw Exception(FMTLOAD(NOT_LOCKED, FileName.c_str())); + } + else + { + struct ne_lock * Unlock; + if (Lock2 == nullptr) + { + DebugAssert(!LockToken.IsEmpty()); + Unlock = ne_lock_create(); + Unlock->uri.path = ne_strdup(Path.c_str()); + Unlock->token = ne_strdup(LockToken.c_str()); + } + else + { + Unlock = Lock2; + } + CheckStatus(ne_unlock(FNeonSession, Unlock)); + + DiscardLock(Path); + } + } + __finally + { + ne_lock_destroy(Lock); + }; +} + +void TWebDAVFileSystem::UpdateFromMain(TCustomFileSystem * AMainFileSystem) +{ + TWebDAVFileSystem * MainFileSystem = NB_STATIC_DOWNCAST(TWebDAVFileSystem, AMainFileSystem); + if (DebugAlwaysTrue(MainFileSystem != nullptr)) + { + TGuard Guard(FNeonLockStoreSection); + TGuard MainGuard(MainFileSystem->FNeonLockStoreSection); + + if (FNeonLockStore != nullptr) + { + struct ne_lock * Lock; + while ((Lock = ne_lockstore_first(FNeonLockStore)) != nullptr) + { + ne_lockstore_remove(FNeonLockStore, Lock); + } + } + + if (DebugAlwaysTrue(MainFileSystem->FNeonLockStore != nullptr)) + { + RequireLockStore(); + struct ne_lock * Lock = ne_lockstore_first(MainFileSystem->FNeonLockStore); + while (Lock != nullptr) + { + ne_lockstore_add(FNeonLockStore, ne_lock_copy(Lock)); + Lock = ne_lockstore_next(MainFileSystem->FNeonLockStore); + } + } + } +} + +NB_IMPLEMENT_CLASS(TWebDAVFileSystem, NB_GET_CLASS_INFO(TCustomFileSystem), nullptr) + diff --git a/netbox/src/core/WebDAVFileSystem.h b/netbox/src/core/WebDAVFileSystem.h new file mode 100644 index 000000000..62620bde9 --- /dev/null +++ b/netbox/src/core/WebDAVFileSystem.h @@ -0,0 +1,212 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include + +struct TWebDAVCertificateData; +struct ne_ssl_certificate_s; +struct ne_session_s; +struct ne_prop_result_set_s; +struct ne_lock_store_s; +struct TOverwriteFileParams; +struct ssl_st; +struct ne_lock; + +class TWebDAVFileSystem : public TCustomFileSystem +{ +NB_DISABLE_COPY(TWebDAVFileSystem) +NB_DECLARE_CLASS(TWebDAVFileSystem) +public: + explicit TWebDAVFileSystem(TTerminal * ATerminal); + virtual ~TWebDAVFileSystem(); + + virtual void Init(void *); + virtual void FileTransferProgress(int64_t TransferSize, int64_t Bytes); + + virtual void Open(); + virtual void Close(); + virtual bool GetActive() const; + virtual void CollectUsage(); + virtual void Idle(); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local); + virtual UnicodeString GetAbsolutePath(const UnicodeString & APath, bool Local) const; + virtual void AnyCommand(const UnicodeString & Command, + TCaptureOutputEvent OutputEvent); + virtual void ChangeDirectory(const UnicodeString & Directory); + virtual void CachedChangeDirectory(const UnicodeString & Directory); + virtual void AnnounceFileListOperation(); + virtual void ChangeFileProperties(const UnicodeString & AFileName, + const TRemoteFile * AFile, const TRemoteProperties * Properties, + TChmodSessionAction & Action); + virtual bool LoadFilesProperties(TStrings * AFileList); + virtual void CalculateFilesChecksum(const UnicodeString & Alg, + TStrings * AFileList, TStrings * Checksums, + TCalculatedChecksumEvent OnCalculatedChecksum); + virtual void CopyToLocal(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void CopyToRemote(const TStrings * AFilesToCopy, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, + TOnceDoneOperation & OnceDoneOperation); + virtual void RemoteCreateDirectory(const UnicodeString & ADirName); + virtual void CreateLink(const UnicodeString & AFileName, const UnicodeString & PointTo, bool Symbolic); + virtual void RemoteDeleteFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, intptr_t Params, TRmSessionAction & Action); + virtual void CustomCommandOnFile(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & Command, intptr_t Params, TCaptureOutputEvent OutputEvent); + virtual void DoStartup(); + virtual void HomeDirectory(); + virtual bool IsCapable(intptr_t Capability) const; + virtual void LookupUsersGroups(); + virtual void ReadCurrentDirectory(); + virtual void ReadDirectory(TRemoteFileList * FileList); + virtual void ReadFile(const UnicodeString & AFileName, + TRemoteFile *& File); + virtual void ReadSymlink(TRemoteFile * SymlinkFile, + TRemoteFile *& File); + virtual void RemoteRenameFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual void RemoteCopyFile(const UnicodeString & AFileName, + const UnicodeString & ANewName); + virtual TStrings * GetFixedPaths(); + virtual void SpaceAvailable(const UnicodeString & APath, + TSpaceAvailable & ASpaceAvailable); + virtual const TSessionInfo & GetSessionInfo() const; + virtual const TFileSystemInfo & GetFileSystemInfo(bool Retrieve); + virtual bool TemporaryTransferFile(const UnicodeString & AFileName); + virtual bool GetStoredCredentialsTried() const; + virtual UnicodeString FSGetUserName() const; + virtual void GetSupportedChecksumAlgs(TStrings * Algs); + virtual void LockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UnlockFile(const UnicodeString & AFileName, const TRemoteFile * AFile); + virtual void UpdateFromMain(TCustomFileSystem * MainFileSystem); + + void NeonDebug(const UnicodeString & Message); + +protected: + virtual UnicodeString GetCurrDirectory() const; + + void Sink(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t AParams, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TDownloadSessionAction & Action, bool & ChildError); + void SinkRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, const UnicodeString & TargetDir, + const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void SinkFile(const UnicodeString & AFileName, const TRemoteFile * AFile, void * Param); + void SourceRobust(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void Source(const UnicodeString & AFileName, + const TRemoteFile * AFile, + const UnicodeString & TargetDir, const TCopyParamType * CopyParam, intptr_t Params, + TFileOperationProgressType * OperationProgress, uintptr_t Flags, + TUploadSessionAction & Action, bool & ChildError); + void DirectorySource(const UnicodeString & DirectoryName, + const UnicodeString & TargetDir, uintptr_t Attrs, const TCopyParamType * CopyParam, + intptr_t Params, TFileOperationProgressType * OperationProgress, uintptr_t Flags); + void ConfirmOverwrite( + const UnicodeString & ASourceFullFileName, UnicodeString & ADestFileName, + TFileOperationProgressType * OperationProgress, + const TOverwriteFileParams * FileParams, const TCopyParamType * CopyParam, + intptr_t Params, + OUT TOverwriteMode & OverwriteMode, + OUT uintptr_t & Answer); + void CheckStatus(int NeonStatus); + void ClearNeonError(); + static void NeonPropsResult( + void * UserData, const ne_uri * Uri, const ne_prop_result_set_s * Results); + void ParsePropResultSet(TRemoteFile * AFile, + const UnicodeString & APath, const ne_prop_result_set_s * Results); + void TryOpenDirectory(const UnicodeString & ADirectory); + static int NeonBodyReader(void * UserData, const char * Buf, size_t Len); + static void NeonPreSend(ne_request * Request, void * UserData, ne_buffer * Header); + static int NeonBodyAccepter(void * UserData, ne_request * Request, const ne_status * Status); + static void NeonCreateRequest(ne_request * Request, void * UserData, const char * Method, const char * Uri); + static int NeonRequestAuth(void * UserData, const char * Realm, int Attempt, char * UserName, char * Password); + void NeonOpen(UnicodeString & CorrectedUrl, const UnicodeString & Url); + void NeonClientOpenSessionInternal(UnicodeString & CorrectedUrl, UnicodeString Url); + static void NeonNotifier(void * UserData, ne_session_status Status, const ne_session_status_info * StatusInfo); + static ssize_t NeonUploadBodyProvider(void * UserData, char * Buffer, size_t BufLen); + static int NeonPostSend(ne_request * Req, void * UserData, const ne_status * Status); + static void NeonPostHeaders(ne_request * Req, void * UserData, const ne_status * Status); + void ExchangeCapabilities(const char * Path, UnicodeString & CorrectedUrl); + static int NeonServerSSLCallback(void * UserData, int Failures, const struct ne_ssl_certificate_s * Certificate); + static void NeonProvideClientCert(void * UserData, ne_session * Sess, const ne_ssl_dname * const * DNames, int DNCount); + void CloseNeonSession(); + bool CancelTransfer(); + UnicodeString GetNeonError() const; + static void NeonQuotaResult(void * UserData, const ne_uri * Uri, const ne_prop_result_set_s * Results); + static const char * GetProp(const ne_prop_result_set_s * Results, + const char * Name, const char * NameSpace = nullptr); + static void LockResult(void * UserData, const struct ne_lock * Lock, + const ne_uri * Uri, const ne_status * Status); + void RequireLockStore(); + static void InitSslSession(ssl_st * Ssl, ne_session * Session); + void InitSslSessionImpl(ssl_st * Ssl); + void NeonAddAuthentiation(bool UseNegotiate); + void HttpAuthenticationFailed(); + +private: + TFileSystemInfo FFileSystemInfo; + UnicodeString FCurrentDirectory; + UnicodeString FCachedDirectoryChange; + TSessionInfo FSessionInfo; + UnicodeString FUserName; + bool FActive; + bool FHasTrailingSlash; + bool FCancelled; + bool FStoredPasswordTried; + bool FUploading; + bool FDownloading; + ne_session_s * FNeonSession; + ne_lock_store_s * FNeonLockStore; + TCriticalSection FNeonLockStoreSection; + bool FInitialHandshake; + bool FAuthenticationRequested; + UnicodeString FResponse; + RawByteString FPassword; + UnicodeString FTlsVersionStr; + unsigned int FCapabilities; + UnicodeString FHostName; + int FPortNumber; + enum TIgnoreAuthenticationFailure { iafNo, iafWaiting, iafPasswordFailed } FIgnoreAuthenticationFailure; + UnicodeString FAuthorizationProtocol; + bool FAuthenticationRetry; + bool FNtlmAuthenticationFailed; + + void CustomReadFile(const UnicodeString & AFileName, + TRemoteFile *& File, TRemoteFile * ALinkedByFile); + int CustomReadFileInternal(const UnicodeString & AFileName, + TRemoteFile *& File, TRemoteFile * ALinkedByFile); + void RegisterForDebug(); + void UnregisterFromDebug(); + bool VerifyCertificate( + const TWebDAVCertificateData & Data); + void OpenUrl(const UnicodeString & Url); + void CollectTLSSessionInfo(); + UnicodeString GetRedirectUrl(); + UnicodeString ParsePathFromUrl(const UnicodeString & Url); + int ReadDirectoryInternal(const UnicodeString & Path, TRemoteFileList * FileList); + int RenameFileInternal(const UnicodeString & AFileName, const UnicodeString & ANewName); + bool IsValidRedirect(int NeonStatus, UnicodeString & Path); + UnicodeString DirectoryPath(UnicodeString Path); + UnicodeString FilePath(const TRemoteFile * File); + struct ne_lock * FindLock(const RawByteString & Path); + void DiscardLock(const RawByteString & Path); + bool IsNtlmAuthentication() const; +}; + +void NeonInitialize(); +void NeonFinalize(); + diff --git a/netbox/src/core/WinSCPSecurity.cpp b/netbox/src/core/WinSCPSecurity.cpp new file mode 100644 index 000000000..e59ea88af --- /dev/null +++ b/netbox/src/core/WinSCPSecurity.cpp @@ -0,0 +1,187 @@ +#include +#pragma hdrstop + +#include +#ifndef __linux__ +#include +#endif +#include "WinSCPSecurity.h" + +#define PWALG_SIMPLE_INTERNAL 0x00 +#define PWALG_SIMPLE_EXTERNAL 0x01 + +static int random(int range) +{ + return static_cast(ToDouble(rand()) / (ToDouble(RAND_MAX) / range)); +} + +RawByteString SimpleEncryptChar(uint8_t Ch) +{ + Ch = (uint8_t)((~Ch) ^ PWALG_SIMPLE_MAGIC); + return + PWALG_SIMPLE_STRING.SubString(((Ch & 0xF0) >> 4) + 1, 1) + + PWALG_SIMPLE_STRING.SubString(((Ch & 0x0F) >> 0) + 1, 1); +} + +uint8_t SimpleDecryptNextChar(RawByteString & Str) +{ + if (Str.Length() > 0) + { + uint8_t Result = (uint8_t) + ~((((PWALG_SIMPLE_STRING.Pos(Str.c_str()[0])-1) << 4) + + ((PWALG_SIMPLE_STRING.Pos(Str.c_str()[1])-1) << 0)) ^ PWALG_SIMPLE_MAGIC); + Str.Delete(1, 2); + return Result; + } + else + { + return 0x00; + } +} + +RawByteString EncryptPassword(const UnicodeString & APassword, const UnicodeString & AKey, Integer /*Algorithm*/) +{ + UTF8String Password = UTF8String(APassword); + UTF8String Key = UTF8String(AKey); + + RawByteString Result(""); + intptr_t Shift, Index; + + if (!::RandSeed) + { + ::Randomize(); + ::RandSeed = 1; + } + Password = Key + Password; + Shift = (Password.Length() < PWALG_SIMPLE_MAXLEN) ? + static_cast(random(PWALG_SIMPLE_MAXLEN - static_cast(Password.Length()))) : 0; + Result += SimpleEncryptChar(static_cast(PWALG_SIMPLE_FLAG)); // Flag + Result += SimpleEncryptChar(static_cast(PWALG_SIMPLE_INTERNAL)); // Dummy + Result += SimpleEncryptChar(static_cast(Password.Length())); + Result += SimpleEncryptChar(static_cast(Shift)); + for (Index = 0; Index < Shift; ++Index) + Result += SimpleEncryptChar(static_cast(random(256))); + for (Index = 0; Index < Password.Length(); ++Index) + Result += SimpleEncryptChar(static_cast(Password.c_str()[Index])); + while (Result.Length() < PWALG_SIMPLE_MAXLEN * 2) + Result += SimpleEncryptChar(static_cast(random(256))); + return Result; +} + +UnicodeString DecryptPassword(const RawByteString & APassword, const UnicodeString & AKey, Integer /*Algorithm*/) +{ + RawByteString Password = APassword; + UTF8String Key = UTF8String(AKey); + UTF8String Result(""); + Integer Index; + uint8_t Length, Flag; + + Flag = SimpleDecryptNextChar(Password); + if (Flag == PWALG_SIMPLE_FLAG) + { + /* Dummy = */ SimpleDecryptNextChar(Password); + Length = SimpleDecryptNextChar(Password); + } + else + Length = Flag; + Password.Delete(1, (static_cast(SimpleDecryptNextChar(Password)) * 2)); + for (Index = 0; Index < Length; ++Index) + Result += static_cast(SimpleDecryptNextChar(Password)); + if (Flag == PWALG_SIMPLE_FLAG) + { + if (Result.SubString(1, Key.Length()) != Key) + Result = ""; + else + Result.Delete(1, Key.Length()); + } + return UnicodeString(Result); +} + +RawByteString SetExternalEncryptedPassword(const RawByteString & APassword) +{ + RawByteString Result; + Result += SimpleEncryptChar(static_cast(PWALG_SIMPLE_FLAG)); + Result += SimpleEncryptChar(static_cast(PWALG_SIMPLE_EXTERNAL)); + Result += UTF8String(BytesToHex(reinterpret_cast(APassword.c_str()), APassword.Length())); + return Result; +} + +bool GetExternalEncryptedPassword(const RawByteString & AEncrypted, RawByteString & APassword) +{ + RawByteString Encrypted = AEncrypted; + bool Result = + (SimpleDecryptNextChar(Encrypted) == PWALG_SIMPLE_FLAG) && + (SimpleDecryptNextChar(Encrypted) == PWALG_SIMPLE_EXTERNAL); + if (Result) + { + APassword = HexToBytes(UTF8ToString(Encrypted)); + } + return Result; +} + +#ifndef __linux__ +bool WindowsValidateCertificate(const uint8_t * Certificate, size_t Len) +{ + bool Result = false; + + // Parse the certificate into a context. + const CERT_CONTEXT * CertContext = + CertCreateCertificateContext( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, Certificate, static_cast(Len)); + + if (CertContext != nullptr) + { + CERT_CHAIN_PARA ChainPara; + // Retrieve the certificate chain of the certificate + // (a certificate without a valid root does not have a chain). + ClearStruct(ChainPara); + ChainPara.cbSize = sizeof(ChainPara); + + CERT_CHAIN_ENGINE_CONFIG ChainConfig; + + ClearStruct(ChainConfig); + ChainConfig.cbSize = sizeof(CERT_CHAIN_ENGINE_CONFIG); + ChainConfig.hRestrictedRoot = nullptr; + ChainConfig.hRestrictedTrust = nullptr; + ChainConfig.hRestrictedOther = nullptr; + ChainConfig.cAdditionalStore = 0; + ChainConfig.rghAdditionalStore = nullptr; + ChainConfig.dwFlags = CERT_CHAIN_CACHE_END_CERT; + ChainConfig.dwUrlRetrievalTimeout = 0; + ChainConfig.MaximumCachedCertificates = 0; + ChainConfig.CycleDetectionModulus = 0; + + HCERTCHAINENGINE ChainEngine; + if (CertCreateCertificateChainEngine(&ChainConfig, &ChainEngine)) + { + const CERT_CHAIN_CONTEXT * ChainContext = nullptr; + if (CertGetCertificateChain(ChainEngine, CertContext, nullptr, nullptr, &ChainPara, + CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, + nullptr, &ChainContext)) + { + CERT_CHAIN_POLICY_PARA PolicyPara; + + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.dwFlags = 0; + PolicyPara.pvExtraPolicyPara = nullptr; + + CERT_CHAIN_POLICY_STATUS PolicyStatus; + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, + ChainContext, &PolicyPara, &PolicyStatus)) + { + // Windows thinks the certificate is valid. + Result = (PolicyStatus.dwError == S_OK); + } + + CertFreeCertificateChain(ChainContext); + } + CertFreeCertificateChainEngine(ChainEngine); + } + CertFreeCertificateContext(CertContext); + } + return Result; +} +#endif diff --git a/netbox/src/core/WinSCPSecurity.h b/netbox/src/core/WinSCPSecurity.h new file mode 100644 index 000000000..9181ec328 --- /dev/null +++ b/netbox/src/core/WinSCPSecurity.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define PWALG_SIMPLE 1 +#define PWALG_SIMPLE_MAGIC 0xA3 +#define PWALG_SIMPLE_STRING ((RawByteString)"0123456789ABCDEF") +#define PWALG_SIMPLE_MAXLEN 50 +#define PWALG_SIMPLE_FLAG 0xFF + +RawByteString EncryptPassword(const UnicodeString & APassword, const UnicodeString & AKey, Integer AAlgorithm = PWALG_SIMPLE); +UnicodeString DecryptPassword(const RawByteString & APassword, const UnicodeString & AKey, Integer AAlgorithm = PWALG_SIMPLE); +RawByteString SetExternalEncryptedPassword(const RawByteString & APassword); +bool GetExternalEncryptedPassword(const RawByteString & AEncrypted, RawByteString & APassword); + +bool WindowsValidateCertificate(const uint8_t * Certificate, size_t Len); + diff --git a/netbox/src/filezilla/ApiLog.cpp b/netbox/src/filezilla/ApiLog.cpp new file mode 100644 index 000000000..52078afc2 --- /dev/null +++ b/netbox/src/filezilla/ApiLog.cpp @@ -0,0 +1,90 @@ + +#include "stdafx.h" +#include "ApiLog.h" + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CApiLog::CApiLog() +{ + FIntern = NULL; +} + +CApiLog::~CApiLog() +{ +} + +void CApiLog::InitIntern(TFileZillaIntern * Intern) +{ + FIntern = Intern; +} + +TFileZillaIntern * CApiLog::GetIntern() +{ + return FIntern; +} + +bool CApiLog::LoggingMessageType(int nMessageType) const +{ + return + (nMessageType < FZ_LOG_APIERROR) || + ((nMessageType-FZ_LOG_APIERROR) < FIntern->GetDebugLevel()); +} + +void CApiLog::LogMessage(int nMessageType, LPCTSTR pMsgFormat, ...) const +{ + DebugAssert(nMessageType>=FZ_LOG_STATUS && nMessageType<=FZ_LOG_DEBUG); + if (!LoggingMessageType(nMessageType)) + return; + + va_list ap; + + va_start(ap, pMsgFormat); + CString text; + text.FormatV(pMsgFormat, ap); + va_end(ap); + + if (nMessageType>=FZ_LOG_DEBUG) + return; + SendLogMessage(nMessageType, text); +} + +void CApiLog::LogMessageRaw(int nMessageType, LPCTSTR pMsg) const +{ + DebugAssert(nMessageType>=FZ_LOG_STATUS && nMessageType<=FZ_LOG_DEBUG); + if (!LoggingMessageType(nMessageType)) + return; + + if (nMessageType>=FZ_LOG_DEBUG) + return; + SendLogMessage(nMessageType, pMsg); +} + +void CApiLog::SendLogMessage(int nMessageType, LPCTSTR pMsg) const +{ + if (!LoggingMessageType(nMessageType)) + return; + //Displays a message in the message log + t_ffam_statusmessage *pStatus = new t_ffam_statusmessage(); + pStatus->post = TRUE; + pStatus->status = pMsg; + pStatus->type = nMessageType; + if (!FIntern->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_STATUS, 0), (LPARAM)pStatus)) + delete pStatus; +} + +CString CApiLog::GetOption(int OptionID) const +{ + DebugAssert(FIntern != NULL); + return FIntern->GetOption(OptionID); +} + +int CApiLog::GetOptionVal(int OptionID) const +{ + DebugAssert(FIntern != NULL); + return FIntern->GetOptionVal(OptionID); +} + +NB_IMPLEMENT_CLASS(CApiLog, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/filezilla/ApiLog.h b/netbox/src/filezilla/ApiLog.h new file mode 100644 index 000000000..4c16d2147 --- /dev/null +++ b/netbox/src/filezilla/ApiLog.h @@ -0,0 +1,28 @@ + +#pragma once + +#include "FileZillaIntern.h" + +class CApiLog : public TObject +{ +NB_DECLARE_CLASS(CApiLog) +public: + CApiLog(); + virtual ~CApiLog(); + + void InitIntern(TFileZillaIntern * Intern); + TFileZillaIntern * GetIntern(); + + bool LoggingMessageType(int nMessageType) const; + + void LogMessage(int nMessageType, LPCTSTR pMsgFormat, ...) const; + void LogMessageRaw(int nMessageType, LPCTSTR pMsg) const; + + CString GetOption(int OptionID) const; + int GetOptionVal(int OptionID) const; + +protected: + void SendLogMessage(int nMessageType, LPCTSTR pMsg) const; + + TFileZillaIntern * FIntern; +}; diff --git a/netbox/src/filezilla/AsyncProxySocketLayer.cpp b/netbox/src/filezilla/AsyncProxySocketLayer.cpp new file mode 100644 index 000000000..47f659b21 --- /dev/null +++ b/netbox/src/filezilla/AsyncProxySocketLayer.cpp @@ -0,0 +1,956 @@ +// CAsyncProxySocketLayer by Tim Kosse (Tim.Kosse@gmx.de) +// Version 1.6 (2003-03-26) + +// Feel free to use this class, as long as you don't claim that you wrote it +// and this copyright notice stays intact in the source files. +// If you use this class in commercial applications, please send a short message +// to tim.kosse@gmx.de + +#include "stdafx.h" +#include "AsyncProxySocketLayer.h" +#include "atlconv.h" //Unicode<->Ascii conversion macros declared here +#include "misc/CBase64Coding.hpp" + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CAsyncProxySocketLayer::CAsyncProxySocketLayer() +{ + m_nProxyOpID=0; + m_nProxyOpState=0; + m_pRecvBuffer=0; + m_nRecvBufferLen=0; + m_nRecvBufferPos=0; + m_ProxyData.nProxyType=0; + m_ProxyData.nProxyPort=0; + m_nProxyPeerIp=0; + m_nProxyPeerPort=0; + m_pProxyPeerHost = NULL; + m_pStrBuffer = NULL; + m_ProxyData.pProxyHost = NULL; + m_ProxyData.pProxyUser = NULL; + m_ProxyData.pProxyPass = NULL; + m_ProxyData.bUseLogon = FALSE; + m_pProxyPeerHost = NULL; +} + +CAsyncProxySocketLayer::~CAsyncProxySocketLayer() +{ + nb_free(m_ProxyData.pProxyHost); + nb_free(m_ProxyData.pProxyUser); + nb_free(m_ProxyData.pProxyPass); + nb_free(m_pProxyPeerHost); + ClearBuffer(); +} + +///////////////////////////////////////////////////////////////////////////// +// Member-Funktion CAsyncProxySocketLayer + +void CAsyncProxySocketLayer::SetProxy(int nProxyType, const char * pProxyHost, int ProxyPort, bool bUseLogon, const char * pProxyUser, const char * pProxyPass) +{ + USES_CONVERSION; + //Validate the parameters + DebugAssert(!m_nProxyOpID); + DebugAssert(pProxyHost && *pProxyHost); + DebugAssert(ProxyPort>0); + DebugAssert(ProxyPort<=65535); + + nb_free(m_ProxyData.pProxyHost); + nb_free(m_ProxyData.pProxyUser); + nb_free(m_ProxyData.pProxyPass); + m_ProxyData.pProxyUser = NULL; + m_ProxyData.pProxyPass = NULL; + + m_ProxyData.nProxyType = nProxyType; + m_ProxyData.pProxyHost = static_cast(nb_calloc(1, strlen(pProxyHost)+1)); + strcpy(m_ProxyData.pProxyHost, pProxyHost); + m_ProxyData.nProxyPort=ProxyPort; + if (pProxyUser) + { + m_ProxyData.pProxyUser = static_cast(nb_calloc(1, strlen(pProxyUser)+1)); + strcpy(m_ProxyData.pProxyUser, pProxyUser); + } + if (pProxyPass) + { + m_ProxyData.pProxyPass = static_cast(nb_calloc(1, strlen(pProxyPass)+1)); + strcpy(m_ProxyData.pProxyPass, pProxyPass); + } + m_ProxyData.bUseLogon = bUseLogon; +} + +void CAsyncProxySocketLayer::OnReceive(int nErrorCode) +{ + //Here we handle the responses from the SOCKS proxy + if (!m_nProxyOpID) + { + TriggerEvent(FD_READ, nErrorCode, TRUE); + return; + } + if (nErrorCode) + { + TriggerEvent(FD_READ, nErrorCode, TRUE); + } + if (!m_nProxyOpState) //We should not receive a response yet! + { + //Ignore it + return; + } + if (m_ProxyData.nProxyType==PROXYTYPE_SOCKS4 || m_ProxyData.nProxyType==PROXYTYPE_SOCKS4A) + { + if (m_nProxyOpState==1) //Both for PROXYOP_CONNECT and PROXYOP_BIND + { + if (!m_pRecvBuffer) + m_pRecvBuffer=static_cast(nb_calloc(1, 8)); + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos, 8-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==8) + { + if (m_pRecvBuffer[1]!=90 || m_pRecvBuffer[0]!=0) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + if (m_nProxyOpID==PROXYOP_CONNECT) + { + //OK, we are connected with the remote server + ConnectionEstablished(); + return; + } + else + { + //Listen socket created + m_nProxyOpState++; + unsigned long ip; + int port = 0; + memcpy(&ip,&m_pRecvBuffer[4],4); + if (!ip) + { + //No IP return, use the IP of the proxy server + SOCKADDR SockAddr; + memset(&SockAddr,0,sizeof(SockAddr)); + int SockAddrLen=sizeof(SockAddr); + if (GetPeerName(&SockAddr, &SockAddrLen )) + { + ip=((LPSOCKADDR_IN)&SockAddr)->sin_addr.S_un.S_addr; + } + else + { + ConnectionFailed(WSAECONNABORTED); + return; + } + } + memcpy(&port,&m_pRecvBuffer[2],2); + t_ListenSocketCreatedStruct data; + data.ip=ip; + data.nPort=port; + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYSTATUS_LISTENSOCKETCREATED, (intptr_t)&data); + } + ClearBuffer(); + } + } + else if (m_nProxyOpID==PROXYOP_LISTEN) + { + if (!m_pRecvBuffer) + m_pRecvBuffer=static_cast(nb_calloc(1, 8)); + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos,8-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==8) + { + if (m_pRecvBuffer[1]!=90 || m_pRecvBuffer[0]!=0) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + //Connection to remote server established + ConnectionEstablished(); + } + } + } + else if (m_ProxyData.nProxyType==PROXYTYPE_SOCKS5) + { + if (m_nProxyOpState==1) //Get respone to initialization message + { + if (!m_pRecvBuffer) + m_pRecvBuffer=static_cast(nb_calloc(1, 2)); + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos,2-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==2) + { + if (m_pRecvBuffer[0]!=5) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + if (m_pRecvBuffer[1]) + { + //Auth needed + if (m_pRecvBuffer[1]!=2) + { + //Unknown auth type + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHTYPEUNKNOWN, 0); + if (m_nProxyOpID==PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + + if (!m_ProxyData.bUseLogon) + { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHNOLOGON, 0); + if (m_nProxyOpID==PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + //Send authentication + LPCSTR lpszAsciiUser = m_ProxyData.pProxyUser; + LPCSTR lpszAsciiPass = m_ProxyData.pProxyPass; + DebugAssert(strlen(lpszAsciiUser)<=255); + DebugAssert(strlen(lpszAsciiPass)<=255); + uint8_t *buffer = static_cast(nb_calloc(1, 3 + (lpszAsciiUser?strlen(lpszAsciiUser):0) + (lpszAsciiPass?strlen(lpszAsciiPass):0) + 1)); + sprintf((char *)buffer, " %s %s", lpszAsciiUser?lpszAsciiUser:"", lpszAsciiPass?lpszAsciiPass:""); + buffer[0]=1; + buffer[1]=static_cast(strlen(lpszAsciiUser)); + buffer[2+strlen(lpszAsciiUser)]=static_cast(strlen(lpszAsciiPass)); + intptr_t len=3+strlen(lpszAsciiUser)+strlen(lpszAsciiPass); + int res=SendNext(buffer,len); + nb_free(buffer); + if (res==SOCKET_ERROR || res(nb_calloc(1, 10+strlen(lpszAsciiHost)+1)); + memset(command,0,10+strlen(lpszAsciiHost)+1); + command[0]=5; + command[1]=(m_nProxyOpID==PROXYOP_CONNECT)?1:2; + command[2]=0; + command[3]=m_nProxyPeerIp?1:3; + int len=4; + if (m_nProxyPeerIp) + { + memcpy(&command[len],&m_nProxyPeerIp,4); + len+=4; + } + else + { + command[len]=strlen(lpszAsciiHost); + strcpy(&command[len+1], lpszAsciiHost); + len += strlen(lpszAsciiHost) + 1; + } + memcpy(&command[len], &m_nProxyPeerPort, 2); + len+=2; + int res=SendNext(command,len); + nb_free(command); + if (res==SOCKET_ERROR || res(nb_calloc(1, 2)); + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos, 2-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==2) + { + if (m_pRecvBuffer[1]!=0) + { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_AUTHFAILED, 0); + if (m_nProxyOpID==PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, WSAECONNABORTED, TRUE); + else + TriggerEvent(FD_ACCEPT, WSAECONNABORTED, TRUE); + Reset(); + ClearBuffer(); + return; + } + const char * lpszAsciiHost = m_pProxyPeerHost?m_pProxyPeerHost:""; + char *command = static_cast(nb_calloc(1, 10+strlen(lpszAsciiHost)+1)); + memset(command,0,10+strlen(lpszAsciiHost)+1); + command[0]=5; + command[1]=(m_nProxyOpID==PROXYOP_CONNECT)?1:2; + command[2]=0; + command[3]=m_nProxyPeerIp?1:3; + int len=4; + if (m_nProxyPeerIp) + { + memcpy(&command[len],&m_nProxyPeerIp,4); + len+=4; + } + else + { + command[len]=strlen(lpszAsciiHost); + strcpy(&command[len+1],lpszAsciiHost); + len+=strlen(lpszAsciiHost)+1; + } + memcpy(&command[len],&m_nProxyPeerPort,2); + len+=2; + int res=SendNext(command,len); + nb_free(command); + if (res==SOCKET_ERROR || res(nb_calloc(1, 10)); + m_nRecvBufferLen=5; + } + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos,m_nRecvBufferLen-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==m_nRecvBufferLen) + { + //Check for errors + if (m_pRecvBuffer[1]!=0 || m_pRecvBuffer[0]!=5) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + if (m_nRecvBufferLen==5) + { + //Check which kind of address the response contains + if (m_pRecvBuffer[3]==1) + m_nRecvBufferLen=10; + else + { + char *tmp=static_cast(nb_calloc(1, m_nRecvBufferLen+=m_pRecvBuffer[4]+2)); + memcpy(tmp,m_pRecvBuffer,5); + nb_free(m_pRecvBuffer); + m_pRecvBuffer=tmp; + m_nRecvBufferLen+=m_pRecvBuffer[4]+2; + } + return; + } + + if (m_nProxyOpID==PROXYOP_CONNECT) + { + //OK, we are connected with the remote server + ConnectionEstablished(); + } + else + { + //Listen socket created + m_nProxyOpState++; + unsigned long ip; + unsigned short port; + DebugAssert(m_pRecvBuffer[3]==1); + memcpy(&ip, &m_pRecvBuffer[4], 4); + memcpy(&port, &m_pRecvBuffer[8], 2); + t_ListenSocketCreatedStruct data; + data.ip=ip; + data.nPort=port; + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYSTATUS_LISTENSOCKETCREATED, (intptr_t)&data); + } + ClearBuffer(); + } + } + else if (m_nProxyOpState==4) + { + DebugAssert(m_nProxyOpID == PROXYOP_LISTEN); + if (!m_pRecvBuffer) + m_pRecvBuffer=static_cast(nb_calloc(1, 10)); + int numread=ReceiveNext(m_pRecvBuffer+m_nRecvBufferPos,10-m_nRecvBufferPos); + if (numread==SOCKET_ERROR) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + ConnectionFailed(WSAGetLastError()); + } + return; + } + m_nRecvBufferPos+=numread; + if (m_nRecvBufferPos==10) + { + if (m_pRecvBuffer[1]!=0) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + //Connection to remote server established + ConnectionEstablished(); + } + } + } + else if (m_ProxyData.nProxyType==PROXYTYPE_HTTP11) + { + DebugAssert(m_nProxyOpID==PROXYOP_CONNECT); + char buffer[9]; + memset(buffer, 0, sizeof(buffer)); + for(;;) + { + int numread = ReceiveNext(buffer, m_pStrBuffer?1:8); + if (numread==SOCKET_ERROR) + { + int nError=WSAGetLastError(); + if (nError!=WSAEWOULDBLOCK) + { + ConnectionFailed(nError); + } + return; + } + //Response begins with HTTP/ + if (!m_pStrBuffer) + { + m_pStrBuffer = static_cast(nb_calloc(1, strlen(buffer) + 1)); + strcpy(m_pStrBuffer, buffer); + } + else + { + char *tmp = m_pStrBuffer; + m_pStrBuffer = static_cast(nb_calloc(1, strlen(tmp) + strlen(buffer) + 1)); + strcpy(m_pStrBuffer, tmp); + strcpy(m_pStrBuffer + strlen(tmp), buffer); + nb_free(tmp); + } + memset(buffer, 0, 9); + const char start[] = "HTTP/"; + if (memcmp(start, m_pStrBuffer, (strlen(start)>strlen(m_pStrBuffer)) ? strlen(m_pStrBuffer) : strlen(start))) + { + char* str = static_cast(nb_calloc(1, strlen("No valid HTTP response") + 1)); + strcpy(str, "No valid HTTP response"); + ConnectionFailed(WSAECONNABORTED, str); + return; + } + char *pos = strstr(m_pStrBuffer, "\r\n"); + if (pos) + { + char *pos2 = strstr(m_pStrBuffer, " "); + if (!pos2 || *(pos2+1)!='2' || pos2>pos) + { + char *tmp = static_cast(nb_calloc(1, pos-m_pStrBuffer + 1)); + tmp[pos-m_pStrBuffer] = 0; + strncpy(tmp, m_pStrBuffer, pos-m_pStrBuffer); + ConnectionFailed(WSAECONNABORTED, tmp); + return; + } + } + if (strlen(m_pStrBuffer)>3 && !memcmp(m_pStrBuffer+strlen(m_pStrBuffer)-4, "\r\n\r\n", 4)) //End of the HTTP header + { + ConnectionEstablished(); + return; + } + } + } +} + +void CAsyncProxySocketLayer::ConnectionFailed(int nErrorCode, char * Str) +{ + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_REQUESTFAILED, 0, Str); + if (m_nProxyOpID == PROXYOP_CONNECT) + { + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + } + else + { + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + } + Reset(); + ClearBuffer(); +} + +void CAsyncProxySocketLayer::ConnectionEstablished() +{ + int Event = (m_nProxyOpID == PROXYOP_CONNECT) ? FD_CONNECT : FD_ACCEPT; + ClearBuffer(); + Reset(); + + TriggerEvent(Event, 0, TRUE); + TriggerEvent(FD_READ, 0, TRUE); + TriggerEvent(FD_WRITE, 0, TRUE); +} + +BOOL CAsyncProxySocketLayer::Connect( LPCTSTR lpszHostAddress, UINT nHostPort ) +{ + if (!m_ProxyData.nProxyType) + //Connect normally because there is no proxy + return ConnectNext(lpszHostAddress, nHostPort); + + USES_CONVERSION; + + //Translate the host address + DebugAssert(lpszHostAddress != NULL); + + if (m_ProxyData.nProxyType != PROXYTYPE_SOCKS4) + { + // We can send hostname to proxy, no need to resolve it + + //Connect to proxy server + BOOL res = ConnectNext(A2CT(m_ProxyData.pProxyHost), m_ProxyData.nProxyPort); + if (!res) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, 0); + return FALSE; + } + } + m_nProxyPeerPort = htons((u_short)nHostPort); + m_nProxyPeerIp = 0; + nb_free(m_pProxyPeerHost); + m_pProxyPeerHost = static_cast(nb_calloc(1, _tcslen(lpszHostAddress)+1)); + strcpy(m_pProxyPeerHost, T2CA(lpszHostAddress)); + m_nProxyOpID=PROXYOP_CONNECT; + return TRUE; + } + + SOCKADDR_IN sockAddr; + memset(&sockAddr,0,sizeof(sockAddr)); + + LPCSTR lpszAscii = T2A((LPTSTR)lpszHostAddress); + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = inet_addr(lpszAscii); + + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + LPHOSTENT lphost; + lphost = gethostbyname(lpszAscii); + if (lphost != NULL) + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; + else + { + //Can't resolve hostname + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_CANTRESOLVEHOST, 0); + WSASetLastError(WSAEINVAL); + return FALSE; + } + } + + sockAddr.sin_port = htons((u_short)nHostPort); + BOOL res=CAsyncProxySocketLayer::Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + if (res || WSAGetLastError()==WSAEWOULDBLOCK) + { + nb_free(m_pProxyPeerHost); + m_pProxyPeerHost = static_cast(nb_calloc(1, strlen(T2CA(lpszHostAddress))+1)); + strcpy(m_pProxyPeerHost, T2CA(lpszHostAddress)); + } + return res; + +} + +BOOL CAsyncProxySocketLayer::Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen ) +{ + if (!m_ProxyData.nProxyType) + { + //Connect normally because there is no proxy + return ConnectNext(lpSockAddr, nSockAddrLen ); + } + + LPSOCKADDR_IN sockAddr=(LPSOCKADDR_IN)lpSockAddr; + + //Save server details + m_nProxyPeerIp=sockAddr->sin_addr.S_un.S_addr; + m_nProxyPeerPort=sockAddr->sin_port; + nb_free(m_pProxyPeerHost); + m_pProxyPeerHost = NULL; + + m_nProxyOpID=PROXYOP_CONNECT; + + USES_CONVERSION; + + BOOL res = ConnectNext(A2T(m_ProxyData.pProxyHost), m_ProxyData.nProxyPort); + if (!res) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, 0); + return FALSE; + } + } + + return res; +} + +void CAsyncProxySocketLayer::OnConnect(int nErrorCode) +{ + if (m_ProxyData.nProxyType==PROXYTYPE_NOPROXY) + { + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + return; + } + DebugAssert(m_nProxyOpID); + if (!m_nProxyOpID) + { + //This should not happen + return; + }; + + if (nErrorCode) + { //Can't connect to proxy + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, 0); + if (m_nProxyOpID==PROXYOP_CONNECT) + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); + else + TriggerEvent(FD_ACCEPT, nErrorCode, TRUE); + Reset(); + ClearBuffer(); + return; + } + if (m_nProxyOpID==PROXYOP_CONNECT || m_nProxyOpID==PROXYOP_LISTEN) + { + if (m_nProxyOpState) + //Somehow OnConnect has been called more than once + return; + DebugAssert(m_ProxyData.nProxyType!=PROXYTYPE_NOPROXY); + ClearBuffer(); + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOERROR, 0); + //Send the initial request + if (m_ProxyData.nProxyType==PROXYTYPE_SOCKS4 || m_ProxyData.nProxyType==PROXYTYPE_SOCKS4A) + { //SOCKS4 proxy + //Send request + LPCSTR lpszAscii = m_pProxyPeerHost?m_pProxyPeerHost:""; + char *command=static_cast(nb_calloc(1, 9+strlen(lpszAscii)+1)); + memset(command,0,9+strlen(lpszAscii)+1); + int len=9; + command[0]=4; + command[1]=(m_nProxyOpID==PROXYOP_CONNECT)?1:2; //CONNECT or BIND request + memcpy(&command[2],&m_nProxyPeerPort,2); //Copy target address + if (!m_nProxyPeerIp || m_ProxyData.nProxyType==PROXYTYPE_SOCKS4A) + { + DebugAssert(m_ProxyData.nProxyType==PROXYTYPE_SOCKS4A); + DebugAssert(strcmp(lpszAscii, "")); + //Set the IP to 0.0.0.x (x is nonzero) + command[4]=0; + command[5]=0; + command[6]=0; + command[7]=1; + //Add host as URL + strcpy(&command[9],lpszAscii); + len+=strlen(lpszAscii)+1; + } + else + memcpy(&command[4],&m_nProxyPeerIp,4); + int res=SendNext(command,len); //Send command + nb_free(command); + int nErrorCode=WSAGetLastError(); + if (res==SOCKET_ERROR)//nErrorCode!=WSAEWOULDBLOCK) + { + ConnectionFailed((m_nProxyOpID == PROXYOP_CONNECT) && (nErrorCode == WSAEWOULDBLOCK) ? WSAECONNABORTED : nErrorCode); + return; + } + else if (res(nb_calloc(1, strlen(m_pProxyPeerHost)+1)); + strcpy(pHost, m_pProxyPeerHost); + } + else + { + pHost = static_cast(nb_calloc(1, 16)); + sprintf(pHost, "%d.%d.%d.%d", m_nProxyPeerIp%256, (m_nProxyPeerIp>>8) % 256, (m_nProxyPeerIp>>16) %256, m_nProxyPeerIp>>24); + } + if (!m_ProxyData.bUseLogon) + sprintf(str, "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n\r\n", pHost, ntohs(m_nProxyPeerPort), + pHost, ntohs(m_nProxyPeerPort)); + else + { + sprintf(str, "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", pHost, ntohs(m_nProxyPeerPort), + pHost, ntohs(m_nProxyPeerPort)); + + char userpass[4096]; + sprintf(userpass, "%s:%s", m_ProxyData.pProxyUser?m_ProxyData.pProxyUser:"", m_ProxyData.pProxyPass?m_ProxyData.pProxyPass:""); + + char base64str[4096]; + + CBase64Coding base64coding; + base64coding.Encode(userpass, strlen(userpass), base64str); + strcat(str, "Authorization: Basic "); + strcat(str, base64str); + strcat(str, "\r\nProxy-Authorization: Basic "); + strcat(str, base64str); + strcat(str, "\r\n\r\n"); + } + nb_free(pHost); + + USES_CONVERSION; + int numsent=SendNext(str, strlen(str) ); + int nErrorCode=WSAGetLastError(); + if (numsent==SOCKET_ERROR)//nErrorCode!=WSAEWOULDBLOCK) + { + ConnectionFailed((m_nProxyOpID == PROXYOP_CONNECT) && (nErrorCode == WSAEWOULDBLOCK) ? WSAECONNABORTED : nErrorCode); + return; + } + else if ( numsent < static_cast( strlen(str) ) ) + { + ConnectionFailed(WSAECONNABORTED); + return; + } + m_nProxyOpState++; + return; + } + else + DebugFail(); + //Now we'll wait for the response, handled in OnReceive + m_nProxyOpState++; + } +} + +void CAsyncProxySocketLayer::ClearBuffer() +{ + nb_free(m_pStrBuffer); + m_pStrBuffer = NULL; + if (m_pRecvBuffer) + { + nb_free(m_pRecvBuffer); + m_pRecvBuffer=0; + } + m_nRecvBufferLen=0; + m_nRecvBufferPos=0; +} + +BOOL CAsyncProxySocketLayer::Listen( int nConnectionBacklog) +{ + if (m_ProxyData.nProxyType==PROXYTYPE_NOPROXY) + return ListenNext(nConnectionBacklog); + + USES_CONVERSION; + + //Connect to proxy server + BOOL res = ConnectNext(A2T(m_ProxyData.pProxyHost), m_ProxyData.nProxyPort); + if (!res) + { + if (WSAGetLastError()!=WSAEWOULDBLOCK) + { + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, PROXYERROR_NOCONN, 0); + return FALSE; + } + } + m_nProxyPeerPort=0; + m_nProxyPeerIp=(unsigned int)nConnectionBacklog; + + m_nProxyOpID=PROXYOP_LISTEN; + return TRUE; +} + +BOOL CAsyncProxySocketLayer::GetPeerName(CString &rPeerAddress, UINT &rPeerPort) +{ + if (m_ProxyData.nProxyType==PROXYTYPE_NOPROXY) + { + return GetPeerNameNext(rPeerAddress, rPeerPort); + } + + if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState()!=connected) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + else if (!m_nProxyPeerIp || !m_nProxyPeerPort) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + DebugAssert(m_ProxyData.nProxyType); + BOOL res=GetPeerNameNext( rPeerAddress, rPeerPort ); + if (res) + { + rPeerPort=ntohs(m_nProxyPeerPort); + rPeerAddress.Format(L"%d.%d.%d.%d", m_nProxyPeerIp%256,(m_nProxyPeerIp>>8)%256,(m_nProxyPeerIp>>16)%256, m_nProxyPeerIp>>24); + } + return res; +} + +BOOL CAsyncProxySocketLayer::GetPeerName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if (m_ProxyData.nProxyType==PROXYTYPE_NOPROXY) + { + return GetPeerNameNext(lpSockAddr, lpSockAddrLen); + } + + if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState()!=connected) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + else if (!m_nProxyPeerIp || !m_nProxyPeerPort) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + DebugAssert(m_ProxyData.nProxyType); + BOOL res=GetPeerNameNext(lpSockAddr,lpSockAddrLen); + if (res) + { + LPSOCKADDR_IN addr=(LPSOCKADDR_IN)lpSockAddr; + addr->sin_port=m_nProxyPeerPort; + addr->sin_addr.S_un.S_addr=m_nProxyPeerIp; + } + return res; +} + +void CAsyncProxySocketLayer::Close() +{ + nb_free(m_ProxyData.pProxyHost); + nb_free(m_ProxyData.pProxyUser); + nb_free(m_ProxyData.pProxyPass); + nb_free(m_pProxyPeerHost); + m_ProxyData.pProxyHost = NULL; + m_ProxyData.pProxyUser = NULL; + m_ProxyData.pProxyPass = NULL; + m_pProxyPeerHost = NULL; + ClearBuffer(); + Reset(); + CloseNext(); +} + +void CAsyncProxySocketLayer::Reset() +{ + m_nProxyOpState=0; + m_nProxyOpID=0; +} + +int CAsyncProxySocketLayer::Send(const void* lpBuf, int nBufLen, int nFlags) +{ + if (m_nProxyOpID) + { + WSASetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + + return SendNext(lpBuf, nBufLen, nFlags); +} + +int CAsyncProxySocketLayer::Receive(void* lpBuf, int nBufLen, int nFlags) +{ + if (m_nProxyOpID) + { + WSASetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + + return ReceiveNext(lpBuf, nBufLen, nFlags); +} + +BOOL CAsyncProxySocketLayer::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + if (!m_ProxyData.nProxyType) + return AcceptNext(rConnectedSocket, lpSockAddr, lpSockAddrLen); + + GetPeerName(lpSockAddr, lpSockAddrLen); + return TRUE; +} diff --git a/netbox/src/filezilla/AsyncProxySocketLayer.h b/netbox/src/filezilla/AsyncProxySocketLayer.h new file mode 100644 index 000000000..75983cc02 --- /dev/null +++ b/netbox/src/filezilla/AsyncProxySocketLayer.h @@ -0,0 +1,183 @@ +/*CAsyncProxySocketLayer by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.6 (2003-03-26) +-------------------------------------------------------- + +Introduction: +------------- + +This class is layer class for CAsyncSocketEx. With this class you +can connect through SOCKS4/5 and HTTP 1.1 proxies. This class works +as semi-transparent layer between CAsyncSocketEx and the actual socket. +This class is used in FileZilla, a powerful open-source FTP client. +It can be found under http://sourceforge.net/projects/filezilla +For more information about SOCKS4/5 goto +http://www.socks.nec.com/socksprot.html +For more information about HTTP 1.1 goto http://www.rfc-editor.org +and search for RFC2616 + +How to use? +----------- + +You don't have to change much in you already existing code to use +CAsyncProxySocketLayer. +To use it, create an instance of CAsyncProxySocketLayer, call SetProxy +and attach it to a CAsyncSocketEx instance. +You have to process OnLayerCallback in you CAsyncSocketEx instance as it will +receive all layer nofications. +The following notifications are sent: + +//Error codes +PROXYERROR_NOERROR 0 +PROXYERROR_NOCONN 1 //Can't connect to proxy server, use GetLastError for more information +PROXYERROR_REQUESTFAILED 2 //Request failed, can't send data +PROXYERROR_AUTHREQUIRED 3 //Authentication required +PROXYERROR_AUTHTYPEUNKNOWN 4 //Authtype unknown or not supported +PROXYERROR_AUTHFAILED 5 //Authentication failed +PROXYERROR_AUTHNOLOGON 6 +PROXYERROR_CANTRESOLVEHOST 7 + +//Status messages +PROXYSTATUS_LISTENSOCKETCREATED 8 //Called when a listen socket was created successfully. Unlike the normal listen function, + //a socksified socket has to connect to the proxy to negotiate the details with the server + //on which the listen socket will be created + //The two parameters will contain the ip and port of the listen socket on the server. + +Description of important functions and their parameters: +-------------------------------------------------------- + +void SetProxy(int nProxyType, const char * ProxyHost, int nProxyPort); +void SetProxy(int nProxyType, const char *, int nProxyPort, const char * ProxyUser, const char * ProxyPass); + +Call one of this functions to set the proxy type. +Parameters: +- nProxyType specifies the Proxy Type. +- ProxyHost and nProxyPort specify the address of the proxy +- ProxyUser and ProxyPass are only available for SOCKS5 proxies. + +supported proxy types: +PROXYTYPE_NOPROXY +PROXYTYPE_SOCKS4 +PROXYTYPE_SOCKS4A +PROXYTYPE_SOCKS5 +PROXYTYPE_HTTP11 + +There are also some other functions: + +GetProxyPeerName +Like GetPeerName of CAsyncSocket, but returns the address of the +server connected through the proxy. If using proxies, GetPeerName +only returns the address of the proxy. + +int GetProxyType(); +Returns the used proxy + +const int GetLastProxyError() const; +Returns the last proxy error + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ + +#ifndef AsyncProxySocketLayerH +#define AsyncProxySocketLayerH + +#include "AsyncSocketExLayer.h" + +class CAsyncProxySocketLayer : public CAsyncSocketExLayer +{ +public: + +public: + CAsyncProxySocketLayer(); + virtual ~CAsyncProxySocketLayer(); + +public: + virtual void Close(); + virtual BOOL Connect(LPCTSTR lpHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR * lpSockAddr, int nSockAddrLen); + virtual BOOL Listen(int nConnectionBacklog); + + // Sets the proxy details. + // nProxyType - Type of the proxy. May be PROXYTYPE_NONE, PROXYTYPE_SOCKS4, PROXYTYPE_SOCKS5 or PROXYTYPE_HTTP11 + // ProxyHost - The address of the proxy. Can be either IP or URL + // ProxyPort - The port of the proxy + // ProxyUser - the username for SOCKS5 proxies + // ProxyPass - the password for SOCKS5 proxies + void SetProxy(int nProxyType, const char * pProxyHost, int ProxyPort, bool bUseLogon, const char * pProxyUser, const char * pProxyPass); + + // Returns the address of the server behind the SOCKS proxy you are connected to + virtual BOOL GetPeerName(CString & rPeerAddress, UINT & rPeerPort); + virtual BOOL GetPeerName(SOCKADDR * lpSockAddr, int * lpSockAddrLen); + +protected: + virtual BOOL Accept(CAsyncSocketEx & rConnectedSocket, SOCKADDR * lpSockAddr = NULL, int * lpSockAddrLen = NULL); + virtual void OnReceive(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual int Send(const void * lpBuf, int nBufLen, int nFlags = 0); + virtual int Receive(void * lpBuf, int nBufLen, int nFlags = 0); + +private: + void Reset(); + void ClearBuffer(); // Clears the receive buffer + void ConnectionEstablished(); + void ConnectionFailed(int nErrorCode, char * Str = NULL); + char *m_pRecvBuffer; // The receive buffer + int m_nRecvBufferLen; // Length of the RecvBuffer + int m_nRecvBufferPos; // Position within the receive buffer + char *m_pStrBuffer; // Recvbuffer needed by HTTP1.1 proxy + int m_nProxyOpState; // State of an operation + int m_nProxyOpID; // Currently active operation (0 if none) + int m_nProxyPeerPort; // Port of the server you are connected to, retrieve via GetPeerName + ULONG m_nProxyPeerIp; // IP of the server you are connected to, retrieve via GetPeerName + typedef struct + { + int nProxyType; + char * pProxyHost; + int nProxyPort; + char * pProxyUser; + char * pProxyPass; + BOOL bUseLogon; + } t_proxydata; // This structure will be used to hold the proxy details + + t_proxydata m_ProxyData; // Structure to hold the data set by SetProxy + char * m_pProxyPeerHost; // The host connected to +}; + +// Errorcodes +#define PROXYERROR_NOERROR 0 +#define PROXYERROR_NOCONN 1 //Can't connect to proxy server, use GetLastError for more information +#define PROXYERROR_REQUESTFAILED 2 //Request failed, can't send data +#define PROXYERROR_AUTHREQUIRED 3 //Authentication required +#define PROXYERROR_AUTHTYPEUNKNOWN 4 //Authtype unknown or not supported +#define PROXYERROR_AUTHFAILED 5 //Authentication failed +#define PROXYERROR_AUTHNOLOGON 6 +#define PROXYERROR_CANTRESOLVEHOST 7 + +// Status messages +// Called when a listen socket was created successfully. Unlike the normal listen function, +// a socksified socket has to connect to the proxy to negotiate the details with the server +// on which the listen socket will be created +// The two parameters will contain the ip and port of the listen socket on the server. +#define PROXYSTATUS_LISTENSOCKETCREATED 8 +struct t_ListenSocketCreatedStruct +{ + unsigned long ip; + UINT nPort; +}; + +// Proxytypes +#define PROXYTYPE_NOPROXY 0 +#define PROXYTYPE_SOCKS4 1 +#define PROXYTYPE_SOCKS4A 2 +#define PROXYTYPE_SOCKS5 3 +#define PROXYTYPE_HTTP11 4 + +#define PROXYOP_CONNECT 1 +#define PROXYOP_LISTEN 2 + +#endif // AsyncProxySocketLayerH diff --git a/netbox/src/filezilla/AsyncSocketEx.cpp b/netbox/src/filezilla/AsyncSocketEx.cpp new file mode 100644 index 000000000..51e8802da --- /dev/null +++ b/netbox/src/filezilla/AsyncSocketEx.cpp @@ -0,0 +1,1703 @@ +// CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) +// Version 1.3 (2003-04-26) + +// Feel free to use this class, as long as you don't claim that you wrote it +// and this copyright notice stays intact in the source files. +// If you use this class in commercial applications, please send a short message +// to tim.kosse@gmx.de + +#include "stdafx.h" +#include "AsyncSocketEx.h" + +#include "AsyncSocketExLayer.h" + +#ifndef GWL_USERDATA +#define GWL_USERDATA (-21) +#endif + +#undef TRACE_TRANSMIT + +CCriticalSectionWrapper CAsyncSocketEx::m_sGlobalCriticalSection; +CAsyncSocketEx::t_AsyncSocketExThreadDataList *CAsyncSocketEx::m_spAsyncSocketExThreadDataList = 0; + + +///////////////////////////// +//Helper Window class +#define WM_SOCKETEX_NOTIFY (WM_USER+3) +#define MAX_SOCKETS (0xBFFF-WM_SOCKETEX_NOTIFY+1) + +class CAsyncSocketExHelperWindow : public TObject +{ +public: + CAsyncSocketExHelperWindow(CAsyncSocketEx::t_AsyncSocketExThreadData* pThreadData) + { + //Initialize data + m_pAsyncSocketExWindowData = static_cast(nb_calloc(512, sizeof(t_AsyncSocketExWindowData))); //Reserve space for 512 active sockets + memset(m_pAsyncSocketExWindowData, 0, 512*sizeof(t_AsyncSocketExWindowData)); + m_nWindowDataSize=512; + m_nSocketCount=0; + m_nWindowDataPos=0; + m_pThreadData = pThreadData; + + //Create window + WNDCLASSEX wndclass; + wndclass.cbSize=sizeof wndclass; + wndclass.style=0; + wndclass.lpfnWndProc=WindowProc; + wndclass.cbClsExtra=0; + wndclass.cbWndExtra=0; + wndclass.hInstance=GetModuleHandle(0); + wndclass.hIcon=0; + wndclass.hCursor=0; + wndclass.hbrBackground=0; + wndclass.lpszMenuName=0; + wndclass.lpszClassName=L"CAsyncSocketEx Helper Window"; + wndclass.hIconSm=0; + + #ifdef _DEBUG + ATOM ClassAtom = + #endif + RegisterClassEx(&wndclass); + + m_hWnd=CreateWindow(L"CAsyncSocketEx Helper Window", L"CAsyncSocketEx Helper Window", 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(0), 0); + DebugAssert(m_hWnd); + SetWindowLongPtr(m_hWnd, GWL_USERDATA, (LONG_PTR)this); + }; + + virtual ~CAsyncSocketExHelperWindow() + { + //Clean up socket storage + nb_free(m_pAsyncSocketExWindowData); + m_pAsyncSocketExWindowData=0; + m_nWindowDataSize=0; + m_nSocketCount=0; + + //Destroy window + if (m_hWnd) + { + DestroyWindow(m_hWnd); + m_hWnd=0; + } + } + + //Adds a socket to the list of attached sockets + BOOL AddSocket(CAsyncSocketEx *pSocket, int &nSocketIndex) + { + DebugAssert(pSocket); + if (!m_nWindowDataSize) + { + DebugAssert(!m_nSocketCount); + m_nWindowDataSize=512; + m_pAsyncSocketExWindowData=static_cast(nb_calloc(512, sizeof(t_AsyncSocketExWindowData))); //Reserve space for 512 active sockets + memset(m_pAsyncSocketExWindowData, 0, 512*sizeof(t_AsyncSocketExWindowData)); + } + + if (nSocketIndex!=-1) + { + DebugAssert(m_pAsyncSocketExWindowData); + DebugAssert(m_nWindowDataSize>nSocketIndex); + DebugAssert(m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket==pSocket); + DebugAssert(m_nSocketCount); + return TRUE; + } + + //Increase socket storage if too small + if (m_nSocketCount>=(m_nWindowDataSize-10)) + { + int nOldWindowDataSize=m_nWindowDataSize; + DebugAssert(m_nWindowDataSizeMAX_SOCKETS) + m_nWindowDataSize=MAX_SOCKETS; + t_AsyncSocketExWindowData *tmp=m_pAsyncSocketExWindowData; + m_pAsyncSocketExWindowData = static_cast(nb_calloc(m_nWindowDataSize, sizeof(t_AsyncSocketExWindowData))); + memcpy(m_pAsyncSocketExWindowData, tmp, nOldWindowDataSize * sizeof(t_AsyncSocketExWindowData)); + memset(m_pAsyncSocketExWindowData+nOldWindowDataSize, 0, (m_nWindowDataSize-nOldWindowDataSize)*sizeof(t_AsyncSocketExWindowData)); + nb_free(tmp); + } + + //Search for free slot + for (int i=m_nWindowDataPos;i<(m_nWindowDataSize+m_nWindowDataPos);i++) + { + if (!m_pAsyncSocketExWindowData[i%m_nWindowDataSize].m_pSocket) + { + m_pAsyncSocketExWindowData[i%m_nWindowDataSize].m_pSocket=pSocket; + nSocketIndex=i%m_nWindowDataSize; + m_nWindowDataPos=(i+1)%m_nWindowDataSize; + m_nSocketCount++; + return TRUE; + } + } + + //No slot found, maybe there are too much sockets! + return FALSE; + } + + //Removes a socket from the socket storage + BOOL RemoveSocket(CAsyncSocketEx *pSocket, int &nSocketIndex) + { + DebugAssert(pSocket); + if (nSocketIndex==-1) + return TRUE; + + // Remove additional messages from queue + MSG msg; + while (PeekMessage(&msg, m_hWnd, WM_SOCKETEX_NOTIFY + nSocketIndex, WM_SOCKETEX_NOTIFY + nSocketIndex, PM_REMOVE)); + + DebugAssert(m_pAsyncSocketExWindowData); + DebugAssert(m_nWindowDataSize>0); + DebugAssert(m_nSocketCount>0); + DebugAssert(m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket==pSocket); + m_pAsyncSocketExWindowData[nSocketIndex].m_pSocket=0; + nSocketIndex=-1; + m_nSocketCount--; + + return TRUE; + } + + void RemoveLayers(CAsyncSocketEx *pOrigSocket) + { + // Remove all layer messages from old socket + std::list msgList; + MSG msg; + while (PeekMessage(&msg, m_hWnd, WM_USER, WM_USER, PM_REMOVE)) + { + //Verify parameters, lookup socket and notification message + //Verify parameters + if (msg.wParam >= static_cast(m_nWindowDataSize)) //Index is within socket storage + continue; + + CAsyncSocketEx *pSocket = m_pAsyncSocketExWindowData[msg.wParam].m_pSocket; + CAsyncSocketExLayer::t_LayerNotifyMsg *pMsg=(CAsyncSocketExLayer::t_LayerNotifyMsg *)msg.lParam; + if (!pMsg || !pSocket || pSocket == pOrigSocket || pSocket->m_SocketData.hSocket != pMsg->hSocket) + { + delete pMsg; + continue; + } + + msgList.push_back(msg); + } + + for (std::list::iterator iter = msgList.begin(); iter != msgList.end(); iter++) + { + PostMessage(m_hWnd, iter->message, iter->wParam, iter->lParam); + } + } + + //Processes event notifications sent by the sockets or the layers + static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + //Verify parameters + DebugAssert(hWnd); + if (!hWnd) + return 0; + CAsyncSocketExHelperWindow *pWnd=(CAsyncSocketExHelperWindow *)GetWindowLongPtr(hWnd, GWL_USERDATA); + if (message>=WM_SOCKETEX_NOTIFY) + { + DebugAssert(pWnd); + + if (message(WM_SOCKETEX_NOTIFY+pWnd->m_nWindowDataSize)) //Index is within socket storage + { + //Lookup socket and verify if it's valid + CAsyncSocketEx *pSocket=pWnd->m_pAsyncSocketExWindowData[message-WM_SOCKETEX_NOTIFY].m_pSocket; + SOCKET hSocket=wParam; + if (!pSocket) + return 0; + if (hSocket==INVALID_SOCKET) + return 0; + if (pSocket->m_SocketData.hSocket != hSocket) + return 0; + + int nEvent=lParam&0xFFFF; + int nErrorCode=lParam>>16; + + //Dispatch notification + if (!pSocket->m_pFirstLayer) + { + //Dispatch to CAsyncSocketEx instance + switch (nEvent) + { + case FD_READ: + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_READ; + break; + } + else if (pSocket->GetState() == attached) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + + // Ignore further FD_READ events after FD_CLOSE has been received + if (pSocket->m_SocketData.onCloseCalled) + break; + + if (pSocket->m_lEvent & FD_READ) + { + DWORD nBytes = 0; + if (!nErrorCode) + if (!pSocket->IOCtl(FIONREAD, &nBytes)) + nErrorCode = WSAGetLastError(); + if (nErrorCode) + { + pSocket->SetState(aborted); + } + if (nBytes != 0 || nErrorCode != 0) + pSocket->OnReceive(nErrorCode); + } + break; + case FD_FORCEREAD: //Forceread does not check if there's data waiting + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_FORCEREAD; + break; + } + else if (pSocket->GetState() == attached) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + if (pSocket->m_lEvent & FD_READ) + { + if (nErrorCode) + { + pSocket->SetState(aborted); + } + pSocket->OnReceive(nErrorCode); + } + break; + case FD_WRITE: + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_WRITE; + break; + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + if (pSocket->m_lEvent & FD_WRITE) + { + if (nErrorCode) + { + pSocket->SetState(aborted); + } + pSocket->OnSend(nErrorCode); + } + break; + case FD_CONNECT: + if (pSocket->GetState() == connecting) + { + if (nErrorCode && pSocket->m_SocketData.nextAddr) + { + if (pSocket->TryNextProtocol()) + break; + } + pSocket->SetState(connected); + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->m_lEvent & FD_CONNECT) + pSocket->OnConnect(nErrorCode); + if (!nErrorCode) + { + if ((pSocket->m_nPendingEvents&FD_READ) && pSocket->GetState() == connected) + pSocket->OnReceive(0); + if ((pSocket->m_nPendingEvents&FD_FORCEREAD) && pSocket->GetState() == connected) + pSocket->OnReceive(0); + if ((pSocket->m_nPendingEvents&FD_WRITE) && pSocket->GetState() == connected) + pSocket->OnSend(0); + } + pSocket->m_nPendingEvents = 0; + break; + case FD_ACCEPT: + if (pSocket->GetState() != listening && pSocket->GetState() != attached) + break; + if (pSocket->m_lEvent & FD_ACCEPT) + pSocket->OnAccept(nErrorCode); + break; + case FD_CLOSE: + if (pSocket->GetState() != connected && pSocket->GetState() != attached) + break; + + // If there are still bytes left to read, call OnReceive instead of + // OnClose and trigger a new OnClose + DWORD nBytes = 0; + if (!nErrorCode && pSocket->IOCtl(FIONREAD, &nBytes)) + { + if (nBytes > 0) + { + // Just repeat message. + PostMessage(hWnd, message, wParam, lParam); + pSocket->m_SocketData.onCloseCalled = true; + pSocket->OnReceive(WSAESHUTDOWN); + break; + } + } + + pSocket->SetState(nErrorCode?aborted:closed); + pSocket->OnClose(nErrorCode); + break; + } + } + else //Dispatch notification to the lowest layer + { + if (nEvent == FD_READ) + { + // Ignore further FD_READ events after FD_CLOSE has been received + if (pSocket->m_SocketData.onCloseCalled) + return 0; + + DWORD nBytes; + if (!pSocket->IOCtl(FIONREAD, &nBytes)) + nErrorCode = WSAGetLastError(); + if (nBytes != 0 || nErrorCode != 0) + pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode); + } + else if (nEvent == FD_CLOSE) + { + // If there are still bytes left to read, call OnReceive instead of + // OnClose and trigger a new OnClose + DWORD nBytes = 0; + if (!nErrorCode && pSocket->IOCtl(FIONREAD, &nBytes)) + { + if (nBytes > 0) + { + // Just repeat message. + pSocket->ResendCloseNotify(); + pSocket->m_pLastLayer->CallEvent(FD_READ, 0); + return 0; + } + } + pSocket->m_SocketData.onCloseCalled = true; + pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode); + } + else + { + pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode); + } + } + } + return 0; + } + else if (message == WM_USER) //Notification event sent by a layer + { + DebugAssert(pWnd); + + if (wParam >= static_cast(pWnd->m_nWindowDataSize)) //Index is within socket storage + { + return 0; + } + + CAsyncSocketEx *pSocket=pWnd->m_pAsyncSocketExWindowData[wParam].m_pSocket; + CAsyncSocketExLayer::t_LayerNotifyMsg *pMsg=(CAsyncSocketExLayer::t_LayerNotifyMsg *)lParam; + if (!pMsg || !pSocket || pSocket->m_SocketData.hSocket != pMsg->hSocket) + { + delete pMsg; + return 0; + } + int nEvent=pMsg->lEvent&0xFFFF; + int nErrorCode=pMsg->lEvent>>16; + + //Dispatch to layer + if (pMsg->pLayer) + { + pMsg->pLayer->CallEvent(nEvent, nErrorCode); + } + else + { + //Dispatch to CAsyncSocketEx instance + switch (nEvent) + { + case FD_READ: + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_READ; + break; + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + if (pSocket->m_lEvent & FD_READ) + { + if (nErrorCode) + { + pSocket->SetState(aborted); + } + pSocket->OnReceive(nErrorCode); + } + break; + case FD_FORCEREAD: //Forceread does not check if there's data waiting + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_FORCEREAD; + break; + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + if (pSocket->m_lEvent & FD_READ) + { + if (nErrorCode) + { + pSocket->SetState(aborted); + } + pSocket->OnReceive(nErrorCode); + } + break; + case FD_WRITE: + if (pSocket->GetState() == connecting && !nErrorCode) + { + pSocket->m_nPendingEvents |= FD_WRITE; + break; + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->GetState() != connected) + break; + if (pSocket->m_lEvent & FD_WRITE) + { + if (nErrorCode) + { + pSocket->SetState(aborted); + } + pSocket->OnSend(nErrorCode); + } + break; + case FD_CONNECT: + if (pSocket->GetState() == connecting) + { + pSocket->SetState(connected); + } + else if (pSocket->GetState() == attached && !nErrorCode) + { + pSocket->SetState(connected); + } + if (pSocket->m_lEvent & FD_CONNECT) + pSocket->OnConnect(nErrorCode); + if (!nErrorCode) + { + if (((pSocket->m_nPendingEvents&FD_READ) && pSocket->GetState() == connected) && (pSocket->m_lEvent & FD_READ)) + pSocket->OnReceive(0); + if (((pSocket->m_nPendingEvents&FD_FORCEREAD) && pSocket->GetState() == connected) && (pSocket->m_lEvent & FD_READ)) + pSocket->OnReceive(0); + if (((pSocket->m_nPendingEvents&FD_WRITE) && pSocket->GetState() == connected) && (pSocket->m_lEvent & FD_WRITE)) + pSocket->OnSend(0); + } + pSocket->m_nPendingEvents = 0; + break; + case FD_ACCEPT: + if ((pSocket->GetState() == listening || pSocket->GetState() == attached) && (pSocket->m_lEvent & FD_ACCEPT)) + { + pSocket->OnAccept(nErrorCode); + } + break; + case FD_CLOSE: + if ((pSocket->GetState() == connected || pSocket->GetState() == attached) && (pSocket->m_lEvent & FD_CLOSE)) + { + pSocket->SetState(nErrorCode?aborted:closed); + pSocket->OnClose(nErrorCode); + } + break; + } + } + delete pMsg; + return 0; + } + else if (message == WM_USER+1) + { + DebugAssert(pWnd); + // WSAAsyncGetHostByName reply + + // Verify parameters + DebugAssert(hWnd); + CAsyncSocketExHelperWindow *pWnd = (CAsyncSocketExHelperWindow *)GetWindowLongPtr(hWnd, GWL_USERDATA); + DebugAssert(pWnd); + + CAsyncSocketEx *pSocket = NULL; + for (int i = 0; i < pWnd->m_nWindowDataSize; i++) + { + pSocket = pWnd->m_pAsyncSocketExWindowData[i].m_pSocket; + if (pSocket && pSocket->m_hAsyncGetHostByNameHandle && + pSocket->m_hAsyncGetHostByNameHandle == (HANDLE)wParam) + break; + } + if (!pSocket) + return 0; + + int nErrorCode = lParam >> 16; + if (nErrorCode) + { + pSocket->OnConnect(nErrorCode); + return 0; + } + + SOCKADDR_IN sockAddr; + memset(&sockAddr,0,sizeof(sockAddr)); + sockAddr.sin_family=AF_INET; + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)((LPHOSTENT)pSocket->m_pAsyncGetHostByNameBuffer)->h_addr)->s_addr; + + sockAddr.sin_port = htons(pSocket->m_nAsyncGetHostByNamePort); + + BOOL res = pSocket->Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + nb_free(pSocket->m_pAsyncGetHostByNameBuffer); + pSocket->m_pAsyncGetHostByNameBuffer=0; + pSocket->m_hAsyncGetHostByNameHandle=0; + + if (!res) + if (GetLastError()!=WSAEWOULDBLOCK) + pSocket->OnConnect(GetLastError()); + return 0; + } + else if (message == WM_USER + 2) + { + DebugAssert(pWnd); + if (wParam >= static_cast(pWnd->m_nWindowDataSize)) //Index is within socket storage + return 0; + + CAsyncSocketEx *pSocket = pWnd->m_pAsyncSocketExWindowData[wParam].m_pSocket; + if (!pSocket) + return 0; + + // Process pending callbacks + std::list tmp; + std::swap(tmp, pSocket->m_pendingCallbacks); + pSocket->OnLayerCallback(tmp); + } + else if (message == WM_TIMER) + { + DebugAssert(pWnd); + if (wParam != 1) + return 0; + + if (pWnd->m_pThreadData->layerCloseNotify.empty()) + { + KillTimer(hWnd, 1); + return 0; + } + CAsyncSocketEx* socket = pWnd->m_pThreadData->layerCloseNotify.front(); + pWnd->m_pThreadData->layerCloseNotify.pop_front(); + if (pWnd->m_pThreadData->layerCloseNotify.empty()) + KillTimer(hWnd, 1); + + PostMessage(hWnd, socket->m_SocketData.nSocketIndex + WM_SOCKETEX_NOTIFY, socket->m_SocketData.hSocket, FD_CLOSE); + return 0; + } + return DefWindowProc(hWnd, message, wParam, lParam); + } + + HWND GetHwnd() + { + return m_hWnd; + } + +private: + HWND m_hWnd; + struct t_AsyncSocketExWindowData + { + CAsyncSocketEx *m_pSocket; + } *m_pAsyncSocketExWindowData; + int m_nWindowDataSize; + int m_nWindowDataPos; + int m_nSocketCount; + CAsyncSocketEx::t_AsyncSocketExThreadData* m_pThreadData; +}; + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CAsyncSocketEx::CAsyncSocketEx() +{ + m_SocketData.hSocket = INVALID_SOCKET; + m_SocketData.nSocketIndex = -1; + m_SocketData.nFamily = AF_UNSPEC; + m_SocketData.onCloseCalled = false; + m_pLocalAsyncSocketExThreadData = 0; + + m_nPendingEvents = 0; + m_nState = notsock; + + m_pFirstLayer = 0; + m_pLastLayer = 0; + m_lEvent = 0; + m_pAsyncGetHostByNameBuffer = NULL; + m_hAsyncGetHostByNameHandle = NULL; + m_nAsyncGetHostByNamePort = 0; + + m_nSocketPort = 0; + m_lpszSocketAddress = 0; + + m_SocketData.addrInfo = 0; + m_SocketData.nextAddr = 0; +} + +CAsyncSocketEx::~CAsyncSocketEx() +{ + Close(); + FreeAsyncSocketExInstance(); +} + +BOOL CAsyncSocketEx::Create(UINT nSocketPort /*=0*/, int nSocketType /*=SOCK_STREAM*/, long lEvent /*=FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/, LPCTSTR lpszSocketAddress /*=NULL*/, int nFamily /*=AF_INET*/) +{ + DebugAssert(GetSocketHandle() == INVALID_SOCKET); + + //Close the socket, although this should not happen + if (GetSocketHandle() != INVALID_SOCKET) + { + WSASetLastError(WSAEALREADY); + return FALSE; + } + + BOOL res = InitAsyncSocketExInstance(); + DebugAssert(res); + if (!res) + { + WSASetLastError(WSANOTINITIALISED); + return FALSE; + } + + m_SocketData.nFamily = nFamily; + + if (m_pFirstLayer) + { + BOOL res = m_pFirstLayer->Create(nSocketPort, nSocketType, lEvent, lpszSocketAddress, nFamily); + if (res) + { + SetState(unconnected); + } + return res; + } + else + { + if (m_SocketData.nFamily == AF_UNSPEC) + { + SetState(unconnected); + m_lEvent = lEvent; + + m_nSocketPort = nSocketPort; + + nb_free(m_lpszSocketAddress); + if (lpszSocketAddress && *lpszSocketAddress) + { + m_lpszSocketAddress = static_cast(nb_calloc(_tcslen(lpszSocketAddress) + 1, sizeof(TCHAR))); + _tcscpy(m_lpszSocketAddress, lpszSocketAddress); + } + else + m_lpszSocketAddress = 0; + + return TRUE; + } + else + { + SOCKET hSocket = socket(m_SocketData.nFamily, nSocketType, 0); + if (hSocket == INVALID_SOCKET) + return FALSE; + m_SocketData.hSocket = hSocket; + AttachHandle(hSocket); + + if (m_pFirstLayer) + { + m_lEvent = lEvent; + if (WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE) ) + { + Close(); + return FALSE; + } + } + else + { + if (!AsyncSelect(lEvent)) + { + Close(); + return FALSE; + } + } + + if (!Bind(nSocketPort, lpszSocketAddress)) + { + Close(); + return FALSE; + } + + SetState(unconnected); + + return TRUE; + } + } +} + +void CAsyncSocketEx::OnReceive(int nErrorCode) +{ +} + +void CAsyncSocketEx::OnSend(int nErrorCode) +{ +} + +void CAsyncSocketEx::OnConnect(int nErrorCode) +{ +} + +void CAsyncSocketEx::OnAccept(int nErrorCode) +{ +} + +void CAsyncSocketEx::OnClose(int nErrorCode) +{ +} + +BOOL CAsyncSocketEx::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress) +{ + nb_free(m_lpszSocketAddress); + if (lpszSocketAddress && *lpszSocketAddress) + { + m_lpszSocketAddress = static_cast(nb_calloc(_tcslen(lpszSocketAddress) + 1, sizeof(TCHAR))); + _tcscpy(m_lpszSocketAddress, lpszSocketAddress); + } + else + m_lpszSocketAddress = 0; + m_nSocketPort = nSocketPort; + + if (m_SocketData.nFamily == AF_UNSPEC) + return TRUE; + + USES_CONVERSION; + + LPSTR lpszAscii = (lpszSocketAddress && *lpszSocketAddress) ? T2A((LPTSTR)lpszSocketAddress) : 0; + + if ((m_SocketData.nFamily == AF_INET6 || m_SocketData.nFamily == AF_INET) && lpszAscii) + { + addrinfo hints, *res0, *res; + int error; + char port[10]; + BOOL ret = FALSE; + + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = m_SocketData.nFamily; + hints.ai_socktype = SOCK_STREAM; + _snprintf(port, 9, "%lu", nSocketPort); + error = p_getaddrinfo ? p_getaddrinfo(lpszAscii, port, &hints, &res0) : 1; + if (error) + return FALSE; + + for (res = res0; res; res = res->ai_next) + if (Bind(res->ai_addr, res->ai_addrlen)) + { + ret = TRUE; + break; + } + else + continue ; + + if (p_freeaddrinfo) p_freeaddrinfo(res0); + + return ret ; + } + else if (!lpszAscii && m_SocketData.nFamily == AF_INET6) + { + SOCKADDR_IN6 sockAddr6; + + memset(&sockAddr6, 0, sizeof(sockAddr6)); + sockAddr6.sin6_family = AF_INET6 ; + sockAddr6.sin6_addr = in6addr_any ; + sockAddr6.sin6_port = htons((u_short)nSocketPort); + + return Bind((SOCKADDR*)&sockAddr6, sizeof(sockAddr6)); + } + else if (!lpszAscii && m_SocketData.nFamily == AF_INET) + { + SOCKADDR_IN sockAddr; + + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET ; + sockAddr.sin_addr.s_addr = INADDR_ANY ; + sockAddr.sin_port = htons((u_short)nSocketPort); + + return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + } + else + return FALSE ; +} + +BOOL CAsyncSocketEx::Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ + if (!bind(m_SocketData.hSocket, lpSockAddr, nSockAddrLen)) + return TRUE; + else + return FALSE; +} + +void CAsyncSocketEx::AttachHandle(SOCKET hSocket) +{ + DebugAssert(m_pLocalAsyncSocketExThreadData); + DebugCheck(m_pLocalAsyncSocketExThreadData->m_pHelperWindow->AddSocket(this, m_SocketData.nSocketIndex)); + SetState(attached); +} + +void CAsyncSocketEx::DetachHandle(SOCKET hSocket) +{ + DebugAssert(m_pLocalAsyncSocketExThreadData); + if (!m_pLocalAsyncSocketExThreadData) + return; + DebugAssert(m_pLocalAsyncSocketExThreadData->m_pHelperWindow); + if (!m_pLocalAsyncSocketExThreadData->m_pHelperWindow) + return; + DebugCheck(m_pLocalAsyncSocketExThreadData->m_pHelperWindow->RemoveSocket(this, m_SocketData.nSocketIndex)); + SetState(notsock); +} + +void CAsyncSocketEx::Close() +{ + m_nPendingEvents = 0; + if (m_pFirstLayer) + m_pFirstLayer->Close(); + if (m_SocketData.hSocket != INVALID_SOCKET) + { + DebugCheck((closesocket(m_SocketData.hSocket) != SOCKET_ERROR)); + DetachHandle(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + } + if (m_SocketData.addrInfo) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_SocketData.addrInfo); + m_SocketData.addrInfo = 0; + m_SocketData.nextAddr = 0; + } + m_SocketData.nFamily = AF_UNSPEC; + nb_free(m_lpszSocketAddress); + m_lpszSocketAddress = 0; + m_nSocketPort = 0; + RemoveAllLayers(); + nb_free(m_pAsyncGetHostByNameBuffer); + m_pAsyncGetHostByNameBuffer = NULL; + if (m_hAsyncGetHostByNameHandle) + WSACancelAsyncRequest(m_hAsyncGetHostByNameHandle); + m_hAsyncGetHostByNameHandle = NULL; + m_SocketData.onCloseCalled = false; +} + +BOOL CAsyncSocketEx::InitAsyncSocketExInstance() +{ + //Check if already initialized + if (m_pLocalAsyncSocketExThreadData) + return TRUE; + + DWORD id=GetCurrentThreadId(); + + m_sGlobalCriticalSection.Lock(); + + //Get thread specific data + if (m_spAsyncSocketExThreadDataList) + { + t_AsyncSocketExThreadDataList *pList=m_spAsyncSocketExThreadDataList; + while (pList) + { + DebugAssert(pList->pThreadData); + DebugAssert(pList->pThreadData->nInstanceCount>0); + + if (pList->pThreadData->nThreadId==id) + { + m_pLocalAsyncSocketExThreadData=pList->pThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount++; + break; + } + pList=pList->pNext; + } + //Current thread yet has no sockets + if (!pList) + { + //Initialize data for current thread + pList=new t_AsyncSocketExThreadDataList; + pList->pNext=m_spAsyncSocketExThreadDataList; + m_spAsyncSocketExThreadDataList=pList; + m_pLocalAsyncSocketExThreadData=new t_AsyncSocketExThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount=1; + m_pLocalAsyncSocketExThreadData->nThreadId=id; + m_pLocalAsyncSocketExThreadData->m_pHelperWindow=new CAsyncSocketExHelperWindow(m_pLocalAsyncSocketExThreadData); + m_spAsyncSocketExThreadDataList->pThreadData=m_pLocalAsyncSocketExThreadData; + } + } + else + { //No thread has instances of CAsyncSocketEx; Initialize data + m_spAsyncSocketExThreadDataList=new t_AsyncSocketExThreadDataList; + m_spAsyncSocketExThreadDataList->pNext=0; + m_pLocalAsyncSocketExThreadData=new t_AsyncSocketExThreadData; + m_pLocalAsyncSocketExThreadData->nInstanceCount=1; + m_pLocalAsyncSocketExThreadData->nThreadId=id; + m_pLocalAsyncSocketExThreadData->m_pHelperWindow=new CAsyncSocketExHelperWindow(m_pLocalAsyncSocketExThreadData); + m_spAsyncSocketExThreadDataList->pThreadData=m_pLocalAsyncSocketExThreadData; + } + m_sGlobalCriticalSection.Unlock(); + return TRUE; +} + +void CAsyncSocketEx::FreeAsyncSocketExInstance() +{ + //Check if already freed + if (!m_pLocalAsyncSocketExThreadData) + return; + + for (std::list::iterator iter = m_pLocalAsyncSocketExThreadData->layerCloseNotify.begin(); iter != m_pLocalAsyncSocketExThreadData->layerCloseNotify.end(); iter++) + { + if (*iter != this) + continue; + + m_pLocalAsyncSocketExThreadData->layerCloseNotify.erase(iter); + if (m_pLocalAsyncSocketExThreadData->layerCloseNotify.empty()) + KillTimer(m_pLocalAsyncSocketExThreadData->m_pHelperWindow->GetHwnd(), 1); + break; + } + + DWORD id=m_pLocalAsyncSocketExThreadData->nThreadId; + m_sGlobalCriticalSection.Lock(); + + DebugAssert(m_spAsyncSocketExThreadDataList); + t_AsyncSocketExThreadDataList *pList=m_spAsyncSocketExThreadDataList; + t_AsyncSocketExThreadDataList *pPrev=0; + + //Serach for data for current thread and decrease instance count + while (pList) + { + DebugAssert(pList->pThreadData); + DebugAssert(pList->pThreadData->nInstanceCount>0); + + if (pList->pThreadData->nThreadId==id) + { + DebugAssert(m_pLocalAsyncSocketExThreadData==pList->pThreadData); + m_pLocalAsyncSocketExThreadData->nInstanceCount--; + + //Freeing last instance? + //If so, destroy helper window + if (!m_pLocalAsyncSocketExThreadData->nInstanceCount) + { + delete m_pLocalAsyncSocketExThreadData->m_pHelperWindow; + delete m_pLocalAsyncSocketExThreadData; + if (pPrev) + pPrev->pNext=pList->pNext; + else + m_spAsyncSocketExThreadDataList=pList->pNext; + delete pList; + break; + } + + break; + } + pPrev=pList; + pList=pList->pNext; + DebugAssert(pList); + } + + m_sGlobalCriticalSection.Unlock(); +} + +int CAsyncSocketEx::Receive(void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ + if (m_pFirstLayer) + return m_pFirstLayer->Receive(lpBuf, nBufLen, nFlags); + else + return recv(m_SocketData.hSocket, (LPSTR)lpBuf, nBufLen, nFlags); +} + + +int CAsyncSocketEx::Send(const void* lpBuf, int nBufLen, int nFlags /*=0*/, int nDupFF /*=0*/) +{ + const void* vBuf = lpBuf; + int vBufLen = nBufLen; + CStringA Buf; + if (nDupFF) + { + LPSTR strBuf = (LPSTR)lpBuf; + for (int n = 0; n < nBufLen; n++) + { + if(strBuf[n] == '\xFF') + { + Buf.AppendChar('\xFF'); + Buf.AppendChar('\xFF'); + } + else + Buf.AppendChar(strBuf[n]); + } + vBuf = Buf.GetBuffer(); + vBufLen = Buf.GetLength(); + } + if (m_pFirstLayer) + { + return m_pFirstLayer->Send(vBuf, vBufLen, nFlags); + } + else + { + return send(m_SocketData.hSocket, (LPSTR)vBuf, vBufLen, nFlags); + } +} + +BOOL CAsyncSocketEx::Connect(LPCTSTR lpszHostAddress, UINT nHostPort) +{ + if (m_pFirstLayer) + { + BOOL res = m_pFirstLayer->Connect(lpszHostAddress, nHostPort); + if (res || GetLastError()==WSAEWOULDBLOCK) + { + SetState(connecting); + } + return res; + } else + if (m_SocketData.nFamily == AF_INET) + { + USES_CONVERSION; + + DebugAssert(lpszHostAddress != NULL); + + SOCKADDR_IN sockAddr; + memset(&sockAddr,0,sizeof(sockAddr)); + + LPSTR lpszAscii = T2A((LPTSTR)lpszHostAddress); + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = inet_addr(lpszAscii); + + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + if (m_pAsyncGetHostByNameBuffer) + nb_free(m_pAsyncGetHostByNameBuffer); + m_pAsyncGetHostByNameBuffer=static_cast(nb_calloc(1, MAXGETHOSTSTRUCT)); + + m_nAsyncGetHostByNamePort=nHostPort; + + m_hAsyncGetHostByNameHandle=WSAAsyncGetHostByName(GetHelperWindowHandle(), WM_USER+1, lpszAscii, m_pAsyncGetHostByNameBuffer, MAXGETHOSTSTRUCT); + if (!m_hAsyncGetHostByNameHandle) + return FALSE; + + WSASetLastError(WSAEWOULDBLOCK); + SetState(connecting); + return FALSE; + } + + sockAddr.sin_port = htons((u_short)nHostPort); + + return CAsyncSocketEx::Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr)); + } + else + { + USES_CONVERSION; + + DebugAssert( lpszHostAddress != NULL ); + + if (m_SocketData.addrInfo) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_SocketData.addrInfo); + m_SocketData.addrInfo = 0; + m_SocketData.nextAddr = 0; + } + + addrinfo hints; + int error; + BOOL ret; + char port[10]; + + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = m_SocketData.nFamily; + hints.ai_socktype = SOCK_STREAM; + _snprintf(port, 9, "%lu", nHostPort); + error = p_getaddrinfo ? p_getaddrinfo(T2CA(lpszHostAddress), port, &hints, &m_SocketData.addrInfo) : 1; + if (error) + return FALSE; + + for (m_SocketData.nextAddr = m_SocketData.addrInfo; m_SocketData.nextAddr; m_SocketData.nextAddr = m_SocketData.nextAddr->ai_next) + { + bool newSocket = false; + if (m_SocketData.nFamily == AF_UNSPEC) + { + newSocket = true; + m_SocketData.hSocket = socket(m_SocketData.nextAddr->ai_family, m_SocketData.nextAddr->ai_socktype, m_SocketData.nextAddr->ai_protocol); + + if (m_SocketData.hSocket == INVALID_SOCKET) + continue; + + m_SocketData.nFamily = m_SocketData.nextAddr->ai_family; + AttachHandle(m_SocketData.hSocket); + if (!AsyncSelect(m_lEvent)) + { + if (newSocket) + { + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + } + continue; + } + } + else if (m_SocketData.hSocket == INVALID_SOCKET) + continue; + + if (m_pFirstLayer) + { + if (WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE)) + { + if (newSocket) + { + m_SocketData.nFamily = AF_UNSPEC; + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + } + continue; + } + } + + if (newSocket) + { + m_SocketData.nFamily = m_SocketData.nextAddr->ai_family; + if (!Bind(m_nSocketPort, m_lpszSocketAddress)) + { + m_SocketData.nFamily = AF_UNSPEC; + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + } + + if (!(ret = CAsyncSocketEx::Connect(m_SocketData.nextAddr->ai_addr, m_SocketData.nextAddr->ai_addrlen)) && GetLastError() != WSAEWOULDBLOCK) + { + if (newSocket) + { + m_SocketData.nFamily = AF_UNSPEC; + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + } + continue; + } + + break; + } + + if (m_SocketData.nextAddr) + m_SocketData.nextAddr = m_SocketData.nextAddr->ai_next; + + if (!m_SocketData.nextAddr) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_SocketData.addrInfo); + m_SocketData.nextAddr = 0; + m_SocketData.addrInfo = 0; + } + + if (m_SocketData.hSocket == INVALID_SOCKET || !ret) + return FALSE; + else + return TRUE; + } +} + +BOOL CAsyncSocketEx::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen) +{ + BOOL res; + if (m_pFirstLayer) + res = SOCKET_ERROR!=m_pFirstLayer->Connect(lpSockAddr, nSockAddrLen); + else + { + ConfigureSocket(); + res = SOCKET_ERROR!=connect(m_SocketData.hSocket, lpSockAddr, nSockAddrLen); + } + + if (res || GetLastError()==WSAEWOULDBLOCK) + { + SetState(connecting); + } + return res; +} + +BOOL CAsyncSocketEx::GetPeerName( CString& rPeerAddress, UINT& rPeerPort ) +{ + if (m_pFirstLayer) + return m_pFirstLayer->GetPeerName(rPeerAddress, rPeerPort); + + SOCKADDR* sockAddr = NULL; + int nSockAddrLen = 0; + + if (m_SocketData.nFamily == AF_INET6) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN6; + nSockAddrLen = sizeof(SOCKADDR_IN6); + } + else if (m_SocketData.nFamily == AF_INET) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN; + nSockAddrLen = sizeof(SOCKADDR_IN); + } + + memset(sockAddr, 0, nSockAddrLen); + + BOOL bResult = GetPeerName(sockAddr, &nSockAddrLen); + + if (bResult) + { + if (m_SocketData.nFamily == AF_INET6) + { + rPeerPort = ntohs(((SOCKADDR_IN6*)sockAddr)->sin6_port); + LPTSTR buf = Inet6AddrToString(((SOCKADDR_IN6*)sockAddr)->sin6_addr); + rPeerAddress = buf; + nb_free(buf); + } + else if (m_SocketData.nFamily == AF_INET) + { + rPeerPort = ntohs(((SOCKADDR_IN*)sockAddr)->sin_port); + rPeerAddress = inet_ntoa(((SOCKADDR_IN*)sockAddr)->sin_addr); + } + else + { + delete sockAddr; + return FALSE; + } + } + delete sockAddr; + + return bResult; +} + +BOOL CAsyncSocketEx::GetPeerName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if (m_pFirstLayer) + return m_pFirstLayer->GetPeerName(lpSockAddr, lpSockAddrLen); + + if (!getpeername(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen)) + { + return TRUE; + } + else + { + return FALSE; + } +} + +BOOL CAsyncSocketEx::GetSockName(CString& rSocketAddress, UINT& rSocketPort) +{ + SOCKADDR* sockAddr = NULL; + int nSockAddrLen = 0; + + if (m_SocketData.nFamily == AF_INET6) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN6; + nSockAddrLen = sizeof(SOCKADDR_IN6); + } + else if (m_SocketData.nFamily == AF_INET) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN; + nSockAddrLen = sizeof(SOCKADDR_IN); + } + + memset(sockAddr, 0, nSockAddrLen); + + BOOL bResult = GetSockName(sockAddr, &nSockAddrLen); + + if (bResult) + { + if (m_SocketData.nFamily == AF_INET6) + { + rSocketPort = ntohs(((SOCKADDR_IN6*)sockAddr)->sin6_port); + LPTSTR buf = Inet6AddrToString(((SOCKADDR_IN6*)sockAddr)->sin6_addr); + rSocketAddress = buf; + nb_free(buf); + } + else if (m_SocketData.nFamily == AF_INET) + { + rSocketPort = ntohs(((SOCKADDR_IN*)sockAddr)->sin_port); + rSocketAddress = inet_ntoa(((SOCKADDR_IN*)sockAddr)->sin_addr); + } + else + { + delete sockAddr; + return FALSE; + } + } + delete sockAddr; + + return bResult; +} + +BOOL CAsyncSocketEx::GetSockName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if ( !getsockname(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen) ) + return TRUE; + else + return FALSE; +} + +BOOL CAsyncSocketEx::ShutDown(int nHow /*=sends*/) +{ + if (m_pFirstLayer) + { + return m_pFirstLayer->ShutDown(); + } + else + { + if (!shutdown(m_SocketData.hSocket, nHow)) + return TRUE; + else + return FALSE; + } +} + +SOCKET CAsyncSocketEx::Detach() +{ + SOCKET socket = m_SocketData.hSocket; + DetachHandle(socket); + m_SocketData.hSocket = INVALID_SOCKET; + m_SocketData.nFamily = AF_UNSPEC; + return socket; +} + +BOOL CAsyncSocketEx::Attach(SOCKET hSocket, long lEvent /*= FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/) +{ + if (hSocket==INVALID_SOCKET || !hSocket) + return FALSE; + + DebugCheck(InitAsyncSocketExInstance()); + m_SocketData.hSocket=hSocket; + AttachHandle(hSocket); + + if (m_pFirstLayer) + { + m_lEvent = lEvent; + return !WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + } + else + { + return AsyncSelect(lEvent); + } +} + +BOOL CAsyncSocketEx::AsyncSelect( long lEvent /*= FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE*/ ) +{ + DebugAssert(m_pLocalAsyncSocketExThreadData); + m_lEvent = lEvent; + if (m_pFirstLayer) + return TRUE; + else + { + if (m_SocketData.hSocket == INVALID_SOCKET && m_SocketData.nFamily == AF_UNSPEC) + return true; + + if ( !WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, lEvent) ) + return TRUE; + else + return FALSE; + } + return TRUE; +} + +BOOL CAsyncSocketEx::Listen( int nConnectionBacklog /*=5*/ ) +{ + if (m_pFirstLayer) + return m_pFirstLayer->Listen(nConnectionBacklog); + + if (!listen(m_SocketData.hSocket, nConnectionBacklog)) + { + SetState(listening); + return TRUE; + } + else + return FALSE; +} + +BOOL CAsyncSocketEx::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + DebugAssert(rConnectedSocket.m_SocketData.hSocket == INVALID_SOCKET); + if (m_pFirstLayer) + { + return m_pFirstLayer->Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen); + } + else + { + SOCKET hTemp = accept(m_SocketData.hSocket, lpSockAddr, lpSockAddrLen); + + if (hTemp == INVALID_SOCKET) + return FALSE; + DebugCheck(rConnectedSocket.InitAsyncSocketExInstance()); + rConnectedSocket.m_SocketData.hSocket=hTemp; + rConnectedSocket.AttachHandle(hTemp); + rConnectedSocket.SetFamily(GetFamily()); + rConnectedSocket.SetState(connected); + } + return TRUE; +} + +BOOL CAsyncSocketEx::IOCtl( long lCommand, DWORD* lpArgument ) +{ + return ioctlsocket(m_SocketData.hSocket, lCommand, lpArgument) != SOCKET_ERROR; +} + +int CAsyncSocketEx::GetLastError() +{ + return WSAGetLastError(); +} + +BOOL CAsyncSocketEx::TriggerEvent(long lEvent) +{ + if (m_SocketData.hSocket==INVALID_SOCKET) + return FALSE; + + DebugAssert(m_pLocalAsyncSocketExThreadData); + DebugAssert(m_pLocalAsyncSocketExThreadData->m_pHelperWindow); + DebugAssert(m_SocketData.nSocketIndex!=-1); + + if (m_pFirstLayer) + { + CAsyncSocketExLayer::t_LayerNotifyMsg *pMsg = new CAsyncSocketExLayer::t_LayerNotifyMsg(); + pMsg->hSocket = m_SocketData.hSocket; + pMsg->lEvent=lEvent%0xFFFF; + pMsg->pLayer=0; + BOOL res=PostMessage(GetHelperWindowHandle(), WM_USER, (WPARAM)m_SocketData.nSocketIndex, (LPARAM)pMsg); + if (!res) + delete pMsg; + return res; + } + else + { + return PostMessage(GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, m_SocketData.hSocket, lEvent%0xFFFF); + } + +} + +SOCKET CAsyncSocketEx::GetSocketHandle() +{ + return m_SocketData.hSocket; +} + +HWND CAsyncSocketEx::GetHelperWindowHandle() +{ + if (!m_pLocalAsyncSocketExThreadData) + return 0; + if (!m_pLocalAsyncSocketExThreadData->m_pHelperWindow) + return 0; + return m_pLocalAsyncSocketExThreadData->m_pHelperWindow->GetHwnd(); +} + +BOOL CAsyncSocketEx::AddLayer(CAsyncSocketExLayer *pLayer) +{ + DebugAssert(pLayer); + + if (m_pFirstLayer) + { + DebugAssert(m_pLastLayer); + m_pLastLayer=m_pLastLayer->AddLayer(pLayer, this); + return m_pLastLayer?TRUE:FALSE; + } + else + { + DebugAssert(!m_pLastLayer); + pLayer->Init(0, this); + m_pFirstLayer=pLayer; + m_pLastLayer=m_pFirstLayer; + if (m_SocketData.hSocket != INVALID_SOCKET) + if (WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE)) + return FALSE; + } + + return TRUE; +} + +void CAsyncSocketEx::RemoveAllLayers() +{ + for (std::list::iterator iter = m_pendingCallbacks.begin(); iter != m_pendingCallbacks.end(); iter++) + nb_free(iter->str); + m_pendingCallbacks.clear(); + + m_pFirstLayer = 0; + m_pLastLayer = 0; + + if (!m_pLocalAsyncSocketExThreadData) + return; + if (!m_pLocalAsyncSocketExThreadData->m_pHelperWindow) + return; + m_pLocalAsyncSocketExThreadData->m_pHelperWindow->RemoveLayers(this); +} + +int CAsyncSocketEx::OnLayerCallback(std::list& callbacks) +{ + for (std::list::iterator iter = callbacks.begin(); iter != callbacks.end(); iter++) + { + nb_free(iter->str); + } + return 0; +} + +BOOL CAsyncSocketEx::IsLayerAttached() const +{ + return m_pFirstLayer ? TRUE : FALSE; +} + +BOOL CAsyncSocketEx::GetSockOpt(int nOptionName, void* lpOptionValue, int* lpOptionLen, int nLevel /*=SOL_SOCKET*/) +{ + return (SOCKET_ERROR != getsockopt(m_SocketData.hSocket, nLevel, nOptionName, (LPSTR)lpOptionValue, lpOptionLen)); +} + +BOOL CAsyncSocketEx::SetSockOpt(int nOptionName, const void* lpOptionValue, int nOptionLen, int nLevel /*=SOL_SOCKET*/) +{ + return (SOCKET_ERROR != setsockopt(m_SocketData.hSocket, nLevel, nOptionName, (LPSTR)lpOptionValue, nOptionLen)); +} + +int CAsyncSocketEx::GetState() const +{ + return m_nState; +} + +void CAsyncSocketEx::SetState(int nState) +{ + m_nState = nState; +} + +const TCHAR * CAsyncSocketEx::GetStateDesc(int nState) +{ + switch (nState) + { + case notsock: + return L"none"; + case unconnected: + return L"unconnected"; + case connecting: + return L"connecting"; + case listening: + return L"listening"; + case connected: + return L"connected"; + case closed: + return L"closed"; + case aborted: + return L"aborted"; + case attached: + return L"attached"; + default: + return L"unknown"; + } +} + +bool CAsyncSocketEx::LogStateChange(int nState1, int nState2) +{ + return (nState2 != notsock) || (nState1 != unconnected); +} + +int CAsyncSocketEx::GetFamily() const +{ + return m_SocketData.nFamily; +} + +bool CAsyncSocketEx::SetFamily(int nFamily) +{ + if (m_SocketData.nFamily != AF_UNSPEC) + return false; + + m_SocketData.nFamily = nFamily; + return true; +} + +bool CAsyncSocketEx::TryNextProtocol() +{ + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + + BOOL ret = FALSE; + for (; m_SocketData.nextAddr; m_SocketData.nextAddr = m_SocketData.nextAddr->ai_next) + { + m_SocketData.hSocket = socket(m_SocketData.nextAddr->ai_family, m_SocketData.nextAddr->ai_socktype, m_SocketData.nextAddr->ai_protocol); + + if (m_SocketData.hSocket == INVALID_SOCKET) + continue; + + AttachHandle(m_SocketData.hSocket); + m_SocketData.nFamily = m_SocketData.nextAddr->ai_family; + if (!AsyncSelect(m_lEvent)) + { + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + if (m_pFirstLayer) + { + if (WSAAsyncSelect(m_SocketData.hSocket, GetHelperWindowHandle(), m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE)) + { + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + } + + if (!Bind(m_nSocketPort, m_lpszSocketAddress)) + { + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + ret = CAsyncSocketEx::Connect(m_SocketData.nextAddr->ai_addr, m_SocketData.nextAddr->ai_addrlen); + if (!ret && GetLastError() != WSAEWOULDBLOCK) + { + DetachHandle(m_SocketData.hSocket); + closesocket(m_SocketData.hSocket); + m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + ret = true; + break; + } + + if (m_SocketData.nextAddr) + m_SocketData.nextAddr = m_SocketData.nextAddr->ai_next; + + if (!m_SocketData.nextAddr) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_SocketData.addrInfo); + m_SocketData.nextAddr = 0; + m_SocketData.addrInfo = 0; + } + + if (m_SocketData.hSocket == INVALID_SOCKET || !ret) + return FALSE; + else + return TRUE; +} + +void CAsyncSocketEx::AddCallbackNotification(const t_callbackMsg& msg) +{ + m_pendingCallbacks.push_back(msg); + + if(m_pendingCallbacks.size() == 1 && m_SocketData.nSocketIndex != -1) + { + PostMessage(GetHelperWindowHandle(), WM_USER + 2, (WPARAM)m_SocketData.nSocketIndex, 0); + } +} + +void CAsyncSocketEx::ResendCloseNotify() +{ + for (std::list::iterator iter = m_pLocalAsyncSocketExThreadData->layerCloseNotify.begin(); iter != m_pLocalAsyncSocketExThreadData->layerCloseNotify.end(); iter++) + { + if (*iter == this) + return; + } + m_pLocalAsyncSocketExThreadData->layerCloseNotify.push_back(this); + if (m_pLocalAsyncSocketExThreadData->layerCloseNotify.size() == 1) + { + SetTimer(m_pLocalAsyncSocketExThreadData->m_pHelperWindow->GetHwnd(), 1, 10, 0); + } +} diff --git a/netbox/src/filezilla/AsyncSocketEx.h b/netbox/src/filezilla/AsyncSocketEx.h new file mode 100644 index 000000000..2628d1c03 --- /dev/null +++ b/netbox/src/filezilla/AsyncSocketEx.h @@ -0,0 +1,348 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.3 (2003-04-26) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx. If you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window recieve WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ + +#ifndef AsyncSocketExH +#define AsyncSocketExH + +#define FD_FORCEREAD (1<<15) + +#include +#include + +class CAsyncSocketExHelperWindow; +class CAsyncSocketExLayer; +class CCriticalSectionWrapper; + +struct t_callbackMsg +{ + CAsyncSocketExLayer* pLayer; + int nType; + intptr_t nParam1; + intptr_t nParam2; + char* str; +}; + +class CAsyncSocketEx +{ +public: + CAsyncSocketEx(); + virtual ~CAsyncSocketEx(); + + BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, + long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, + LPCTSTR lpszSocketAddress = NULL, int nFamily = AF_INET); + + // Attaches a socket handle to a CAsyncSocketEx object. + BOOL Attach(SOCKET hSocket, + long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + + // Detaches a socket handle from a CAsyncSocketEx object. + SOCKET Detach( ); + + // Gets the error status for the last operation that failed. + static int GetLastError(); + + // Gets the address of the peer socket to which the socket is connected. + BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort); + BOOL GetPeerName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + + // Gets the local name for a socket. + BOOL GetSockName(CString& rSocketAddress, UINT& rSocketPort); + BOOL GetSockName(SOCKADDR* lpSockAddr, int* lpSockAddrLen); + + // Retrieves a socket option. + BOOL GetSockOpt(int nOptionName, void* lpOptionValue, int* lpOptionLen, int nLevel = SOL_SOCKET); + + // Sets a socket option. + BOOL SetSockOpt(int nOptionName, const void* lpOptionValue, int nOptionLen, int nLevel = SOL_SOCKET); + + // Gets the socket family + int GetFamily() const; + + // Sets the socket family + bool SetFamily(int nFamily); + + // Operations + + // Accepts a connection on the socket. + virtual BOOL Accept(CAsyncSocketEx& rConnectedSocket, SOCKADDR * lpSockAddr = NULL, int * lpSockAddrLen = NULL); + + // Requests event notification for the socket. + BOOL AsyncSelect(long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); + + // Associates a local address with the socket. + BOOL Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress); + BOOL Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen); + + // Closes the socket. + virtual void Close(); + + // Establishes a connection to a peer socket. + virtual BOOL Connect(LPCTSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR * lpSockAddr, int nSockAddrLen); + + // Controls the mode of the socket. + BOOL IOCtl(long lCommand, DWORD * lpArgument); + + // Establishes a socket to listen for incoming connection requests. + BOOL Listen(int nConnectionBacklog = 5); + + // Receives data from the socket. + virtual int Receive(void * lpBuf, int nBufLen, int nFlags = 0); + + // Sends data to a connected socket. + virtual int Send(const void * lpBuf, int nBufLen, int nFlags = 0, int nDupFF = 0); + + // Disables Send and/or Receive calls on the socket. + BOOL ShutDown(int nHow = sends); + enum { receives = 0, sends = 1, both = 2 }; + + // Overridable Notification Functions + //---------------------------------- + + // Notifies a listening socket that it can accept pending connection requests by calling Accept. + virtual void OnAccept(int nErrorCode); + + // Notifies a socket that the socket connected to it has closed. + virtual void OnClose(int nErrorCode); + + // Notifies a connecting socket that the connection attempt is complete, whether successfully or in error. + virtual void OnConnect(int nErrorCode); + + // Notifies a listening socket that there is data to be retrieved by calling Receive. + virtual void OnReceive(int nErrorCode); + + // Notifies a socket that it can send data by calling Send. + virtual void OnSend(int nErrorCode); + + // Additional functions + + // Resets layer chain. + void RemoveAllLayers(); + + // Attaches a new layer to the socket. + BOOL AddLayer(CAsyncSocketExLayer * pLayer); + + // Is a layer attached to the socket? + BOOL IsLayerAttached() const; + + // Returns the handle of the socket. + SOCKET GetSocketHandle(); + + // Trigers an event on the socket + // Any combination of FD_READ, FD_WRITE, FD_CLOSE, FD_ACCEPT, FD_CONNECT and FD_FORCEREAD is valid for lEvent. + BOOL TriggerEvent(long lEvent); + +protected: + // Strucure to hold the socket data + struct t_AsyncSocketExData + { + SOCKET hSocket; // Socket handle + int nSocketIndex; // Index of socket, required by CAsyncSocketExHelperWindow + int nFamily; + addrinfo * addrInfo, * nextAddr; // Iterate through protocols on connect failure + bool onCloseCalled; // Set to true on first received OnClose event + } m_SocketData; + + // If using layers, only the events specified with m_lEvent will send to the event handlers. + long m_lEvent; + + // AsyncGetHostByName + char *m_pAsyncGetHostByNameBuffer; // Buffer for hostend structure + HANDLE m_hAsyncGetHostByNameHandle; // TaskHandle + int m_nAsyncGetHostByNamePort; // Port to connect to + + // Returns the handle of the helper window + HWND GetHelperWindowHandle(); + + // Attaches socket handle to helper window + void AttachHandle(SOCKET hSocket); + + // Detaches socket handle to helper window + void DetachHandle(SOCKET hSocket); + + // Critical section for thread synchronization + static CCriticalSectionWrapper m_sGlobalCriticalSection; + + // Pointer to the data of the local thread + struct t_AsyncSocketExThreadData + { + CAsyncSocketExHelperWindow * m_pHelperWindow; + int nInstanceCount; + DWORD nThreadId; + std::list layerCloseNotify; + } * m_pLocalAsyncSocketExThreadData; + + // List of the data structures for all threads + static struct t_AsyncSocketExThreadDataList + { + t_AsyncSocketExThreadDataList * pNext; + t_AsyncSocketExThreadData * pThreadData; + } *m_spAsyncSocketExThreadDataList; + + // Initializes Thread data and helper window, fills m_pLocalAsyncSocketExThreadData + BOOL InitAsyncSocketExInstance(); + + // Destroys helper window after last instance of CAsyncSocketEx in current thread has been closed + void FreeAsyncSocketExInstance(); + + // Iterate through protocols on failure + bool TryNextProtocol(); + + void ResendCloseNotify(); + + // Add a new notification to the list of pending callbacks + void AddCallbackNotification(const t_callbackMsg & msg); + + int m_nPendingEvents; + + int GetState() const; + virtual void SetState(int nState); + static const TCHAR * GetStateDesc(int nState); + static bool LogStateChange(int nState1, int nState2); + + int m_nState; + + // Layer chain + CAsyncSocketExLayer * m_pFirstLayer; + CAsyncSocketExLayer * m_pLastLayer; + + friend CAsyncSocketExLayer; + + // Called by the layers to notify application of some events + virtual int OnLayerCallback(std::list & callbacks); + + // Used by Bind with AF_UNSPEC sockets + UINT m_nSocketPort; + LPTSTR m_lpszSocketAddress; + + friend CAsyncSocketExHelperWindow; + + // Pending callbacks + std::list m_pendingCallbacks; + + virtual void LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg) {}; + virtual bool LoggingSocketMessage(int nMessageType) { return true; }; + virtual void ConfigureSocket() {}; +}; + +#define LAYERCALLBACK_STATECHANGE 0 +#define LAYERCALLBACK_LAYERSPECIFIC 1 + +enum SocketState +{ + notsock, + unconnected, + connecting, + listening, + connected, + closed, + aborted, + attached +}; + +inline TCHAR * Inet6AddrToString(in6_addr & addr) +{ + LPTSTR buf = static_cast(nb_calloc(512, sizeof(TCHAR))); + + _sntprintf(buf, 512, L"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + addr.s6_bytes[0], addr.s6_bytes[1], addr.s6_bytes[2], addr.s6_bytes[3], + addr.s6_bytes[4], addr.s6_bytes[5], addr.s6_bytes[6], addr.s6_bytes[7], + addr.s6_bytes[8], addr.s6_bytes[9], addr.s6_bytes[10], addr.s6_bytes[11], + addr.s6_bytes[12], addr.s6_bytes[13], addr.s6_bytes[14], addr.s6_bytes[15]); + + return buf; +} + +class CCriticalSectionWrapper +{ +public: + CCriticalSectionWrapper() + { + m_bInitialized = TRUE; + InitializeCriticalSection(&m_criticalSection); + } + + ~CCriticalSectionWrapper() + { + if (m_bInitialized) + DeleteCriticalSection(&m_criticalSection); + m_bInitialized = FALSE; + } + + void Lock() + { + if (m_bInitialized) + EnterCriticalSection(&m_criticalSection); + } + void Unlock() + { + if (m_bInitialized) + LeaveCriticalSection(&m_criticalSection); + } +protected: + CRITICAL_SECTION m_criticalSection; + BOOL m_bInitialized; +}; + +#endif // AsyncSocketExH diff --git a/netbox/src/filezilla/AsyncSocketExLayer.cpp b/netbox/src/filezilla/AsyncSocketExLayer.cpp new file mode 100644 index 000000000..4c63acabc --- /dev/null +++ b/netbox/src/filezilla/AsyncSocketExLayer.cpp @@ -0,0 +1,988 @@ +// CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) +// Version 1.1 (2002-11-01) + +// Feel free to use this class, as long as you don't claim that you wrote it +// and this copyright notice stays intact in the source files. +// If you use this class in commercial applications, please send a short message +// to tim.kosse@gmx.de + +#include "stdafx.h" +#include "AsyncSocketExLayer.h" + +#include "AsyncSocketEx.h" + +#define WM_SOCKETEX_NOTIFY (WM_USER+3) + + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CAsyncSocketExLayer::CAsyncSocketExLayer() +{ + m_pOwnerSocket = NULL; + m_pNextLayer = NULL; + m_pPrevLayer = NULL; + + m_nLayerState = notsock; + m_nCriticalError=0; + + m_nPendingEvents = 0; + + m_nFamily = AF_UNSPEC; + m_lEvent = 0; + m_lpszSocketAddress = 0; + m_nSocketPort = 0; + + m_nextAddr = 0; + m_addrInfo = 0; +} + +CAsyncSocketExLayer::~CAsyncSocketExLayer() +{ + nb_free(m_lpszSocketAddress); +} + +CAsyncSocketExLayer *CAsyncSocketExLayer::AddLayer(CAsyncSocketExLayer *pLayer, CAsyncSocketEx *pOwnerSocket) +{ + DebugAssert(pLayer); + DebugAssert(pOwnerSocket); + if (m_pNextLayer) + { + return m_pNextLayer->AddLayer(pLayer, pOwnerSocket); + } + else + { + DebugAssert(m_pOwnerSocket==pOwnerSocket); + pLayer->Init(this, m_pOwnerSocket); + m_pNextLayer=pLayer; + } + return m_pNextLayer; +} + +int CAsyncSocketExLayer::Receive(void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ + int Result = ReceiveNext(lpBuf, nBufLen, nFlags); + return Result; +} + +int CAsyncSocketExLayer::Send(const void* lpBuf, int nBufLen, int nFlags /*=0*/) +{ + int Result = SendNext(lpBuf, nBufLen, nFlags); + return Result; +} + + +void CAsyncSocketExLayer::OnReceive(int nErrorCode) +{ + if (m_pPrevLayer) + { + m_pPrevLayer->OnReceive(nErrorCode); + } + else + { + if (m_pOwnerSocket->m_lEvent&FD_READ) + { + m_pOwnerSocket->OnReceive(nErrorCode); + } + } +} + +void CAsyncSocketExLayer::OnSend(int nErrorCode) +{ + if (m_pPrevLayer) + { + m_pPrevLayer->OnSend(nErrorCode); + } + else + { + if (m_pOwnerSocket->m_lEvent&FD_WRITE) + { + m_pOwnerSocket->OnSend(nErrorCode); + } + } +} + +void CAsyncSocketExLayer::OnConnect(int nErrorCode) +{ + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); +} + +void CAsyncSocketExLayer::OnAccept(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnAccept(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_ACCEPT) + m_pOwnerSocket->OnAccept(nErrorCode); +} + +void CAsyncSocketExLayer::OnClose(int nErrorCode) +{ + if (m_pPrevLayer) + m_pPrevLayer->OnClose(nErrorCode); + else + if (m_pOwnerSocket->m_lEvent&FD_CLOSE) + m_pOwnerSocket->OnClose(nErrorCode); +} + +BOOL CAsyncSocketExLayer::TriggerEvent(long lEvent, int nErrorCode, BOOL bPassThrough /*=FALSE*/ ) +{ + DebugAssert(m_pOwnerSocket); + if (m_pOwnerSocket->m_SocketData.hSocket==INVALID_SOCKET) + { + return FALSE; + } + + if (!bPassThrough) + { + if (m_nPendingEvents & lEvent) + { + return TRUE; + } + + m_nPendingEvents |= lEvent; + } + + if (lEvent & FD_CONNECT) + { + DebugAssert(bPassThrough); + if (!nErrorCode) + { + DebugAssert(bPassThrough && (GetLayerState()==connected || GetLayerState()==attached)); + } + else if (nErrorCode) + { + SetLayerState(aborted); + m_nCriticalError=nErrorCode; + } + } + else if (lEvent & FD_CLOSE) + { + if (!nErrorCode) + { + SetLayerState(closed); + } + else + { + SetLayerState(aborted); + m_nCriticalError = nErrorCode; + } + } + DebugAssert(m_pOwnerSocket->m_pLocalAsyncSocketExThreadData); + DebugAssert(m_pOwnerSocket->m_pLocalAsyncSocketExThreadData->m_pHelperWindow); + DebugAssert(m_pOwnerSocket->m_SocketData.nSocketIndex!=-1); + t_LayerNotifyMsg *pMsg=new t_LayerNotifyMsg; + pMsg->hSocket = m_pOwnerSocket->m_SocketData.hSocket; + pMsg->lEvent = ( lEvent % 0xffff ) + ( nErrorCode << 16); + pMsg->pLayer=bPassThrough?m_pPrevLayer:this; + BOOL res=PostMessage(m_pOwnerSocket->GetHelperWindowHandle(), WM_USER, (WPARAM)m_pOwnerSocket->m_SocketData.nSocketIndex, (LPARAM)pMsg); + if (!res) + { + delete pMsg; + } + return res; +} + +void CAsyncSocketExLayer::Close() +{ + CloseNext(); +} + +void CAsyncSocketExLayer::CloseNext() +{ + if (m_addrInfo) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_addrInfo); + } + m_nextAddr = 0; + m_addrInfo = 0; + + m_nPendingEvents = 0; + + SetLayerState(notsock); + if (m_pNextLayer) + m_pNextLayer->Close(); +} + +BOOL CAsyncSocketExLayer::Connect(LPCTSTR lpszHostAddress, UINT nHostPort) +{ + return ConnectNext(lpszHostAddress, nHostPort); +} + +BOOL CAsyncSocketExLayer::Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen ) +{ + return ConnectNext(lpSockAddr, nSockAddrLen); +} + +int CAsyncSocketExLayer::SendNext(const void *lpBuf, int nBufLen, int nFlags /*=0*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return SOCKET_ERROR; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return SOCKET_ERROR; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return SOCKET_ERROR; + } + + if (!m_pNextLayer) + { + DebugAssert(m_pOwnerSocket); + int sent = send(m_pOwnerSocket->GetSocketHandle(), (LPSTR)lpBuf, nBufLen, nFlags); + return sent; + } + else + { + return m_pNextLayer->Send(lpBuf, nBufLen, nFlags); + } +} + +int CAsyncSocketExLayer::ReceiveNext(void *lpBuf, int nBufLen, int nFlags /*=0*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return SOCKET_ERROR; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return SOCKET_ERROR; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return SOCKET_ERROR; + } + + if (!m_pNextLayer) + { + DebugAssert(m_pOwnerSocket); + return recv(m_pOwnerSocket->GetSocketHandle(), (LPSTR)lpBuf, nBufLen, nFlags); + } + else + { + return m_pNextLayer->Receive(lpBuf, nBufLen, nFlags); + } +} + +BOOL CAsyncSocketExLayer::ConnectNext(LPCTSTR lpszHostAddress, UINT nHostPort) +{ + DebugAssert(GetLayerState()==unconnected); + DebugAssert(m_pOwnerSocket); + BOOL res = FALSE; + if (m_pNextLayer) + res = m_pNextLayer->Connect(lpszHostAddress, nHostPort); + else if (m_nFamily == AF_INET) + { + USES_CONVERSION; + + DebugAssert(lpszHostAddress != NULL); + + SOCKADDR_IN sockAddr; + memset(&sockAddr,0,sizeof(sockAddr)); + + LPSTR lpszAscii = T2A((LPTSTR)lpszHostAddress); + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = inet_addr(lpszAscii); + + if (sockAddr.sin_addr.s_addr == INADDR_NONE) + { + LPHOSTENT lphost; + lphost = gethostbyname(lpszAscii); + if (lphost != NULL) + sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; + else + { + WSASetLastError(WSAEINVAL); + res = FALSE; + } + } + + sockAddr.sin_port = htons((u_short)nHostPort); + + res = (SOCKET_ERROR != connect(m_pOwnerSocket->GetSocketHandle(), (SOCKADDR*)&sockAddr, sizeof(sockAddr)) ); + } + else if (m_nFamily == AF_INET6 || m_nFamily == AF_UNSPEC) + { + USES_CONVERSION; + + DebugAssert(lpszHostAddress != NULL); + + addrinfo hints, *res0, *res1; + SOCKET hSocket; + int error; + char port[10]; + + if (p_freeaddrinfo) p_freeaddrinfo(m_addrInfo); + m_nextAddr = 0; + m_addrInfo = 0; + + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = m_nFamily; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + _snprintf(port, 9, "%lu", nHostPort); + error = p_getaddrinfo ? p_getaddrinfo(T2CA(lpszHostAddress), port, &hints, &res0) : 1; + if (error) + return FALSE; + + for (res1 = res0; res1; res1 = res1->ai_next) + { + if (m_nFamily == AF_UNSPEC) + hSocket = socket(res1->ai_family, res1->ai_socktype, res1->ai_protocol); + else + hSocket = m_pOwnerSocket->GetSocketHandle(); + + if (INVALID_SOCKET == hSocket) + { + res = FALSE; + continue; + } + + if (m_nFamily == AF_UNSPEC) + { + m_pOwnerSocket->m_SocketData.hSocket = hSocket; + m_pOwnerSocket->AttachHandle(hSocket); + if (!m_pOwnerSocket->AsyncSelect(m_lEvent)) + { + m_pOwnerSocket->Close(); + res = FALSE; + continue ; + } + if (m_pOwnerSocket->m_pFirstLayer) + { + if (WSAAsyncSelect(m_pOwnerSocket->m_SocketData.hSocket, m_pOwnerSocket->GetHelperWindowHandle(), m_pOwnerSocket->m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE) ) + { + m_pOwnerSocket->Close(); + res = FALSE; + continue; + } + } + if (m_pOwnerSocket->m_pendingCallbacks.size()) + PostMessage(m_pOwnerSocket->GetHelperWindowHandle(), WM_USER + 2, (WPARAM)m_pOwnerSocket->m_SocketData.nSocketIndex, 0); + } + + if (m_nFamily == AF_UNSPEC) + { + m_pOwnerSocket->m_SocketData.nFamily = m_nFamily = res1->ai_family; + if (!m_pOwnerSocket->Bind(m_nSocketPort, m_lpszSocketAddress)) + { + m_pOwnerSocket->m_SocketData.nFamily = m_nFamily = AF_UNSPEC; + Close(); + continue; + } + } + + if (!( res = ( SOCKET_ERROR != connect(m_pOwnerSocket->GetSocketHandle(), res1->ai_addr, res1->ai_addrlen) ) ) + && WSAGetLastError() != WSAEWOULDBLOCK) + { + if (hints.ai_family == AF_UNSPEC) + { + m_nFamily = AF_UNSPEC; + Close(); + } + continue ; + } + + m_nFamily = res1->ai_family; + m_pOwnerSocket->m_SocketData.nFamily = res1->ai_family; + res = TRUE; + break; + } + + if (res1) + res1 = res0->ai_next; + + if (res1) + { + m_addrInfo = res0; + m_nextAddr = res1; + } + else + { + if (p_freeaddrinfo) p_freeaddrinfo(res0); + } + + if (INVALID_SOCKET == m_pOwnerSocket->GetSocketHandle()) + res = FALSE ; + } + + if (res || WSAGetLastError() == WSAEWOULDBLOCK) + { + SetLayerState(connecting); + } + return res; +} + +BOOL CAsyncSocketExLayer::ConnectNext( const SOCKADDR* lpSockAddr, int nSockAddrLen ) +{ + DebugAssert(GetLayerState()==unconnected); + DebugAssert(m_pOwnerSocket); + BOOL res; + if (m_pNextLayer) + res=m_pNextLayer->Connect(lpSockAddr, nSockAddrLen); + else + res = (SOCKET_ERROR!=connect(m_pOwnerSocket->GetSocketHandle(), lpSockAddr, nSockAddrLen)); + + if (res || WSAGetLastError()==WSAEWOULDBLOCK) + SetLayerState(connecting); + return res; +} + +//Gets the address of the peer socket to which the socket is connected +BOOL CAsyncSocketExLayer::GetPeerName( CString& rPeerAddress, UINT& rPeerPort ) +{ + return GetPeerNameNext(rPeerAddress, rPeerPort); +} + +BOOL CAsyncSocketExLayer::GetPeerNameNext( CString& rPeerAddress, UINT& rPeerPort ) +{ + if (m_pNextLayer) + { + return m_pNextLayer->GetPeerName(rPeerAddress, rPeerPort); + } + else + { + SOCKADDR* sockAddr = NULL; + int nSockAddrLen = 0; + + if (m_nFamily == AF_INET6) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN6; + nSockAddrLen = sizeof(SOCKADDR_IN6); + } + else if (m_nFamily == AF_INET) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN; + nSockAddrLen = sizeof(SOCKADDR_IN); + } + + memset(sockAddr, 0, nSockAddrLen); + + BOOL bResult = GetPeerName(sockAddr, &nSockAddrLen); + + if (bResult) + { + if (m_nFamily == AF_INET6) + { + rPeerPort = ntohs(((SOCKADDR_IN6*)sockAddr)->sin6_port); + LPTSTR buf = Inet6AddrToString(((SOCKADDR_IN6*)sockAddr)->sin6_addr); + rPeerAddress = buf; + nb_free(buf); + } + else if (m_nFamily == AF_INET) + { + rPeerPort = ntohs(((SOCKADDR_IN*)sockAddr)->sin_port); + rPeerAddress = inet_ntoa(((SOCKADDR_IN*)sockAddr)->sin_addr); + } + else + { + delete sockAddr; + return FALSE; + } + } + delete sockAddr; + + return bResult; + } +} + +BOOL CAsyncSocketExLayer::GetPeerName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + return GetPeerNameNext(lpSockAddr, lpSockAddrLen); +} + +BOOL CAsyncSocketExLayer::GetPeerNameNext( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if (m_pNextLayer) + { + return m_pNextLayer->GetPeerName(lpSockAddr, lpSockAddrLen); + } + else + { + DebugAssert(m_pOwnerSocket); + if ( !getpeername(m_pOwnerSocket->GetSocketHandle(), lpSockAddr, lpSockAddrLen) ) + { + return TRUE; + } + else + { + return FALSE; + } + } +} + +//Gets the address of the sock socket to which the socket is connected +BOOL CAsyncSocketExLayer::GetSockName( CString& rSockAddress, UINT& rSockPort ) +{ + return GetSockNameNext(rSockAddress, rSockPort); +} + +BOOL CAsyncSocketExLayer::GetSockNameNext( CString& rSockAddress, UINT& rSockPort ) +{ + if (m_pNextLayer) + return m_pNextLayer->GetSockName(rSockAddress, rSockPort); + else + { + SOCKADDR* sockAddr = NULL; + int nSockAddrLen = 0; + + if (m_nFamily == AF_INET6) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN6; + nSockAddrLen = sizeof(SOCKADDR_IN6); + } + else if (m_nFamily == AF_INET) + { + sockAddr = (SOCKADDR*)new SOCKADDR_IN; + nSockAddrLen = sizeof(SOCKADDR_IN); + } + + memset(sockAddr, 0, nSockAddrLen); + + BOOL bResult = GetSockName(sockAddr, &nSockAddrLen); + + if (bResult) + { + if (m_nFamily == AF_INET6) + { + rSockPort = ntohs(((SOCKADDR_IN6*)sockAddr)->sin6_port); + LPTSTR buf = Inet6AddrToString(((SOCKADDR_IN6*)sockAddr)->sin6_addr); + rSockAddress = buf; + nb_free(buf); + } + else if (m_nFamily == AF_INET) + { + rSockPort = ntohs(((SOCKADDR_IN*)sockAddr)->sin_port); + rSockAddress = inet_ntoa(((SOCKADDR_IN*)sockAddr)->sin_addr); + } + else + { + delete sockAddr; + return FALSE; + } + } + delete sockAddr; + + return bResult; + } +} + +BOOL CAsyncSocketExLayer::GetSockName( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + return GetSockNameNext(lpSockAddr, lpSockAddrLen); +} + +BOOL CAsyncSocketExLayer::GetSockNameNext( SOCKADDR* lpSockAddr, int* lpSockAddrLen ) +{ + if (m_pNextLayer) + return m_pNextLayer->GetSockName(lpSockAddr, lpSockAddrLen); + else + { + DebugAssert(m_pOwnerSocket); + if ( !getsockname(m_pOwnerSocket->GetSocketHandle(), lpSockAddr, lpSockAddrLen) ) + return TRUE; + else + return FALSE; + } +} + +void CAsyncSocketExLayer::Init(CAsyncSocketExLayer *pPrevLayer, CAsyncSocketEx *pOwnerSocket) +{ + DebugAssert(pOwnerSocket); + m_pPrevLayer=pPrevLayer; + m_pOwnerSocket=pOwnerSocket; + m_pNextLayer=0; + SetLayerState(pOwnerSocket->GetState()); +} + +int CAsyncSocketExLayer::GetLayerState() +{ + return m_nLayerState; +} + +void CAsyncSocketExLayer::SetLayerState(int nLayerState) +{ + DebugAssert(m_pOwnerSocket); + int nOldLayerState=GetLayerState(); + m_nLayerState=nLayerState; + if (nOldLayerState!=nLayerState) + DoLayerCallback(LAYERCALLBACK_STATECHANGE, GetLayerState(), nOldLayerState); +} + +void CAsyncSocketExLayer::CallEvent(int nEvent, int nErrorCode) +{ + if (m_nCriticalError) + { + return; + } + m_nCriticalError = nErrorCode; + switch (nEvent) + { + case FD_READ: + case FD_FORCEREAD: + if (GetLayerState()==connecting && !nErrorCode) + { + m_nPendingEvents |= nEvent; + break; + } + else if (GetLayerState()==attached) + SetLayerState(connected); + if (nEvent & FD_READ) + m_nPendingEvents &= ~FD_READ; + else + m_nPendingEvents &= ~FD_FORCEREAD; + if (GetLayerState()==connected || nErrorCode) + { + if (nErrorCode) + SetLayerState(aborted); + OnReceive(nErrorCode); + } + break; + case FD_WRITE: + if (GetLayerState()==connecting && !nErrorCode) + { + m_nPendingEvents |= nEvent; + break; + } + else if (GetLayerState()==attached) + SetLayerState(connected); + m_nPendingEvents &= ~FD_WRITE; + if (GetLayerState()==connected || nErrorCode) + { + if (nErrorCode) + SetLayerState(aborted); + OnSend(nErrorCode); + } + break; + case FD_CONNECT: + if (GetLayerState()==connecting || GetLayerState() == attached) + { + if (!nErrorCode) + SetLayerState(connected); + else + { + if (!m_pNextLayer && m_nextAddr) + if (TryNextProtocol()) + { + m_nCriticalError = 0; + return; + } + SetLayerState(aborted); + } + m_nPendingEvents &= ~FD_CONNECT; + OnConnect(nErrorCode); + + if (!nErrorCode) + { + if ((m_nPendingEvents & FD_READ) && GetLayerState()==connected) + OnReceive(0); + if ((m_nPendingEvents & FD_FORCEREAD) && GetLayerState()==connected) + OnReceive(0); + if ((m_nPendingEvents & FD_WRITE) && GetLayerState()==connected) + OnSend(0); + } + m_nPendingEvents = 0; + } + break; + case FD_ACCEPT: + if (GetLayerState()==listening) + { + if (nErrorCode) + SetLayerState(aborted); + m_nPendingEvents &= ~FD_ACCEPT; + OnAccept(nErrorCode); + } + break; + case FD_CLOSE: + if (GetLayerState()==connected || GetLayerState()==attached) + { + if (nErrorCode) + SetLayerState(aborted); + else + SetLayerState(closed); + m_nPendingEvents &= ~FD_CLOSE; + OnClose(nErrorCode); + } + break; + } +} + +//Creates a socket +BOOL CAsyncSocketExLayer::Create(UINT nSocketPort, int nSocketType, + long lEvent, LPCTSTR lpszSocketAddress, int nFamily /*=AF_INET*/) +{ + return CreateNext(nSocketPort, nSocketType, lEvent, lpszSocketAddress, nFamily); +} + +BOOL CAsyncSocketExLayer::CreateNext(UINT nSocketPort, int nSocketType, long lEvent, LPCTSTR lpszSocketAddress, int nFamily /*=AF_INET*/) +{ + DebugAssert(GetLayerState()==notsock); + BOOL res = FALSE; + + m_nFamily = nFamily; + + if (m_pNextLayer) + res = m_pNextLayer->Create(nSocketPort, nSocketType, lEvent, lpszSocketAddress, nFamily); + else if (m_nFamily == AF_UNSPEC) + { + m_lEvent = lEvent; + nb_free(m_lpszSocketAddress); + if (lpszSocketAddress && *lpszSocketAddress) + { + m_lpszSocketAddress = static_cast(nb_calloc(_tcslen(lpszSocketAddress) + 1, sizeof(TCHAR))); + _tcscpy(m_lpszSocketAddress, lpszSocketAddress); + } + else + m_lpszSocketAddress = 0; + m_nSocketPort = nSocketPort; + res = TRUE; + } + else + { + SOCKET hSocket=socket(nFamily, nSocketType, 0); + if (hSocket == INVALID_SOCKET) + { + m_pOwnerSocket->Close(); + return FALSE; + } + m_pOwnerSocket->m_SocketData.hSocket=hSocket; + m_pOwnerSocket->AttachHandle(hSocket); + if (!m_pOwnerSocket->AsyncSelect(lEvent)) + { + m_pOwnerSocket->Close(); + return FALSE; + } + if (m_pOwnerSocket->m_pFirstLayer) + { + if (WSAAsyncSelect(m_pOwnerSocket->m_SocketData.hSocket, m_pOwnerSocket->GetHelperWindowHandle(), m_pOwnerSocket->m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE) ) + { + m_pOwnerSocket->Close(); + return FALSE; + } + } + if (!m_pOwnerSocket->Bind(nSocketPort, lpszSocketAddress)) + { + m_pOwnerSocket->Close(); + return FALSE; + } + res = TRUE; + } + if (res) + SetLayerState(unconnected); + return res; +} + +int CAsyncSocketExLayer::DoLayerCallback(int nType, intptr_t nParam1, intptr_t nParam2, char* str /*=0*/) +{ + if (!m_pOwnerSocket) + return 0; + + int nError = WSAGetLastError(); + + t_callbackMsg msg; + msg.pLayer = this; + msg.nType = nType; + msg.nParam1 = nParam1; + msg.nParam2 = nParam2; + msg.str = str; + + m_pOwnerSocket->AddCallbackNotification(msg); + + WSASetLastError(nError); + + return 0; +} + +BOOL CAsyncSocketExLayer::Listen( int nConnectionBacklog) +{ + return ListenNext( nConnectionBacklog); +} + +BOOL CAsyncSocketExLayer::ListenNext( int nConnectionBacklog) +{ + DebugAssert(GetLayerState()==unconnected); + BOOL res; + if (m_pNextLayer) + res=m_pNextLayer->Listen(nConnectionBacklog); + else + res=listen(m_pOwnerSocket->GetSocketHandle(), nConnectionBacklog); + if (res!=SOCKET_ERROR) + { + SetLayerState(listening); + } + return res!=SOCKET_ERROR; +} + +BOOL CAsyncSocketExLayer::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + return AcceptNext(rConnectedSocket, lpSockAddr, lpSockAddrLen); +} + +BOOL CAsyncSocketExLayer::AcceptNext( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ ) +{ + DebugAssert(GetLayerState()==listening); + BOOL res; + if (m_pNextLayer) + res=m_pNextLayer->Accept(rConnectedSocket, lpSockAddr, lpSockAddrLen); + else + { + SOCKET hTemp = accept(m_pOwnerSocket->m_SocketData.hSocket, lpSockAddr, lpSockAddrLen); + + if (hTemp == INVALID_SOCKET) + return FALSE; + DebugCheck(rConnectedSocket.InitAsyncSocketExInstance()); + rConnectedSocket.m_SocketData.hSocket=hTemp; + rConnectedSocket.AttachHandle(hTemp); + rConnectedSocket.SetFamily(GetFamily()); + rConnectedSocket.SetState(connected); + } + return TRUE; +} + +BOOL CAsyncSocketExLayer::ShutDown(int nHow /*=sends*/) +{ + return ShutDownNext(nHow); +} + +BOOL CAsyncSocketExLayer::ShutDownNext(int nHow /*=sends*/) +{ + if (m_nCriticalError) + { + WSASetLastError(m_nCriticalError); + return FALSE; + } + else if (GetLayerState()==notsock) + { + WSASetLastError(WSAENOTSOCK); + return FALSE; + } + else if (GetLayerState()==unconnected || GetLayerState()==connecting || GetLayerState()==listening) + { + WSASetLastError(WSAENOTCONN); + return FALSE; + } + + if (!m_pNextLayer) + { + DebugAssert(m_pOwnerSocket); + return (shutdown(m_pOwnerSocket->GetSocketHandle(), nHow) == 0); + } + else + { + return m_pNextLayer->ShutDown(nHow); + } +} + +int CAsyncSocketExLayer::GetFamily() const +{ + return m_nFamily; +} + +bool CAsyncSocketExLayer::SetFamily(int nFamily) +{ + if (m_nFamily != AF_UNSPEC) + return false; + + m_nFamily = nFamily; + return true; +} + +bool CAsyncSocketExLayer::TryNextProtocol() +{ + m_pOwnerSocket->DetachHandle(m_pOwnerSocket->m_SocketData.hSocket); + closesocket(m_pOwnerSocket->m_SocketData.hSocket); + m_pOwnerSocket->m_SocketData.hSocket = INVALID_SOCKET; + + BOOL ret = FALSE; + for (; m_nextAddr; m_nextAddr = m_nextAddr->ai_next) + { + m_pOwnerSocket->m_SocketData.hSocket = socket(m_nextAddr->ai_family, m_nextAddr->ai_socktype, m_nextAddr->ai_protocol); + + if (m_pOwnerSocket->m_SocketData.hSocket == INVALID_SOCKET) + continue; + + m_pOwnerSocket->AttachHandle(m_pOwnerSocket->m_SocketData.hSocket); + if (!m_pOwnerSocket->AsyncSelect(m_lEvent)) + { + m_pOwnerSocket->DetachHandle(m_pOwnerSocket->m_SocketData.hSocket); + closesocket(m_pOwnerSocket->m_SocketData.hSocket); + m_pOwnerSocket->m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + if (m_pOwnerSocket->m_pFirstLayer) + { + if (WSAAsyncSelect(m_pOwnerSocket->m_SocketData.hSocket, m_pOwnerSocket->GetHelperWindowHandle(), m_pOwnerSocket->m_SocketData.nSocketIndex+WM_SOCKETEX_NOTIFY, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE)) + { + m_pOwnerSocket->DetachHandle(m_pOwnerSocket->m_SocketData.hSocket); + closesocket(m_pOwnerSocket->m_SocketData.hSocket); + m_pOwnerSocket->m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + } + + m_pOwnerSocket->m_SocketData.nFamily = m_nextAddr->ai_family; + m_nFamily = m_nextAddr->ai_family; + if (!m_pOwnerSocket->Bind(m_nSocketPort, m_lpszSocketAddress)) + { + m_pOwnerSocket->DetachHandle(m_pOwnerSocket->m_SocketData.hSocket); + closesocket(m_pOwnerSocket->m_SocketData.hSocket); + m_pOwnerSocket->m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + if (connect(m_pOwnerSocket->GetSocketHandle(), m_nextAddr->ai_addr, m_nextAddr->ai_addrlen) == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) + { + m_pOwnerSocket->DetachHandle(m_pOwnerSocket->m_SocketData.hSocket); + closesocket(m_pOwnerSocket->m_SocketData.hSocket); + m_pOwnerSocket->m_SocketData.hSocket = INVALID_SOCKET; + continue; + } + + SetLayerState(connecting); + + ret = true; + break; + } + + if (m_nextAddr) + m_nextAddr = m_nextAddr->ai_next; + + if (!m_nextAddr) + { + if (p_freeaddrinfo) p_freeaddrinfo(m_addrInfo); + m_nextAddr = 0; + m_addrInfo = 0; + } + + if (m_pOwnerSocket->m_SocketData.hSocket == INVALID_SOCKET || !ret) + return FALSE; + else + return TRUE; +} + +void CAsyncSocketExLayer::LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg) +{ + if (m_pPrevLayer) + m_pPrevLayer->LogSocketMessageRaw(nMessageType, pMsg); + else + m_pOwnerSocket->LogSocketMessageRaw(nMessageType, pMsg); +} + +bool CAsyncSocketExLayer::LoggingSocketMessage(int nMessageType) +{ + if (m_pPrevLayer) + return m_pPrevLayer->LoggingSocketMessage(nMessageType); + else + return m_pOwnerSocket->LoggingSocketMessage(nMessageType); +} diff --git a/netbox/src/filezilla/AsyncSocketExLayer.h b/netbox/src/filezilla/AsyncSocketExLayer.h new file mode 100644 index 000000000..776fe0e59 --- /dev/null +++ b/netbox/src/filezilla/AsyncSocketExLayer.h @@ -0,0 +1,172 @@ +/*CAsyncSocketEx by Tim Kosse (Tim.Kosse@gmx.de) + Version 1.1 (2002-11-01) +-------------------------------------------------------- + +Introduction: +------------- + +CAsyncSocketEx is a replacement for the MFC class CAsyncSocket. +This class was written because CAsyncSocket is not the fastest WinSock +wrapper and it's very hard to add new functionality to CAsyncSocket +derived classes. This class offers the same functionality as CAsyncSocket. +Also, CAsyncSocketEx offers some enhancements which were not possible with +CAsyncSocket without some tricks. + +How do I use it? +---------------- +Basically exactly like CAsyncSocket. +To use CAsyncSocketEx, just replace all occurrences of CAsyncSocket in your +code with CAsyncSocketEx, if you did not enhance CAsyncSocket yourself in +any way, you won't have to change anything else in your code. + +Why is CAsyncSocketEx faster? +----------------------------- + +CAsyncSocketEx is slightly faster when dispatching notification event messages. +First have a look at the way CAsyncSocket works. For each thread that uses +CAsyncSocket, a window is created. CAsyncSocket calls WSAAsyncSelect with +the handle of that window. Until here, CAsyncSocketEx works the same way. +But CAsyncSocket uses only one window message (WM_SOCKET_NOTIFY) for all +sockets within one thread. When the window recieve WM_SOCKET_NOTIFY, wParam +contains the socket handle and the window looks up an CAsyncSocket instance +using a map. CAsyncSocketEx works differently. It's helper window uses a +wide range of different window messages (WM_USER through 0xBFFF) and passes +a different message to WSAAsyncSelect for each socket. When a message in +the specified range is received, CAsyncSocketEx looks up the pointer to a +CAsyncSocketEx instance in an Array using the index of message - WM_USER. +As you can see, CAsyncSocketEx uses the helper window in a more efficient +way, as it don't have to use the slow maps to lookup it's own instance. +Still, speed increase is not very much, but it may be noticeable when using +a lot of sockets at the same time. +Please note that the changes do not affect the raw data throughput rate, +CAsyncSocketEx only dispatches the notification messages faster. + +What else does CAsyncSocketEx offer? +------------------------------------ + +CAsyncSocketEx offers a flexible layer system. One example is the proxy layer. +Just create an instance of the proxy layer, configure it and add it to the layer +chain of your CAsyncSocketEx instance. After that, you can connect through +proxies. +Benefit: You don't have to change much to use the layer system. +Another layer that is currently in development is the SSL layer to establish +SSL encrypted connections. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you use this class in commercial applications, please send a short message +to tim.kosse@gmx.de +*/ + +#ifndef AsyncSocketExLayerH +#define AsyncSocketExLayerH + +#include "AsyncSocketEx.h" + +class CAsyncSocketEx; + +class CAsyncSocketExLayer : public TObject +{ + friend CAsyncSocketEx; + friend CAsyncSocketExHelperWindow; +protected: + // Protected constructor so that CAsyncSocketExLayer can't be instantiated + CAsyncSocketExLayer(); + virtual ~CAsyncSocketExLayer(); + + // Notification event handlers + virtual void OnAccept(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual void OnReceive(int nErrorCode); + virtual void OnSend(int nErrorCode); + + // Operations + virtual BOOL Accept(CAsyncSocketEx & rConnectedSocket, SOCKADDR * lpSockAddr = NULL, int * lpSockAddrLen = NULL); + virtual void Close(); + virtual BOOL Connect(LPCTSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR * lpSockAddr, int nSockAddrLen); + virtual BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, + long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, + LPCTSTR lpszSocketAddress = NULL, int nFamily = AF_INET); + virtual BOOL GetPeerName(SOCKADDR * lpSockAddr, int * lpSockAddrLen); + virtual BOOL GetSockName(SOCKADDR * lpSockAddr, int * lpSockAddrLen); + virtual BOOL GetPeerName(CString& rPeerAddress, UINT& rPeerPort); + virtual BOOL GetSockName(CString& rPeerAddress, UINT& rPeerPort); + virtual BOOL Listen(int nConnectionBacklog); + virtual int Receive(void * lpBuf, int nBufLen, int nFlags = 0); + virtual int Send(const void * lpBuf, int nBufLen, int nFlags = 0); + virtual BOOL ShutDown(int nHow = sends); + enum { receives = 0, sends = 1, both = 2 }; + + // Functions that will call next layer + BOOL ShutDownNext(int nHow = sends); + BOOL AcceptNext(CAsyncSocketEx& rConnectedSocket, SOCKADDR * lpSockAddr = NULL, int * lpSockAddrLen = NULL); + void CloseNext(); + BOOL ConnectNext(LPCTSTR lpszHostAddress, UINT nHostPort); + BOOL ConnectNext( const SOCKADDR * lpSockAddr, int nSockAddrLen); + BOOL CreateNext(UINT nSocketPort, int nSocketType, long lEvent, LPCTSTR lpszSocketAddress, int nFamily = AF_INET); + BOOL GetPeerNameNext(SOCKADDR * lpSockAddr, int* lpSockAddrLen); + BOOL GetSockNameNext(SOCKADDR * lpSockAddr, int* lpSockAddrLen); + BOOL GetPeerNameNext(CString & rPeerAddress, UINT& rPeerPort); + BOOL GetSockNameNext(CString & rPeerAddress, UINT& rPeerPort); + BOOL ListenNext(int nConnectionBacklog); + int ReceiveNext(void * lpBuf, int nBufLen, int nFlags = 0); + int SendNext(const void * lpBuf, int nBufLen, int nFlags = 0); + + CAsyncSocketEx *m_pOwnerSocket; + + // Calls OnLayerCallback on owner socket + int DoLayerCallback(int nType, intptr_t nParam1, intptr_t nParam2, char * str = 0); + + int GetLayerState(); + BOOL TriggerEvent(long lEvent, int nErrorCode, BOOL bPassThrough = FALSE); + + // Gets the socket family + int GetFamily() const; + + // Sets the socket family + bool SetFamily(int nFamily); + + // Iterate through protocols + bool TryNextProtocol(); + + void LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg); + bool LoggingSocketMessage(int nMessageType); + +private: + // Layer state can't be set directly from derived classes + void SetLayerState(int nLayerState); + int m_nLayerState; + + int m_nFamily; + int m_lEvent; + LPTSTR m_lpszSocketAddress; + UINT m_nSocketPort; + + addrinfo * m_addrInfo, * m_nextAddr; + + // Called by helper window, dispatches event notification and updated layer state + void CallEvent(int nEvent, int nErrorCode); + + int m_nPendingEvents; + int m_nCriticalError; + + void Init(CAsyncSocketExLayer * pPrevLayer, CAsyncSocketEx * pOwnerSocket); + CAsyncSocketExLayer * AddLayer(CAsyncSocketExLayer * pLayer, CAsyncSocketEx * pOwnerSocket); + + CAsyncSocketExLayer * m_pNextLayer; + CAsyncSocketExLayer * m_pPrevLayer; + + struct t_LayerNotifyMsg : public TObject + { + SOCKET hSocket; + CAsyncSocketExLayer * pLayer; + long lEvent; + }; +}; + +#endif // AsyncSocketExLayerH diff --git a/netbox/src/filezilla/AsyncSslSocketLayer.cpp b/netbox/src/filezilla/AsyncSslSocketLayer.cpp new file mode 100644 index 000000000..bcb63e465 --- /dev/null +++ b/netbox/src/filezilla/AsyncSslSocketLayer.cpp @@ -0,0 +1,1868 @@ +// CAsyncSslSocketLayer by Tim Kosse (Tim.Kosse@gmx.de) +// Version 2.0 (2005-02-27) + +// Feel free to use this class, as long as you don't claim that you wrote it +// and this copyright notice stays intact in the source files. +// If you use this class in commercial applications, please send a short message +// to tim.kosse@gmx.de + +#include "stdafx.h" +#include "AsyncSslSocketLayer.h" +#include + +#include +#include + +///////////////////////////////////////////////////////////////////////////// +// CAsyncSslSocketLayer +CCriticalSectionWrapper CAsyncSslSocketLayer::m_sCriticalSection; + +CAsyncSslSocketLayer::t_SslLayerList* CAsyncSslSocketLayer::m_pSslLayerList = 0; +int CAsyncSslSocketLayer::m_nSslRefCount = 0; +std::map CAsyncSslSocketLayer::m_contextRefCount; + +CAsyncSslSocketLayer::CAsyncSslSocketLayer() +{ + m_ssl = 0; + m_sslbio = 0; + m_ibio = 0; + m_nbio = 0; + m_ssl_ctx = 0; + + m_bUseSSL = false; + m_bSslInitialized = FALSE; + m_bSslEstablished = FALSE; + m_nNetworkSendBufferLen = 0; + m_nNetworkSendBufferMaxLen = 0; + m_pNetworkSendBuffer = NULL; + m_pRetrySendBuffer = 0; + m_nRetrySendBufferLen = 0; + m_nNetworkError = 0; + m_nShutDown = 0; + + m_bBlocking = FALSE; + m_nSslAsyncNotifyId = 0; + m_bFailureSent = FALSE; + m_nVerificationResult = 0; + m_nVerificationDepth = 0; + m_mayTriggerRead = true; + m_mayTriggerWrite = true; + m_mayTriggerReadUp = true; + m_mayTriggerWriteUp = true; + + m_onCloseCalled = false; + m_Main = NULL; + m_sessionid = NULL; + m_sessionreuse = true; + + FCertificate = NULL; + FPrivateKey = NULL; +} + +CAsyncSslSocketLayer::~CAsyncSslSocketLayer() +{ + UnloadSSL(); + nb_free(m_pNetworkSendBuffer); + nb_free(m_pRetrySendBuffer); +} + +int CAsyncSslSocketLayer::InitSSL() +{ + if (m_bSslInitialized) + return 0; + + m_sCriticalSection.Lock(); + + if (!m_nSslRefCount) + { + SSL_load_error_strings(); + if (!SSL_library_init()) + { + return SSL_FAILURE_INITSSL; + } + } + + m_nSslRefCount++; + m_sCriticalSection.Unlock(); + + m_bSslInitialized = true; + + return 0; +} + +void CAsyncSslSocketLayer::OnReceive(int nErrorCode) +{ + if (m_bUseSSL) + { + if (m_bBlocking) + { + m_mayTriggerRead = true; + return; + } + if (m_nNetworkError) + { + return; + } + + char buffer[16384]; + + m_mayTriggerRead = false; + + //Get number of bytes we can receive and store in the network input bio + int len = BIO_ctrl_get_write_guarantee(m_nbio); + if (len > 16384) + len = 16384; + else if (!len) + { + m_mayTriggerRead = true; + TriggerEvents(); + return; + } + + int numread = 0; + + // Receive data + numread = ReceiveNext(buffer, len); + if (numread > 0) + { + //Store it in the network input bio and process data + int numwritten = BIO_write(m_nbio, buffer, numread); + BIO_ctrl(m_nbio, BIO_CTRL_FLUSH, 0, NULL); + + // I have no idea why this call is needed, but without it, connections + // will stall. Perhaps it triggers some internal processing. + // Also, ignore return value, don't do any error checking. This function + // can report errors, even though a later call can succeed. + char buffer; + BIO_read(m_sslbio, &buffer, 0); + } + if (!numread) + { + if (GetLayerState() == connected) + TriggerEvent(FD_CLOSE, nErrorCode, TRUE); + } + else if (numread == SOCKET_ERROR) + { + int nError = GetLastError(); + if (nError != WSAEWOULDBLOCK && nError != WSAENOTCONN) + { + m_nNetworkError = GetLastError(); + TriggerEvent(FD_CLOSE, 0, TRUE); + return; + } + } + + if (m_pRetrySendBuffer) + { + int numwrite = BIO_write(m_sslbio, m_pRetrySendBuffer, m_nRetrySendBufferLen); + if (numwrite >= 0) + { + BIO_ctrl(m_sslbio, BIO_CTRL_FLUSH, 0, NULL); + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + } + else if (numwrite == -1) + { + if (!BIO_should_retry(m_sslbio)) + { + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + + ::SetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, 0, TRUE); + return; + } + } + } + + if (!m_nShutDown && SSL_get_shutdown(m_ssl)) + { + size_t pending = BIO_ctrl_pending(m_sslbio); + if (pending <= 0) + { + if (ShutDown() || GetLastError() == WSAEWOULDBLOCK) + { + if (ShutDownComplete()) + TriggerEvent(FD_CLOSE, 0, TRUE); + } + else + { + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, WSAECONNABORTED, TRUE); + } + return; + } + } + + if (ShutDownComplete() && m_nShutDown == 1) + { + //Send shutdown notification if all pending data has been sent + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_INFO, SSL_INFO_SHUTDOWNCOMPLETE); + m_nShutDown++; + } + + TriggerEvents(); + } + else + { + TriggerEvent(FD_READ, nErrorCode, TRUE); + } +} + +void CAsyncSslSocketLayer::OnSend(int nErrorCode) +{ + if (m_bUseSSL) + { + if (m_nNetworkError) + { + return; + } + + m_mayTriggerWrite = false; + + //Send data in the send buffer + while (m_nNetworkSendBufferLen) + { + int numsent = SendNext(m_pNetworkSendBuffer, m_nNetworkSendBufferLen); + if (numsent == SOCKET_ERROR) + { + int nError = GetLastError(); + if (nError != WSAEWOULDBLOCK && nError != WSAENOTCONN) + { + m_nNetworkError = nError; + TriggerEvent(FD_CLOSE, 0, TRUE); + } + return; + } + else if (!numsent) + { + if (GetLayerState() == connected) + TriggerEvent(FD_CLOSE, nErrorCode, TRUE); + } + if (numsent == m_nNetworkSendBufferLen) + { + m_nNetworkSendBufferLen = 0; + } + else + { + memmove(m_pNetworkSendBuffer, m_pNetworkSendBuffer + numsent, m_nNetworkSendBufferLen - numsent); + m_nNetworkSendBufferLen -= numsent; + } + } + + //Send the data waiting in the network bio + char buffer[32 * 1024]; + size_t len = BIO_ctrl_pending(m_nbio); + int numread = BIO_read(m_nbio, buffer, Min(len, sizeof(buffer))); + if (numread <= 0) + m_mayTriggerWrite = true; + while (numread > 0) + { + int numsent = SendNext(buffer, numread); + if (!numsent) + { + if (GetLayerState() == connected) + TriggerEvent(FD_CLOSE, nErrorCode, TRUE); + } + if (numsent == SOCKET_ERROR || numsent < numread) + { + if (numsent == SOCKET_ERROR) + if (GetLastError() != WSAEWOULDBLOCK && GetLastError() != WSAENOTCONN) + { + m_nNetworkError = GetLastError(); + TriggerEvent(FD_CLOSE, 0, TRUE); + return; + } + else + numsent = 0; + + // Add all data that was retrieved from the network bio but could not be sent to the send buffer. + if (m_nNetworkSendBufferMaxLen < (m_nNetworkSendBufferLen + numread - numsent)) + { + char * tmp = m_pNetworkSendBuffer; + m_nNetworkSendBufferMaxLen = static_cast((m_nNetworkSendBufferLen + numread - numsent) * 1.5); + m_pNetworkSendBuffer = static_cast(nb_calloc(1, m_nNetworkSendBufferMaxLen)); + if (tmp) + { + memcpy(m_pNetworkSendBuffer, tmp, m_nNetworkSendBufferLen); + nb_free(tmp); + } + } + DebugAssert(m_pNetworkSendBuffer); + memcpy(m_pNetworkSendBuffer + m_nNetworkSendBufferLen, buffer, numread-numsent); + m_nNetworkSendBufferLen += numread - numsent; + } + if (!numsent) + { + break; + } + len = BIO_ctrl_pending(m_nbio); + if (!len) + { + m_mayTriggerWrite = true; + break; + } + numread = BIO_read(m_nbio, buffer, len); + if (numread <= 0) + { + m_mayTriggerWrite = true; + } + } + + if (m_pRetrySendBuffer) + { + int numwrite = BIO_write(m_sslbio, m_pRetrySendBuffer, m_nRetrySendBufferLen); + if (numwrite >= 0) + { + BIO_ctrl(m_sslbio, BIO_CTRL_FLUSH, 0, NULL); + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + } + else if (numwrite == -1) + { + if (!BIO_should_retry(m_sslbio)) + { + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + + ::SetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, 0, TRUE); + return; + } + } + } + + // No more data available, ask for more. + TriggerEvents(); + if (m_nShutDown == 1 && ShutDownComplete()) + { + //Send shutdown notification if all pending data has been sent + // FileZilla3 calls ShutDownNext() here + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_INFO, SSL_INFO_SHUTDOWNCOMPLETE); + m_nShutDown++; + } + } + else + { + TriggerEvent(FD_WRITE, nErrorCode, TRUE); + } +} + +int CAsyncSslSocketLayer::Send(const void* lpBuf, int nBufLen, int nFlags) +{ + if (m_bUseSSL) + { + if (!lpBuf) + { + return 0; + } + if (m_bBlocking || m_pRetrySendBuffer) + { + m_mayTriggerWriteUp = true; + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + if (m_nNetworkError) + { + SetLastError(m_nNetworkError); + return SOCKET_ERROR; + } + if (m_nShutDown) + { + ::SetLastError(WSAESHUTDOWN); + return SOCKET_ERROR; + } + if (!m_bSslEstablished) + { + m_mayTriggerWriteUp = true; + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + if (!nBufLen) + { + return 0; + } + + if (m_onCloseCalled) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + return 0; + } + + int len = BIO_ctrl_get_write_guarantee(m_sslbio); + if (nBufLen > len) + nBufLen = len; + if (!len) + { + m_mayTriggerWriteUp = true; + TriggerEvents(); + ::SetLastError(WSAEWOULDBLOCK); + } + + m_pRetrySendBuffer = static_cast(nb_calloc(1, nBufLen)); + m_nRetrySendBufferLen = nBufLen; + memcpy(m_pRetrySendBuffer, lpBuf, nBufLen); + + int numwrite = BIO_write(m_sslbio, m_pRetrySendBuffer, m_nRetrySendBufferLen); + if (numwrite >= 0) + { + BIO_ctrl(m_sslbio, BIO_CTRL_FLUSH, 0, NULL); + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + } + else if (numwrite == -1) + { + if (BIO_should_retry(m_sslbio)) + { + if (GetLayerState() == closed) + { + return 0; + } + else if (GetLayerState() != connected) + { + SetLastError(m_nNetworkError); + return SOCKET_ERROR; + } + + TriggerEvents(); + + return nBufLen; + } + else + { + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + + ::SetLastError(WSAECONNABORTED); + } + return SOCKET_ERROR; + } + + m_mayTriggerWriteUp = true; + TriggerEvents(); + + return numwrite; + } + else + { + return SendNext(lpBuf, nBufLen, nFlags); + } +} + +int CAsyncSslSocketLayer::Receive(void* lpBuf, int nBufLen, int nFlags) +{ + if (m_bUseSSL) + { + if (m_bBlocking) + { + m_mayTriggerReadUp = true; + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + if (m_nNetworkError) + { + size_t pending = BIO_ctrl_pending(m_sslbio); + if (pending && !m_nShutDown) + { + m_mayTriggerReadUp = true; + TriggerEvents(); + return BIO_read(m_sslbio, lpBuf,nBufLen); + } + WSASetLastError(m_nNetworkError); + return SOCKET_ERROR; + } + if (m_nShutDown) + { + ::SetLastError(WSAESHUTDOWN); + return SOCKET_ERROR; + } + if (!nBufLen) + { + return 0; + } + size_t pending = BIO_ctrl_pending(m_sslbio); + if (!pending) + { + if (GetLayerState() == closed) + { + return 0; + } + if (m_onCloseCalled) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + return 0; + } + else if (GetLayerState() != connected) + { + SetLastError(m_nNetworkError); + return SOCKET_ERROR; + } + else + { + if (SSL_get_shutdown(m_ssl)) + { + if (ShutDown() || GetLastError() == WSAEWOULDBLOCK) + { + if (ShutDownComplete()) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + return 0; + } + else + WSASetLastError(WSAEWOULDBLOCK); + } + else + { + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, WSAECONNABORTED, TRUE); + } + return SOCKET_ERROR; + } + } + m_mayTriggerReadUp = true; + TriggerEvents(); + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + int numread = BIO_read(m_sslbio, lpBuf, nBufLen); + if (!numread) + { + if (SSL_get_shutdown(m_ssl)) + { + if (ShutDown() || GetLastError() == WSAEWOULDBLOCK) + { + if (ShutDownComplete()) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + return 0; + } + else + WSASetLastError(WSAEWOULDBLOCK); + } + else + { + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, WSAECONNABORTED, TRUE); + } + return SOCKET_ERROR; + } + m_mayTriggerReadUp = true; + TriggerEvents(); + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + if (numread < 0) + { + if (!BIO_should_retry(m_sslbio)) + { + PrintLastErrorMsg(); + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, 0, TRUE); + return SOCKET_ERROR; + } + else + { + if (SSL_get_shutdown(m_ssl)) + { + if (ShutDown() || GetLastError() == WSAEWOULDBLOCK) + { + if (ShutDownComplete()) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + return 0; + } + else + WSASetLastError(WSAEWOULDBLOCK); + } + else + { + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + TriggerEvent(FD_CLOSE, 0, TRUE); + } + return SOCKET_ERROR; + } + m_mayTriggerReadUp = true; + TriggerEvents(); + ::SetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + } + + m_mayTriggerReadUp = true; + TriggerEvents(); + return numread; + } + else + { + return ReceiveNext(lpBuf, nBufLen, nFlags); + } +} + +void CAsyncSslSocketLayer::Close() +{ + m_nShutDown = 0; + m_onCloseCalled = false; + ResetSslSession(); + CloseNext(); +} + +BOOL CAsyncSslSocketLayer::Connect(const SOCKADDR *lpSockAddr, int nSockAddrLen) +{ + BOOL res = ConnectNext(lpSockAddr, nSockAddrLen); + if (!res) + { + if (GetLastError() != WSAEWOULDBLOCK) + { + ResetSslSession(); + } + } + return res; +} + +BOOL CAsyncSslSocketLayer::Connect(LPCTSTR lpszHostAddress, UINT nHostPort) +{ + BOOL res = ConnectNext(lpszHostAddress, nHostPort); + if (!res) + { + if (GetLastError()!=WSAEWOULDBLOCK) + { + ResetSslSession(); + } + } + return res; +} + +int CAsyncSslSocketLayer::InitSSLConnection(bool clientMode, + CAsyncSslSocketLayer* main, bool sessionreuse, + int minTlsVersion, int maxTlsVersion, + void * pSslContext /*=0*/) +{ + if (m_bUseSSL) + return 0; + int res = InitSSL(); + if (res) + return res; + + m_sCriticalSection.Lock(); + if ((SSL_CTX*)pSslContext) + { + if (m_ssl_ctx) + { + m_sCriticalSection.Unlock(); + ResetSslSession(); + return SSL_FAILURE_INITSSL; + } + + std::map::iterator iter = m_contextRefCount.find((SSL_CTX*)pSslContext); + if (iter == m_contextRefCount.end() || iter->second < 1) + { + m_sCriticalSection.Unlock(); + ResetSslSession(); + return SSL_FAILURE_INITSSL; + } + m_ssl_ctx = (SSL_CTX*)pSslContext; + iter->second++; + } + else if (!m_ssl_ctx) + { + // Create new context if none given + if (!(m_ssl_ctx = SSL_CTX_new( SSLv23_method()))) + { + m_sCriticalSection.Unlock(); + ResetSslSession(); + return SSL_FAILURE_INITSSL; + } + m_contextRefCount[m_ssl_ctx] = 1; + + if (clientMode) + { + USES_CONVERSION; + SSL_CTX_set_verify(m_ssl_ctx, SSL_VERIFY_PEER, verify_callback); + SSL_CTX_set_client_cert_cb(m_ssl_ctx, ProvideClientCert); + CFileStatus Dummy; + if (CFile::GetStatus((LPCTSTR)m_CertStorage, Dummy)) + { + SSL_CTX_load_verify_locations(m_ssl_ctx, T2CA(m_CertStorage), 0); + } + } + } + + //Create new SSL session + if (!(m_ssl = SSL_new(m_ssl_ctx))) + { + m_sCriticalSection.Unlock(); + ResetSslSession(); + return SSL_FAILURE_INITSSL; + } + +#ifdef _DEBUG + if ((main == NULL) && LoggingSocketMessage(FZ_LOG_INFO)) + { + USES_CONVERSION; + LogSocketMessageRaw(FZ_LOG_INFO, L"Supported ciphersuites:"); + STACK_OF(SSL_CIPHER) * ciphers = SSL_get_ciphers(m_ssl); + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) + { + SSL_CIPHER * cipher = sk_SSL_CIPHER_value(ciphers, i); + LogSocketMessageRaw(FZ_LOG_INFO, A2CT(cipher->name)); + } + } +#endif + + //Add current instance to list of active instances + t_SslLayerList *tmp = m_pSslLayerList; + m_pSslLayerList = new t_SslLayerList(); + m_pSslLayerList->pNext = tmp; + m_pSslLayerList->pLayer = this; + m_sCriticalSection.Unlock(); + + SSL_set_info_callback(m_ssl, apps_ssl_info_callback); + + //Create bios + m_sslbio = BIO_new(BIO_f_ssl()); + BIO_new_bio_pair(&m_ibio, 32 * 1024, &m_nbio, 32 * 1024); + + if (!m_sslbio || !m_nbio || !m_ibio) + { + ResetSslSession(); + return SSL_FAILURE_INITSSL; + } + + // See also TWebDAVFileSystem::InitSslSession + #define MASK_TLS_VERSION(VERSION, FLAG) ((minTlsVersion > VERSION) || (maxTlsVersion < VERSION) ? FLAG : 0) + long options = + SSL_OP_ALL | + MASK_TLS_VERSION(SSL_VERSION_SSL2, SSL_OP_NO_SSLv2) | + MASK_TLS_VERSION(SSL_VERSION_SSL3, SSL_OP_NO_SSLv3) | + MASK_TLS_VERSION(SSL_VERSION_TLS10, SSL_OP_NO_TLSv1) | + MASK_TLS_VERSION(SSL_VERSION_TLS11, SSL_OP_NO_TLSv1_1) | + MASK_TLS_VERSION(SSL_VERSION_TLS12, SSL_OP_NO_TLSv1_2); + // SSL_ctrl() with SSL_CTRL_OPTIONS adds flags (not sets) + SSL_ctrl(m_ssl, SSL_CTRL_OPTIONS, options, NULL); + + //Init SSL connection + void * ssl_sessionid = NULL; + m_Main = main; + m_sessionreuse = sessionreuse; + if ((m_Main != NULL) && m_sessionreuse) + { + if (m_Main->m_sessionid != NULL) + { + if (!SSL_set_session(m_ssl, m_Main->m_sessionid)) + { + LogSocketMessageRaw(FZ_LOG_INFO, L"SSL_set_session failed"); + return SSL_FAILURE_INITSSL; + } + LogSocketMessageRaw(FZ_LOG_INFO, L"Trying reuse main TLS session ID"); + } + else + { + LogSocketMessageRaw(FZ_LOG_INFO, L"Main TLS session ID was not reused previously, not trying again"); + SSL_set_session(m_ssl, NULL); + } + } + else + { + SSL_set_session(m_ssl, NULL); + } + if (clientMode) + { + SSL_set_connect_state(m_ssl); + } + else + { + SSL_set_accept_state(m_ssl); + } + SSL_set_bio(m_ssl, m_ibio, m_ibio); + BIO_ctrl(m_sslbio, BIO_C_SET_SSL, BIO_NOCLOSE, m_ssl); + BIO_read(m_sslbio, (void *)1, 0); + + // Trigger FD_WRITE so that we can initialize SSL negotiation + if (GetLayerState() == connected || GetLayerState() == attached) + { + TriggerEvent(FD_READ, 0); + TriggerEvent(FD_WRITE, 0); + TriggerEvent(FD_READ, 0, TRUE); + TriggerEvent(FD_WRITE, 0, TRUE); + } + + m_bUseSSL = true; + + return 0; +} + +void CAsyncSslSocketLayer::ResetSslSession() +{ + if (m_pRetrySendBuffer) + { + nb_free(m_pRetrySendBuffer); + m_pRetrySendBuffer = 0; + } + + m_bFailureSent = FALSE; + m_bBlocking = FALSE; + m_nSslAsyncNotifyId++; + m_nNetworkError = 0; + m_bUseSSL = FALSE; + m_nVerificationResult = 0; + m_nVerificationDepth = 0; + + m_bSslEstablished = FALSE; + if (m_sslbio) + { + BIO_free(m_sslbio); + } + if (m_ssl) + { + SSL_set_session(m_ssl,NULL); + } + if (m_nbio) + { + BIO_free(m_nbio); + } + if (m_ibio) + { + BIO_free(m_ibio); + } + + m_nNetworkSendBufferLen = 0; + + m_nbio = 0; + m_ibio = 0; + m_sslbio = 0; + + if (m_ssl) + { + SSL_free(m_ssl); + } + + m_sCriticalSection.Lock(); + + if (m_ssl_ctx) + { + std::map::iterator iter = m_contextRefCount.find(m_ssl_ctx); + if (iter != m_contextRefCount.end()) + { + if (iter->second <= 1) + { + SSL_CTX_free(m_ssl_ctx); + m_contextRefCount.erase(m_ssl_ctx); + } + else + iter->second--; + } + m_ssl_ctx = 0; + } + + m_ssl = 0; + t_SslLayerList *cur = m_pSslLayerList; + if (!cur) + { + m_sCriticalSection.Unlock(); + return; + } + + if (cur->pLayer == this) + { + m_pSslLayerList = cur->pNext; + delete cur; + } + else + while (cur->pNext) + { + if (cur->pNext->pLayer == this) + { + t_SslLayerList *tmp = cur->pNext; + cur->pNext = cur->pNext->pNext; + delete tmp; + + m_sCriticalSection.Unlock(); + return; + } + cur = cur->pNext; + } + + if (m_sessionid != NULL) + { + SSL_SESSION_free(m_sessionid); + m_sessionid = NULL; + } + m_sessionreuse = true; + + m_sCriticalSection.Unlock(); +} + +bool CAsyncSslSocketLayer::IsUsingSSL() +{ + return m_bUseSSL; +} + +BOOL CAsyncSslSocketLayer::ShutDown(int nHow /*=sends*/) +{ + if (m_bUseSSL) + { + if (m_pRetrySendBuffer) + { + if (!m_nShutDown) + m_nShutDown = 1; + WSASetLastError(WSAEWOULDBLOCK); + return false; + } + if (!m_bSslEstablished) + { + m_mayTriggerWriteUp = true; + ::SetLastError(WSAEWOULDBLOCK); + return false; + } + if (!m_nShutDown) + m_nShutDown = 1; + else + { + if (ShutDownComplete()) + return ShutDownNext(); + else + { + TriggerEvents(); + WSASetLastError(WSAEWOULDBLOCK); + return false; + } + } + + int res = SSL_shutdown(m_ssl); + if (res != -1) + { + if (!res) + { + SSL_shutdown(m_ssl); + } + if (ShutDownComplete()) + return ShutDownNext(); + else + { + TriggerEvents(); + WSASetLastError(WSAEWOULDBLOCK); + return FALSE; + } + } + else + { + int error = SSL_get_error(m_ssl, -1); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) + { + // retry shutdown later + m_nShutDown = 0; + TriggerEvents(); + WSASetLastError(WSAEWOULDBLOCK); + return FALSE; + } + else if (ShutDownComplete()) + return ShutDownNext(); + else + { + TriggerEvents(); + WSASetLastError(WSAEWOULDBLOCK); + return FALSE; + } + } + } + else + { + if (!m_nShutDown) + m_nShutDown = 1; + return ShutDownNext(nHow); + } +} + +BOOL CAsyncSslSocketLayer::ShutDownComplete() +{ + //If a ShutDown was issued, has the connection already been shut down? + if (!m_nShutDown) + return FALSE; + else if (!m_bUseSSL) + return FALSE; + else if (m_nNetworkSendBufferLen) + return FALSE; + else if (m_pRetrySendBuffer) + return FALSE; + + // Empty read buffer + char buffer[1000]; + int numread; + do + { + numread = BIO_read(m_sslbio, buffer, 1000); + } while (numread > 0); + + size_t pending = BIO_ctrl_pending(m_nbio); + if (pending) + { + return FALSE; + } + else + { + return TRUE; + } +} + +void CAsyncSslSocketLayer::apps_ssl_info_callback(const SSL *s, int where, int ret) +{ + USES_CONVERSION; + CAsyncSslSocketLayer *pLayer = 0; + m_sCriticalSection.Lock(); + t_SslLayerList *cur = m_pSslLayerList; + while (cur) + { + if (cur->pLayer->m_ssl == s) + break; + cur = cur->pNext; + } + if (!cur) + { + m_sCriticalSection.Unlock(); + MessageBox(0, L"Can't lookup TLS session!", L"Critical error", MB_ICONEXCLAMATION); + return; + } + else + pLayer = cur->pLayer; + m_sCriticalSection.Unlock(); + + // Called while unloading? + if (!pLayer->m_bUseSSL && (where != SSL_CB_LOOP)) + return; + + const char * str; + int w; + + w = where& ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + { + str = "TLS connect"; + if (pLayer->m_sessionreuse) + { + SSL_SESSION * sessionid = SSL_get1_session(pLayer->m_ssl); + if (pLayer->m_sessionid != sessionid) + { + if (pLayer->m_sessionid == NULL) + { + if (SSL_session_reused(pLayer->m_ssl)) + { + pLayer->LogSocketMessageRaw(FZ_LOG_PROGRESS, L"Session ID reused"); + } + else + { + if ((pLayer->m_Main != NULL) && (pLayer->m_Main->m_sessionid != NULL)) + { + pLayer->LogSocketMessageRaw(FZ_LOG_INFO, L"Main TLS session ID not reused, will not try again"); + SSL_SESSION_free(pLayer->m_Main->m_sessionid); + pLayer->m_Main->m_sessionid = NULL; + } + } + pLayer->LogSocketMessageRaw(FZ_LOG_DEBUG, L"Saving session ID"); + } + else + { + SSL_SESSION_free(pLayer->m_sessionid); + pLayer->LogSocketMessageRaw(FZ_LOG_INFO, L"Session ID changed"); + } + pLayer->m_sessionid = sessionid; + } + else + { + SSL_SESSION_free(sessionid); + } + } + } + else if (w & SSL_ST_ACCEPT) + str = "TLS accept"; + else + str = "Undefined"; + + if (where & SSL_CB_LOOP) + { + char* debug = NULL; + // exact SSL_CB_LOOP is abused for debugging + if (where == SSL_CB_LOOP) + { + debug = reinterpret_cast(ret); + } + char *buffer = static_cast(nb_calloc(1, 4096 + ((debug != NULL) ? strlen(debug) : 0))); + sprintf(buffer, "%s: %s", + str, + SSL_state_string_long(s)); + if (debug != NULL) + { + sprintf(buffer + strlen(buffer), " [%s]", debug); + OPENSSL_free(debug); + } + USES_CONVERSION; + pLayer->LogSocketMessageRaw(FZ_LOG_INFO, A2T(buffer)); + nb_free(buffer); + } + else if (where & SSL_CB_ALERT) + { + str=(where & SSL_CB_READ)? "read" : "write"; + const char* desc = SSL_alert_desc_string_long(ret); + + // Don't send close notify warning + if (desc) + { + if (strcmp(desc, "close notify")) + { + char *buffer = static_cast(nb_calloc(1, 4 * 1024)); + sprintf(buffer, "SSL3 alert %s: %s: %s", + str, + SSL_alert_type_string_long(ret), + desc); + pLayer->LogSocketMessageRaw(FZ_LOG_WARNING, A2T(buffer)); + pLayer->PrintLastErrorMsg(); + nb_free(buffer); + } + } + } + + else if (where & SSL_CB_EXIT) + { + if (ret == 0) + { + char *buffer = static_cast(nb_calloc(1, 4 * 1024)); + sprintf(buffer, "%s: failed in %s", + str, + SSL_state_string_long(s)); + pLayer->LogSocketMessageRaw(FZ_LOG_WARNING, A2T(buffer)); + pLayer->PrintLastErrorMsg(); + nb_free(buffer); + if (!pLayer->m_bFailureSent) + { + pLayer->m_bFailureSent=TRUE; + pLayer->DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_FAILURE, pLayer->m_bSslEstablished ? SSL_FAILURE_UNKNOWN : SSL_FAILURE_ESTABLISH); + } + } + else if (ret < 0) + { + int error = SSL_get_error(s,ret); + if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) + { + char *buffer = static_cast(nb_calloc(1, 4 * 1024)); + sprintf(buffer, "%s: error in %s", + str, + SSL_state_string_long(s)); + pLayer->LogSocketMessageRaw(FZ_LOG_WARNING, A2T(buffer)); + nb_free(buffer); + if (!pLayer->m_bFailureSent) + { + pLayer->m_bFailureSent=TRUE; + pLayer->DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_FAILURE, pLayer->m_bSslEstablished ? SSL_FAILURE_UNKNOWN : SSL_FAILURE_ESTABLISH); + } + } + } + } + if (where & SSL_CB_HANDSHAKE_DONE) + { + int error = SSL_get_verify_result(pLayer->m_ssl); + pLayer->DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_VERIFY_CERT, error); + pLayer->m_bBlocking = TRUE; + } +} + + +void CAsyncSslSocketLayer::UnloadSSL() +{ + if (!m_bSslInitialized) + return; + ResetSslSession(); + + m_bSslInitialized = false; + + m_sCriticalSection.Lock(); + m_nSslRefCount--; + if (m_nSslRefCount) + { + m_sCriticalSection.Unlock(); + return; + } + + m_sCriticalSection.Unlock(); +} + +bool AsnTimeToValidTime(ASN1_TIME * AsnTime, t_SslCertData::t_validTime & ValidTime) +{ + int i = AsnTime->length; + const char * v = (const char *)AsnTime->data; + + if (i < 10) + { + return FALSE; + } + + for (int i2 = 0; i2 < 10; i2++) + { + if ((v[i2] > '9') || (v[i2] < '0')) + { + return FALSE; + } + } + + if (AsnTime->type == V_ASN1_UTCTIME) + { + ValidTime.y= (v[0]-'0')*10+(v[1]-'0'); + if (ValidTime.y < 50) ValidTime.y+=100; + ValidTime.y += 1900; + v += 2; + i -= 2; + } + else if (AsnTime->type == V_ASN1_GENERALIZEDTIME) + { + if (i < 12) + { + return FALSE; + } + ValidTime.y = (v[0]-'0')*1000+(v[1]-'0')*100 + (v[2]-'0')*10+(v[3]-'0'); + v += 4; + i -= 4; + } + else + { + return FALSE; + } + + ValidTime.M = (v[0]-'0')*10+(v[1]-'0'); + if ((ValidTime.M > 12) || (ValidTime.M < 1)) + { + return FALSE; + } + + ValidTime.d = (v[2]-'0')*10+(v[3]-'0'); + ValidTime.h = (v[4]-'0')*10+(v[5]-'0'); + ValidTime.m = (v[6]-'0')*10+(v[7]-'0'); + + if ((i >= 10) && + (v[8] >= '0') && (v[8] <= '9') && + (v[9] >= '0') && (v[9] <= '9')) + { + ValidTime.s = (v[8]-'0')*10+(v[9]-'0'); + } + else + { + ValidTime.s = 0; + } + + return TRUE; +} + +BOOL CAsyncSslSocketLayer::GetPeerCertificateData(t_SslCertData &SslCertData, LPCTSTR & Error) +{ + X509 *pX509=SSL_get_peer_certificate(m_ssl); + if (!pX509) + { + Error = L"Cannot get certificate"; + return FALSE; + } + + //Reset the contents of SslCertData + memset(&SslCertData, 0, sizeof(t_SslCertData)); + + //Set subject data fields + X509_NAME *pX509Name=X509_get_subject_name(pX509); + + if (pX509Name) + { + int count=X509_NAME_entry_count(pX509Name); + for (int i=0;i 0) + { + // Keep it huge + LPWSTR unicode = static_cast(nb_calloc(len * 10, sizeof(WCHAR))); + memset(unicode, 0, sizeof(WCHAR) * len * 10); + int unicodeLen = MultiByteToWideChar(CP_UTF8, 0, (const char *)out, len, unicode, len * 10); + if (unicodeLen > 0) + { +#ifdef _UNICODE + str = unicode; +#else + LPSTR ansi = static_cast(nb_calloc(len * 10, sizeof(CHAR))); + memset(ansi, 0, sizeof(CHAR) * len * 10); + int ansiLen = WideCharToMultiByte(CP_ACP, 0, unicode, unicodeLen, ansi, len * 10, 0, 0); + if (ansiLen > 0) + str = ansi; + + nb_free(ansi); +#endif + } + nb_free(unicode); + CRYPTO_free(out); + } + + switch(OBJ_obj2nid(pObject)) + { + case NID_organizationName: + _tcsncpy(SslCertData.subject.Organization, str, 255); + SslCertData.subject.Organization[255] = 0; + break; + case NID_organizationalUnitName: + _tcsncpy(SslCertData.subject.Unit, str, 255); + SslCertData.subject.Unit[255] = 0; + break; + case NID_commonName: + _tcsncpy(SslCertData.subject.CommonName, str, 255); + SslCertData.subject.CommonName[255] = 0; + break; + case NID_pkcs9_emailAddress: + _tcsncpy(SslCertData.subject.Mail, str, 255); + SslCertData.subject.Mail[255] = 0; + break; + case NID_countryName: + _tcsncpy(SslCertData.subject.Country, str, 255); + SslCertData.subject.Country[255] = 0; + break; + case NID_stateOrProvinceName: + _tcsncpy(SslCertData.subject.StateProvince, str, 255); + SslCertData.subject.StateProvince[255] = 0; + break; + case NID_localityName: + _tcsncpy(SslCertData.subject.Town, str, 255); + SslCertData.subject.Town[255] = 0; + break; + default: + if ( !OBJ_nid2sn(OBJ_obj2nid(pObject)) ) + { + TCHAR tmp[20]; + _stprintf(tmp, L"%d", OBJ_obj2nid(pObject)); + int maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), tmp, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), L"=", maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), str, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), L";", maxlen); + } + else + { + int maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + + USES_CONVERSION; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), A2CT(OBJ_nid2sn(OBJ_obj2nid(pObject))), maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), L"=", maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), str, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.subject.Other)-1; + _tcsncpy(SslCertData.subject.Other+_tcslen(SslCertData.subject.Other), L";", maxlen); + } + break; + } + } + } + + //Set issuer data fields + pX509Name=X509_get_issuer_name(pX509); + if (pX509Name) + { + int count=X509_NAME_entry_count(pX509Name); + for (int i=0;i 0) + { + // Keep it huge + LPWSTR unicode = static_cast(nb_calloc(len * 10, sizeof(WCHAR))); + memset(unicode, 0, sizeof(WCHAR) * len * 10); + int unicodeLen = MultiByteToWideChar(CP_UTF8, 0, (const char *)out, len, unicode, len * 10); + if (unicodeLen > 0) + { +#ifdef _UNICODE + str = unicode; +#else + LPSTR ansi = static_cast(nb_calloc(len * 10, sizeof(CHAR))); + memset(ansi, 0, sizeof(CHAR) * len * 10); + int ansiLen = WideCharToMultiByte(CP_ACP, 0, unicode, unicodeLen, ansi, len * 10, 0, 0); + if (ansiLen > 0) + str = ansi; + + nb_free(ansi); +#endif + } + nb_free(unicode); + CRYPTO_free(out); + } + + switch(OBJ_obj2nid(pObject)) + { + case NID_organizationName: + _tcsncpy(SslCertData.issuer.Organization, str, 255); + SslCertData.issuer.Organization[255] = 0; + break; + case NID_organizationalUnitName: + _tcsncpy(SslCertData.issuer.Unit, str, 255); + SslCertData.issuer.Unit[255] = 0; + break; + case NID_commonName: + _tcsncpy(SslCertData.issuer.CommonName, str, 255); + SslCertData.issuer.CommonName[255] = 0; + break; + case NID_pkcs9_emailAddress: + _tcsncpy(SslCertData.issuer.Mail, str, 255); + SslCertData.issuer.Mail[255] = 0; + break; + case NID_countryName: + _tcsncpy(SslCertData.issuer.Country, str, 255); + SslCertData.issuer.Country[255] = 0; + break; + case NID_stateOrProvinceName: + _tcsncpy(SslCertData.issuer.StateProvince, str, 255); + SslCertData.issuer.StateProvince[255] = 0; + break; + case NID_localityName: + _tcsncpy(SslCertData.issuer.Town, str, 255); + SslCertData.issuer.Town[255] = 0; + break; + default: + if ( !OBJ_nid2sn(OBJ_obj2nid(pObject)) ) + { + TCHAR tmp[20]; + _stprintf(tmp, L"%d", OBJ_obj2nid(pObject)); + int maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), tmp, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), L"=", maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), str, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), L";", maxlen); + } + else + { + int maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + + USES_CONVERSION; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), A2CT(OBJ_nid2sn(OBJ_obj2nid(pObject))), maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), L"=", maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), str, maxlen); + + maxlen = 1024 - _tcslen(SslCertData.issuer.Other)-1; + _tcsncpy(SslCertData.issuer.Other+_tcslen(SslCertData.issuer.Other), L";", maxlen); + } + break; + } + } + } + + //Set date fields + + //Valid from + ASN1_TIME *pTime=X509_get_notBefore(pX509); + if (!pTime) + { + X509_free(pX509); + Error = L"Cannot get start time"; + return FALSE; + } + + if (!AsnTimeToValidTime(pTime, SslCertData.validFrom)) + { + X509_free(pX509); + Error = L"Invalid start time"; + return FALSE; + } + + //Valid until + pTime = X509_get_notAfter(pX509); + if (!pTime) + { + X509_free(pX509); + Error = L"Cannot get end time"; + return FALSE; + } + + if (!AsnTimeToValidTime(pTime, SslCertData.validUntil)) + { + X509_free(pX509); + Error = L"Invalid end time"; + return FALSE; + } + + int subjectAltNamePos = X509_get_ext_by_NID(pX509, NID_subject_alt_name, -1); + if (subjectAltNamePos >= 0) + { + X509_EXTENSION * subjectAltNameExtension = X509_get_ext(pX509, subjectAltNamePos); + BIO * subjectAltNameBio = BIO_new(BIO_s_mem()); + + if (X509V3_EXT_print(subjectAltNameBio, subjectAltNameExtension, 0, 0) == 1) + { + USES_CONVERSION; + u_char *data; + int len = BIO_get_mem_data(subjectAltNameBio, &data); + char * buf = static_cast(nb_calloc(1, len + 1)); + + memcpy(buf, data, len); + buf[len] = '\0'; + _tcsncpy(SslCertData.subjectAltName, A2CT(buf), _countof(SslCertData.subjectAltName)); + SslCertData.subjectAltName[_countof(SslCertData.subjectAltName) - 1] = '\0'; + nb_free(buf); + } + + BIO_vfree(subjectAltNameBio); + } + + unsigned int length = 20; + X509_digest(pX509, EVP_sha1(), SslCertData.hash, &length); + + // Inspired by ne_ssl_cert_export() + // Find the length of the DER encoding. + SslCertData.certificateLen = i2d_X509(pX509, NULL); + SslCertData.certificate = static_cast(nb_calloc(1, SslCertData.certificateLen)); + unsigned char * p = SslCertData.certificate; + i2d_X509(pX509, &p); + + SslCertData.priv_data = m_nSslAsyncNotifyId; + + X509_free(pX509); + + SslCertData.verificationResult = m_nVerificationResult; + SslCertData.verificationDepth = m_nVerificationDepth; + + return TRUE; +} + +std::string CAsyncSslSocketLayer::GetTlsVersionStr() +{ + return m_TlsVersionStr; +} + +std::string CAsyncSslSocketLayer::GetCipherName() +{ + return m_CipherName; +} + +void CAsyncSslSocketLayer::SetNotifyReply(int nID, int nCode, int result) +{ + if (!m_bBlocking) + return; + if (nID != m_nSslAsyncNotifyId) + return; + if (nCode != SSL_VERIFY_CERT) + return; + + m_bBlocking = FALSE; + + if (!result) + { + m_nNetworkError = WSAECONNABORTED; + WSASetLastError(WSAECONNABORTED); + if (!m_bFailureSent) + { + m_bFailureSent = TRUE; + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_FAILURE, SSL_FAILURE_CERTREJECTED); + } + TriggerEvent(FD_CLOSE, 0, TRUE); + return; + } + m_bSslEstablished = TRUE; + PrintSessionInfo(); + DoLayerCallback(LAYERCALLBACK_LAYERSPECIFIC, SSL_INFO, SSL_INFO_ESTABLISHED); + + TriggerEvents(); +} + +void CAsyncSslSocketLayer::PrintSessionInfo() +{ + const SSL_CIPHER *ciph; + X509 *cert; + + ciph = SSL_get_current_cipher(m_ssl); + char enc[4096] = {0}; + cert=SSL_get_peer_certificate(m_ssl); + + if (cert != NULL) + { + EVP_PKEY *pkey = X509_get_pubkey(cert); + if (pkey != NULL) + { + if (0) + ; +#ifndef NO_RSA + else if (pkey->type == EVP_PKEY_RSA && pkey->pkey.rsa != NULL + && pkey->pkey.rsa->n != NULL) + sprintf(enc, "%d bit RSA", BN_num_bits(pkey->pkey.rsa->n)); +#endif +#ifndef NO_DSA + else if (pkey->type == EVP_PKEY_DSA && pkey->pkey.dsa != NULL + && pkey->pkey.dsa->p != NULL) + sprintf(enc, "%d bit DSA", BN_num_bits(pkey->pkey.dsa->p)); +#endif + EVP_PKEY_free(pkey); + } + X509_free(cert); + /* The SSL API does not allow us to look at temporary RSA/DH keys, + * otherwise we should print their lengths too */ + } + + const int buffer_size = 4 * 1024; + char *buffer = static_cast(nb_calloc(1, buffer_size)); + char *buffer2 = static_cast(nb_calloc(1, buffer_size)); + // see also ne_ssl_get_version and ne_ssl_get_cipher + m_TlsVersionStr = SSL_get_version(m_ssl); + sprintf(buffer, "%s: %s, %s, %s", + SSL_CIPHER_get_version(ciph), + SSL_CIPHER_get_name(ciph), + enc, + SSL_CIPHER_description(ciph, buffer2, buffer_size)); + m_CipherName = buffer; + // see TWebDAVFileSystem::CollectTLSSessionInfo() + sprintf(buffer, "Using %s, cipher %s", + m_TlsVersionStr.c_str(), + m_CipherName.c_str()); + USES_CONVERSION; + LogSocketMessageRaw(FZ_LOG_WARNING, A2T(buffer)); + nb_free(buffer2); + nb_free(buffer); +} + +void CAsyncSslSocketLayer::OnConnect(int nErrorCode) +{ + if (m_bUseSSL && nErrorCode) + TriggerEvent(FD_WRITE, 0); + TriggerEvent(FD_CONNECT, nErrorCode, TRUE); +} + +CAsyncSslSocketLayer * CAsyncSslSocketLayer::LookupLayer(SSL * Ssl) +{ + CAsyncSslSocketLayer * Result = NULL; + m_sCriticalSection.Lock(); + t_SslLayerList * Cur = m_pSslLayerList; + while (Cur != NULL) + { + if (Cur->pLayer->m_ssl == Ssl) + { + break; + } + Cur = Cur->pNext; + } + m_sCriticalSection.Unlock(); + + if (Cur == NULL) + { + MessageBox(0, L"Can't lookup TLS session!", L"Critical error", MB_ICONEXCLAMATION); + Result = NULL; + } + else + { + Result = Cur->pLayer; + } + + return Result; +} + +int CAsyncSslSocketLayer::verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + X509 *err_cert; + int err, depth; + SSL *ssl; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + /* + * Retrieve the pointer to the SSL of the connection currently treated + * and the application specific data stored into the SSL object. + */ + ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + + + CAsyncSslSocketLayer * pLayer = LookupLayer(ssl); + + if (pLayer == NULL) + { + return 1; + } + + /* + * Catch a too long certificate chain. The depth limit set using + * SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so + * that whenever the "depth>verify_depth" condition is met, we + * have violated the limit and want to log this error condition. + * We must do it here, because the CHAIN_TOO_LONG error would not + * be found explicitly; only errors introduced by cutting off the + * additional certificates would be logged. + */ + if (depth > 10) {//mydata->verify_depth) { + preverify_ok = 0; + err = X509_V_ERR_CERT_CHAIN_TOO_LONG; + X509_STORE_CTX_set_error(ctx, err); + } + + if (!preverify_ok) + { + if (!pLayer->m_nVerificationResult) + { + pLayer->m_nVerificationDepth = depth; + pLayer->m_nVerificationResult = err; + } + } + return 1; +} + +int CAsyncSslSocketLayer::ProvideClientCert( + SSL * Ssl, X509 ** Certificate, EVP_PKEY ** PrivateKey) +{ + CAsyncSslSocketLayer * Layer = LookupLayer(Ssl); + + USES_CONVERSION; + CString Message; + Message.LoadString(NEED_CLIENT_CERTIFICATE); + char * Buffer = static_cast(nb_calloc(1, Message.GetLength() + 1)); + strcpy(Buffer, T2A(Message)); + + int Level; + int Result; + if ((Layer->FCertificate == NULL) || (Layer->FPrivateKey == NULL)) + { + Level = FZ_LOG_WARNING; + Result = 0; + } + else + { + Level = FZ_LOG_PROGRESS; + *Certificate = X509_dup(Layer->FCertificate); + CRYPTO_add(&Layer->FPrivateKey->references, 1, CRYPTO_LOCK_EVP_PKEY); + *PrivateKey = Layer->FPrivateKey; + Result = 1; + } + + Layer->LogSocketMessageRaw(Level, A2T(Buffer)); + nb_free(Buffer); + + return Result; +} + +void CAsyncSslSocketLayer::SetClientCertificate(X509 * Certificate, EVP_PKEY * PrivateKey) +{ + FCertificate = Certificate; + FPrivateKey = PrivateKey; +} + +BOOL CAsyncSslSocketLayer::SetCertStorage(CString file) +{ + m_CertStorage = file; + return TRUE; +} + +void CAsyncSslSocketLayer::OnClose(int nErrorCode) +{ + m_onCloseCalled = true; + if (m_bUseSSL && &BIO_ctrl) + { + size_t pending = BIO_ctrl_pending(m_sslbio); + if (pending > 0) + { + TriggerEvents(); + } + else TriggerEvent(FD_CLOSE, nErrorCode, TRUE); + } + else + TriggerEvent(FD_CLOSE, nErrorCode, TRUE); +} + +void CAsyncSslSocketLayer::PrintLastErrorMsg() +{ + int err = ERR_get_error(); + while (err) + { + char *buffer = static_cast(nb_calloc(1, 512)); + const char *reason = ERR_reason_error_string(err); + ERR_error_string(err, buffer); + err = ERR_get_error(); + USES_CONVERSION; + LogSocketMessageRaw(FZ_LOG_PROGRESS, A2T(buffer)); + LogSocketMessageRaw(FZ_LOG_WARNING, A2T(reason)); + nb_free(buffer); + } +} + +void CAsyncSslSocketLayer::TriggerEvents() +{ + size_t pending = BIO_ctrl_pending(m_nbio); + if (pending > 0) + { + if (m_mayTriggerWrite) + { + m_mayTriggerWrite = false; + TriggerEvent(FD_WRITE, 0); + } + } + else if (!m_nNetworkSendBufferLen && m_bSslEstablished && !m_pRetrySendBuffer) + { + if (BIO_ctrl_get_write_guarantee(m_sslbio) > 0 && m_mayTriggerWriteUp) + { + m_mayTriggerWriteUp = false; + TriggerEvent(FD_WRITE, 0, TRUE); + } + } + + if (m_bSslEstablished && BIO_ctrl_pending(m_sslbio) > 0) + { + if (m_mayTriggerReadUp && !m_bBlocking) + { + m_mayTriggerReadUp = false; + TriggerEvent(FD_READ, 0, TRUE); + } + } + else + { + int len = BIO_ctrl_get_write_guarantee(m_nbio); + if (len > 0 && m_mayTriggerRead) + { + m_mayTriggerRead = false; + TriggerEvent(FD_READ, 0); + } + } + + if (m_onCloseCalled && m_bSslEstablished) + { + if (BIO_ctrl_pending(m_sslbio) <= 0) + { + TriggerEvent(FD_CLOSE, 0, TRUE); + } + } +} + + diff --git a/netbox/src/filezilla/AsyncSslSocketLayer.h b/netbox/src/filezilla/AsyncSslSocketLayer.h new file mode 100644 index 000000000..514b60116 --- /dev/null +++ b/netbox/src/filezilla/AsyncSslSocketLayer.h @@ -0,0 +1,253 @@ +/* CAsyncSslSocketLayer by Tim Kosse + mailto: tim.kosse@filezilla-project.org) + Version 2.0 (2005-02-27) +------------------------------------------------------------- + +Introduction +------------ + +CAsyncSslSocketLayer is a layer class for CAsyncSocketEx which allows you to establish SSL secured +connections. Support for both client and server side is provided. + +How to use +---------- + +Using this class is really simple. In the easiest case, just add an instance of +CAsyncSslSocketLayer to your socket and call InitClientSsl after creation of the socket. + +This class only has a couple of public functions: +- int InitSSLConnection(bool clientMode); + This functions establishes an SSL connection. The clientMode parameter specifies whether the SSL connection + is in server or in client mode. + Most likely you want to call this function right after calling Create for the socket. + But sometimes, you'll need to call this function later. One example is for an FTP connection + with explicit SSL: In this case you would have to call InitSSLConnection after receiving the reply + to an 'AUTH SSL' command. + InitSSLConnection returns 0 on success, else an error code as described below under SSL_FAILURE +- Is UsingSSL(); + Returns true if you've previously called InitClientSsl() +- SetNotifyReply(SetNotifyReply(int nID, int nCode, int result); + You can call this function only after receiving a layerspecific callback with the SSL_VERIFY_CERT + id. Set result to 1 if you trust the certificate and 0 if you don't trust it. + nID has to be the priv_data element of the t_SslCertData structure and nCode has to be SSL_VERIFY_CERT. + +This layer sends some layerspecific notifications to your socket instance, you can handle them in +OnLayerCallback of your socket class. +Valid notification IDs are: +- SSL_INFO 0 + There are two possible values for param2: + SSL_INFO_ESTABLISHED 0 - You'll get this notification if the SSL negotiation was successful + SSL_INFO_SHUTDOWNCOMPLETE 1 - You'll get this notification if the SSL connection has been shut + down successfully. See below for details. +- SSL_FAILURE 1 + This notification is sent if the SSL connection could not be established or if an existing + connection failed. Valid values for param2 are: + - SSL_FAILURE_NONE 0 - Everything OK + - SSL_FAILURE_UNKNOWN 1 - Details may have been sent with a SSL_VERBOSE_* notification. + - SSL_FAILURE_ESTABLISH 2 - Problem during SSL negotiation + - SSL_FAILURE_INITSSL 8 + - SSL_FAILURE_VERIFYCERT 16 - The remote SSL certificate was invalid + - SSL_FAILURE_CERTREJECTED 32 - The remote SSL certificate was rejected by user +- SSL_VERIFY_CERT 2 + This notification is sent each time a remote certificate has to be verified. + param2 is a pointer to a t_SslCertData structure which contains some information + about the remote certificate. + You have to set the reply to this message using the SetNotifyReply function. + +Be careful with closing the connection after sending data, not all data may have been sent already. +Before closing the connection, you should call Shutdown() and wait for the SSL_INFO_SHUTDOWNCOMPLETE +notification. This assures that all encrypted data really has been sent. + +License +------- + +Feel free to use this class, as long as you don't claim that you wrote it +and this copyright notice stays intact in the source files. +If you want to use this class in a commercial application, a short message +to tim.kosse@filezilla-project.org would be appreciated but is not required. + +This product includes software developed by the OpenSSL Project +for use in the OpenSSL Toolkit. (http://www.openssl.org/) +*/ + +#ifndef AsyncSslSocketLayerH +#define AsyncSslSocketLayerH + +#include "AsyncSocketExLayer.h" +#include + +// Details of SSL certificate, can be used by app to verify if certificate is valid +struct t_SslCertData +{ + CUSTOM_MEM_ALLOCATION_IMPL + + ~t_SslCertData() + { + nb_free(certificate); + } + + struct t_Contact + { + TCHAR Organization[256]; + TCHAR Unit[256]; + TCHAR CommonName[256]; + TCHAR Mail[256]; + TCHAR Country[256]; + TCHAR StateProvince[256]; + TCHAR Town[256]; + TCHAR Other[1024]; + } subject, issuer; + + struct t_validTime + { + // Year, Month, day, hour, minute, second + int y, M, d, h, m, s; + } validFrom, validUntil; + + TCHAR subjectAltName[10240]; + + uint8_t hash[20]; + + uint8_t * certificate; + size_t certificateLen; + + int verificationResult; + int verificationDepth; + + int priv_data; //Internal data, do not modify +}; + +class CCriticalSectionWrapper; + +class CAsyncSslSocketLayer : public CAsyncSocketExLayer +{ +public: + BOOL SetCertStorage(CString file); + CAsyncSslSocketLayer(); + virtual ~CAsyncSslSocketLayer(); + + void SetNotifyReply(int nID, int nCode, int result); + BOOL GetPeerCertificateData(t_SslCertData & SslCertData, LPCTSTR & Error); + std::string GetTlsVersionStr(); + std::string GetCipherName(); + void SetClientCertificate(X509 * Certificate, EVP_PKEY * PrivateKey); + + bool IsUsingSSL(); + int InitSSLConnection(bool clientMode, + CAsyncSslSocketLayer * main, + bool sessionreuse, int minTlsVersion, int maxTlsVersion, + void * pContext = 0); + + // Send raw text, useful to send a confirmation after the ssl connection + // has been initialized + int SendRaw(const void * lpBuf, int nBufLen, int nFlags = 0); + + void * GetContext() { return m_ssl_ctx; } + +private: + virtual void Close(); + virtual BOOL Connect(LPCTSTR lpszHostAddress, UINT nHostPort); + virtual BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen); + virtual void OnConnect(int nErrorCode); + virtual void OnReceive(int nErrorCode); + virtual void OnSend(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual int Receive(void * lpBuf, int nBufLen, int nFlags = 0); + virtual int Send(const void * lpBuf, int nBufLen, int nFlags = 0); + virtual BOOL ShutDown( int nHow = sends ); + + void ResetSslSession(); + void PrintSessionInfo(); + BOOL ShutDownComplete(); + int InitSSL(); + void UnloadSSL(); + void PrintLastErrorMsg(); + + void TriggerEvents(); + + // Will be called from the OpenSSL library + static void apps_ssl_info_callback(const SSL * s, int where, int ret); + static int verify_callback(int preverify_ok, X509_STORE_CTX * ctx); + static int ProvideClientCert( + SSL * Ssl, X509 ** Certificate, EVP_PKEY ** PrivateKey); + static CAsyncSslSocketLayer * LookupLayer(SSL * Ssl); + + bool m_bUseSSL; + BOOL m_bFailureSent; + + // Critical section for thread synchronization + static CCriticalSectionWrapper m_sCriticalSection; + + // Status variables + static int m_nSslRefCount; + BOOL m_bSslInitialized; + int m_nShutDown; + int m_nNetworkError; + int m_nSslAsyncNotifyId; + BOOL m_bBlocking; + BOOL m_bSslEstablished; + CString m_CertStorage; + int m_nVerificationResult; + int m_nVerificationDepth; + + static struct t_SslLayerList : public TObject + { + CAsyncSslSocketLayer * pLayer; + t_SslLayerList * pNext; + } * m_pSslLayerList; + + // SSL data + SSL_CTX* m_ssl_ctx; // SSL context + static std::map m_contextRefCount; + SSL* m_ssl; // current session handle + SSL_SESSION * m_sessionid; + bool m_sessionreuse; + CAsyncSslSocketLayer * m_Main; + + // Data channels for encrypted/unencrypted data + BIO* m_nbio; // Network side, sends/receives encrypted data + BIO* m_ibio; // Internal side, won't be used directly + BIO* m_sslbio; // The data to encrypt / the decrypted data has to go though this bio + + // Send buffer + char* m_pNetworkSendBuffer; + int m_nNetworkSendBufferLen; + int m_nNetworkSendBufferMaxLen; + + char* m_pRetrySendBuffer; + int m_nRetrySendBufferLen; + + bool m_mayTriggerRead; + bool m_mayTriggerWrite; + bool m_mayTriggerReadUp; + bool m_mayTriggerWriteUp; + + bool m_onCloseCalled; + + std::string m_TlsVersionStr; + std::string m_CipherName; + + X509 * FCertificate; + EVP_PKEY * FPrivateKey; +}; + +#define SSL_INFO 0 +#define SSL_FAILURE 1 +#define SSL_VERIFY_CERT 2 + +#define SSL_INFO_ESTABLISHED 0 +#define SSL_INFO_SHUTDOWNCOMPLETE 1 + +#define SSL_FAILURE_UNKNOWN 0 +#define SSL_FAILURE_ESTABLISH 1 +#define SSL_FAILURE_INITSSL 4 +#define SSL_FAILURE_VERIFYCERT 8 +#define SSL_FAILURE_CERTREJECTED 0x10 + +#define SSL_VERSION_SSL2 2 +#define SSL_VERSION_SSL3 3 +#define SSL_VERSION_TLS10 10 +#define SSL_VERSION_TLS11 11 +#define SSL_VERSION_TLS12 12 + +#endif // AsyncSslSocketLayerH diff --git a/netbox/src/filezilla/FileZillaApi.cpp b/netbox/src/filezilla/FileZillaApi.cpp new file mode 100644 index 000000000..9d34cca21 --- /dev/null +++ b/netbox/src/filezilla/FileZillaApi.cpp @@ -0,0 +1,564 @@ + +#include "stdafx.h" +#include "FileZillaApi.h" +#include "mainthread.h" + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CFileZillaApi::CFileZillaApi() +{ + m_nInternalMessageID=0; + m_pMainThread=0; + m_bInitialized=FALSE; +} + +CFileZillaApi::~CFileZillaApi() +{ + Destroy(); +} + +int CFileZillaApi::Init(TFileZillaIntern * Intern, CFileZillaTools * pTools) +{ + //Check if call allowed + if (m_bInitialized) + return FZ_REPLY_ALREADYINIZIALIZED; + + //Initialize variables + m_nInternalMessageID=RegisterWindowMessage( L"FileZillaInternalApiMessage{F958620E-040C-4b33-A091-7E04E10AA660}" ); + if (!m_nInternalMessageID) + return FZ_REPLY_NOTINITIALIZED; + + //Create thread object + m_pMainThread = CMainThread::Create(THREAD_PRIORITY_BELOW_NORMAL, CREATE_SUSPENDED); + + //Initialize Thread variables + m_pMainThread->m_nInternalMessageID=m_nInternalMessageID; + m_pMainThread->m_pTools=pTools; + + m_pMainThread->InitIntern(Intern); + + //Resume Thread + m_pMainThread->ResumeThread(); + + //Initialization OK + m_bInitialized=TRUE; + return FZ_REPLY_OK; +} + +int CFileZillaApi::IsConnected() const +{ + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + return m_pMainThread->IsConnected()?FZ_REPLY_OK:FZ_REPLY_NOTCONNECTED; +} + +int CFileZillaApi::IsBusy() +{ + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + return m_pMainThread->IsBusy()?FZ_REPLY_BUSY:FZ_REPLY_IDLE; +} + +int CFileZillaApi::Connect(const t_server &server) +{ + //Check parameters + if (server.host==L"" || server.port<1 || server.port>65535) + return FZ_REPLY_INVALIDPARAM; + +#ifndef MPEXT_NO_GSS + BOOL bUseGSS = FALSE; + if (GetOptionVal(OPTION_USEGSS)) + { + USES_CONVERSION; + + CString GssServers = GetOption(OPTION_GSSSERVERS); + hostent *fullname = gethostbyname(T2CA(server.host)); + CString host; + if (fullname) + host = fullname->h_name; + else + host = server.host; + host.MakeLower(); + int i; + while ((i=GssServers.Find( L";" ))!=-1) + { + if ((L"."+GssServers.Left(i))==host.Right(GssServers.Left(i).GetLength()+1) || GssServers.Left(i)==host) + { + bUseGSS = TRUE; + break; + } + GssServers = GssServers.Mid(i+1); + } + } + if (!bUseGSS && server.user == L"") + return FZ_REPLY_INVALIDPARAM; +#endif + + if (!(server.nServerType&FZ_SERVERTYPE_HIGHMASK)) + return FZ_REPLY_INVALIDPARAM; + + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (m_pMainThread->IsBusy()) + return FZ_REPLY_BUSY; + + t_command command; + command.id=FZ_COMMAND_CONNECT; + command.server=server; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::Cancel() +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsBusy()!=FZ_REPLY_BUSY) + return FZ_REPLY_NOTBUSY; + m_pMainThread->PostThreadMessage(m_nInternalMessageID, FZAPI_THREADMSG_CANCEL, 0); + return FZ_REPLY_WOULDBLOCK; +} + +void CFileZillaApi::Destroy() +{ + if (!m_bInitialized) + return; + DebugAssert(m_pMainThread); + HANDLE tmp=m_pMainThread->m_hThread; + m_pMainThread->Quit(); + //Wait for the main thread to quit + ::WaitForSingleObject(tmp, 10000); + + m_pMainThread=0; + m_bInitialized=FALSE; +} + +int CFileZillaApi::Disconnect() +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + + m_pMainThread->PostThreadMessage(m_nInternalMessageID,FZAPI_THREADMSG_DISCONNECT,0); + return FZ_REPLY_WOULDBLOCK; +} + +int CFileZillaApi::List(const CServerPath& path) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (path.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + + if (m_pMainThread->IsBusy()) + return FZ_REPLY_BUSY; + + t_command command; + command.id=FZ_COMMAND_LIST; + command.path=path; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::ListFile(const CString & FileName, const CServerPath & path) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (path.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + if (FileName=="") + return FZ_REPLY_INVALIDPARAM; + + if (m_pMainThread->IsBusy()) + return FZ_REPLY_BUSY; + t_command command; + command.id=FZ_COMMAND_LISTFILE; + command.param1=FileName; + command.path=path; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::FileTransfer(const t_transferfile &TransferFile) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (TransferFile.remotefile==L"" || TransferFile.localfile==L"" || TransferFile.remotepath.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + t_command command; + command.id=FZ_COMMAND_FILETRANSFER; + command.transferfile=TransferFile; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::GetCurrentServer(t_server &server) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (m_pMainThread->GetCurrentServer(server)) + return FZ_REPLY_OK; + else + return FZ_REPLY_NOTCONNECTED; +} + +int CFileZillaApi::SetCurrentPath(CServerPath path) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + m_pMainThread->SetCurrentPath(path); + return FZ_REPLY_OK; +} + +int CFileZillaApi::GetCurrentPath(CServerPath & path) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + return (m_pMainThread->GetCurrentPath(path) ? FZ_REPLY_OK : FZ_REPLY_NOTCONNECTED); +} + +bool CFileZillaApi::UsingMlsd() +{ + //Check if call allowed + if (!m_bInitialized) + return false; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return false; + return m_pMainThread->UsingMlsd(); +} + +bool CFileZillaApi::UsingUtf8() +{ + //Check if call allowed + if (!m_bInitialized) + return false; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return false; + return m_pMainThread->UsingUtf8(); +} + +std::string CFileZillaApi::GetTlsVersionStr() +{ + //Check if call allowed + if (!m_bInitialized) + return std::string(); + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return std::string(); + return m_pMainThread->GetTlsVersionStr(); +} + +std::string CFileZillaApi::GetCipherName() +{ + //Check if call allowed + if (!m_bInitialized) + return std::string(); + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return std::string(); + return m_pMainThread->GetCipherName(); +} + +int CFileZillaApi::CustomCommand(CString CustomCommand) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + t_server server; + int res=GetCurrentServer(server); + if (res!=FZ_REPLY_OK) + return res; + if (CustomCommand==L"") + return FZ_REPLY_INVALIDPARAM; + + t_command command; + command.id=FZ_COMMAND_CUSTOMCOMMAND; + command.param1=CustomCommand; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::Delete(CString FileName, const CServerPath &path /*=CServerPath()*/) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + if (FileName=="") + return FZ_REPLY_INVALIDPARAM; + + CServerPath path2=path; + if (path2.IsEmpty()) + { + m_pMainThread->GetCurrentPath(path2); + if (path2.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + } + + t_command command; + command.id=FZ_COMMAND_DELETE; + command.param1=FileName; + command.path=path2; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::RemoveDir(CString DirName, const CServerPath &path /*=CServerPath()*/) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + if (DirName==L"") + return FZ_REPLY_INVALIDPARAM; + + CServerPath path2=path; + if (path2.IsEmpty()) + { + m_pMainThread->GetCurrentPath(path2); + if (path2.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + } + + t_command command; + command.id=FZ_COMMAND_REMOVEDIR; + command.param1=DirName; + command.path=path2; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::MakeDir(const CServerPath &path) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + if (path.IsEmpty() || !path.HasParent()) + return FZ_REPLY_INVALIDPARAM; + + t_command command; + command.id=FZ_COMMAND_MAKEDIR; + command.path=path; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::Rename(CString oldName, CString newName, const CServerPath &path /*=CServerPath()*/, const CServerPath &newPath /*=CServerPath()*/) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + if (oldName==L"" || newName==L"") + return FZ_REPLY_INVALIDPARAM; + + CServerPath path2 = path; + if (path2.IsEmpty()) + { + m_pMainThread->GetCurrentPath(path2); + if (path2.IsEmpty()) + return FZ_REPLY_INVALIDPARAM; + } + + t_command command; + command.id = FZ_COMMAND_RENAME; + command.param1 = oldName; + command.param2 = newName; + command.path = path2; + command.newPath = newPath; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +int CFileZillaApi::SetAsyncRequestResult(int nAction, CAsyncRequestData *pData) +{ + if (!this || !pData) + return FZ_REPLY_CRITICALERROR | FZ_REPLY_INVALIDPARAM; + + if (IsBadWritePtr(pData, sizeof(CAsyncRequestData))) + return FZ_REPLY_CRITICALERROR; + + if (!m_bInitialized) + { + delete pData; + return FZ_REPLY_NOTINITIALIZED; + } + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + { + delete pData; + return FZ_REPLY_NOTCONNECTED; + } + + switch(pData->nRequestType) + { + case FZ_ASYNCREQUEST_OVERWRITE: + break; + case FZ_ASYNCREQUEST_VERIFYCERT: + if (!(static_cast(pData))->pCertData) + { + delete pData; + return FZ_REPLY_INVALIDPARAM; + } + break; + case FZ_ASYNCREQUEST_NEEDPASS: + break; +#ifndef MPEXT_NO_GSS + case FZ_ASYNCREQUEST_GSS_AUTHFAILED: + case FZ_ASYNCREQUEST_GSS_NEEDUSER: + case FZ_ASYNCREQUEST_GSS_NEEDPASS: + break; +#endif + default: + delete pData; + return FZ_REPLY_INVALIDPARAM; + } + pData->nRequestResult = nAction; + if (!m_pMainThread) + { + delete pData; + return FZ_REPLY_NOTINITIALIZED; + } + + m_pMainThread->PostThreadMessage(m_nInternalMessageID, FZAPI_THREADMSG_ASYNCREQUESTREPLY, reinterpret_cast(pData)); + + return FZ_REPLY_OK; +} + +int CFileZillaApi::Chmod(int nValue, const CString & FileName, const CServerPath & path /*=CServerPath()*/ ) +{ + //Check if call allowed + if (!m_bInitialized) + return FZ_REPLY_NOTINITIALIZED; + if (IsConnected()==FZ_REPLY_NOTCONNECTED) + return FZ_REPLY_NOTCONNECTED; + if (IsBusy()==FZ_REPLY_BUSY) + return FZ_REPLY_BUSY; + if (FileName==L"") + return FZ_REPLY_INVALIDPARAM; + + t_command command; + command.id=FZ_COMMAND_CHMOD; + command.param1=FileName; + command.param4=nValue; + command.path=path; + m_pMainThread->Command(command); + return m_pMainThread->LastOperationSuccessful()?FZ_REPLY_OK:FZ_REPLY_ERROR; +} + +void CFileZillaApi::SetDebugLevel(int nDebugLevel) +{ + m_pMainThread->GetIntern()->SetDebugLevel(nDebugLevel); +} + +//CAsyncRequestData derived classes +CAsyncRequestData::CAsyncRequestData() +{ + nRequestType = 0; + nRequestID = 0; + nRequestResult = 0; +} + +CAsyncRequestData::~CAsyncRequestData() +{ +} + +COverwriteRequestData::COverwriteRequestData() +{ + size1 = 0; + size2 = 0; + nRequestType=FZ_ASYNCREQUEST_OVERWRITE; + localtime=0; + localFileHandle = INVALID_HANDLE_VALUE; + remotetime.hasdate = false; + pTransferFile=0; +} + +COverwriteRequestData::~COverwriteRequestData() +{ + delete pTransferFile; + delete localtime; +} + +CVerifyCertRequestData::CVerifyCertRequestData() +{ + nRequestType=FZ_ASYNCREQUEST_VERIFYCERT; + pCertData=0; +} + +CVerifyCertRequestData::~CVerifyCertRequestData() +{ + delete pCertData; +} + +CNeedPassRequestData::CNeedPassRequestData() +{ + nRequestType=FZ_ASYNCREQUEST_NEEDPASS; + nOldOpState=0; +} + +CNeedPassRequestData::~CNeedPassRequestData() +{ +} + +#ifndef MPEXT_NO_GSS +CGssNeedPassRequestData::CGssNeedPassRequestData() +{ + nRequestType=FZ_ASYNCREQUEST_GSS_NEEDPASS; +} + +CGssNeedPassRequestData::~CGssNeedPassRequestData() +{ +} + +CGssNeedUserRequestData::CGssNeedUserRequestData() +{ + nRequestType = FZ_ASYNCREQUEST_GSS_NEEDUSER; +} + +CGssNeedUserRequestData::~CGssNeedUserRequestData() +{ +} +#endif diff --git a/netbox/src/filezilla/FileZillaApi.h b/netbox/src/filezilla/FileZillaApi.h new file mode 100644 index 000000000..8a32c0daa --- /dev/null +++ b/netbox/src/filezilla/FileZillaApi.h @@ -0,0 +1,288 @@ + +#pragma once + +#include + +#include "FzApiStructures.h" +#include "structures.h" +#include "AsyncSslSocketLayer.h" +#include "FileZillaIntern.h" +#include "FileZillaOpt.h" + +#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ + typedef rettype (WINAPI *t_##name) params; \ + linkage t_##name p_##name +#define STR1(x) #x +#define STR(x) STR1(x) +#define GET_WINDOWS_FUNCTION_PP(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL) +#define GET_WINDOWS_FUNCTION(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL) + +#ifndef NO_IPV6 +DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, + (const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res)); +DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); +DECL_WINDOWS_FUNCTION(static, int, getnameinfo, + (const struct sockaddr FAR * sa, socklen_t salen, + char FAR * host, size_t hostlen, char FAR * serv, + size_t servlen, int flags)); +#endif + +// This structure holds the commands which will be processed by the api. +// You don't have to fill this struct, you may use the command specific +// functions which is easier. +// See below for a list of supported commands and their parameters. +struct t_command : public TObject +{ + int id; // Type of command, see below + CString param1; // Parameters for this command + CString param2; + int param4; + CServerPath path; + CServerPath newPath; // Used for rename + t_transferfile transferfile; + t_server server; +}; + +//Description of all api commands + +#define FZ_COMMAND_CONNECT 0x0001 +// Connects to the server passed in t_command::server +// Possible return values: +// FZ_REPLY_BUSY, FZ_REPLY_ERROR, FZ_REPLY_INVALIDPARAM, +// FZ_REPLY_NOTINITIALIZED, FZ_REPLY_OK, FZ_REPLY_WOULDBLOCK + +#define FZ_COMMAND_LIST 0x0002 +// Lists the contents of a directory. If no parameter is given, the current +// directory will be listed, else t_command::path specifies the directory +// which contents will be listed. t_command::param1 may specify the name +// of a direct child or parent directory (Use only the last path segment or +// ".."). When the directory listing is successful, it will be sent to the +// owner window (see FZ_DATA_LIST) +// t_command::param4 controls the list mode. (See list modes section) +// Possible return values: +// FZ_REPLY_BUSY, FZ_REPLY_ERROR, FZ_REPLY_INVALIDPARAM, +// FZ_REPLY_NOTCONNECTED, FZ_REPLY_NOTINITIALIZED, FZ_REPLY_OK, +// FZ_REPLY_WOULDBLOCK + +#define FZ_COMMAND_FILETRANSFER 0x0004 +// Transfers the file specified with t_command::transferfile, see +// t_transferfile for detailed information +// Possible return values: +// FZ_REPLY_BUSY, FZ_REPLY_ERROR, FZ_REPLY_INVALIDPARAM, +// FZ_REPLY_NOTCONNECTED, FZ_REPLY_NOTINITIALIZED, FZ_REPLY_OK, +// FZ_REPLY_WOULDBLOCK + +#define FZ_COMMAND_DISCONNECT 0x0008 +#define FZ_COMMAND_CUSTOMCOMMAND 0x0010 +#define FZ_COMMAND_DELETE 0x0020 +#define FZ_COMMAND_REMOVEDIR 0x0040 +#define FZ_COMMAND_RENAME 0x0080 +#define FZ_COMMAND_MAKEDIR 0x0100 +#define FZ_COMMAND_CHMOD 0x0200 +#define FZ_COMMAND_LISTFILE 0x0400 + +#define FZ_MSG_OFFSET 16 +#define FZ_MSG_OFFSETMASK 0xFFFF +#define FZ_MSG_ID(x) ((x >> FZ_MSG_OFFSET) & FZ_MSG_OFFSETMASK) +#define FZ_MSG_PARAM(x) ( x & FZ_MSG_OFFSETMASK) +#define FZ_MSG_MAKEMSG(id, param) ((((DWORD)(id & FZ_MSG_OFFSETMASK)) << FZ_MSG_OFFSET) + (param & FZ_MSG_OFFSETMASK) ) + +#define FZ_MSG_REPLY 0 +#define FZ_MSG_LISTDATA 1 +#define FZ_MSG_ASYNCREQUEST 5 +#define FZ_MSG_STATUS 6 +#define FZ_MSG_TRANSFERSTATUS 7 +#define FZ_MSG_CAPABILITIES 9 + +#define FZ_ASYNCREQUEST_OVERWRITE 1 +#define FZ_ASYNCREQUEST_VERIFYCERT 2 +#ifndef MPEXT_NO_GSS +#define FZ_ASYNCREQUEST_GSS_AUTHFAILED 3 +#define FZ_ASYNCREQUEST_GSS_NEEDPASS 4 +#endif +#ifndef MPEXT_NO_GSS +#define FZ_ASYNCREQUEST_GSS_NEEDUSER 8 +#endif +#define FZ_ASYNCREQUEST_NEEDPASS 10 + +class CAsyncRequestData : public TObject +{ +public: + CAsyncRequestData(); + virtual ~CAsyncRequestData(); + int nRequestType; + __int64 nRequestID; //Unique for every request sent + int nRequestResult; +}; + +class COverwriteRequestData : public CAsyncRequestData +{ +public: + COverwriteRequestData(); + virtual ~COverwriteRequestData(); + CString FileName1; + CString FileName2; + CString path1,path2; + __int64 size1; + __int64 size2; + CTime * localtime; + HANDLE localFileHandle; + t_directory::t_direntry::t_date remotetime; + const t_transferfile * pTransferFile; +}; + +class CVerifyCertRequestData : public CAsyncRequestData +{ +public: + CVerifyCertRequestData(); + virtual ~CVerifyCertRequestData(); + t_SslCertData * pCertData; +}; + +class CNeedPassRequestData : public CAsyncRequestData +{ +public: + CNeedPassRequestData(); + virtual ~CNeedPassRequestData(); + CString Password; + int nOldOpState; +}; + +#ifndef MPEXT_NO_GSS +class CGssNeedPassRequestData : public CAsyncRequestData +{ +public: + CGssNeedPassRequestData(); + virtual ~CGssNeedPassRequestData(); + CString pass; + int nOldOpState; +}; + +class CGssNeedUserRequestData : public CAsyncRequestData +{ +public: + CGssNeedUserRequestData(); + virtual ~CGssNeedUserRequestData(); + CString user; + int nOldOpState; +}; +#endif + +#define FZAPI_OPTION_SHOWHIDDEN 1 + +#define FTP_CONNECT 0 // SERVER USER PASS PORT +#define FTP_COMMAND 1 // COMMAND - - - +#define FTP_LIST 2 // - - - - +#define FTP_FILETRANSFER 3 // TRANSFERFILE +#define FTP_DISCONNECT 4 // - - - - +#define FTP_RECONNECT 5 // - - - - +#define FTP_DELETE 7 // FILENAME +#define FTP_REMOVEDIR 8 // DIRNAME + +#define FZ_REPLY_OK 0x0001 +#define FZ_REPLY_WOULDBLOCK 0x0002 +#define FZ_REPLY_ERROR 0x0004 +#define FZ_REPLY_OWNERNOTSET 0x0008 +#define FZ_REPLY_INVALIDPARAM 0x0010 +#define FZ_REPLY_NOTCONNECTED 0x0020 +#define FZ_REPLY_ALREADYCONNECTED 0x0040 +#define FZ_REPLY_BUSY 0x0080 +#define FZ_REPLY_IDLE 0x0100 +#define FZ_REPLY_NOTINITIALIZED 0x0200 +#define FZ_REPLY_ALREADYINIZIALIZED 0x0400 +#define FZ_REPLY_CANCEL 0x0800 +#define FZ_REPLY_DISCONNECTED 0x1000 // Always sent when disconnected from server +#define FZ_REPLY_CRITICALERROR 0x2000 // Used for FileTransfers only +#define FZ_REPLY_ABORTED 0x4000 // Used for FileTransfers only +#define FZ_REPLY_NOTSUPPORTED 0x8000 // Command is not supported for the current server + +// Additional replies +#define FZ_REPLY_NOTBUSY FZ_REPLY_IDLE + +// Servertypes +// General types +#define FZ_SERVERTYPE_HIGHMASK 0xF000 +#define FZ_SERVERTYPE_SUBMASK 0x00FF +#define FZ_SERVERTYPE_LAYERMASK 0x0FF0 + +#define FZ_SERVERTYPE_FTP 0x1000 +#define FZ_SERVERTYPE_LOCAL 0x2000 + +#define FZ_SERVERTYPE_LAYER_SSL_IMPLICIT 0x0100 +#define FZ_SERVERTYPE_LAYER_SSL_EXPLICIT 0x0200 +#define FZ_SERVERTYPE_LAYER_TLS_EXPLICIT 0x0400 + +#define FZ_SERVERTYPE_SUB_FTP_VMS 0x0001 +#define FZ_SERVERTYPE_SUB_FTP_SFTP 0x0002 +#define FZ_SERVERTYPE_SUB_FTP_WINDOWS 0x0004 +#define FZ_SERVERTYPE_SUB_FTP_MVS 0x0010 +#define FZ_SERVERTYPE_SUB_FTP_BS2000 0x0020 + +// Log messages +#define FZ_LOG_STATUS 0 +#define FZ_LOG_ERROR 1 +#define FZ_LOG_COMMAND 2 +#define FZ_LOG_REPLY 3 +// By calling CFileZillaApi::SetDebugLevel, the aplication can enable logging of the following messages: +#define FZ_LOG_APIERROR 5 +#define FZ_LOG_WARNING 6 +#define FZ_LOG_PROGRESS 7 +#define FZ_LOG_INFO 8 +#define FZ_LOG_DEBUG 9 + +class CMainThread; +class CFileZillaTools; + +class CFileZillaApi : public TObject +{ +public: + CFileZillaApi(); + virtual ~CFileZillaApi(); + + void SetDebugLevel(int nDebugLevel); + + int CustomCommand(CString command); + int Delete(CString FileName, const CServerPath & path = CServerPath()); + int RemoveDir(CString DirName, const CServerPath & path = CServerPath()); + int Rename(CString oldName, CString newName, const CServerPath & path = CServerPath(), const CServerPath & newPath = CServerPath()); + int MakeDir(const CServerPath & path); + + // General async request reply function + int SetAsyncRequestResult(int nAction, CAsyncRequestData * pData); + + int Disconnect(); + int Cancel(); + int Chmod(int nValue, const CString & FileName, const CServerPath & path = CServerPath()); + + //Initialization + int Init(TFileZillaIntern * Intern, CFileZillaTools * pTools); + + // Operations + int Connect(const t_server & server); + + int List(const CServerPath & path); + + int ListFile(const CString & FileName, const CServerPath & path); //Get info about specified file + + int FileTransfer(const t_transferfile & TransferFile); + int GetCurrentServer(t_server & server); + + int SetCurrentPath(CServerPath path); + int GetCurrentPath(CServerPath & path); + bool UsingMlsd(); + bool UsingUtf8(); + std::string GetTlsVersionStr(); + std::string GetCipherName(); + +protected: + CMainThread * m_pMainThread; + unsigned int m_nInternalMessageID; + BOOL m_bInitialized; + + void Destroy(); + int IsBusy(); + int IsConnected() const; +}; diff --git a/netbox/src/filezilla/FileZillaIntern.cpp b/netbox/src/filezilla/FileZillaIntern.cpp new file mode 100644 index 000000000..e3fb3fcd4 --- /dev/null +++ b/netbox/src/filezilla/FileZillaIntern.cpp @@ -0,0 +1,59 @@ + +#include "stdafx.h" + +#include "FileZillaIntern.h" +#include "FileZillaIntf.h" + +TFileZillaIntern::TFileZillaIntern(TFileZillaIntf * AOwner) : + FOwner(AOwner) +{ + FDebugLevel = 0; +} + +bool TFileZillaIntern::PostMessage(WPARAM wParam, LPARAM lParam) const +{ + bool Result; + unsigned int MessageID = FZ_MSG_ID(wParam); + + switch (MessageID) + { + case FZ_MSG_STATUS: + case FZ_MSG_ASYNCREQUEST: + case FZ_MSG_LISTDATA: + case FZ_MSG_TRANSFERSTATUS: + case FZ_MSG_REPLY: + case FZ_MSG_CAPABILITIES: + Result = FOwner->PostMessage(wParam, lParam); + break; + + default: + DebugFail(); + Result = false; + break; + } + + return Result; +} + +CString TFileZillaIntern::GetOption(int OptionID) const +{ + return FOwner->Option(OptionID); +} + +int TFileZillaIntern::GetOptionVal(int OptionID) const +{ + return FOwner->OptionVal(OptionID); +} + +int TFileZillaIntern::GetDebugLevel() const +{ + return FDebugLevel; +} + +void TFileZillaIntern::SetDebugLevel(int DebugLevel) +{ + FDebugLevel = DebugLevel; +} + +NB_IMPLEMENT_CLASS(TFileZillaIntern, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/filezilla/FileZillaIntern.h b/netbox/src/filezilla/FileZillaIntern.h new file mode 100644 index 000000000..274420a86 --- /dev/null +++ b/netbox/src/filezilla/FileZillaIntern.h @@ -0,0 +1,28 @@ + +#ifndef FileZillaInternH +#define FileZillaInternH + +class TFileZillaIntf; + +class TFileZillaIntern : public TObject +{ +NB_DECLARE_CLASS(TFileZillaIntern) +NB_DISABLE_COPY(TFileZillaIntern) +public: + explicit TFileZillaIntern(TFileZillaIntf * AOwner); + + bool PostMessage(WPARAM wParam, LPARAM lParam) const; + CString GetOption(int OptionID) const; + int GetOptionVal(int OptionID) const; + + inline const TFileZillaIntf * GetOwner() const { return FOwner; } + + int GetDebugLevel() const; + void SetDebugLevel(int DebugLevel); + +protected: + TFileZillaIntf * FOwner; + int FDebugLevel; +}; + +#endif // FileZillaInternH diff --git a/netbox/src/filezilla/FileZillaIntf.cpp b/netbox/src/filezilla/FileZillaIntf.cpp new file mode 100644 index 000000000..8ffdd6cd7 --- /dev/null +++ b/netbox/src/filezilla/FileZillaIntf.cpp @@ -0,0 +1,587 @@ + +#include "stdafx.h" + +#include + +#include "FileZillaIntf.h" +#include "FileZillaIntern.h" +#include "FzApiStructures.h" +#include "FileZillaApi.h" +#include "structures.h" + +#ifndef _DEBUG +#pragma comment(lib, "uafxcw.lib") +#else +#pragma comment(lib, "uafxcwd.lib") +#endif + +static HMODULE winsock_module = NULL; +#ifndef NO_IPV6 +static HMODULE winsock2_module = NULL; +static HMODULE wship6_module = NULL; +#endif // NO_IPV6 + +void TFileZillaIntf::Initialize() +{ +#ifndef NO_IPV6 + winsock2_module = +#endif + winsock_module = ::LoadLibrary(L"ws2_32.dll"); + if (!winsock_module) + { + winsock_module = ::LoadLibrary(L"wsock32.dll"); + } + if (!winsock_module) + { +// fatalbox("Unable to load any WinSock library"); + } +#ifndef NO_IPV6 + /* Check if we have getaddrinfo in Winsock */ + if (::GetProcAddress(winsock_module, "getaddrinfo") != NULL) + { + GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, getnameinfo); + } + else + { + /* Fall back to wship6.dll for Windows 2000 */ + wship6_module = ::LoadLibrary(L"wship6.dll"); + if (wship6_module) + { + GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, getnameinfo); + } + } +#endif // NO_IPV6 +} + +void TFileZillaIntf::Finalize() +{ +#ifndef NO_IPV6 + if (wship6_module) + ::FreeLibrary(wship6_module); + if (winsock2_module) + ::FreeLibrary(winsock2_module); +#endif + if (winsock_module) + ::FreeLibrary(winsock_module); +} + +void TFileZillaIntf::SetResourceModule(void * ResourceHandle) +{ + // set afx resource handles, taken from AfxWinInit (mfc/appinit.cpp) + AFX_MODULE_STATE * ModuleState = AfxGetModuleState(); + ModuleState->m_hCurrentInstanceHandle = static_cast(ResourceHandle); + ModuleState->m_hCurrentResourceHandle = static_cast(ResourceHandle); +} + +TFileZillaIntf::TFileZillaIntf() : + FFileZillaApi(NULL), + FServer(new t_server()) +{ + FIntern = new TFileZillaIntern(this); +} + +TFileZillaIntf::~TFileZillaIntf() +{ + DebugAssert(FFileZillaApi == NULL); + + delete FIntern; + FIntern = NULL; + delete FServer; + FServer = NULL; +} + +bool TFileZillaIntf::Init() +{ + DebugAssert(FFileZillaApi == NULL); + + FFileZillaApi = new CFileZillaApi(); + + bool Result = Check(FFileZillaApi->Init(FIntern, this), L"init"); + + if (!Result) + { + delete FFileZillaApi; + FFileZillaApi = NULL; + } + + return Result; +} + +void TFileZillaIntf::Destroying() +{ + // need to close FZAPI before calling destructor as it in turn post messages + // back while being destroyed, what may result in calling virtual methods + // of already destroyed descendants + delete FFileZillaApi; + FFileZillaApi = NULL; +} + +bool TFileZillaIntf::SetCurrentPath(const wchar_t * APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->SetCurrentPath(Path), L"setcurrentpath"); +} + +bool TFileZillaIntf::GetCurrentPath(wchar_t * Path, size_t MaxLen) +{ + CServerPath APath; + bool Result = Check(FFileZillaApi->GetCurrentPath(APath), L"getcurrentpath"); + if (Result) + { + wcsncpy(Path, APath.GetPath(), MaxLen); + Path[MaxLen - 1] = L'\0'; + } + return Result; +} + +bool TFileZillaIntf::Cancel() +{ + DebugAssert(FFileZillaApi != NULL); + // tolerate even "idle" state, quite possible in MT environment + return Check(FFileZillaApi->Cancel(), L"cancel", FZ_REPLY_WOULDBLOCK | FZ_REPLY_IDLE); +} + +bool TFileZillaIntf::Connect(const wchar_t * Host, int Port, const wchar_t * User, + const wchar_t * Pass, const wchar_t * Account, + const wchar_t * Path, int ServerType, int Pasv, int TimeZoneOffset, int UTF8, int CodePage, + int iForcePasvIp, int iUseMlsd, + int iDupFF, int iUndupFF, + X509 * Certificate, EVP_PKEY * PrivateKey) +{ + DebugAssert(FFileZillaApi != NULL); + DebugAssert((ServerType & FZ_SERVERTYPE_HIGHMASK) == FZ_SERVERTYPE_FTP); + + t_server Server; + + Server.host = Host; + Server.port = Port; + Server.user = User; + Server.pass = Pass; + Server.account = Account; + Server.path = Path; + Server.nServerType = ServerType; + Server.nPasv = Pasv; + Server.nTimeZoneOffset = TimeZoneOffset; + Server.nUTF8 = UTF8; + Server.nCodePage = CodePage; + Server.iForcePasvIp = iForcePasvIp; + Server.iUseMlsd = iUseMlsd; + Server.iDupFF = iDupFF; + Server.iUndupFF = iUndupFF; + Server.Certificate = Certificate; + Server.PrivateKey = PrivateKey; + + *FServer = Server; + + return Check(FFileZillaApi->Connect(Server), L"connect"); +} + +bool TFileZillaIntf::Close(bool AllowBusy) +{ + bool Result; + int ReturnCode = FFileZillaApi->Disconnect(); + + switch (ReturnCode) + { + // If the connection terminated itself meanwhile, + // do not try to wait for close response. + case FZ_REPLY_NOTCONNECTED: + // We might check AllowBusy here, as it's actually similar scenario, + // as we expect this to happen during authentication only + Result = false; + break; + + // waiting for disconnect + case FZ_REPLY_WOULDBLOCK: + Result = true; + break; + + // allowing busy while opening, not sure if it safe, + // but we need it, when cancelling password prompt + case FZ_REPLY_BUSY: + if (AllowBusy) + { + Result = false; + break; + } + + case FZ_REPLY_NOTINITIALIZED: + default: + Result = Check(ReturnCode, L"disconnect"); + break; + } + return Result; +} + +bool TFileZillaIntf::CustomCommand(const wchar_t * Command) +{ + DebugAssert(FFileZillaApi != NULL); + return Check(FFileZillaApi->CustomCommand(Command), L"customcommand"); +} + +bool TFileZillaIntf::MakeDir(const wchar_t* APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->MakeDir(Path), L"makedir"); +} + +bool TFileZillaIntf::Chmod(int Value, const wchar_t* FileName, + const wchar_t* APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->Chmod(Value, FileName, Path), L"chmod"); +} + +bool TFileZillaIntf::Delete(const wchar_t* FileName, const wchar_t* APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->Delete(FileName, Path), L"delete"); +} + +bool TFileZillaIntf::RemoveDir(const wchar_t* FileName, const wchar_t* APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->RemoveDir(FileName, Path), L"removedir"); +} + +bool TFileZillaIntf::Rename(const wchar_t* OldName, + const wchar_t* NewName, const wchar_t* APath, const wchar_t* ANewPath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + CServerPath NewPath(ANewPath); + return Check(FFileZillaApi->Rename(OldName, NewName, Path, NewPath), L"rename"); +} + +bool TFileZillaIntf::List(const wchar_t * APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->List(Path), L"list"); +} + +bool TFileZillaIntf::ListFile(const wchar_t * FileName, const wchar_t * APath) +{ + DebugAssert(FFileZillaApi != NULL); + CServerPath Path(APath); + return Check(FFileZillaApi->ListFile(FileName, Path), L"listfile"); +} + +bool TFileZillaIntf::FileTransfer(const wchar_t * LocalFile, + const wchar_t * RemoteFile, const wchar_t * RemotePath, bool Get, __int64 Size, + int Type, void * UserData) +{ + t_transferfile Transfer; + + Transfer.localfile = LocalFile; + Transfer.remotefile = RemoteFile; + Transfer.remotepath = CServerPath(RemotePath); + Transfer.get = Get; + Transfer.size = Size; + Transfer.server = *FServer; + // 1 = ascii, 2 = binary + Transfer.nType = Type; + Transfer.nUserData = UserData; + + return Check(FFileZillaApi->FileTransfer(Transfer), L"filetransfer"); +} + +void TFileZillaIntf::SetDebugLevel(TLogLevel Level) +{ + FIntern->SetDebugLevel(Level - LOG_APIERROR + 1); +} + +bool TFileZillaIntf::PostMessage(WPARAM wParam, LPARAM lParam) +{ + unsigned int MessageID = FZ_MSG_ID(wParam); + TMessageType Type; + switch (MessageID) + { + case FZ_MSG_TRANSFERSTATUS: + Type = MSG_TRANSFERSTATUS; + break; + + default: + Type = MSG_OTHER; + break; + } + return DoPostMessage(Type, wParam, lParam); +} + +void CopyContact(TFtpsCertificateData::TContact & Dest, + const t_SslCertData::t_Contact& Source) +{ + Dest.Organization = Source.Organization; + Dest.Unit = Source.Unit; + Dest.CommonName = Source.CommonName; + Dest.Mail = Source.Mail; + Dest.Country = Source.Country; + Dest.StateProvince = Source.StateProvince; + Dest.Town = Source.Town; + Dest.Other = Source.Other; +} + +void CopyValidityTime(TFtpsCertificateData::TValidityTime & Dest, + const t_SslCertData::t_validTime& Source) +{ + Dest.Year = Source.y; + Dest.Month = Source.M; + Dest.Day = Source.d; + Dest.Hour = Source.h; + Dest.Min = Source.m; + Dest.Sec = Source.s; +} + +void CopyFileTime(TRemoteFileTime & Dest, const t_directory::t_direntry::t_date & Source) +{ + Dest.Year = (WORD)Source.year; + Dest.Month = (WORD)Source.month; + Dest.Day = (WORD)Source.day; + Dest.Hour = (WORD)Source.hour; + Dest.Minute = (WORD)Source.minute; + Dest.Second = (WORD)Source.second; + Dest.HasTime = Source.hastime; + Dest.HasDate = Source.hasdate; + Dest.HasSeconds = Source.hasseconds; + Dest.Utc = Source.utc; +} + +bool TFileZillaIntf::HandleMessage(WPARAM wParam, LPARAM lParam) +{ + bool Result; + + unsigned int MessageID = FZ_MSG_ID(wParam); + + switch (MessageID) + { + case FZ_MSG_STATUS: + { + DebugAssert(FZ_MSG_PARAM(wParam) == 0); + t_ffam_statusmessage * Status = (t_ffam_statusmessage *)lParam; + DebugAssert(Status->post); + Result = HandleStatus(Status->status, Status->type); + delete Status; + } + + break; + + case FZ_MSG_ASYNCREQUEST: + if (FZ_MSG_PARAM(wParam) == FZ_ASYNCREQUEST_OVERWRITE) + { + int RequestResult = 0; + wchar_t FileName1[MAX_PATH]; + COverwriteRequestData * Data = (COverwriteRequestData *)lParam; + try + { + DebugAssert(Data != NULL); + wcsncpy(FileName1, Data->FileName1, _countof(FileName1)); + FileName1[_countof(FileName1) - 1] = L'\0'; + TRemoteFileTime RemoteTime; + CopyFileTime(RemoteTime, Data->remotetime); + Result = HandleAsynchRequestOverwrite( + FileName1, _countof(FileName1), Data->FileName2, Data->path1, Data->path2, + Data->size1, Data->size2, + (Data->localtime != NULL) ? Data->localtime->GetTime() : 0, + (Data->localtime != NULL) && ((Data->localtime->GetHour() != 0) || (Data->localtime->GetMinute() != 0)), + RemoteTime, + ToPtr(Data->pTransferFile->nUserData), + Data->localFileHandle, + RequestResult); + } + catch (...) + { + FFileZillaApi->SetAsyncRequestResult(FILEEXISTS_SKIP, Data); + throw; + } + + if (Result) + { + Data->FileName1 = FileName1; + Result = Check(FFileZillaApi->SetAsyncRequestResult(RequestResult, Data), + L"setasyncrequestresult"); + } + } + else if (FZ_MSG_PARAM(wParam) == FZ_ASYNCREQUEST_VERIFYCERT) + { + int RequestResult; + CVerifyCertRequestData * AData = (CVerifyCertRequestData *)lParam; + try + { + DebugAssert(AData != NULL); + TFtpsCertificateData Data; + CopyContact(Data.Subject, AData->pCertData->subject); + CopyContact(Data.Issuer, AData->pCertData->issuer); + CopyValidityTime(Data.ValidFrom, AData->pCertData->validFrom); + CopyValidityTime(Data.ValidUntil, AData->pCertData->validUntil); + Data.SubjectAltName = AData->pCertData->subjectAltName; + Data.Hash = AData->pCertData->hash; + Data.Certificate = AData->pCertData->certificate; + Data.CertificateLen = AData->pCertData->certificateLen; + Data.VerificationResult = AData->pCertData->verificationResult; + Data.VerificationDepth = AData->pCertData->verificationDepth; + + Result = HandleAsynchRequestVerifyCertificate(Data, RequestResult); + } + catch (...) + { + FFileZillaApi->SetAsyncRequestResult(0, AData); + throw; + } + + if (Result) + { + Result = Check(FFileZillaApi->SetAsyncRequestResult(RequestResult, AData), + L"setasyncrequestresult"); + } + } + else if (FZ_MSG_PARAM(wParam) == FZ_ASYNCREQUEST_NEEDPASS) + { + int RequestResult = 0; + CNeedPassRequestData * AData = (CNeedPassRequestData *)lParam; + try + { + TNeedPassRequestData Data; + Data.Password = NULL; + Data.Password = AData->Password.GetBuffer(AData->Password.GetLength()); + Result = HandleAsynchRequestNeedPass(Data, RequestResult); + AData->Password.ReleaseBuffer(AData->Password.GetLength()); + if (Result && (RequestResult == TFileZillaIntf::REPLY_OK)) + { + AData->Password = Data.Password; + free(Data.Password); + Data.Password = NULL; + } + } + catch (...) + { + FFileZillaApi->SetAsyncRequestResult(0, AData); + throw; + } + if (Result) + { + Result = Check(FFileZillaApi->SetAsyncRequestResult(RequestResult, AData), + L"setasyncrequestresult"); + } + } + else + { + // FZ_ASYNCREQUEST_GSS_AUTHFAILED + // FZ_ASYNCREQUEST_GSS_NEEDUSER + // FZ_ASYNCREQUEST_GSS_NEEDPASS + DebugFail(); + Result = false; + } + break; + + case FZ_MSG_LISTDATA: + { + DebugAssert(FZ_MSG_PARAM(wParam) == 0); + t_directory * Directory = (t_directory *)lParam; + CString Path = Directory->path.GetPath(); + std::vector Entries(Directory->num); + + for (intptr_t Index = 0; Index < Directory->num; ++Index) + { + t_directory::t_direntry & Source = Directory->direntry[Index]; + TListDataEntry & Dest = Entries[Index]; + + Dest.Name = Source.name; + Dest.Permissions = Source.permissionstr; + Dest.HumanPerm = Source.humanpermstr; + Dest.OwnerGroup = Source.ownergroup; + Dest.Size = Source.size; + Dest.Dir = Source.dir; + Dest.Link = Source.bLink; + CopyFileTime(Dest.Time, Source.date); + Dest.LinkTarget = Source.linkTarget; + } + + int Num = Directory->num; + TListDataEntry * pEntries = Num > 0 ? &Entries[0] : NULL; + Result = HandleListData(Path, pEntries, Num); + + delete Directory; + } + break; + + case FZ_MSG_TRANSFERSTATUS: + { + DebugAssert(FZ_MSG_PARAM(wParam) == 0); + t_ffam_transferstatus * Status = reinterpret_cast(lParam); + if (Status != NULL) + { + Result = HandleTransferStatus( + true, Status->transfersize, Status->bytes, Status->bFileTransfer); + delete Status; + } + else + { + Result = HandleTransferStatus(false, -1, -1, false); + } + } + break; + + case FZ_MSG_REPLY: + Result = HandleReply(FZ_MSG_PARAM(wParam), lParam); + break; + + case FZ_MSG_CAPABILITIES: + Result = HandleCapabilities((TFTPServerCapabilities *)lParam); + break; + + default: + DebugFail(); + Result = false; + break; + } + + return Result; +} + +bool TFileZillaIntf::CheckError(intptr_t /*ReturnCode*/, const wchar_t * /*Context*/) +{ + return false; +} + +inline bool TFileZillaIntf::Check(intptr_t ReturnCode, + const wchar_t * Context, intptr_t Expected) +{ + if ((ReturnCode & (Expected == -1 ? FZ_REPLY_OK : Expected)) == ReturnCode) + { + return true; + } + else + { + return CheckError(ReturnCode, Context); + } +} + +bool TFileZillaIntf::UsingMlsd() +{ + return FFileZillaApi->UsingMlsd(); +} + +bool TFileZillaIntf::UsingUtf8() +{ + return FFileZillaApi->UsingUtf8(); +} + +std::string TFileZillaIntf::GetTlsVersionStr() +{ + return FFileZillaApi->GetTlsVersionStr(); +} + +std::string TFileZillaIntf::GetCipherName() +{ + return FFileZillaApi->GetCipherName(); +} + diff --git a/netbox/src/filezilla/FileZillaIntf.h b/netbox/src/filezilla/FileZillaIntf.h new file mode 100644 index 000000000..e39b84485 --- /dev/null +++ b/netbox/src/filezilla/FileZillaIntf.h @@ -0,0 +1,299 @@ + +#ifndef FileZillaIntfH +#define FileZillaIntfH + +#include + +#include +#include +#include + +class CFileZillaApi; +class TFileZillaIntern; + +struct TRemoteFileTime +{ + WORD Year; + WORD Month; + WORD Day; + WORD Hour; + WORD Minute; + WORD Second; + bool HasTime; + bool HasSeconds; + bool HasDate; + bool Utc; +}; + +struct TListDataEntry +{ + TRemoteFileTime Time; + __int64 Size; + const wchar_t * LinkTarget; + const wchar_t * Name; + const wchar_t * Permissions; + const wchar_t * HumanPerm; + const wchar_t * OwnerGroup; + bool Dir; + bool Link; +}; + +struct TFtpsCertificateData +{ + struct TContact + { + const wchar_t * Organization; + const wchar_t * Unit; + const wchar_t * CommonName; + const wchar_t * Mail; + const wchar_t * Country; + const wchar_t * StateProvince; + const wchar_t * Town; + const wchar_t * Other; + }; + + TContact Subject; + TContact Issuer; + + struct TValidityTime + { + int Year; + int Month; + int Day; + int Hour; + int Min; + int Sec; + }; + + TValidityTime ValidFrom; + TValidityTime ValidUntil; + + const wchar_t * SubjectAltName; + + const uint8_t * Hash; + static const size_t HashLen = 20; + + const uint8_t * Certificate; + size_t CertificateLen; + + int VerificationResult; + int VerificationDepth; +}; + +struct TNeedPassRequestData +{ + wchar_t * Password; +}; + +class t_server; +class TFTPServerCapabilities; +typedef struct x509_st X509; +typedef struct evp_pkey_st EVP_PKEY; + +class TFileZillaIntf : public CFileZillaTools +{ +NB_DISABLE_COPY(TFileZillaIntf) +friend class TFileZillaIntern; + +public: + enum TLogLevel + { + LOG_STATUS = 0, + LOG_ERROR = 1, + LOG_COMMAND = 2, + LOG_REPLY = 3, + LOG_APIERROR = 5, + LOG_WARNING = 6, + LOG_PROGRESS = 7, + LOG_INFO = 8, + LOG_DEBUG = 9, + }; + + enum TMessageType + { + MSG_OTHER = 0, + MSG_TRANSFERSTATUS = 1 + }; + + enum + { + FILEEXISTS_OVERWRITE = 0, + FILEEXISTS_RESUME = 1, + FILEEXISTS_RENAME = 2, + FILEEXISTS_SKIP = 3, + FILEEXISTS_COMPLETE = 4, + }; + + enum + { + REPLY_OK = 0x0001, + REPLY_WOULDBLOCK = 0x0002, + REPLY_ERROR = 0x0004, + REPLY_OWNERNOTSET = 0x0008, + REPLY_INVALIDPARAM = 0x0010, + REPLY_NOTCONNECTED = 0x0020, + REPLY_ALREADYCONNECTED = 0x0040, + REPLY_BUSY = 0x0080, + REPLY_IDLE = 0x0100, + REPLY_NOTINITIALIZED = 0x0200, + REPLY_ALREADYINIZIALIZED = 0x0400, + REPLY_CANCEL = 0x0800, + REPLY_DISCONNECTED = 0x1000, // Always sent when disconnected from server + REPLY_CRITICALERROR = 0x2000, // Used for FileTransfers only + REPLY_ABORTED = 0x4000, // Used for FileTransfers only + REPLY_NOTSUPPORTED = 0x8000 // Command is not supported for the current server + }; + + enum + { + SERVER_FTP = 0x1000, + SERVER_FTP_SSL_IMPLICIT = 0x1100, + SERVER_FTP_SSL_EXPLICIT = 0x1200, + SERVER_FTP_TLS_EXPLICIT = 0x1400 + }; + + static void Initialize(); + static void Finalize(); + static void SetResourceModule(void * ResourceHandle); + + explicit TFileZillaIntf(); + virtual ~TFileZillaIntf(); + + bool Init(); + void Destroying(); + + bool SetCurrentPath(const wchar_t * Path); + bool GetCurrentPath(wchar_t * Path, size_t MaxLen); + + bool UsingMlsd(); + bool UsingUtf8(); + std::string GetTlsVersionStr(); + std::string GetCipherName(); + + bool Cancel(); + + bool Connect(const wchar_t * Host, int Port, const wchar_t * User, + const wchar_t * Pass, const wchar_t * Account, + const wchar_t * Path, int ServerType, int Pasv, int TimeZoneOffset, int UTF8, int CodePage, + int iForcePasvIp, int iUseMlsd, + int iDupFF, int iUndupFF, + X509 * Certificate, EVP_PKEY * PrivateKey); + bool Close(bool AllowBusy); + + bool List(const wchar_t * Path); + bool ListFile(const wchar_t * FileName, const wchar_t * APath); + + bool CustomCommand(const wchar_t * Command); + + bool MakeDir(const wchar_t* Path); + bool Chmod(int Value, const wchar_t* FileName, const wchar_t* Path); + bool Delete(const wchar_t* FileName, const wchar_t* Path); + bool RemoveDir(const wchar_t* FileName, const wchar_t* Path); + bool Rename(const wchar_t* OldName, const wchar_t* NewName, + const wchar_t* Path, const wchar_t* NewPath); + + bool FileTransfer(const wchar_t * LocalFile, const wchar_t * RemoteFile, + const wchar_t * RemotePath, bool Get, __int64 Size, int Type, void * UserData); + + virtual const wchar_t * Option(intptr_t OptionID) const = 0; + virtual intptr_t OptionVal(intptr_t OptionID) const = 0; + + void SetDebugLevel(TLogLevel Level); + bool HandleMessage(WPARAM wParam, LPARAM lParam); + +protected: + bool PostMessage(WPARAM wParam, LPARAM lParam); + virtual bool DoPostMessage(TMessageType Type, WPARAM wParam, LPARAM lParam) = 0; + + virtual bool HandleStatus(const wchar_t * Status, int Type) = 0; + virtual bool HandleAsynchRequestOverwrite( + wchar_t * FileName1, size_t FileName1Len, const wchar_t * FileName2, + const wchar_t * Path1, const wchar_t * Path2, + __int64 Size1, __int64 Size2, time_t LocalTime, + bool HasLocalTime1, const TRemoteFileTime & RemoteTime, void * UserData, + HANDLE & LocalFileHandle, + int & RequestResult) = 0; + virtual bool HandleAsynchRequestVerifyCertificate( + const TFtpsCertificateData & Data, int & RequestResult) = 0; + virtual bool HandleAsynchRequestNeedPass( + struct TNeedPassRequestData & Data, int & RequestResult) = 0; + virtual bool HandleListData(const wchar_t * Path, const TListDataEntry * Entries, + uintptr_t Count) = 0; + virtual bool HandleTransferStatus(bool Valid, __int64 TransferSize, + __int64 Bytes, bool FileTransfer) = 0; + virtual bool HandleReply(intptr_t Command, uintptr_t Reply) = 0; + virtual bool HandleCapabilities(TFTPServerCapabilities * ServerCapabilities) = 0; + virtual bool CheckError(intptr_t ReturnCode, const wchar_t * Context); + + inline bool Check(intptr_t ReturnCode, const wchar_t * Context, intptr_t Expected = -1); + +private: + CFileZillaApi * FFileZillaApi; + TFileZillaIntern * FIntern; + t_server * FServer; +}; + +enum ftp_capabilities_t +{ + unknown, + yes, + no +}; + +enum ftp_capability_names_t +{ + syst_command = 1, // reply of SYST command as option + feat_command, + clnt_command, // set to 'yes' if CLNT should be sent + utf8_command, // set to 'yes' if OPTS UTF8 ON should be sent + mlsd_command, + opts_mlst_command, // Arguments for OPTS MLST command + mfmt_command, + pret_command, + mdtm_command, + size_command, + mode_z_support, + tvfs_support, // Trivial virtual file store (RFC 3659) + list_hidden_support, // LIST -a command + rest_stream, // supports REST+STOR in addition to APPE +}; + +class TFTPServerCapabilities : public TObject +{ +NB_DISABLE_COPY(TFTPServerCapabilities) +public: + TFTPServerCapabilities(){} + ftp_capabilities_t GetCapability(ftp_capability_names_t Name); + ftp_capabilities_t GetCapabilityString(ftp_capability_names_t Name, std::string * Option = NULL); + void SetCapability(ftp_capability_names_t Name, ftp_capabilities_t Cap); + void SetCapability(ftp_capability_names_t Name, ftp_capabilities_t Cap, const std::string & Option); + void Clear() { FCapabilityMap.clear(); } + void Assign(TFTPServerCapabilities * Source) + { + FCapabilityMap.clear(); + if (Source != NULL) + { + for (std::map::iterator it = Source->FCapabilityMap.begin(); + it != Source->FCapabilityMap.end(); ++it) + { + FCapabilityMap.insert(*it); + } + } + } +protected: + struct t_cap + { + t_cap() : + cap(unknown), + option(), + number(0) + {} + ftp_capabilities_t cap; + std::string option; + int number; + }; + + std::map FCapabilityMap; +}; + +#endif // FileZillaIntfH diff --git a/netbox/src/filezilla/FileZillaOpt.h b/netbox/src/filezilla/FileZillaOpt.h new file mode 100644 index 000000000..4bc8feb74 --- /dev/null +++ b/netbox/src/filezilla/FileZillaOpt.h @@ -0,0 +1,56 @@ + +#ifndef FileZillaOptH +#define FileZillaOptH + +#define OPTION_LOGONTYPE 1 +#define OPTION_PASV 2 +#define OPTION_FWHOST 3 +#define OPTION_FWPORT 4 +#define OPTION_FWUSER 5 +#define OPTION_FWPASS 6 +#define OPTION_TIMEOUTLENGTH 7 +#define OPTION_KEEPALIVE 8 +#define OPTION_INTERVALLOW 9 +#define OPTION_INTERVALHIGH 10 +#define OPTION_PROXYTYPE 17 +#define OPTION_PROXYHOST 18 +#define OPTION_PROXYPORT 19 +#define OPTION_PROXYUSER 20 +#define OPTION_PROXYPASS 21 +#define OPTION_PROXYUSELOGON 22 +#ifndef MPEXT_NO_GSS +#define OPTION_USEGSS 43 +#define OPTION_GSSSERVERS 44 +#endif +#define OPTION_DEBUGSHOWLISTING 49 +#define OPTION_LIMITPORTRANGE 52 +#define OPTION_PORTRANGELOW 53 +#define OPTION_PORTRANGEHIGH 54 +#define OPTION_PRESERVEDOWNLOADFILETIME 70 +#define OPTION_SPEEDLIMIT_DOWNLOAD_TYPE 86 +#define OPTION_SPEEDLIMIT_UPLOAD_TYPE 87 +#define OPTION_SPEEDLIMIT_DOWNLOAD_VALUE 88 +#define OPTION_SPEEDLIMIT_UPLOAD_VALUE 89 +#define OPTION_TRANSFERIP 100 +#ifdef MPEXT_NO_ZLIB +#define OPTION_MODEZ_USE 110 +#define OPTION_MODEZ_LEVEL 111 +#endif +#define OPTION_TRANSFERIP6 112 +#define OPTION_ENABLE_IPV6 113 +#define OPTION_VMSALLREVISIONS 115 +#define OPTION_ANONPWD 117 +#define OPTION_MPEXT_SHOWHIDDEN 1000 +#define OPTION_MPEXT_PRESERVEUPLOADFILETIME 1001 +#define OPTION_MPEXT_SSLSESSIONREUSE 1002 +#define OPTION_MPEXT_SNDBUF 1003 +#define OPTION_MPEXT_MIN_TLS_VERSION 1004 +#define OPTION_MPEXT_MAX_TLS_VERSION 1005 +#define OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY 1006 +#define OPTION_MPEXT_REMOVE_BOM 1007 +#define OPTION_MPEXT_LOG_SENSITIVE 1008 +#define OPTION_MPEXT_HOST 1009 +#define OPTION_MPEXT_NODELAY 1010 +#define OPTION_MPEXT_NOLIST 1011 + +#endif // FileZillaOptH diff --git a/netbox/src/filezilla/FilezillaTools.h b/netbox/src/filezilla/FilezillaTools.h new file mode 100644 index 000000000..939fd4245 --- /dev/null +++ b/netbox/src/filezilla/FilezillaTools.h @@ -0,0 +1,17 @@ + +#ifndef FileZillaToolsH +#define FileZillaToolsH + +#include +#include + +class CFileZillaTools : public TObject +{ +public: + virtual void PreserveDownloadFileTime(HANDLE AHandle, void * UserData) = 0; + virtual bool GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time) = 0; + virtual wchar_t * LastSysErrorMessage() const = 0; + virtual std::wstring GetClientString() const = 0; +}; + +#endif // FileZillaToolsH diff --git a/netbox/src/filezilla/FtpControlSocket.cpp b/netbox/src/filezilla/FtpControlSocket.cpp new file mode 100644 index 000000000..c8d4f77de --- /dev/null +++ b/netbox/src/filezilla/FtpControlSocket.cpp @@ -0,0 +1,6519 @@ + +#include "stdafx.h" +#include "FtpControlSocket.h" +#include "mainthread.h" +#include "transfersocket.h" +#include "asyncproxysocketlayer.h" +#include "AsyncSslSocketLayer.h" +#ifndef MPEXT_NO_GSS +#include "AsyncGssSocketLayer.h" +#endif +#include "FileZillaApi.h" + +#include +#include +#include +#include +#include +#include + +class CFtpControlSocket::CFileTransferData : public CFtpControlSocket::t_operation::COpData +{ +public: + CFileTransferData() + { + pDirectoryListing=0; + nGotTransferEndReply=0; + nWaitNextOpState=0; + nMKDOpState=-1; + hasRemoteDate = false; + pFileSize=0; + bUseAbsolutePaths = FALSE; + bTriedPortPasvOnce = FALSE; + askOnResumeFail = false; + }; + ~CFileTransferData() + { + if (pDirectoryListing) + delete pDirectoryListing; + pDirectoryListing=0; + nb_free(pFileSize); + }; + CString rawpwd; + t_transferfile transferfile; + t_transferdata transferdata; + CString host; + int port; + BOOL bPasv; + int nGotTransferEndReply; + t_directory *pDirectoryListing; + int nWaitNextOpState; + CServerPath MKDCurrent; + std::list MKDSegments; + int nMKDOpState; + bool hasRemoteDate; + t_directory::t_direntry::t_date remoteDate; + //CTime *pFileTime; //Used when downloading and OPTION_PRESERVEDOWNLOADFILETIME is set or when LIST fails + _int64 *pFileSize; //Used when LIST failes + BOOL bUseAbsolutePaths; + BOOL bTriedPortPasvOnce; +#ifndef MPEXT_NO_ZLIB + int newZlibLevel; +#endif + bool askOnResumeFail; +}; + +class CFtpControlSocket::CLogonData:public CFtpControlSocket::t_operation::COpData +{ +public: + CLogonData() + { + waitForAsyncRequest = false; + gotPassword = false; + } + virtual ~CLogonData() + { + } + bool waitForAsyncRequest; + bool gotPassword; +}; + +class CFtpControlSocket::CListData:public CFtpControlSocket::t_operation::COpData +{ +public: + CListData() + { + pDirectoryListing = 0; + bTriedPortPasvOnce = FALSE; + lastCmdSentCDUP = false; + } + virtual ~CListData() + { + if (pDirectoryListing) + delete pDirectoryListing; + } + CString rawpwd; + CServerPath path; + CString fileName; + CString subdir; + BOOL bPasv; + CString host; + UINT port; + int nFinish; + t_directory *pDirectoryListing; + BOOL bTriedPortPasvOnce; +#ifndef MPEXT_NO_ZLIB + int newZlibLevel; +#endif + bool lastCmdSentCDUP; +}; + +class CFtpControlSocket::CListFileData:public CFtpControlSocket::t_operation::COpData +{ +public: + CListFileData() + { + direntry = NULL; + } + ~CListFileData() + { + delete [] direntry; + } + CString fileName; + CString dir; + CString path; + CServerPath pwd; + t_directory::t_direntry * direntry; +}; + +class CFtpControlSocket::CMakeDirData : public CFtpControlSocket::t_operation::COpData +{ +public: + CMakeDirData() {} + virtual ~CMakeDirData() {} + CServerPath path; + CServerPath Current; + std::list Segments; +}; + +#define MKD_INIT -1 +#define MKD_FINDPARENT 0 +#define MKD_MAKESUBDIRS 1 +#define MKD_CHANGETOSUBDIR 2 + +///////////////////////////////////////////////////////////////////////////// +// CFtpControlSocket + +std::list CFtpControlSocket::m_InstanceList[2]; + +CTime CFtpControlSocket::m_CurrentTransferTime[2] = { CTime::GetCurrentTime(), CTime::GetCurrentTime() }; +_int64 CFtpControlSocket::m_CurrentTransferLimit[2] = {0, 0}; + +CCriticalSection CFtpControlSocket::m_SpeedLimitSync; + +#define BUFSIZE 16384 + +CFtpControlSocket::CFtpControlSocket(CMainThread *pMainThread, CFileZillaTools * pTools) +{ + DebugAssert(pMainThread); + m_pOwner=pMainThread; + m_pTools=pTools; + + m_Operation.nOpMode=0; + m_Operation.nOpState=-1; + m_Operation.pData=0; + + m_pProxyLayer = NULL; + m_pSslLayer = NULL; +#ifndef MPEXT_NO_GSS + m_pGssLayer = NULL; +#endif + + m_pDirectoryListing=0; + + m_pTransferSocket=0; + m_pDataFile=0; + srand( (unsigned)time( NULL ) ); + m_bKeepAliveActive=FALSE; + m_bCheckForTimeout=TRUE; + m_bDidRejectCertificate = FALSE; + +#ifndef MPEXT_NO_ZLIB + m_useZlib = false; + m_zlibSupported = false; + m_zlibLevel = 8; +#endif + + m_bUTF8 = true; + m_bAnnouncesUTF8 = false; + m_nCodePage = 0; + m_hasClntCmd = false; + m_serverCapabilities.Clear(); + m_ListFile = ""; + + m_isFileZilla = false; + m_awaitsReply = false; + m_skipReply = false; + + m_sendBuffer = 0; + m_sendBufferLen = 0; + + m_bProtP = false; + + m_mayBeMvsFilesystem = false; + m_mayBeBS2000Filesystem = false; +} + +CFtpControlSocket::~CFtpControlSocket() +{ + DoClose(); + if (m_pTransferSocket) + { + m_pTransferSocket->Close(); + delete m_pTransferSocket; + m_pTransferSocket=0; + } + if (m_pDataFile) + { + delete m_pDataFile; + m_pDataFile=0; + } + Close(); +} + +void CFtpControlSocket::ShowStatus(UINT nID, int type) const +{ + CString str; + str.LoadString(nID); + ShowStatus(str, type); +} + +void CFtpControlSocket::ShowStatus(CString status, int type) const +{ + if (!GetOptionVal(OPTION_MPEXT_LOG_SENSITIVE)) + { + if ( status.Left(5)==L"PASS " ) + { + int len=status.GetLength()-5; + status=L"PASS "; + for (int i=0;iserver; + m_pDirectoryListing=new t_directory; + *m_pDirectoryListing=*pDirectory; + + if (bSetWorkingDir) + m_pOwner->SetWorkingDir(pDirectory); +} + +///////////////////////////////////////////////////////////////////////////// +// Member-Funktion CFtpControlSocket +#define CONNECT_INIT -1 +#ifndef MPEXT_NO_GSS +#define CONNECT_GSS_INIT -2 +#define CONNECT_GSS_AUTHDONE -3 +#define CONNECT_GSS_CWD -4 +#define CONNECT_GSS_FAILED -5 +#define CONNECT_GSS_NEEDPASS -6 +#define CONNECT_GSS_NEEDUSER -7 +#endif +#undef CONNECT_SSL_INIT +#define CONNECT_SSL_INIT -8 +#undef CONNECT_SSL_NEGOTIATE +#define CONNECT_SSL_NEGOTIATE -9 +#define CONNECT_TLS_NEGOTIATE -10 +#undef CONNECT_SSL_WAITDONE +#define CONNECT_SSL_WAITDONE -11 +#define CONNECT_SSL_PBSZ -12 +#define CONNECT_SSL_PROT -13 +#define CONNECT_FEAT -14 +#define CONNECT_SYST -15 +#define CONNECT_OPTSUTF8 -16 +#define CONNECT_CLNT -17 +#define CONNECT_OPTSMLST -18 +#define CONNECT_NEEDPASS -19 +#define CONNECT_HOST -20 + +bool CFtpControlSocket::InitConnect() +{ + USES_CONVERSION; + + // Reset detected capabilities + m_bAnnouncesUTF8 = false; + m_hasClntCmd = false; + m_serverCapabilities.Clear(); + m_ListFile = ""; + m_isFileZilla = false; + + if (m_CurrentServer.nUTF8 == 2) // no UTF8 + m_bUTF8 = false; + else if (m_CurrentServer.nUTF8 == 1) // always UTF8 + m_bUTF8 = true; + else if (m_CurrentServer.nUTF8 == 0) // auto detect + { + if (m_CurrentServer.nCodePage != 0) + { + m_bUTF8 = false; + m_nCodePage = m_CurrentServer.nCodePage; + } + } + else + m_bUTF8 = true; + + // Some sanity checks + if (m_pOwner->IsConnected()) + { + ShowStatus(L"Internal error: Connect called while still connected", FZ_LOG_ERROR); + if (!m_Operation.nOpMode) + m_Operation.nOpMode = CSMODE_CONNECT; + DoClose(FZ_REPLY_CRITICALERROR); + return false; + } + + if (m_pSslLayer) + { + ShowStatus(L"Internal error: m_pSslLayer not zero in Connect", FZ_LOG_ERROR); + DoClose(FZ_REPLY_CRITICALERROR); + return false; + } + if (m_pProxyLayer) + { + ShowStatus(L"Internal error: m_pProxyLayer not zero in Connect", FZ_LOG_ERROR); + DoClose(FZ_REPLY_CRITICALERROR); + return false; + } + + if (m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYER_SSL_IMPLICIT || + m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYER_SSL_EXPLICIT || + m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYER_TLS_EXPLICIT) + { + m_pSslLayer = new CAsyncSslSocketLayer(); + AddLayer(m_pSslLayer); + + m_pSslLayer->SetClientCertificate(m_CurrentServer.Certificate, m_CurrentServer.PrivateKey); + + TCHAR buffer[1000]; + GetModuleFileName(NULL, buffer, 1000); + CString filename = buffer; + int pos = filename.ReverseFind(L'\\'); + if (pos != -1) + { + filename = filename.Left(pos + 1); + filename += L"cacert.pem"; + } + else + filename = L"cacert.pem"; + m_pSslLayer->SetCertStorage(filename); + } + + int nProxyType = GetOptionVal(OPTION_PROXYTYPE); + if (nProxyType != PROXYTYPE_NOPROXY) + { + m_pProxyLayer = new CAsyncProxySocketLayer; + m_pProxyLayer->SetProxy( + nProxyType, T2CA(GetOption(OPTION_PROXYHOST)), GetOptionVal(OPTION_PROXYPORT), + GetOptionVal(OPTION_PROXYUSELOGON), T2CA(GetOption(OPTION_PROXYUSER)), T2CA(GetOption(OPTION_PROXYPASS))); + AddLayer(m_pProxyLayer); + } + +#ifndef MPEXT_NO_GSS + BOOL bUseGSS = FALSE; + if (GetOptionVal(OPTION_USEGSS) && !m_pSslLayer) + { + CString GssServers=GetOption(OPTION_GSSSERVERS); + LPCSTR lpszAscii=T2CA(m_CurrentServer.host); + hostent *fullname=gethostbyname(lpszAscii); + CString host; + if (fullname) + host=fullname->h_name; + else + host=m_CurrentServer.host; + host.MakeLower(); + int i; + while ((i=GssServers.Find(L";"))!=-1) + { + if ((L"."+GssServers.Left(i))==host.Right(GssServers.Left(i).GetLength()+1) || GssServers.Left(i)==host) + { + bUseGSS=TRUE; + break; + } + GssServers=GssServers.Mid(i+1); + } + } + if (bUseGSS) + { + m_pGssLayer = new CAsyncGssSocketLayer(); + AddLayer(m_pGssLayer); + if (!m_pGssLayer->InitGSS()) + { + ShowStatus(L"Unable to initialize GSS api", FZ_LOG_ERROR); + DoClose(FZ_REPLY_CRITICALERROR); + return false; + } + } +#endif + + return true; +} + +int CFtpControlSocket::InitConnectState() +{ + if ((m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYERMASK) & (FZ_SERVERTYPE_LAYER_SSL_EXPLICIT | FZ_SERVERTYPE_LAYER_TLS_EXPLICIT)) + return CONNECT_SSL_INIT; + else +#ifndef MPEXT_NO_GSS + if (m_pGssLayer) + return CONNECT_GSS_INIT; + else +#endif + return CONNECT_INIT; +} + +void CFtpControlSocket::Connect(t_server &server) +{ + USES_CONVERSION; + + if (m_Operation.nOpMode) + { + ShowStatus(L"Internal error: m_Operation.nOpMode not zero in Connect", FZ_LOG_ERROR); + m_Operation.nOpMode = CSMODE_CONNECT; + DoClose(FZ_REPLY_CRITICALERROR); + return; + } + + m_Operation.nOpMode = CSMODE_CONNECT; + + m_CurrentServer = server; + if (!InitConnect()) + return; + + if (!Create()) + { + DoClose(); + return; + } + AsyncSelect(); + + if (GetOptionVal(OPTION_MPEXT_HOST)) + { + m_Operation.nOpState = CONNECT_HOST; + } + else + { + m_Operation.nOpState = InitConnectState(); + } + + if (server.nServerType & FZ_SERVERTYPE_LAYER_SSL_IMPLICIT) + { + if (!m_pSslLayer) + { + ShowStatus(L"Internal error: m_pSslLayer not initialized", FZ_LOG_ERROR); + DoClose(FZ_REPLY_CRITICALERROR); + return; + } + int res = m_pSslLayer->InitSSLConnection(true, NULL, + GetOptionVal(OPTION_MPEXT_SSLSESSIONREUSE), + GetOptionVal(OPTION_MPEXT_MIN_TLS_VERSION), + GetOptionVal(OPTION_MPEXT_MAX_TLS_VERSION)); + if (res == SSL_FAILURE_INITSSL) + ShowStatus(IDS_ERRORMSG_CANTINITSSL, FZ_LOG_ERROR); + if (res) + { + DoClose(); + return; + } + } + + int logontype = GetOptionVal(OPTION_LOGONTYPE); + int port; + CString buf,temp; + // are we connecting directly to the host (logon type 0) or via a firewall? (logon type>0) + CString fwhost; + int fwport; + if(!logontype) + { + temp=server.host; + port=server.port; + } + else + { + fwhost=GetOption(OPTION_FWHOST); + fwport=GetOptionVal(OPTION_FWPORT); + temp=fwhost; + port=fwport; + if(fwport!=21) + fwhost.Format( L"%s:%d", (LPCTSTR)fwhost, fwport); // add port to fwhost (only if port is not 21) + } + + CString hostname = server.host; + if(server.port!=21) + hostname.Format( L"%s:%d", (LPCTSTR)hostname, server.port); // add port to hostname (only if port is not 21) + CString str; + str.Format(IDS_STATUSMSG_CONNECTING, (LPCTSTR)hostname); + ShowStatus(str, FZ_LOG_STATUS); + + if (!Connect(temp, port)) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + DoClose(); + return; + } + } + m_ServerName = logontype?fwhost:hostname; + m_LastRecvTime = m_LastSendTime = CTime::GetCurrentTime(); + + m_Operation.pData = new CLogonData(); +} + +void CFtpControlSocket::LogOnToServer(BOOL bSkipReply /*=FALSE*/) +{ + int logontype = +#ifndef MPEXT_NO_GSS + m_pGssLayer ? 0 : +#endif + GetOptionVal(OPTION_LOGONTYPE); + const int LO = -2, ER = -1; + CString buf, temp; + const int NUMLOGIN = 9; // currently supports 9 different login sequences + int logonseq[NUMLOGIN][20] = { + // this array stores all of the logon sequences for the various firewalls + // in blocks of 3 nums. 1st num is command to send, 2nd num is next point in logon sequence array + // if 200 series response is rec'd from server as the result of the command, 3rd num is next + // point in logon sequence if 300 series rec'd + {0,LO,3, 1,LO,6, 12,LO,ER}, // no firewall + {3,6,3, 4,6,ER, 5,9,9, 0,LO,12, 1,LO,15, 12,LO,ER}, // SITE hostname + {3,6,3, 4,6,ER, 6,LO,9, 1,LO,12, 12,LO,ER}, // USER after logon + {7,3,3, 0,LO,6, 1,LO,9, 12,LO,ER}, //proxy OPEN + {3,6,3, 4,6,ER, 0,LO,9, 1,LO,12, 12,LO,ER}, // Transparent + {6,LO,3, 1,LO,6, 12,LO,ER}, // USER remoteID@remotehost + {8,6,3, 4,6,ER, 0,LO,9, 1,LO,12, 12,LO,ER}, //USER fireID@remotehost + {9,ER,3, 1,LO,6, 2,LO,ER}, //USER remoteID@remotehost fireID + {10,LO,3,11,LO,6,2,LO,ER} // USER remoteID@fireID@remotehost + }; + + if (m_Operation.nOpState == CONNECT_SSL_INIT) + { + if (m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYER_SSL_EXPLICIT) + { + if (!SendAuthSsl()) + { + return; + } + } + else + { + if (!Send("AUTH TLS")) + return; + m_Operation.nOpState = CONNECT_TLS_NEGOTIATE; + } + return; + } + else if ((m_Operation.nOpState == CONNECT_SSL_NEGOTIATE) || + (m_Operation.nOpState == CONNECT_TLS_NEGOTIATE)) + { + int res = GetReplyCode(); + if (res!=2 && res!=3) + { + if (m_Operation.nOpState == CONNECT_TLS_NEGOTIATE) + { + // Try to fall back to AUTH SSL + if (!SendAuthSsl()) + { + return; + } + } + else + { + DoClose(); + } + return; + } + else + { + if (!m_pSslLayer) + { + ShowStatus(L"Internal error: m_pSslLayer not initialized", FZ_LOG_ERROR); + DoClose(FZ_REPLY_CRITICALERROR); + return; + } + int res = m_pSslLayer->InitSSLConnection(true, NULL, + GetOptionVal(OPTION_MPEXT_SSLSESSIONREUSE), + GetOptionVal(OPTION_MPEXT_MIN_TLS_VERSION), + GetOptionVal(OPTION_MPEXT_MAX_TLS_VERSION)); + if (res == SSL_FAILURE_INITSSL) + ShowStatus(IDS_ERRORMSG_CANTINITSSL, FZ_LOG_ERROR); + if (res) + { + DoClose(); + return; + } + } + m_Operation.nOpState = CONNECT_SSL_WAITDONE; + return; + } + else if (m_Operation.nOpState == CONNECT_SSL_WAITDONE) + { + m_Operation.nOpState = CONNECT_INIT; + } + else if (m_Operation.nOpState == CONNECT_SSL_PBSZ) + { + if (!Send(L"PROT P")) + return; + m_Operation.nOpState = CONNECT_SSL_PROT; + return; + } + else if (m_Operation.nOpState == CONNECT_SSL_PROT) + { + int code = GetReplyCode(); + if (code == 2 || code == 3) + m_bProtP = true; + + ShowStatus(IDS_STATUSMSG_CONNECTED, FZ_LOG_STATUS); + m_pOwner->SetConnected(TRUE); + ResetOperation(FZ_REPLY_OK); + return; + } +#ifndef MPEXT_NO_GSS + else + if (m_Operation.nOpState==CONNECT_GSS_FAILED || + m_Operation.nOpState == CONNECT_GSS_NEEDPASS || + m_Operation.nOpState == CONNECT_GSS_NEEDUSER) + { + if (!m_RecvBuffer.empty() && m_RecvBuffer.front() != "") + { + //Incoming reply from server during async is not allowed + DoClose(); + return; + } + } +#endif + else if (m_Operation.nOpState == CONNECT_HOST) + { + if (Send(L"HOST " + m_CurrentServer.host)) + { + m_Operation.nOpState = InitConnectState(); + return; + } + } + else if (m_Operation.nOpState == CONNECT_OPTSMLST) + { + int code = GetReplyCode(); + if (code != 2 && code != 3) + m_serverCapabilities.SetCapability(mlsd_command, no); + ShowStatus(IDS_STATUSMSG_CONNECTED, FZ_LOG_STATUS); + m_pOwner->SetConnected(TRUE); + ResetOperation(FZ_REPLY_OK); + return; + } + else if (m_Operation.nOpState == CONNECT_FEAT) + { + std::string facts; + if (m_serverCapabilities.GetCapabilityString(mlsd_command, &facts) == yes) + { + ftp_capabilities_t cap = m_serverCapabilities.GetCapabilityString(opts_mlst_command); + if (cap == unknown) + { + std::transform(facts.begin(), facts.end(), facts.begin(), ::tolower); + bool had_unset = false; + std::string opts_facts; + // Create a list of all facts understood by both FZ and the server. + // Check if there's any supported fact not enabled by default, should that + // be the case we need to send OPTS MLST + while (!facts.empty()) + { + size_t delim = facts.find_first_of(';'); + if (delim == -1) + break; + + if (!delim) + { + facts = facts.substr(1, std::string::npos); + continue; + } + + bool enabled = false; + std::string fact; + + if (facts[delim - 1] == '*') + { + if (delim == 1) + { + facts = facts.substr(delim + 1, std::string::npos); + continue; + } + enabled = true; + fact = facts.substr(0, delim - 1); + } + else + { + enabled = false; + fact = facts.substr(0, delim); + } + facts = facts.substr(delim + 1, std::string::npos); + + if (!strcmp(fact.c_str(), "type") || + !strcmp(fact.c_str(), "size") || + !strcmp(fact.c_str(), "modify") || + !strcmp(fact.c_str(), "create") || + !strcmp(fact.c_str(), "perm") || + !strcmp(fact.c_str(), "unix.mode") || + !strcmp(fact.c_str(), "unix.owner") || + !strcmp(fact.c_str(), "unix.user") || + !strcmp(fact.c_str(), "unix.group") || + !strcmp(fact.c_str(), "unix.uid") || + !strcmp(fact.c_str(), "unix.gid")) + { + had_unset |= !enabled; + opts_facts += fact.c_str(); + opts_facts += ";"; + } + } + if (had_unset) + { + m_serverCapabilities.SetCapability(opts_mlst_command, yes, opts_facts); + } + else + { + m_serverCapabilities.SetCapability(opts_mlst_command, no); + } + } + } + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_CAPABILITIES, 0), (LPARAM)&m_serverCapabilities); + if (!m_bAnnouncesUTF8 && !m_CurrentServer.nUTF8) + m_bUTF8 = false; + if (m_bUTF8 && m_hasClntCmd && !m_isFileZilla) + { + // Some servers refuse to enable UTF8 if client does not send CLNT command + // to fix compatibility with Internet Explorer, but in the process breaking + // compatibility with other clients. + // Rather than forcing MS to fix Internet Explorer, letting other clients + // suffer is a questionable decision in my opinion. + if (Send(CString(L"CLNT ") + m_pTools->GetClientString().c_str())) + m_Operation.nOpState = CONNECT_CLNT; + return; + } + if (m_bUTF8 && !m_isFileZilla) + { + // Handle servers that disobey RFC 2640 that have UTF8 in the FEAT + // response but do not use UTF8 unless OPTS UTF8 ON gets send. + // However these servers obey a conflicting ietf draft: + // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt + // servers are, amongst others, G6 FTP Server and RaidenFTPd. + if (Send(L"OPTS UTF8 ON")) + m_Operation.nOpState = CONNECT_OPTSUTF8; + return; + } + + if ((m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYERMASK) & (FZ_SERVERTYPE_LAYER_SSL_IMPLICIT | FZ_SERVERTYPE_LAYER_SSL_EXPLICIT | FZ_SERVERTYPE_LAYER_TLS_EXPLICIT)) + { + m_Operation.nOpState = CONNECT_SSL_PBSZ; + Send(L"PBSZ 0"); + return; + } + + if (m_serverCapabilities.GetCapability(mlsd_command) == yes) + { + std::string args; + // this is never true, see comment is DiscardLine + if (m_serverCapabilities.GetCapabilityString(opts_mlst_command, &args) == yes && + !args.empty()) + { + m_Operation.nOpState = CONNECT_OPTSMLST; + Send("OPTS MLST " + CString(args.c_str())); + return; + } + } + + ShowStatus(IDS_STATUSMSG_CONNECTED, FZ_LOG_STATUS); + m_pOwner->SetConnected(TRUE); + ResetOperation(FZ_REPLY_OK); + return; + } + else if (m_Operation.nOpState == CONNECT_CLNT) + { + // See above why we send this command + if (Send(L"OPTS UTF8 ON")) + m_Operation.nOpState = CONNECT_OPTSUTF8; + return; + } + else if (m_Operation.nOpState == CONNECT_OPTSUTF8) + { + if ((m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYERMASK) & (FZ_SERVERTYPE_LAYER_SSL_IMPLICIT | FZ_SERVERTYPE_LAYER_SSL_EXPLICIT | FZ_SERVERTYPE_LAYER_TLS_EXPLICIT)) + { + m_Operation.nOpState = CONNECT_SSL_PBSZ; + Send(L"PBSZ 0"); + return; + } + + ShowStatus(IDS_STATUSMSG_CONNECTED, FZ_LOG_STATUS); + m_pOwner->SetConnected(TRUE); + ResetOperation(FZ_REPLY_OK); + return; + } + else if (m_Operation.nOpState == CONNECT_SYST) + { + if (GetReplyCode() == 2) + { + const CStringA reply = m_RecvBuffer.front(); + if (reply.GetLength() > 7 && reply.Mid(3, 4) == " MVS") + m_mayBeMvsFilesystem = true; + else if (reply.GetLength() >= 11 && reply.Mid(3, 8) == " BS-2000") + m_mayBeBS2000Filesystem = true; + + if (reply.Find("FileZilla") != -1) + m_isFileZilla = true; + } + + if (Send(L"FEAT")) + m_Operation.nOpState = CONNECT_FEAT; + return; + } + else if (!bSkipReply) + { + int res = GetReplyCode(); + if (res != 2 && res != 3 && m_Operation.nOpState >= 0) // get initial connect msg off server + { + int nError = 0; + if (m_bUTF8 && m_CurrentServer.nUTF8 != 1) + { + // Fall back to local charset for the case that the server might not + // support UTF8 and the login data contains non-ascii characters. + bool asciiOnly = true; + for (int i = 0; i < m_CurrentServer.user.GetLength(); i++) + if (static_cast(m_CurrentServer.user.GetAt(i)) > 127) + asciiOnly = false; + for (int i = 0; i < m_CurrentServer.pass.GetLength(); i++) + if (static_cast(m_CurrentServer.pass.GetAt(i)) > 127) + asciiOnly = false; + for (int i = 0; i < m_CurrentServer.account.GetLength(); i++) + if (static_cast(m_CurrentServer.account.GetAt(i)) > 127) + asciiOnly = false; + if (!asciiOnly) + { + ShowStatus(L"Login data contains non-ascii characters and server might not be UTF-8 aware. Trying local charset.", FZ_LOG_STATUS); + m_bUTF8 = false; + m_Operation.nOpState = CONNECT_INIT; + } + else + nError = FZ_REPLY_ERROR; + } + else + nError = FZ_REPLY_ERROR; + + if (nError) + { + if (res==5 && logonseq[logontype][m_Operation.nOpState]==1) + nError|=FZ_REPLY_CRITICALERROR; + + DoClose(nError); + return; + } + } + } + CString hostname = m_CurrentServer.host; + if (m_CurrentServer.port != 21) + hostname.Format(hostname+ L":%d", m_CurrentServer.port); // add port to hostname (only if port is not 21) + + USES_CONVERSION; +#ifndef MPEXT_NO_GSS + //**** GSS Authentication **** + if (m_Operation.nOpState==CONNECT_GSS_INIT) //authenticate + { + int i = m_pGssLayer->GetClientAuth(T2CA(m_CurrentServer.host)); + if (i==-1) + m_Operation.nOpState = CONNECT_GSS_AUTHDONE; + else if (i != GSSAPI_AUTHENTICATION_SUCCEEDED) + { + m_Operation.nOpState = CONNECT_GSS_FAILED; + CAsyncRequestData *pData=new CAsyncRequestData; + pData->nRequestType=FZ_ASYNCREQUEST_GSS_AUTHFAILED; + pData->nRequestID=m_pOwner->GetNextAsyncRequestID(); + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_GSS_AUTHFAILED), (LPARAM)pData)) + { + delete pData; + } + else + { + m_bCheckForTimeout = FALSE; + } + } + else + { + // we got authentication, we need to check whether we have forwardable tickets + if (Send(L"CWD .")) + m_Operation.nOpState = CONNECT_GSS_CWD; + } + return; + } + else if (m_Operation.nOpState == CONNECT_GSS_AUTHDONE) + { + if (!m_pGssLayer->AuthSuccessful()) + { + m_Operation.nOpState = CONNECT_GSS_FAILED; + CAsyncRequestData *pData=new CAsyncRequestData; + pData->nRequestType = FZ_ASYNCREQUEST_GSS_AUTHFAILED; + pData->nRequestID = m_pOwner->GetNextAsyncRequestID(); + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_GSS_AUTHFAILED), (LPARAM)pData)) + { + delete pData; + } + else + { + m_bCheckForTimeout = FALSE; + } + return; + } + else + { + // we got authentication, we need to check whether we have forwardable tickets + if (Send(L"CWD .")) + m_Operation.nOpState = CONNECT_GSS_CWD; + return; + } + } + else if (m_Operation.nOpState == CONNECT_GSS_CWD) + { // authentication succeeded, we're now get the response to the CWD command + if (GetReplyCode() == 2) // we're logged on + { + if (Send(L"SYST")) + m_Operation.nOpState = CONNECT_SYST; + return; + } + else + { + //GSS authentication complete but we still have to go through the standard logon procedure + m_Operation.nOpState = CONNECT_INIT; + } + } +#endif + + if (m_Operation.nOpState==CONNECT_INIT) + { + if (logontype) + { + CString str; + str.Format(IDS_STATUSMSG_FWCONNECT,(LPCTSTR)hostname); + ShowStatus(str,FZ_LOG_STATUS); + } + m_Operation.nOpState++; + } + else if (m_Operation.nOpState >= 0 && !bSkipReply) + { + m_Operation.nOpState=logonseq[logontype][m_Operation.nOpState+GetReplyCode()-1]; //get next command from array + switch(m_Operation.nOpState) + { + case ER: // ER means summat has gone wrong + DoClose(); + return; + case LO: //LO means we are logged on + if (Send(L"SYST")) + m_Operation.nOpState = CONNECT_SYST; + return; + } + } + + // go through appropriate logon procedure +#ifndef MPEXT_NO_GSS + int i = logonseq[logontype][m_Operation.nOpState]; + if (m_pGssLayer) + { + if ((i == 0 || i == 6 || i == 9 || i == 10) && + (m_CurrentServer.user == L"anonymous" || m_CurrentServer.user == L"")) + { + //Extract user from kerberos ticket + char str[256]; + if (m_pGssLayer->GetUserFromKrbTicket(str)) + m_CurrentServer.user = str; + if (m_CurrentServer.user == L"") + { + CGssNeedUserRequestData *pData = new CGssNeedUserRequestData(); + pData->nRequestID = m_pOwner->GetNextAsyncRequestID(); + pData->nOldOpState = m_Operation.nOpState; + m_Operation.nOpState = CONNECT_GSS_NEEDUSER; + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_GSS_NEEDUSER), (LPARAM)pData)) + { + delete pData; + } + else + { + m_bCheckForTimeout = FALSE; + } + return; + } + } + else if ((i == 1 || i == 11) && (m_CurrentServer.pass == GetOption(OPTION_ANONPWD) || m_CurrentServer.pass == "")) + { + CGssNeedPassRequestData *pData=new CGssNeedPassRequestData; + pData->nRequestID=m_pOwner->GetNextAsyncRequestID(); + pData->nOldOpState = m_Operation.nOpState; + m_Operation.nOpState = CONNECT_GSS_NEEDPASS; + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_GSS_NEEDPASS), (LPARAM)pData)) + { + delete pData; + } + else + { + m_bCheckForTimeout = FALSE; + } + return; + } + } +#endif + CLogonData *pData = static_cast(m_Operation.pData); + bool needpass = (m_CurrentServer.pass == GetOption(OPTION_ANONPWD)) || (m_CurrentServer.pass == L""); + switch(logonseq[logontype][m_Operation.nOpState]) + { + case 0: + temp=L"USER "+m_CurrentServer.user; + pData->gotPassword = false; + break; + case 1: + if (needpass && !pData->waitForAsyncRequest && !pData->gotPassword) + { + CNeedPassRequestData *pNeedPassRequestData = new CNeedPassRequestData(); + pNeedPassRequestData->nRequestID = m_pOwner->GetNextAsyncRequestID(); + pNeedPassRequestData->nOldOpState = m_Operation.nOpState; + m_Operation.nOpState = CONNECT_NEEDPASS; + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_NEEDPASS), (LPARAM)pNeedPassRequestData)) + { + delete pNeedPassRequestData; + ResetOperation(FZ_REPLY_ERROR); + } + else + { + m_bCheckForTimeout = FALSE; + } + pData->waitForAsyncRequest = true; + return; + } + else if (!needpass || pData->gotPassword) + { + temp=L"PASS "+m_CurrentServer.pass; + } + else + { + return; + } + break; + case 2: + temp=L"ACCT "+GetOption(OPTION_FWPASS); + break; + case 3: + temp=L"USER "+GetOption(OPTION_FWUSER); + break; + case 4: + temp=L"PASS "+GetOption(OPTION_FWPASS); + break; + case 5: + temp=L"SITE "+hostname; + break; + case 6: + temp=L"USER "+m_CurrentServer.user+L"@"+hostname; + break; + case 7: + temp=L"OPEN "+hostname; + break; + case 8: + temp=L"USER "+GetOption(OPTION_FWUSER)+L"@"+hostname; + break; + case 9: + temp=L"USER "+m_CurrentServer.user+L"@"+hostname+L" "+GetOption(OPTION_FWUSER); + break; + case 10: + temp=L"USER "+m_CurrentServer.user+L"@"+GetOption(OPTION_FWUSER)+L"@"+hostname; + break; + case 11: + if (needpass && !pData->waitForAsyncRequest && !pData->gotPassword) + { + CNeedPassRequestData *pNeedPassRequestData = new CNeedPassRequestData(); + pNeedPassRequestData->nRequestID = m_pOwner->GetNextAsyncRequestID(); + pNeedPassRequestData->nOldOpState = m_Operation.nOpState; + m_Operation.nOpState = CONNECT_NEEDPASS; + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_NEEDPASS), (LPARAM)pNeedPassRequestData)) + { + delete pNeedPassRequestData; + ResetOperation(FZ_REPLY_ERROR); + } + else + { + m_bCheckForTimeout = FALSE; + } + pData->waitForAsyncRequest = true; + return; + } + else if (!needpass || pData->gotPassword) + { + temp=L"PASS "+m_CurrentServer.pass+L"@"+GetOption(OPTION_FWPASS); + } + else + { + return; + } + break; + case 12: + if (m_CurrentServer.account == L"") + temp = L"ACCT default"; + else + temp = L"ACCT " + m_CurrentServer.account; + break; + } + // send command, get response + if(!Send(temp)) + return; +} + +BOOL CFtpControlSocket::SendAuthSsl() +{ + if (!Send("AUTH SSL")) + return false; + m_Operation.nOpState = CONNECT_SSL_NEGOTIATE; + return true; +} + +#define BUFFERSIZE 4096 +void CFtpControlSocket::OnReceive(int nErrorCode) +{ + m_LastRecvTime = CTime::GetCurrentTime(); + + if (!m_pOwner->IsConnected()) + { + if (!m_Operation.nOpMode) + { + LogMessage(FZ_LOG_INFO, L"Socket has been closed, don't process receive" ); + return; + } + m_MultiLine = ""; + CString str; + str.Format(IDS_STATUSMSG_CONNECTEDWITH, (LPCTSTR)m_ServerName); + ShowStatus(str, FZ_LOG_PROGRESS); + m_pOwner->SetConnected(TRUE); + } + char *buffer = static_cast(nb_calloc(1, BUFFERSIZE)); + int numread = Receive(buffer, BUFFERSIZE); + + if (numread == SOCKET_ERROR) + { + nb_free(buffer); + buffer = NULL; + if (GetLastError() != WSAEWOULDBLOCK) + { + ShowStatus(IDS_STATUSMSG_DISCONNECTED, FZ_LOG_ERROR); + DoClose(); + } + return; + } + if (!numread) + { + nb_free(buffer); + buffer = NULL; + ShowStatus(IDS_STATUSMSG_DISCONNECTED, FZ_LOG_ERROR); + DoClose(); + } + + if (!m_bUTF8 && m_CurrentServer.iUndupFF) + { + CStringA Buf; + LPSTR strBuf = (LPSTR)buffer; + for (int n = 0; n < numread; n++, strBuf++) + { + if ((strBuf[0] == (char)0xFF) && (strBuf[1] == (char)0xFF)) + { + Buf.AppendChar((char)0xFF); + strBuf++; + } + else + Buf.AppendChar(*strBuf); + } + nb_free(buffer); + buffer = NULL; + numread = Buf.GetLength(); + buffer = static_cast(nb_calloc(1, numread)); + memcpy(buffer, Buf.GetBuffer(), numread); + } + + for (int i=0; i < numread; i++) + { + if ((buffer[i] == '\r') || (buffer[i] == '\n') || (buffer[i] == 0)) + { + if (!m_RecvBuffer.empty() && m_RecvBuffer.back() != "") + { + USES_CONVERSION; + if (m_bUTF8) + { + // convert from UTF-8 to ANSI + LPCSTR utf8 = (LPCSTR)m_RecvBuffer.back(); + if (DetectUTF8Encoding(RawByteString(utf8)) == etANSI) + { + if (m_CurrentServer.nUTF8 != 1) + { + LogMessage(FZ_LOG_WARNING, L"Server does not send proper UTF-8, falling back to local charset"); + m_bUTF8 = false; + } + ShowStatus(A2CT(utf8), FZ_LOG_REPLY); + } + else + { + int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (!len) + m_RecvBuffer.back() = ""; + else + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(CP_UTF8, 0, utf8, -1 , (LPWSTR)p1, len + 1); + ShowStatus(W2CT(p1), FZ_LOG_REPLY); + nb_free(p1); + } + } + } + else if (m_nCodePage) + { + LPCSTR str = (LPCSTR)m_RecvBuffer.back(); + int len = MultiByteToWideChar(m_nCodePage, 0, str, -1, NULL, 0); + if (!len) + m_RecvBuffer.back() = ""; + else + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(m_nCodePage, 0, str, -1 , (LPWSTR)p1, len + 1); + ShowStatus(W2CT(p1), FZ_LOG_REPLY); + nb_free(p1); + } + } + else + { + ShowStatus(A2CT(m_RecvBuffer.back()), FZ_LOG_REPLY); + } + // Check for multi-line responses + // Partially duplicated in TFTPFileSystem::HandleReplyStatus + if (m_RecvBuffer.back().GetLength() > 3) + { + if (m_MultiLine != "") + { + if (m_RecvBuffer.back().Left(4) != m_MultiLine) + { + CStringA line = m_RecvBuffer.back(); + if (line.Left(4) == m_MultiLine.Left(3) + '-') + { + line = line.Mid(4, line.GetLength() - 4); + } + DiscardLine(line); + m_RecvBuffer.pop_back(); + } + else // end of multi-line found + { + m_MultiLine = ""; + LPARAM lParam = 0; + #ifdef _DEBUG + lParam = GetTickCount(); + #endif + m_pOwner->PostThreadMessage(m_pOwner->m_nInternalMessageID, FZAPI_THREADMSG_PROCESSREPLY, lParam); + } + } + // start of new multi-line + else if (m_RecvBuffer.back()[3] == '-') + { + // DDD is the end of a multi-line response + m_MultiLine = m_RecvBuffer.back().Left(3) + ' '; + m_RecvBuffer.pop_back(); + } + else + { + LPARAM lParam = 0; + #ifdef _DEBUG + lParam = GetTickCount(); + #endif + m_pOwner->PostThreadMessage(m_pOwner->m_nInternalMessageID, FZAPI_THREADMSG_PROCESSREPLY, lParam); + } + } + else + { + m_RecvBuffer.pop_back(); + } + m_RecvBuffer.push_back(""); + } + } + else + { + //The command may only be 2000 chars long. This ensures that a malicious user can't + //send extremely large commands to fill the memory of the server + if (m_RecvBuffer.empty()) + m_RecvBuffer.push_back(""); + if (m_RecvBuffer.back().GetLength() < 2000) + m_RecvBuffer.back() += buffer[i]; + } + } + + nb_free(buffer); +} + +void CFtpControlSocket::ProcessReply() +{ + if (m_RecvBuffer.empty()) + return; + + if (m_awaitsReply) + { + if (m_sendBuffer) + TriggerEvent(FD_WRITE); + m_awaitsReply = false; + } + + CString reply = GetReply(); + if ( reply == L"" ) + return; + + // After Cancel, we might have to skip a reply + if (m_skipReply) + { + m_skipReply = false; + m_RecvBuffer.pop_front(); + return; + } + + if (m_bKeepAliveActive) + { + m_bKeepAliveActive = FALSE; + m_pOwner->PostThreadMessage(m_pOwner->m_nInternalMessageID,FZAPI_THREADMSG_POSTKEEPALIVE,0); + } + else if (m_Operation.nOpMode&CSMODE_CONNECT) + LogOnToServer(); + else if (m_Operation.nOpMode& (CSMODE_COMMAND|CSMODE_CHMOD) ) + { + if (GetReplyCode()== 2 || GetReplyCode()== 3) + ResetOperation(FZ_REPLY_OK); + else + ResetOperation(FZ_REPLY_ERROR); + } + else if (m_Operation.nOpMode&CSMODE_TRANSFER) + { + FileTransfer(0); + } + else if (m_Operation.nOpMode&CSMODE_LIST) + List(FALSE); + else if (m_Operation.nOpMode&CSMODE_LISTFILE) + ListFile(L"", CServerPath()); + else if (m_Operation.nOpMode&CSMODE_DELETE) + Delete( L"",CServerPath()); + else if (m_Operation.nOpMode&CSMODE_RMDIR) + RemoveDir( L"",CServerPath()); + else if (m_Operation.nOpMode&CSMODE_MKDIR) + MakeDir(CServerPath()); + else if (m_Operation.nOpMode&CSMODE_RENAME) + Rename(L"", L"", CServerPath(), CServerPath()); + + if (!m_RecvBuffer.empty()) + m_RecvBuffer.pop_front(); +} + +void CFtpControlSocket::OnConnect(int nErrorCode) +{ + + if (!m_Operation.nOpMode) + { + if (!m_pOwner->IsConnected()) + DoClose(); + return; + } + if (!nErrorCode) + { + if (!m_pOwner->IsConnected()) + { + m_MultiLine = ""; + m_pOwner->SetConnected(TRUE); + CString str; + str.Format( + m_pSslLayer ? IDS_STATUSMSG_CONNECTEDWITHSSL : IDS_STATUSMSG_CONNECTEDWITH, + (LPCTSTR)m_ServerName); + ShowStatus(str,FZ_LOG_PROGRESS); + } + } + else + { + if (nErrorCode == WSAHOST_NOT_FOUND) + { + CString str; + str.Format(IDS_ERRORMSG_CANTRESOLVEHOST2, (LPCTSTR)m_ServerName); + ShowStatus(str, FZ_LOG_ERROR); + } + else + { + TCHAR Buffer[255]; + int Len = FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, nErrorCode, 0, Buffer, _countof(Buffer), NULL); + while ((Len > 0) && ((Buffer[Len - 1] >= 0) && (Buffer[Len - 1] <= 32))) + { + --Len; + } + ShowStatus(CString(Buffer, Len), FZ_LOG_ERROR); + } + DoClose(); + } +} + +BOOL CFtpControlSocket::Send(CString str) +{ + USES_CONVERSION; + + ShowStatus(str, FZ_LOG_COMMAND); + str += L"\r\n"; + int res = 0; + if (m_bUTF8) + { + LPCWSTR unicode = T2CW(str); + int len = WideCharToMultiByte(CP_UTF8, 0, unicode, -1, 0, 0, 0, 0); + if (!len) + { + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + return FALSE; + } + char* utf8 = static_cast(nb_calloc(1, len + 1)); + WideCharToMultiByte(CP_UTF8, 0, unicode, -1, utf8, len + 1, 0, 0); + + int sendLen = strlen(utf8); + if (!m_awaitsReply && !m_sendBuffer) + res = CAsyncSocketEx::Send(utf8, strlen(utf8)); + else + res = -2; + if ((res == SOCKET_ERROR && GetLastError() != WSAEWOULDBLOCK) || !res) + { + nb_free(utf8); + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + return FALSE; + } + if (res != sendLen) + { + if (res < 0) + res = 0; + if (!m_sendBuffer) + { + m_sendBuffer = static_cast(nb_calloc(1, sendLen - res)); + memcpy(m_sendBuffer, utf8 + res, sendLen - res); + m_sendBufferLen = sendLen - res; + } + else + { + char* tmp = static_cast(nb_calloc(1, m_sendBufferLen + sendLen - res)); + memcpy(tmp, m_sendBuffer, m_sendBufferLen); + memcpy(tmp + m_sendBufferLen, utf8 + res, sendLen - res); + nb_free(m_sendBuffer); + m_sendBuffer = tmp; + m_sendBufferLen += sendLen - res; + } + } + nb_free(utf8); + } + else if (m_nCodePage) + { + LPCWSTR unicode = T2CW(str); + int len = WideCharToMultiByte(m_nCodePage, 0, unicode, -1, 0, 0, 0, 0); + if (!len) + { + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + return FALSE; + } + char* utf8 = static_cast(nb_calloc(1, len + 1)); + WideCharToMultiByte(m_nCodePage, 0, unicode, -1, utf8, len + 1, 0, 0); + + int sendLen = strlen(utf8); + if (!m_awaitsReply && !m_sendBuffer) + res = CAsyncSocketEx::Send(utf8, strlen(utf8)); + else + res = -2; + if ((res == SOCKET_ERROR && GetLastError() != WSAEWOULDBLOCK) || !res) + { + nb_free(utf8); + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + return FALSE; + } + if (res != sendLen) + { + if (res < 0) + res = 0; + if (!m_sendBuffer) + { + m_sendBuffer = static_cast(nb_calloc(1, sendLen - res)); + memcpy(m_sendBuffer, utf8 + res, sendLen - res); + m_sendBufferLen = sendLen - res; + } + else + { + char* tmp = static_cast(nb_calloc(1, m_sendBufferLen + sendLen - res)); + memcpy(tmp, m_sendBuffer, m_sendBufferLen); + memcpy(tmp + m_sendBufferLen, utf8 + res, sendLen - res); + nb_free(m_sendBuffer); + m_sendBuffer = tmp; + m_sendBufferLen += sendLen - res; + } + } + nb_free(utf8); + } + else + { + LPCSTR lpszAsciiSend = T2CA(str); + + int sendLen = strlen(lpszAsciiSend); + if (!m_awaitsReply && !m_sendBuffer) + res = CAsyncSocketEx::Send(lpszAsciiSend, strlen(lpszAsciiSend), 0, m_CurrentServer.iDupFF); + else + res = -2; + if ((res == SOCKET_ERROR && GetLastError() != WSAEWOULDBLOCK) || !res) + { + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + return FALSE; + } + if (res != sendLen) + { + if (res < 0) + res = 0; + if (!m_sendBuffer) + { + m_sendBuffer = static_cast(nb_calloc(1, sendLen - res)); + memcpy(m_sendBuffer, lpszAsciiSend, sendLen - res); + m_sendBufferLen = sendLen - res; + } + else + { + char* tmp = static_cast(nb_calloc(1, m_sendBufferLen + sendLen - res)); + memcpy(tmp, m_sendBuffer, m_sendBufferLen); + memcpy(tmp + m_sendBufferLen, lpszAsciiSend + res, sendLen - res); + nb_free(m_sendBuffer); + m_sendBuffer = tmp; + m_sendBufferLen += sendLen - res; + } + } + } + if (res > 0) + { + m_awaitsReply = true; + m_LastSendTime = CTime::GetCurrentTime(); + // Count timeout since the last request, not only since the last received data + // otherwise we may happen to timeout immediately after sending request if + // CheckForTimeout occurs in between and we haven't received any data for a while + m_LastRecvTime = m_LastSendTime; + } + return TRUE; +} + +int CFtpControlSocket::GetReplyCode() +{ + if (m_RecvBuffer.empty()) + return 0; + CStringA str = m_RecvBuffer.front(); + if (str == "") + return 0; + else + return str[0]-'0'; +} + +void CFtpControlSocket::DoClose(int nError /*=0*/) +{ + m_bCheckForTimeout=TRUE; + m_pOwner->SetConnected(FALSE); + m_bKeepAliveActive=FALSE; + if (nError & FZ_REPLY_CRITICALERROR) + nError |= FZ_REPLY_ERROR; + ResetOperation(FZ_REPLY_ERROR|FZ_REPLY_DISCONNECTED|nError); + m_RecvBuffer.clear(); + m_MultiLine = ""; + m_bDidRejectCertificate = FALSE; + +#ifndef MPEXT_NO_ZLIB + m_useZlib = false; + m_zlibSupported = false; + m_zlibLevel = 0; +#endif + + m_bUTF8 = false; + m_bAnnouncesUTF8 = false; + m_nCodePage = 0; + m_hasClntCmd = false; + m_serverCapabilities.Clear(); + m_ListFile = ""; + + m_awaitsReply = false; + m_skipReply = false; + + nb_free(m_sendBuffer); + m_sendBuffer = 0; + m_sendBufferLen = 0; + + m_bProtP = false; + + m_mayBeMvsFilesystem = false; + m_mayBeBS2000Filesystem = false; + + Close(); +} + +void CFtpControlSocket::Disconnect() +{ + DebugAssert(!m_Operation.nOpMode); + m_Operation.nOpMode=CSMODE_DISCONNECT; + DoClose(); + ShowStatus(IDS_STATUSMSG_DISCONNECTED,FZ_LOG_STATUS); //Send the disconnected message to the message log +} + +void CFtpControlSocket::CheckForTimeout() +{ + if (!m_Operation.nOpMode && !m_bKeepAliveActive) + return; + if (!m_bCheckForTimeout) + return; + int delay=GetOptionVal(OPTION_TIMEOUTLENGTH); + if (m_pTransferSocket) + { + int res=m_pTransferSocket->CheckForTimeout(delay); + if (res) + return; + } + CTimeSpan span=CTime::GetCurrentTime()-m_LastRecvTime; + if ((delay > 0) && (span.GetTotalSeconds()>=delay)) + { + ShowTimeoutError(IDS_CONTROL_CONNECTION); + DoClose(); + } +} + +void CFtpControlSocket::FtpCommand(LPCTSTR pCommand) +{ + m_Operation.nOpMode=CSMODE_COMMAND; + Send(pCommand); +} + +bool CFtpControlSocket::UsingMlsd() +{ + return + // 0 = on, 1 = off, 2 = auto + (m_CurrentServer.iUseMlsd == 0) || + ((m_CurrentServer.iUseMlsd != 1) && + (m_serverCapabilities.GetCapability(mlsd_command) == yes)); +} + +bool CFtpControlSocket::UsingUtf8() +{ + return m_bUTF8; +} + +std::string CFtpControlSocket::GetTlsVersionStr() +{ + if (m_pSslLayer != NULL) + { + return m_pSslLayer->GetTlsVersionStr(); + } + else + { + return std::string(); + } +} + +std::string CFtpControlSocket::GetCipherName() +{ + if (m_pSslLayer != NULL) + { + return m_pSslLayer->GetCipherName(); + } + else + { + return std::string(); + } +} + +CString CFtpControlSocket::GetListingCmd() +{ + CString cmd; + if (UsingMlsd()) + { + cmd = L"MLSD"; + } + else + { + cmd = L"LIST"; + if (GetOptionVal(OPTION_MPEXT_SHOWHIDDEN) && !(m_CurrentServer.nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_VMS | FZ_SERVERTYPE_SUB_FTP_BS2000))) + cmd += L" -a"; + } + return cmd; +} + +void CFtpControlSocket::List(BOOL bFinish, int nError /*=FALSE*/, CServerPath path /*=CServerPath()*/, CString subdir /*=L""*/,int nListMode/*=0*/) +{ + USES_CONVERSION; + + #define LIST_INIT -1 + #define LIST_PWD 0 + #define LIST_CWD 1 + #define LIST_PWD2 2 + #define LIST_CWD2 3 + #define LIST_PWD3 4 + #define LIST_MODE 5 + #define LIST_OPTS 6 + #define LIST_PORT_PASV 7 + #define LIST_TYPE 8 + #define LIST_LIST 9 + #define LIST_WAITFINISH 10 + + DebugAssert(!m_Operation.nOpMode || m_Operation.nOpMode&CSMODE_LIST); + + m_Operation.nOpMode|=CSMODE_LIST; + + if (!m_pOwner->IsConnected()) + { + ResetOperation(FZ_REPLY_ERROR|FZ_REPLY_NOTCONNECTED); + return; + } + + if (bFinish || nError) + if (m_Operation.nOpMode!=CSMODE_LIST) + return; //Old message coming in + + if (nError) + { + delete m_pTransferSocket; + m_pTransferSocket=0; + if (nError&CSMODE_TRANSFERTIMEOUT) + DoClose(); + else + ResetOperation(FZ_REPLY_ERROR); + return; + } + + CListData *pData = static_cast(m_Operation.pData); + + if (bFinish) + { + if (!m_pTransferSocket || m_pTransferSocket->m_bListening) + { + delete m_pDirectoryListing; + m_pDirectoryListing = 0; + delete m_pTransferSocket; + m_pTransferSocket = 0; + ResetOperation(FZ_REPLY_ERROR); + return; + } + + int num = 0; + pData->pDirectoryListing = new t_directory(); + if (GetOptionVal(OPTION_DEBUGSHOWLISTING)) + m_pTransferSocket->m_pListResult->SendToMessageLog(); + pData->pDirectoryListing->direntry = m_pTransferSocket->m_pListResult->getList(num, false); + pData->pDirectoryListing->num = num; + if (m_pTransferSocket->m_pListResult->m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_VMS && m_CurrentServer.nServerType & FZ_SERVERTYPE_FTP) + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS; + + pData->pDirectoryListing->server = m_CurrentServer; + pData->pDirectoryListing->path.SetServer(pData->pDirectoryListing->server); + if (pData->rawpwd != L"") + { + if (!pData->pDirectoryListing->path.SetPath(pData->rawpwd)) + { + delete m_pDirectoryListing; + m_pDirectoryListing=0; + delete m_pTransferSocket; + m_pTransferSocket=0; + ResetOperation(FZ_REPLY_ERROR); + return; + } + m_pOwner->SetCurrentPath(pData->pDirectoryListing->path); + } + else + pData->pDirectoryListing->path = m_pOwner->GetCurrentPath(); + + if (m_Operation.nOpState!=LIST_WAITFINISH) + { + return; + } + else + { + delete m_pTransferSocket; + m_pTransferSocket=0; + } + } + + if (m_Operation.nOpState==LIST_WAITFINISH) + { + if (!bFinish) + { + if (pData->nFinish==-1) + { + int code=GetReplyCode(); + if (code== 2) + { + pData->nFinish=1; + } + else + pData->nFinish=0; + } + } + else + { + if (m_pTransferSocket) + delete m_pTransferSocket; + m_pTransferSocket=0; + } + if (pData->nFinish==0) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + else if (pData->pDirectoryListing && pData->nFinish==1) + { + ShowStatus(IDS_STATUSMSG_DIRLISTSUCCESSFUL,FZ_LOG_PROGRESS); + SetDirectoryListing(pData->pDirectoryListing); + ResetOperation(FZ_REPLY_OK); + return; + } + return; + } + else if (m_Operation.nOpState != LIST_INIT) + { + CString retmsg = GetReply(); + BOOL error = FALSE; + int code = GetReplyCode(); + switch (m_Operation.nOpState) + { + case LIST_PWD: //Reply to PWD command + if (code != 2 && code !=3 ) + { + error = TRUE; + break; + } + + pData->rawpwd = retmsg; + if ((m_mayBeMvsFilesystem || m_mayBeBS2000Filesystem) && m_CurrentServer.nServerType & FZ_SERVERTYPE_FTP && + pData->rawpwd[0] != L'/') + { + m_mayBeMvsFilesystem = false; + m_mayBeBS2000Filesystem = false; + if (m_mayBeBS2000Filesystem) + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_BS2000; + else + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS; + + if (!pData->path.IsEmpty()) + pData->path.SetServer(m_CurrentServer); + } + if (!ParsePwdReply(pData->rawpwd)) + return; + if (pData->path.IsEmpty() || pData->path == m_pOwner->GetCurrentPath()) + { + m_Operation.nOpState = NeedModeCommand() ? LIST_MODE : (NeedOptsCommand() ? LIST_OPTS : LIST_TYPE); + } + else + m_Operation.nOpState = LIST_CWD; + break; + case LIST_CWD: + if (code != 2 && code != 3) + error = TRUE; + m_Operation.nOpState = LIST_PWD2; + break; + case LIST_PWD2: //Reply to PWD command + if (code !=2 && code != 3) + error = TRUE; + else + { + pData->rawpwd = retmsg; + if (!ParsePwdReply(pData->rawpwd)) + return; + } + if (pData->subdir != L"") + { + if (pData->path != m_pOwner->GetCurrentPath()) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + m_Operation.nOpState = LIST_CWD2; + } + else + { + m_Operation.nOpState = NeedModeCommand() ? LIST_MODE : (NeedOptsCommand() ? LIST_OPTS : LIST_TYPE); + } + break; + case LIST_CWD2: + if (pData->lastCmdSentCDUP) + { + CString reply = GetReply().Left(3); + int replycode = _ttoi(reply); + if (replycode >= 500 && replycode < 505) + break; + pData->lastCmdSentCDUP = false; + } + if (code != 2 && code != 3) + error = TRUE; + m_Operation.nOpState = LIST_PWD3; + break; + case LIST_PWD3: //Reply to PWD command + if (code != 2 && code != 3) + error = TRUE; + else + { + pData->rawpwd = retmsg; + if (!ParsePwdReply(pData->rawpwd)) + return; + } + m_Operation.nOpState = NeedModeCommand() ? LIST_MODE : (NeedOptsCommand() ? LIST_OPTS : LIST_TYPE); + break; + case LIST_MODE: +#ifndef MPEXT_NO_ZLIB + if (code == 2 || code == 3) + m_useZlib = !m_useZlib; +#endif + m_Operation.nOpState = NeedOptsCommand() ? LIST_OPTS : LIST_TYPE; + break; + case LIST_OPTS: +#ifndef MPEXT_NO_ZLIB + if (code == 2 || code == 3) + m_zlibLevel = pData->newZlibLevel; +#endif + m_Operation.nOpState = LIST_TYPE; + break; + case LIST_TYPE: + if (code!=2 && code!=3) + error=TRUE; + m_Operation.nOpState = LIST_PORT_PASV; + break; + case LIST_PORT_PASV: + if (code!=2 && code!=3) + { + error=TRUE; + break; + } + if (pData->bPasv) + { + CString temp; + int i,j; + // MP EXT + if((i=retmsg.Find(L"("))>=0&&(j=retmsg.Find(L")"))>=0) + { + i++; + j--; + } + else + { + // MP EXT + if ((i=retmsg.Mid(4).FindOneOf(L"0123456789"))>=0) + { + i += 4; + j = retmsg.GetLength() - 1; + } + else + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + error=TRUE; + break; + } + } + + temp = retmsg.Mid(i,(j-i)+1); + if (GetFamily() == AF_INET) + { + i=temp.ReverseFind(L','); + pData->port=atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); //get ls byte of server socket + temp=temp.Left(i); + i=temp.ReverseFind(L','); + pData->port+=256*atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); // add ms byte to server socket + pData->host = temp.Left(i); + pData->host.Replace(L',', L'.'); + if (!CheckForcePasvIp(pData->host)) + { + error = TRUE; + break; + } + } + else if (GetFamily() == AF_INET6) + { + temp = temp.Mid(3); + pData->port = atol( T2CA(temp.Left(temp.GetLength() - 1) ) ); + if (pData->port < 0 || pData->port > 65535) + { + LogMessage(FZ_LOG_WARNING, L"Port %u not valid", pData->port); + error = TRUE; + break; + } + + unsigned int tmpPort; + if (!GetPeerName(pData->host, tmpPort)) + { + LogMessage(FZ_LOG_WARNING, L"GetPeerName failed"); + error = TRUE; + break; + } + } + else + { + LogMessage(FZ_LOG_WARNING, L"Protocol %d not supported", GetFamily()); + error = TRUE; + break; + } + } + m_Operation.nOpState = LIST_LIST; + break; + case LIST_LIST: + if (IsMisleadingListResponse()) + { + ShowStatus(IDS_STATUSMSG_DIRLISTSUCCESSFUL, FZ_LOG_PROGRESS); + + t_directory listing; + listing.server = m_CurrentServer; + listing.path = m_pOwner->GetCurrentPath(); + + SetDirectoryListing(&listing); + ResetOperation(FZ_REPLY_OK); + return; + } + else if (code != 1) + error = TRUE; + else + m_Operation.nOpState = LIST_WAITFINISH; + break; + default: + error = TRUE; + } + + if (error) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + if (m_Operation.nOpState==LIST_INIT) + { //Initialize some variables + pData=new CListData; + pData->path=path; + pData->subdir=subdir; + m_Operation.pData=pData; + ShowStatus(IDS_STATUSMSG_RETRIEVINGDIRLIST, FZ_LOG_PROGRESS); + pData->nFinish=-1; + if (m_pDirectoryListing) + { + delete m_pDirectoryListing; + m_pDirectoryListing=0; + } + + if (GetOptionVal(OPTION_PROXYTYPE)!=PROXYTYPE_NOPROXY) + pData->bPasv = TRUE; + else if (m_CurrentServer.nPasv == 1) + pData->bPasv = TRUE; + else if (m_CurrentServer.nPasv == 2) + pData->bPasv = FALSE; + else + pData->bPasv = GetOptionVal(OPTION_PASV); + + CServerPath path = pData->path; + CServerPath realpath = m_pOwner->GetCurrentPath(); + if (!realpath.IsEmpty()) + { + if (!pData->path.IsEmpty() && pData->path != realpath) + m_Operation.nOpState=LIST_CWD; + else if (!pData->path.IsEmpty() && pData->subdir!=L"") + m_Operation.nOpState=LIST_CWD2; + else + { + m_Operation.nOpState = NeedModeCommand() ? LIST_MODE : (NeedOptsCommand() ? LIST_OPTS : LIST_TYPE);; + } + } + else + m_Operation.nOpState = LIST_PWD; + } + CString cmd; + if (m_Operation.nOpState == LIST_PWD) + cmd=L"PWD"; + else if (m_Operation.nOpState==LIST_CWD) + cmd=L"CWD " + pData->path.GetPath(); //Command to retrieve the current directory + else if (m_Operation.nOpState==LIST_PWD2) + cmd=L"PWD"; + else if (m_Operation.nOpState==LIST_CWD2) + { + if (!pData->subdir) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + if (pData->subdir != L".." ) + { + if (m_CurrentServer.nServerType & FZ_SERVERTYPE_SUB_FTP_VMS) + { + CServerPath path = m_pOwner->GetCurrentPath(); + path.AddSubdir(pData->subdir); + cmd = L"CWD " + path.GetPath(); + } + else + cmd = L"CWD " + pData->subdir; + } + else + { + if (pData->lastCmdSentCDUP) + { + pData->lastCmdSentCDUP = false; + cmd = L"CWD .."; + } + else + { + pData->lastCmdSentCDUP = true; + cmd = L"CDUP"; + } + } + } + else if (m_Operation.nOpState == LIST_PWD3) + cmd=L"PWD"; + else if (m_Operation.nOpState == LIST_MODE) + { +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + if (m_useZlib) +#endif + cmd = L"MODE S"; +#ifndef MPEXT_NO_ZLIB + else + cmd = L"MODE Z"; +#endif + } + else if (m_Operation.nOpState == LIST_OPTS) + { +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + pData->newZlibLevel = GetOptionVal(OPTION_MODEZ_LEVEL); + cmd.Format(L"OPTS MODE Z LEVEL %d", pData->newZlibLevel); +#endif + } + else if (m_Operation.nOpState == LIST_PORT_PASV) + { + m_pTransferSocket = new CTransferSocket(this, m_Operation.nOpMode); +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pTransferSocket->InitZlib(m_zlibLevel)) + { + ShowStatus(L"Failed to initialize zlib", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + } +#endif + m_pTransferSocket->m_nInternalMessageID = m_pOwner->m_nInternalMessageID; +#ifndef MPEXT_NO_GSS + if (m_pGssLayer && m_pGssLayer->AuthSuccessful()) + m_pTransferSocket->UseGSS(m_pGssLayer); +#endif + m_pTransferSocket->SetFamily(GetFamily()); + if (!m_pTransferSocket->Create(m_pSslLayer && m_bProtP) || + !m_pTransferSocket->AsyncSelect()) + { + ShowStatus(L"Failed to create socket", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + if (pData->bPasv) + switch (GetFamily()) + { + case AF_INET: + cmd = L"PASV"; + break; + case AF_INET6: + cmd = L"EPSV"; + break; + default: + LogMessage(FZ_LOG_WARNING, L"Protocol %d not supported", GetFamily()); + ResetOperation(FZ_REPLY_ERROR); + return; + } + else + { + m_pTransferSocket->m_bListening=TRUE; + if (m_pProxyLayer) + { + SOCKADDR_IN addr; + int len=sizeof(addr); + if (!m_pProxyLayer->GetPeerName((SOCKADDR *)&addr,&len)) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + else if (!m_pTransferSocket->Listen(addr.sin_addr.S_un.S_addr)) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + else + { + //Set up an active file transfer + CString tempHostname; + UINT nPort; + + if (// create listen socket (let MFC choose the port) & start the socket listening + !m_pTransferSocket->Listen() || + !m_pTransferSocket->GetSockName(tempHostname, nPort)) + { + ShowStatus(L"Failed to create listen socket", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + + CString host; + bool bError = false; + + if (GetFamily() == AF_INET) + { + host = GetOption(OPTION_TRANSFERIP); + if (host != L"") + { + DWORD ip = inet_addr(T2CA(host)); + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + { + hostent *fullname = gethostbyname(T2CA(host)); + if (!fullname) + host = L""; + else + { + DWORD ip = ((LPIN_ADDR)fullname->h_addr)->s_addr; + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + host = L""; + } + } + } + if (host == L"") + { + UINT temp; + + if (!GetSockName(host, temp)) + { + ShowStatus(L"Failed to get socket address ", FZ_LOG_ERROR); + bError = true; + } + + host.Replace(L'.', L','); + } + + if (!bError) + { + host.Format(host+L",%d,%d", nPort/256, nPort%256); + cmd = L"PORT " + host; // send PORT cmd to server + } + } + else if (GetFamily() == AF_INET6) + { + host = GetOption(OPTION_TRANSFERIP6); + if (host != L"") + { + USES_CONVERSION; + addrinfo hints, *res; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + if (p_getaddrinfo && !p_getaddrinfo(T2CA(host), "1024", &hints, &res)) + { + host = Inet6AddrToString(((SOCKADDR_IN6 *)res->ai_addr)->sin6_addr); + if (p_freeaddrinfo) p_freeaddrinfo(res); + } + else + host = L""; + } + if (host == L"") + { + UINT temp; + + if(!GetSockName(host, temp)) + bError = true; + } + + if (!bError) + { + // assemble EPRT command + cmd.Format(L"EPRT |2|" + host + L"|%d|", nPort); + } + } + else + { + LogMessage(FZ_LOG_WARNING, L"Protocol %d not supported", GetFamily()); + bError = true; + } + + if (bError) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + } + } + else if (m_Operation.nOpState==LIST_TYPE) + cmd=L"TYPE A"; + else if (m_Operation.nOpState==LIST_LIST) + { + if (!m_pTransferSocket) + { + LogMessage(FZ_LOG_APIERROR, L"Error: m_pTransferSocket==NULL" ); + ResetOperation(FZ_REPLY_ERROR); + return; + } + + m_pTransferSocket->SetActive(); + + cmd = GetListingCmd(); + if (!Send(cmd)) + return; + + if (pData->bPasv) + { + CString hostname; + hostname.Format(L"%s:%d", pData->host, pData->port); + CString str; + str.Format(IDS_STATUSMSG_CONNECTING, hostname); + ShowStatus(str, FZ_LOG_PROGRESS); + + // if PASV create the socket & initiate outbound data channel connection + if (!m_pTransferSocket->Connect(pData->host,pData->port)) + { + if (GetLastError()!=WSAEWOULDBLOCK) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + } + + return; + } + if (cmd != L"") + Send(cmd); +} + +void CFtpControlSocket::ListFile(const CString & filename, const CServerPath & path) +{ + USES_CONVERSION; + + #define LISTFILE_INIT -1 + #define LISTFILE_MLST 1 + #define LISTFILE_TYPE 2 + #define LISTFILE_SIZE 3 + #define LISTFILE_MDTM 4 + #define LISTFILE_PWD 5 + #define LISTFILE_CWD 6 + #define LISTFILE_CWD2 7 + + DebugAssert(!m_Operation.nOpMode || m_Operation.nOpMode&CSMODE_LISTFILE); + + m_Operation.nOpMode|=CSMODE_LISTFILE; + + if (!m_pOwner->IsConnected()) + { + ResetOperation(FZ_REPLY_ERROR|FZ_REPLY_NOTCONNECTED); + return; + } + + CListFileData * pData = static_cast(m_Operation.pData); + + BOOL error = FALSE; + CString cmd; + CString retmsg; + int code = -1; + int num = -1; + switch (m_Operation.nOpState) + { + case LISTFILE_INIT: + //Initialize some variables + pData = new CListFileData(); + pData->fileName = filename; + pData->dir = path.GetPath(); + // special case for listing a root folder + pData->path = (filename == L"/") ? pData->dir : path.FormatFilename(filename); + m_Operation.pData = pData; + ShowStatus(IDS_STATUSMSG_RETRIEVINGLISTFILE, FZ_LOG_PROGRESS); + if (UsingMlsd()) + { + m_Operation.nOpState = LISTFILE_MLST; + cmd = L"MLST " + pData->path; + } + else + { + m_Operation.nOpState = LISTFILE_PWD; + cmd = L"PWD"; + } + if (!Send(cmd)) + { + error = TRUE; + } + break; + case LISTFILE_MLST: + retmsg = GetReply(); + code = GetReplyCode(); + if (IsMisleadingListResponse()) + { + ShowStatus(IDS_STATUSMSG_LISTFILESUCCESSFUL, FZ_LOG_PROGRESS); + num = 0; + } + else if (code != 2) + { + error = TRUE; + } + else + { + USES_CONVERSION; + int size = m_ListFile.GetLength(); + char *buffer = static_cast(nb_calloc(1, size + 1)); + memmove(buffer, (LPCSTR)m_ListFile, m_ListFile.GetLength()); + CFtpListResult * pListResult = new CFtpListResult(m_CurrentServer, &m_bUTF8, &m_nCodePage); + pListResult->InitIntern(GetIntern()); + pListResult->AddData(buffer, size); + if (GetOptionVal(OPTION_DEBUGSHOWLISTING)) + pListResult->SendToMessageLog(); + pData->direntry = pListResult->getList(num, true); + if (pListResult->m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_VMS && m_CurrentServer.nServerType & FZ_SERVERTYPE_FTP) + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS; + delete pListResult; + } + break; + case LISTFILE_PWD: + code = GetReplyCode(); + retmsg = GetReply(); + if ((code == 2) && + ParsePwdReply(retmsg, pData->pwd) && + Send(L"CWD " + pData->path)) + { + m_Operation.nOpState = LISTFILE_CWD; + } + else + { + error = TRUE; + } + break; + case LISTFILE_CWD: + code = GetReplyCode(); + pData->direntry = new t_directory::t_direntry[1]; + pData->direntry->name = pData->fileName; + if (code == 2) + { + pData->direntry->dir = TRUE; + if (Send(L"CWD " + pData->pwd.GetPath())) + { + m_Operation.nOpState = LISTFILE_CWD2; + } + else + { + error = TRUE; + } + } + else + { + // CWD failed, file is not a directory, we should not need to restore PWD. + + // Force binary mode, as according to RFC 6359, + // SIZE command returns size as transferred over the stream. + // Moreover ProFTPD does not even support SIZE command in ASCII mode + if (Send(L"TYPE I")) + { + m_Operation.nOpState = LISTFILE_TYPE; + } + else + { + error = TRUE; + } + } + break; + case LISTFILE_CWD2: + code = GetReplyCode(); + if (code == 2) + { + // No point trying SIZE on directories. + // (More over IIS returns a multi-line response for SIZE /dir and we cannot handle that). + // IIS fails even for MDTM, so skipping even that. + num = 1; + } + // this should never really happen + else + { + error = TRUE; + } + break; + case LISTFILE_TYPE: + // Do not really care if TYPE succeeded or not + if (Send(L"SIZE " + pData->path)) + { + m_Operation.nOpState = LISTFILE_SIZE; + } + else + { + error = TRUE; + } + break; + case LISTFILE_SIZE: + code = GetReplyCode(); + // Ignore SIZE errors for directories + if ((HandleSize(code, pData->direntry->size) || pData->direntry->dir) && + Send(L"MDTM " + pData->path)) + { + m_Operation.nOpState = LISTFILE_MDTM; + } + else + { + error = TRUE; + } + break; + case LISTFILE_MDTM: + code = GetReplyCode(); + if (HandleMdtm(code, pData->direntry->date)) + { + num = 1; + } + else + { + error = TRUE; + } + break; + default: + error = TRUE; + break; + } + + if (error) + { + ResetOperation(FZ_REPLY_ERROR); + } + else if (num >= 0) + { + t_directory * pDirectoryListing = new t_directory(); + pDirectoryListing->direntry = pData->direntry; + pData->direntry = NULL; + pDirectoryListing->num = num; + pDirectoryListing->server = m_CurrentServer; + pDirectoryListing->path.SetServer(pDirectoryListing->server); + pDirectoryListing->path = pData->dir; + ShowStatus(IDS_STATUSMSG_LISTFILESUCCESSFUL,FZ_LOG_PROGRESS); + // do not use SetDirectoryListing as that would make + // later operations believe that there's only this one file in the folder + m_pOwner->SendDirectoryListing(pDirectoryListing); + ResetOperation(FZ_REPLY_OK); + } +} + +void CFtpControlSocket::TransferEnd(int nMode) +{ + if (!m_Operation.nOpMode) + { + LogMessage(FZ_LOG_INFO, L"Ignoring old TransferEnd message"); + return; + } + m_LastRecvTime=CTime::GetCurrentTime(); + if (m_Operation.nOpMode&CSMODE_TRANSFER) + FileTransfer(0,TRUE,nMode&(CSMODE_TRANSFERERROR|CSMODE_TRANSFERTIMEOUT)); + else if (m_Operation.nOpMode&CSMODE_LIST) + List(TRUE,nMode&(CSMODE_TRANSFERERROR|CSMODE_TRANSFERTIMEOUT)); +} + +void CFtpControlSocket::OnClose(int nErrorCode) +{ + ShowStatus(IDS_STATUSMSG_DISCONNECTED, FZ_LOG_ERROR); + if (m_pTransferSocket) + { + m_pTransferSocket->OnClose(0); + m_pTransferSocket->Close(); + delete m_pTransferSocket; + m_pTransferSocket=0; + DoClose(); + return; + } + if (m_bDidRejectCertificate) + DoClose(FZ_REPLY_CANCEL); + else + DoClose(); +} + +void CFtpControlSocket::FileTransfer(t_transferfile *transferfile/*=0*/,BOOL bFinish/*=FALSE*/,int nError/*=0*/) +{ + USES_CONVERSION; + + #define FILETRANSFER_INIT -1 + #define FILETRANSFER_PWD 0 + #define FILETRANSFER_CWD 1 + #define FILETRANSFER_MKD 2 + #define FILETRANSFER_CWD2 3 + #define FILETRANSFER_PWD2 4 + #define FILETRANSFER_LIST_MODE 5 + #define FILETRANSFER_LIST_OPTS 6 + #define FILETRANSFER_LIST_PORTPASV 7 + #define FILETRANSFER_LIST_TYPE 8 + #define FILETRANSFER_LIST_LIST 9 + #define FILETRANSFER_LIST_WAITFINISH 10 + #define FILETRANSFER_NOLIST_SIZE 11 + #define FILETRANSFER_NOLIST_MDTM 12 + #define FILETRANSFER_TYPE 13 + #define FILETRANSFER_REST 14 + #define FILETRANSFER_MODE 15 + #define FILETRANSFER_OPTS 16 + #define FILETRANSFER_PORTPASV 17 + #define FILETRANSFER_RETRSTOR 18 + #define FILETRANSFER_WAITFINISH 19 + + #define FILETRANSFER_WAIT 20 + + #define FILETRANSFER_MFMT 21 + + //Partial flowchart of FileTransfer + // + // +----+ + // /------|Init|--------\ + // | +----+ | + // | | | + // | /---+ | + // | | | | + // | | +---+ | + // | | |PWD| | + // | | +---+ | + // | | | | + // | \---+ | + // | | | + // | +---+ | + // | |CWD|--\ | + // | +---+ | | + // | | | | + // | | | | + // | | +---+ | + // | | |MKD| | + // | | +---+ | + // | | | | + // | | | | + // | | +----+ | + // | | |CWD2| | + // | | +----+ | + // | | | | + // | +----/ | + // | | | + // | +---+ | + // +-------|PWD| | + // | +---+ | + // | | | + // | +----------/ + // | | + // | +---------+ + // | |LIST_TYPE| + // | +---------+ + // | | + // | | + // | +-------------+ + // | |LIST_PORTPASV| + // | +-------------+ + // | | + // | | + // | +---------+ + // | |LIST_LIST|-----\ //List fails, maybe folder is list protected + // | +---------+ | //Use SIZE and MDTM to get file information + // | | +----+ + // | | |SIZE| + // | | +----+ + // | | | + // | | +----+ + // | | |MDTM| + // | | +----+ + // | | | + // | | | + // | +---------------+ | + // | |LIST_WAITFINISH| | + // | +---------------+ | + // | | | + // | | | + // | +----------/ + // | | + // \---------+ + // | + // +----+ + // |TYPE| + // +----+ + // | + // | + // +--------+ + // |PORTPASV|--\ + // +--------+ | + // | | + // | | + // | +----+ + // | |REST| + // | +----+ + // | | + // +------/ + // | + // +--------+ + // |RETRSTOR| + // +--------+ + // | + // | + // +----------+ + // |WAITFINISH| + // +----------+ + + DebugAssert(!m_Operation.nOpMode || m_Operation.nOpMode&CSMODE_TRANSFER); + if (!m_pOwner->IsConnected()) + { + m_Operation.nOpMode=CSMODE_TRANSFER|(transferfile && transferfile->get?CSMODE_DOWNLOAD:CSMODE_UPLOAD); + ResetOperation(FZ_REPLY_ERROR|FZ_REPLY_DISCONNECTED); + return; + } + + CFileTransferData *pData=static_cast(m_Operation.pData); + + //Process finish and error messages + if (bFinish || nError) + { + DebugAssert(m_Operation.nOpMode&CSMODE_TRANSFER); + + // APPE failed, ignore this reply + if (m_Operation.nOpMode == FILETRANSFER_WAIT && bFinish) + return; + + if (!(m_Operation.nOpMode&CSMODE_TRANSFER)) + return; + + if (nError) + { + if (m_Operation.nOpState == FILETRANSFER_LIST_LIST && nError & CSMODE_TRANSFERERROR) + { //Don't abort operation, use fallback to SIZE and MDTM (when actual LIST reply comes in) + if (m_pTransferSocket) + m_pTransferSocket=0; + delete m_pDirectoryListing; + m_pDirectoryListing=0; + } + else if (nError&CSMODE_TRANSFERTIMEOUT) + DoClose(); + else + // we may get here when connection was closed, when the closure + // was first detected while reading/writing, + // when we abort file transfer with regular error, + // possibly preventing automatic reconnect + ResetOperation(FZ_REPLY_ERROR); + return; + } + if (m_Operation.nOpState <= FILETRANSFER_LIST_PORTPASV) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + else if (m_Operation.nOpState<=FILETRANSFER_LIST_WAITFINISH) + { + if (!m_pTransferSocket || m_pTransferSocket->m_bListening) + { + delete m_pDirectoryListing; + m_pDirectoryListing=0; + ResetOperation(FZ_REPLY_ERROR); + return; + } + + int num=0; + pData->pDirectoryListing=new t_directory; + if (GetOptionVal(OPTION_DEBUGSHOWLISTING)) + m_pTransferSocket->m_pListResult->SendToMessageLog(); + pData->pDirectoryListing->direntry=m_pTransferSocket->m_pListResult->getList(num, false); + pData->pDirectoryListing->num=num; + if (m_pTransferSocket->m_pListResult->m_server.nServerType&FZ_SERVERTYPE_SUB_FTP_VMS && m_CurrentServer.nServerType&FZ_SERVERTYPE_FTP) + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS; + pData->pDirectoryListing->server = m_CurrentServer; + pData->pDirectoryListing->path.SetServer(m_CurrentServer); + pData->pDirectoryListing->path = pData->transferfile.remotepath; + if (pData->rawpwd!=L"") + { + if (!pData->pDirectoryListing->path.SetPath(pData->rawpwd)) + { + delete m_pDirectoryListing; + m_pDirectoryListing=0; + delete m_pTransferSocket; + m_pTransferSocket=0; + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + else + pData->pDirectoryListing->path=pData->transferfile.remotepath; + + if (m_Operation.nOpState!=FILETRANSFER_LIST_WAITFINISH) + return; + } + else if (m_Operation.nOpState <= FILETRANSFER_PORTPASV) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + else if (m_Operation.nOpState<=FILETRANSFER_WAITFINISH) + { + if (m_pTransferSocket->m_bListening) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + pData->nGotTransferEndReply |= 2; + if (m_Operation.nOpState!=FILETRANSFER_WAITFINISH) + return; + else + { + delete m_pTransferSocket; + m_pTransferSocket=0; + } + } + } + + ////////////////// + //Initialization// + ////////////////// + int nReplyError = 0; + if (m_Operation.nOpState == FILETRANSFER_INIT) + { + DebugAssert(transferfile); + DebugAssert(!m_Operation.nOpMode); + DebugAssert(!m_Operation.pData); + + CString str; + str.Format(transferfile->get?IDS_STATUSMSG_DOWNLOADSTART:IDS_STATUSMSG_UPLOADSTART, + transferfile->get ? (LPCTSTR)transferfile->remotepath.FormatFilename(transferfile->remotefile) : (LPCTSTR)transferfile->localfile); + ShowStatus(str,FZ_LOG_STATUS); + + m_Operation.nOpMode=CSMODE_TRANSFER|(transferfile->get?CSMODE_DOWNLOAD:CSMODE_UPLOAD); + + m_Operation.pData=new CFileTransferData; + pData=static_cast(m_Operation.pData); + + if (GetOptionVal(OPTION_PROXYTYPE)!=PROXYTYPE_NOPROXY) + pData->bPasv = TRUE; + else if (m_CurrentServer.nPasv == 1) + pData->bPasv = TRUE; + else if (m_CurrentServer.nPasv == 2) + pData->bPasv = FALSE; + else + pData->bPasv = GetOptionVal(OPTION_PASV); + + //Replace invalid characters in the local filename + int pos=transferfile->localfile.ReverseFind(L'\\'); + for (int i=(pos+1);ilocalfile.GetLength();i++) + if (transferfile->localfile[i]==L':') + transferfile->localfile.SetAt(i, L'_'); + + pData->transferfile=*transferfile; + pData->transferdata.transfersize=pData->transferfile.size; + pData->transferdata.transferleft=pData->transferfile.size; + pData->transferdata.bResume = FALSE; + pData->transferdata.bResumeAppend = FALSE; + pData->transferdata.bType = (pData->transferfile.nType == 1) ? TRUE : FALSE; + + CServerPath path; + DebugCheck(m_pOwner->GetCurrentPath(path)); + if (path == pData->transferfile.remotepath) + { + if (m_pDirectoryListing) + { + m_Operation.nOpState=FILETRANSFER_TYPE; + CString remotefile=pData->transferfile.remotefile; + int i; + for (i=0; inum; i++) + { + if (m_pDirectoryListing->direntry[i].name==remotefile && + ( m_pDirectoryListing->direntry[i].bUnsure || m_pDirectoryListing->direntry[i].size==-1 )) + { + delete m_pDirectoryListing; + m_pDirectoryListing=0; + m_Operation.nOpState = NeedModeCommand() ? FILETRANSFER_LIST_MODE : (NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE); + break; + } + } + if (m_pDirectoryListing && i==m_pDirectoryListing->num) + { + nReplyError = CheckOverwriteFile(); + if (!nReplyError) + { + if (pData->transferfile.get) + { + CString path=pData->transferfile.localfile; + if (path.ReverseFind(L'\\')!=-1) + { + path=path.Left(path.ReverseFind(L'\\')+1); + CString path2; + while (path!=L"") + { + path2+=path.Left(path.Find( L"\\" )+1); + path=path.Mid(path.Find( L"\\" )+1); + CreateDirectory(path2, 0); + } + } + } + } + } + } + else + { + m_Operation.nOpState = NeedModeCommand() ? FILETRANSFER_LIST_MODE : (NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE); + } + } + else + { + if (path.IsEmpty()) + m_Operation.nOpState = FILETRANSFER_PWD; + else + m_Operation.nOpState = FILETRANSFER_CWD; + } + } + else + { + /////////// + //Replies// + /////////// + int code = GetReplyCode(); + switch(m_Operation.nOpState) + { + case FILETRANSFER_PWD: + if (code != 2 && code != 3) + { + nReplyError = FZ_REPLY_ERROR; + break; + } + + pData->rawpwd = GetReply(); + if ((m_mayBeMvsFilesystem || m_mayBeBS2000Filesystem) && m_CurrentServer.nServerType & FZ_SERVERTYPE_FTP && + pData->rawpwd[0] != L'/') + { + m_mayBeMvsFilesystem = false; + m_mayBeBS2000Filesystem = false; + + if (m_mayBeBS2000Filesystem) + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_BS2000; + else + m_CurrentServer.nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS; + + pData->transferfile.remotepath.SetServer(m_CurrentServer); + } + if (!ParsePwdReply(pData->rawpwd)) + return; + + if (m_pOwner->GetCurrentPath() == pData->transferfile.remotepath) + { + m_Operation.nOpState = NeedModeCommand() ? FILETRANSFER_LIST_MODE : (NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE); + } + else + m_Operation.nOpState = LIST_CWD; + break; + case FILETRANSFER_CWD: + if (code != 2 && code != 3) + if (pData->transferfile.get) + { + pData->bUseAbsolutePaths = TRUE; + m_Operation.nOpState = FILETRANSFER_NOLIST_SIZE; + } + else + m_Operation.nOpState = FILETRANSFER_MKD; + else + m_Operation.nOpState = FILETRANSFER_PWD2; + break; + case FILETRANSFER_MKD: + switch(pData->nMKDOpState) + { + case MKD_FINDPARENT: + { + if (code == 2 || code == 3) + { + m_pOwner->SetCurrentPath(pData->MKDCurrent); + + pData->nMKDOpState=MKD_CHANGETOSUBDIR; + pData->MKDCurrent.AddSubdir(pData->MKDSegments.front()); + CString Segment=pData->MKDSegments.front(); + pData->MKDSegments.pop_front(); + if (!Send( L"MKD " + Segment)) + return; + } + else + { + if (!pData->MKDCurrent.HasParent()) + nReplyError = FZ_REPLY_ERROR | ((code == 5) ? FZ_REPLY_CRITICALERROR : 0); + else + { + pData->MKDSegments.push_front(pData->MKDCurrent.GetLastSegment()); + pData->MKDCurrent=pData->MKDCurrent.GetParent(); + if (!Send(L"CWD "+pData->MKDCurrent.GetPath())) + return; + } + } + } + break; + case MKD_MAKESUBDIRS: + { + if (code == 2 || code == 3) + { //Create dir entry in parent dir + DebugAssert(!pData->MKDSegments.empty()); + pData->MKDCurrent.AddSubdir(pData->MKDSegments.front()); + CString Segment=pData->MKDSegments.front(); + pData->MKDSegments.pop_front(); + if (Send( L"MKD " + Segment)) + pData->nMKDOpState=MKD_CHANGETOSUBDIR; + else + return; + } + else + nReplyError=FZ_REPLY_ERROR; + } + break; + case MKD_CHANGETOSUBDIR: + { + if (code == 2 || code == 3 || //Creation successful + m_RecvBuffer.front() == "550 Directory already exists") //Creation was successful, although someone else did the work for us + { + CServerPath path2 = pData->MKDCurrent; + if (path2.HasParent()) + { + CString name=path2.GetLastSegment(); + path2=path2.GetParent(); + t_directory dir; + BOOL res=FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path==path2) + { + dir=*m_pDirectoryListing; + res=TRUE; + } + } + t_directory WorkingDir; + BOOL bFound=m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path==path2) + { + dir=WorkingDir; + res=TRUE; + } + if (!res) + { + dir.path=path2; + dir.server=m_CurrentServer; + } + + int i; + for (i=0; ipath==dir.path) + { + updated=TRUE; + SetDirectoryListing(&dir); + } + if (!updated) + if (WorkingDir.path==dir.path) + { + updated=TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + } + + //Continue operation even if MKD failed, maybe another thread did create this directory for us + if (pData->MKDSegments.empty()) + m_Operation.nOpState=FILETRANSFER_CWD2; + else + { + if (Send( L"CWD " + pData->MKDCurrent.GetPath())) + pData->nMKDOpState=MKD_MAKESUBDIRS; + else + return; + } + } + break; + default: + DebugFail(); + } + + break; + case FILETRANSFER_CWD2: + if (code != 2 && code != 3) + if (code == 4) + nReplyError=FZ_REPLY_ERROR; + else + nReplyError=FZ_REPLY_CRITICALERROR; + else + m_Operation.nOpState=FILETRANSFER_PWD2; + break; + case FILETRANSFER_PWD2: + if (code != 2 && code != 3) + nReplyError = FZ_REPLY_ERROR; + else + { + pData->rawpwd = GetReply(); + if (!ParsePwdReply(pData->rawpwd)) + return; + + if (m_pOwner->GetCurrentPath() != pData->transferfile.remotepath) + { + // More user-friendly message when the actual paths differ + if (m_pOwner->GetCurrentPath().GetPath() != pData->transferfile.remotepath.GetPath()) + { + LogMessage(FZ_LOG_WARNING, L"Real path and requested remote path do not match: \"%s\" \"%s\"", m_pOwner->GetCurrentPath().GetPath(), pData->transferfile.remotepath.GetPath()); + } + else + { + LogMessage(FZ_LOG_WARNING, L"Real path and requested remote path do not match: \"%s\" \"%s\"", m_pOwner->GetCurrentPath().GetSafePath(), pData->transferfile.remotepath.GetSafePath()); + } + nReplyError = FZ_REPLY_CRITICALERROR; + } + else + { + m_Operation.nOpState = NeedModeCommand() ? FILETRANSFER_LIST_MODE : (NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE); + } + } + break; + case FILETRANSFER_LIST_MODE: +#ifdef MPEXT_NO_ZLIB + DebugFail(); + m_Operation.nOpState = FILETRANSFER_LIST_TYPE; +#else + if (code == 2 || code == 3) + m_useZlib = !m_useZlib; + m_Operation.nOpState = NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE; +#endif + break; + case FILETRANSFER_LIST_OPTS: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + if (code == 2 || code == 3) + m_zlibLevel = pData->newZlibLevel; + m_Operation.nOpState = FILETRANSFER_LIST_TYPE; +#endif + break; + case FILETRANSFER_LIST_TYPE: + if (code != 2 && code != 3) + nReplyError = FZ_REPLY_ERROR; + else + m_Operation.nOpState = FILETRANSFER_LIST_PORTPASV; + break; + case FILETRANSFER_LIST_PORTPASV: + if (code!=3 && code!=2) + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError=FZ_REPLY_ERROR; + break; + } + + if (pData->bPasv) + { + CString reply = GetReply(); + int i,j; + // MP EXT + if((i=reply.Find(L"("))>=0&&(j=reply.Find(L")"))>=0) + { + i++; + j--; + } + else + { + // MP EXT + if ((i=reply.Mid(4).FindOneOf(L"0123456789"))>=0) + { + i += 4; + j = reply.GetLength() - 1; + } + else + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError = FZ_REPLY_ERROR; + break; + } + } + + CString temp = reply.Mid(i,(j-i)+1); + + if (GetFamily() == AF_INET) + { + int count=0; + int pos=0; + //Convert commas to dots + temp.Replace( L",", L"." ); + while(1) + { + pos=temp.Find(L".",pos); + if (pos!=-1) + count++; + else + break; + pos++; + } + if (count!=5) + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError = FZ_REPLY_ERROR; + break; + } + + i=temp.ReverseFind(L'.'); + pData->port=atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); //get ls byte of server socket + temp=temp.Left(i); + i=temp.ReverseFind(L'.'); + pData->port+=256*atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); // add ms byte to server socket + pData->host=temp.Left(i); + if (!CheckForcePasvIp(pData->host)) + { + nReplyError = FZ_REPLY_ERROR; + break; + } + } + else if (GetFamily() == AF_INET6) + { + temp = temp.Mid(3); + pData->port = atol( T2CA(temp.Left(temp.GetLength() - 1) ) ); + if (pData->port < 0 || pData->port > 65535) + { + LogMessage(FZ_LOG_WARNING, L"Port %u not valid", pData->port); + nReplyError = FZ_REPLY_ERROR; + break; + } + + unsigned int tmpPort; + if (!GetPeerName(pData->host, tmpPort)) + { + LogMessage(FZ_LOG_WARNING, L"GetPeerName failed"); + nReplyError = FZ_REPLY_ERROR; + break; + } + } + + m_pTransferSocket = new CTransferSocket(this, CSMODE_LIST); +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pTransferSocket->InitZlib(m_zlibLevel)) + { + ShowStatus(L"Failed to initialize zlib", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + } +#endif +#ifndef MPEXT_NO_GSS + if (m_pGssLayer && m_pGssLayer->AuthSuccessful()) + m_pTransferSocket->UseGSS(m_pGssLayer); +#endif + m_pTransferSocket->m_nInternalMessageID = m_pOwner->m_nInternalMessageID; + m_pTransferSocket->SetFamily(GetFamily()); + if (!m_pTransferSocket->Create(m_pSslLayer && m_bProtP)) + { + nReplyError = FZ_REPLY_ERROR; + break; + } + + DebugCheck(m_pTransferSocket->AsyncSelect()); + } + m_Operation.nOpState=FILETRANSFER_LIST_LIST; + break; + case FILETRANSFER_LIST_LIST: + if (IsMisleadingListResponse()) + { + ShowStatus(IDS_STATUSMSG_DIRLISTSUCCESSFUL, FZ_LOG_PROGRESS); + + t_directory listing; + listing.server = m_CurrentServer; + listing.path.SetServer(m_CurrentServer); + if (pData->rawpwd != L"") + { + if (!listing.path.SetPath(pData->rawpwd)) + { + delete m_pDirectoryListing; + m_pDirectoryListing = 0; + delete m_pTransferSocket; + m_pTransferSocket = 0; + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + else + listing.path = pData->transferfile.remotepath; + + SetDirectoryListing(&listing); + + m_Operation.nOpState = FILETRANSFER_TYPE; + delete m_pTransferSocket; + m_pTransferSocket = 0; + + nReplyError = CheckOverwriteFile(); + if (!nReplyError) + { + if (pData->transferfile.get) + { + CString path=pData->transferfile.localfile; + if (path.ReverseFind(L'\\')!=-1) + { + path=path.Left(path.ReverseFind(L'\\')+1); + CString path2; + while (path!=L"") + { + path2+=path.Left(path.Find( L"\\" )+1); + path=path.Mid(path.Find( L"\\" )+1); + CreateDirectory(path2, 0); + } + } + } + } + } + else if (code==4 || code==5) //LIST failed, try getting file information using SIZE and MDTM + { + TransferHandleListError(); + } + else if (code!=1) + nReplyError=FZ_REPLY_ERROR; + else + m_Operation.nOpState=FILETRANSFER_LIST_WAITFINISH; + break; + case FILETRANSFER_LIST_WAITFINISH: + if (!bFinish) + { + if (code!=2 && code!=3) + { + if (code==4 || code==5) + { + TransferHandleListError(); + } + else + { + nReplyError=FZ_REPLY_ERROR; + } + } + else + pData->nGotTransferEndReply = 1; + } + if (pData->nGotTransferEndReply && pData->pDirectoryListing) + { + SetDirectoryListing(pData->pDirectoryListing); + delete m_pTransferSocket; + m_pTransferSocket=0; + m_Operation.nOpState=FILETRANSFER_TYPE; + nReplyError = CheckOverwriteFile(); + if (!nReplyError) + { + if (pData->transferfile.get) + { + CString path=pData->transferfile.localfile; + if (path.ReverseFind(L'\\')!=-1) + { + path=path.Left(path.ReverseFind(L'\\')+1); + CString path2; + while (path!=L"") + { + path2+=path.Left(path.Find( L"\\" )+1); + path=path.Mid(path.Find( L"\\" )+1); + CreateDirectory(path2, 0); + } + } + } + } + pData->nGotTransferEndReply=0; + } + break; + case FILETRANSFER_NOLIST_SIZE: + { + __int64 size; + if (HandleSize(code, size)) + { + DebugAssert(!pData->pFileSize); + pData->pFileSize=new _int64; + *pData->pFileSize=size; + } + } + m_Operation.nOpState=FILETRANSFER_NOLIST_MDTM; + break; + case FILETRANSFER_NOLIST_MDTM: + if (HandleMdtm(code, pData->remoteDate)) + { + pData->hasRemoteDate = true; + } + m_Operation.nOpState=FILETRANSFER_TYPE; + nReplyError=CheckOverwriteFile(); + break; + case FILETRANSFER_TYPE: + if (code!=2 && code!=3) + nReplyError = FZ_REPLY_ERROR; + m_Operation.nOpState = NeedModeCommand() ? FILETRANSFER_MODE : (NeedOptsCommand() ? FILETRANSFER_OPTS : FILETRANSFER_PORTPASV); + break; + case FILETRANSFER_WAIT: + if (!pData->nWaitNextOpState) + nReplyError=FZ_REPLY_ERROR; + else + m_Operation.nOpState=pData->nWaitNextOpState; + break; + case FILETRANSFER_MODE: +#ifdef MPEXT_NO_ZLIB + DebugFail(); + m_Operation.nOpState = FILETRANSFER_PORTPASV; +#else + if (code == 2 || code == 3) + m_useZlib = !m_useZlib; + m_Operation.nOpState = NeedOptsCommand() ? FILETRANSFER_OPTS : FILETRANSFER_PORTPASV; +#endif + break; + case FILETRANSFER_OPTS: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + if (code == 2 || code == 3) + m_zlibLevel = pData->newZlibLevel; +#endif + m_Operation.nOpState = FILETRANSFER_PORTPASV; + break; + case FILETRANSFER_PORTPASV: + if (code == 3 || code == 2) + { + if (pData->bPasv) + { + CString reply = GetReply(); + int i,j; + // MP EXT + if((i=reply.Find(L"("))>=0&&(j=reply.Find(L")"))>=0) + { + i++; + j--; + } + else + { + // MP EXT + if ((i=reply.Mid(4).FindOneOf(L"0123456789"))>=0) + { + i += 4; + j = reply.GetLength() - 1; + } + else + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError = FZ_REPLY_ERROR; + break; + } + } + + CString temp = reply.Mid(i,(j-i)+1); + + if (GetFamily() == AF_INET) + { + int count=0; + int pos=0; + //Convert commas to dots + temp.Replace( L",", L"." ); + while(1) + { + pos=temp.Find( L".", pos); + if (pos!=-1) + count++; + else + break; + pos++; + } + if (count!=5) + { + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError = FZ_REPLY_ERROR; + break; + } + + i=temp.ReverseFind(L'.'); + pData->port=atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); //get ls byte of server socket + temp=temp.Left(i); + i=temp.ReverseFind(L'.'); + pData->port+=256*atol( T2CA( temp.Right(temp.GetLength()-(i+1)) ) ); // add ms byte to server socket + pData->host=temp.Left(i); + if (!CheckForcePasvIp(pData->host)) + { + nReplyError = FZ_REPLY_ERROR; + break; + } + } + else if (GetFamily() == AF_INET6) + { + temp = temp.Mid(3); + pData->port = atol( T2CA(temp.Left(temp.GetLength() - 1) ) ); + if (pData->port < 0 || pData->port > 65535) + { + LogMessage(FZ_LOG_WARNING, L"Port %u not valid", pData->port); + nReplyError = FZ_REPLY_ERROR; + break; + } + + unsigned int tmpPort; + if (!GetPeerName(pData->host, tmpPort)) + { + LogMessage(FZ_LOG_WARNING, L"GetPeerName failed"); + nReplyError = FZ_REPLY_ERROR; + break; + } + } + else + { + nReplyError = FZ_REPLY_ERROR; + break; + } + m_pTransferSocket = new CTransferSocket(this, m_Operation.nOpMode); +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pTransferSocket->InitZlib(m_zlibLevel)) + { + ShowStatus(L"Failed to initialize zlib", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + } +#endif +#ifndef MPEXT_NO_GSS + if (m_pGssLayer && m_pGssLayer->AuthSuccessful()) + m_pTransferSocket->UseGSS(m_pGssLayer); +#endif + m_pTransferSocket->m_nInternalMessageID = m_pOwner->m_nInternalMessageID; + m_pTransferSocket->SetFamily(GetFamily()); + if (!m_pTransferSocket->Create(m_pSslLayer && m_bProtP)) + { + nReplyError = FZ_REPLY_ERROR; + break; + } + + DebugCheck(m_pTransferSocket->AsyncSelect()); + } + + if (pData->transferdata.bResume) + m_Operation.nOpState = FILETRANSFER_REST; + else + m_Operation.nOpState = FILETRANSFER_RETRSTOR; + BOOL res = FALSE; + if (!m_pDataFile) + { + if (pData->transferdata.localFileHandle!=INVALID_HANDLE_VALUE) + { + m_pDataFile = new CFile(pData->transferdata.localFileHandle); + m_pDataFile->SetCloseOnDelete(TRUE); + } + else + { + m_pDataFile = new CFile(); + } + } + if (pData->transferfile.get) + { + if (pData->transferdata.bResume && pData->transferdata.localFileHandle==INVALID_HANDLE_VALUE) + res = m_pDataFile->Open(pData->transferfile.localfile,CFile::modeCreate|CFile::modeWrite|CFile::modeNoTruncate|CFile::shareDenyWrite); + else if (pData->transferdata.localFileHandle==INVALID_HANDLE_VALUE) + res = m_pDataFile->Open(pData->transferfile.localfile,CFile::modeWrite|CFile::modeCreate|CFile::shareDenyWrite); + else + res = TRUE; + } + else + res = m_pDataFile->Open(pData->transferfile.localfile,CFile::modeRead|CFile::shareDenyNone); + if (!res) + { + wchar_t * Error = m_pTools->LastSysErrorMessage(); + //Error opening the file + CString str; + str.Format(IDS_ERRORMSG_FILEOPENFAILED,pData->transferfile.localfile); + str += L"\n"; + str += Error; + free(Error); + ShowStatus(str,FZ_LOG_ERROR); + nReplyError = FZ_REPLY_ERROR; + break; + } + + if (!m_pTransferSocket) + { + nReplyError=FZ_REPLY_ERROR; + break; + } + + m_pTransferSocket->m_pFile = m_pDataFile; + if (!pData->transferfile.get) + { + // See comment in !get branch below + pData->transferdata.transfersize=GetLength64(*m_pDataFile); + pData->transferdata.transferleft=pData->transferdata.transfersize; + if (pData->transferdata.bResume) + { + CString remotefile=pData->transferfile.remotefile; + if (m_pDirectoryListing) + for (int i = 0; i < m_pDirectoryListing->num; i++) + { + if (m_pDirectoryListing->direntry[i].name == remotefile) + { + pData->transferdata.transferleft -= m_pDirectoryListing->direntry[i].size; + break; + } + } + _int64 size = pData->transferdata.transfersize-pData->transferdata.transferleft; + LONG low = static_cast(size&0xFFFFFFFF); + LONG high = static_cast(size>>32); + if (SetFilePointer((HANDLE)m_pDataFile->m_hFile, low, &high, FILE_BEGIN)==0xFFFFFFFF && GetLastError()!=NO_ERROR) + { + ShowStatus(IDS_ERRORMSG_SETFILEPOINTER, FZ_LOG_ERROR); + nReplyError = FZ_REPLY_ERROR; + } + } + } + else + { + // Resetting transfersize here is pointless as we + // always provide valid size in call to FileTransfer. + // We unnecessary reply on the file being in the directory listing. + pData->transferdata.transfersize=-1; + CString remotefile=pData->transferfile.remotefile; + if (m_pDirectoryListing) + for (int i=0; inum; i++) + { + if (m_pDirectoryListing->direntry[i].name==remotefile) + { + pData->hasRemoteDate = true; + pData->remoteDate = m_pDirectoryListing->direntry[i].date; + pData->transferdata.transfersize=m_pDirectoryListing->direntry[i].size; + } + } + else if (pData->pFileSize) + pData->transferdata.transfersize=*pData->pFileSize; + pData->transferdata.transferleft=pData->transferdata.transfersize; + } + } + else + if (!pData->bTriedPortPasvOnce) + { + pData->bTriedPortPasvOnce = TRUE; + pData->bPasv = !pData->bPasv; + } + else + nReplyError = FZ_REPLY_ERROR; + break; + case FILETRANSFER_REST: + { //Resume + if (code==3 || code==2) + { + LONG high = 0; + if (pData->transferfile.get) + { + pData->transferdata.transferleft = pData->transferdata.transfersize - GetLength64(*m_pDataFile); + if (SetFilePointer((HANDLE)m_pDataFile->m_hFile, 0, &high, FILE_END)==0xFFFFFFFF && GetLastError()!=NO_ERROR) + { + ShowStatus(IDS_ERRORMSG_SETFILEPOINTER, FZ_LOG_ERROR); + nReplyError = FZ_REPLY_ERROR; + } + else + m_Operation.nOpState = FILETRANSFER_RETRSTOR; + } + else + { + m_Operation.nOpState = FILETRANSFER_RETRSTOR; + } + } + else + { + if (code==5 && GetReply()[1]==L'0') + { + if (pData->transferfile.get) + { + if (pData->transferdata.transfersize!=-1) + { + DebugAssert(m_pDataFile); + if (GetLength64(*m_pDataFile) == pData->transferdata.transfersize) + { + ShowStatus(IDS_ERRORMSG_CANTRESUME_FINISH, FZ_LOG_STATUS); + ResetOperation(FZ_REPLY_OK); + return; + } + } + + ShowStatus(IDS_ERRORMSG_CANTRESUME, FZ_LOG_ERROR); + pData->transferdata.transferleft=pData->transferdata.transfersize; + pData->transferdata.bResume=FALSE; + m_Operation.nOpState=FILETRANSFER_RETRSTOR; + } + else + { + ShowStatus(L"Resume command not supported by server, trying append.", FZ_LOG_PROGRESS); + pData->transferdata.bResumeAppend=TRUE; + m_Operation.nOpState=FILETRANSFER_RETRSTOR; + } + } + else + nReplyError=FZ_REPLY_ERROR; + } + } + break; + case FILETRANSFER_RETRSTOR: + // A '1xy opening data connection' reply is expected if RETR/STOR/APPE + // is successful. + // On failure, it's a 4xy or 5xy reply. + // However, some servers send a 2xy transfer complete reply without opening a data + // connection if there's no data to send. + if (code==2) + { + //Transfer successful, however server did not open data connection + ResetOperation(FZ_REPLY_OK); + return; + } + else if (code!=1) + { + if (!pData->transferfile.get && pData->transferdata.bResume && pData->askOnResumeFail) + { + pData->askOnResumeFail = false; + delete m_pTransferSocket; + m_pTransferSocket = 0; + delete m_pDataFile; + m_pDataFile = 0; + pData->nGotTransferEndReply = 0; + nReplyError = CheckOverwriteFile(); + } + else + { + nReplyError = FZ_REPLY_ERROR; + if (code == 5) + nReplyError |= FZ_REPLY_CRITICALERROR; + } + } + else + { + m_Operation.nOpState=FILETRANSFER_WAITFINISH; + + //Look if we can find any information about the resume offset + if (!pData->transferfile.get && pData->transferdata.bResumeAppend) + { + _int64 nOffset = -1; + CString reply = GetReply(); + reply.MakeLower(); + int pos = reply.Find(L"restarting at offset "); + if (pos != -1) + pos += _tcslen(L"restarting at offset "); + + reply = reply.Mid(pos); + + int i; + for (i=0; i L'9') + break; + } + if (i == reply.GetLength()) + nOffset = _ttoi64(reply); + if (nOffset != -1 && m_pDataFile) + { + LONG low = 0; + LONG high = 0; + if (nOffset >= GetLength64(*m_pDataFile)) + { + if (SetFilePointer((HANDLE)m_pDataFile->m_hFile, 0, &high, FILE_END)==0xFFFFFFFF && GetLastError()!=NO_ERROR) + { + ShowStatus(IDS_ERRORMSG_SETFILEPOINTER, FZ_LOG_ERROR); + nReplyError = FZ_REPLY_ERROR; + } + } + else + { + low=static_cast(nOffset&0xFFFFFFFF); + high=static_cast(nOffset>>32); + if (SetFilePointer((HANDLE)m_pDataFile->m_hFile, low, &high, FILE_BEGIN)==0xFFFFFFFF && GetLastError()!=NO_ERROR) + { + ShowStatus(IDS_ERRORMSG_SETFILEPOINTER, FZ_LOG_ERROR); + nReplyError = FZ_REPLY_ERROR; + } + } + } + if (!nReplyError && !GetOptionVal(OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY)) + { + m_pTransferSocket->SetActive(); + } + } + else if (pData->bPasv && !GetOptionVal(OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY)) + { + m_pTransferSocket->SetActive(); + } + } + break; + case FILETRANSFER_WAITFINISH: + if (!bFinish) + { + if (code == 1) + { + /* Some non-rfc959 compatible servers send more than one code 1yz reply, especially if using APPE. + * Just ignore the additional ones. + */ + LogMessage(FZ_LOG_WARNING, L"Server sent more than one code 1yz reply, ignoring additional reply"); + break; + } + else if (code!=2 && code!=3) + nReplyError = FZ_REPLY_ERROR; + else + { + pData->nGotTransferEndReply |= 1; + } + } + if (pData->nGotTransferEndReply==3) + { + // Not really sure about a reason for the m_pDataFile condition here + TransferFinished(m_pDataFile != NULL); + return; + } + break; + case FILETRANSFER_MFMT: + //Transfer successful + ResetOperation(FZ_REPLY_OK); + break; + } + if (nReplyError) + { //Error transferring the file + ResetOperation(nReplyError); + return; + } + } + ///////////////// + //Send commands// + ///////////////// + BOOL bError=FALSE; + switch(m_Operation.nOpState) + { + case FILETRANSFER_PWD: + if (!Send(L"PWD")) + bError = TRUE; + break; + case FILETRANSFER_CWD: + if (!Send(L"CWD "+pData->transferfile.remotepath.GetPath())) + bError=TRUE; + break; + case FILETRANSFER_MKD: + if (pData->nMKDOpState==MKD_INIT) + { + if (!pData->transferfile.remotepath.HasParent()) + { + LogMessage(FZ_LOG_WARNING, L"Can't create root dir"); + ResetOperation(FZ_REPLY_CRITICALERROR); + return; + } + if (!Send(L"CWD "+pData->transferfile.remotepath.GetParent().GetPath())) + bError=TRUE; + pData->MKDCurrent=pData->transferfile.remotepath.GetParent(); + pData->MKDSegments.push_front(pData->transferfile.remotepath.GetLastSegment()); + pData->nMKDOpState=MKD_FINDPARENT; + } + break; + case FILETRANSFER_CWD2: + if (!Send(L"CWD "+pData->transferfile.remotepath.GetPath())) + bError=TRUE; + break; + case FILETRANSFER_PWD2: + if (!Send(L"PWD")) + bError=TRUE; + break; + case FILETRANSFER_LIST_MODE: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + if (m_useZlib) + { + if (!Send(L"MODE S")) + bError = TRUE; + } + else + if (!Send(L"MODE Z")) + bError = TRUE; +#endif + break; + case FILETRANSFER_LIST_OPTS: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + { + pData->newZlibLevel = GetOptionVal(OPTION_MODEZ_LEVEL); + CString str; + str.Format(L"OPTS MODE Z LEVEL %d", pData->newZlibLevel); + if (!Send(str)) + bError = TRUE; + } +#endif + break; + case FILETRANSFER_LIST_PORTPASV: + delete m_pDirectoryListing; + m_pDirectoryListing=0; + if (pData->bPasv) + { + if (!Send((GetFamily() == AF_INET) ? L"PASV" : L"EPSV")) + bError=TRUE; + } + else + { + if (m_pTransferSocket) + { + delete m_pTransferSocket; + } + m_pTransferSocket = new CTransferSocket(this, CSMODE_LIST); +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pTransferSocket->InitZlib(m_zlibLevel)) + { + ShowStatus(L"Failed to initialize zlib", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + } +#endif +#ifndef MPEXT_NO_GSS + if (m_pGssLayer && m_pGssLayer->AuthSuccessful()) + m_pTransferSocket->UseGSS(m_pGssLayer); +#endif + m_pTransferSocket->m_nInternalMessageID = m_pOwner->m_nInternalMessageID; + m_pTransferSocket->m_bListening = TRUE; + m_pTransferSocket->SetFamily(GetFamily()); + if(!m_pTransferSocket->Create(m_pSslLayer && m_bProtP) || !m_pTransferSocket->AsyncSelect()) + bError=TRUE; + else if (m_pProxyLayer) + { + SOCKADDR_IN addr; + int len=sizeof(addr); + if (!m_pProxyLayer->GetPeerName((SOCKADDR *)&addr,&len)) + { + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + bError=TRUE; + } + else if (!m_pTransferSocket->Listen(addr.sin_addr.S_un.S_addr)) + { + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + bError=TRUE; + } + //Don't send PORT command yet, params are unknown. + //will be sent in TransfersocketListenFinished + } + else + { + //Set up an active file transfer + CString temp; + UINT nPort; + + if (//create listen socket (let Windows choose the port) & start listening + !m_pTransferSocket->Listen() || + !m_pTransferSocket->GetSockName(temp, nPort)) + { + bError = TRUE; + break; + } + + CString host; + if (GetFamily() == AF_INET) + { + host = GetOption(OPTION_TRANSFERIP); + if (host != L"") + { + DWORD ip = inet_addr(T2CA(host)); + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + { + hostent *fullname = gethostbyname(T2CA(host)); + if (!fullname) + host = L""; + else + { + DWORD ip = ((LPIN_ADDR)fullname->h_addr)->s_addr; + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + host = L""; + } + } + } + if (host == L"") + { + UINT temp; + + if (!GetSockName(host, temp)) + { + bError = true; + break; + } + + host.Replace(L'.', L','); + } + + if (!bError) + { + host.Format(host+L",%d,%d", nPort/256, nPort%256); + if (!Send(L"PORT " + host)) // send PORT cmd to server + bError = TRUE; + } + } + else if (GetFamily() == AF_INET6) + { + host = GetOption(OPTION_TRANSFERIP6); + if (host != L"") + { + USES_CONVERSION; + addrinfo hints, *res; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + if (p_getaddrinfo && !p_getaddrinfo(T2CA(host), "1024", &hints, &res)) + { + host = Inet6AddrToString(((SOCKADDR_IN6 *)res->ai_addr)->sin6_addr); + if (p_freeaddrinfo) p_freeaddrinfo(res); + } + else + host = L""; + } + if (host == L"") + { + UINT temp; + + if(!GetSockName(host, temp)) + bError = true; + } + + if (!bError) + { + // assamble EPRT command + CString cmd; + cmd.Format(L"EPRT |2|" + host + L"|%d|", nPort); + if (!Send(cmd)) + bError = TRUE; + } + } + else + { + LogMessage(FZ_LOG_WARNING, L"Protocol %d not supported", GetFamily()); + bError = true; + } + } + } + break; + case FILETRANSFER_LIST_TYPE: + if (!Send(L"TYPE A")) + bError=TRUE; + break; + case FILETRANSFER_LIST_LIST: + { + if (!m_pTransferSocket) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + + m_pTransferSocket->SetActive(); + CString cmd = GetListingCmd(); + if(!Send(cmd)) + bError=TRUE; + else if(pData->bPasv) + { + // if PASV create the socket & initiate outbound data channel connection + if (!m_pTransferSocket->Connect(pData->host,pData->port)) + { + if (GetLastError()!=WSAEWOULDBLOCK) + { + bError=TRUE; + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + } + } + } + } + break; + case FILETRANSFER_NOLIST_SIZE: + { + CString command = L"SIZE "; + command += pData->transferfile.remotepath.FormatFilename(pData->transferfile.remotefile, !pData->bUseAbsolutePaths); + + if (!Send(command)) + bError=TRUE; + } + break; + case FILETRANSFER_NOLIST_MDTM: + { + CString command = L"MDTM "; + command += pData->transferfile.remotepath.FormatFilename(pData->transferfile.remotefile, !pData->bUseAbsolutePaths); + + if (!Send(command)) + bError=TRUE; + } + break; + case FILETRANSFER_TYPE: + if (pData->transferfile.nType==1) + { + if (!Send(L"TYPE A")) + bError=TRUE; + } + else + if (!Send(L"TYPE I")) + bError=TRUE; + break; + case FILETRANSFER_MODE: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + if (m_useZlib) + { + if (!Send(L"MODE S")) + bError = TRUE; + } + else + if (!Send(L"MODE Z")) + bError = TRUE; +#endif + break; + case FILETRANSFER_OPTS: +#ifdef MPEXT_NO_ZLIB + DebugFail(); +#else + { + pData->newZlibLevel = GetOptionVal(OPTION_MODEZ_LEVEL); + CString str; + str.Format(L"OPTS MODE Z LEVEL %d", pData->newZlibLevel); + if (!Send(str)) + bError = TRUE; + } +#endif + break; + case FILETRANSFER_PORTPASV: + if (pData->bPasv) + { + if (!Send((GetFamily() == AF_INET) ? L"PASV" : L"EPSV")) + bError=TRUE; + } + else + { + if (m_pTransferSocket) + { + delete m_pTransferSocket; + } + m_pTransferSocket=new CTransferSocket(this, m_Operation.nOpMode); +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pTransferSocket->InitZlib(m_zlibLevel)) + { + ShowStatus(L"Failed to initialize zlib", FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + return; + } + } +#endif +#ifndef MPEXT_NO_GSS + if (m_pGssLayer && m_pGssLayer->AuthSuccessful()) + m_pTransferSocket->UseGSS(m_pGssLayer); +#endif + m_pTransferSocket->m_nInternalMessageID=m_pOwner->m_nInternalMessageID; + m_pTransferSocket->m_bListening = TRUE; + m_pTransferSocket->SetFamily(GetFamily()); + if(!m_pTransferSocket->Create(m_pSslLayer && m_bProtP) || !m_pTransferSocket->AsyncSelect()) + bError = TRUE; + else if (m_pProxyLayer) + { + SOCKADDR_IN addr; + int len=sizeof(addr); + if (!m_pProxyLayer->GetPeerName((SOCKADDR *)&addr,&len)) + { + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + bError=TRUE; + } + else if (!m_pTransferSocket->Listen(addr.sin_addr.S_un.S_addr)) + { + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + bError=TRUE; + } + //Don't send PORT command yet, params are unknown. + //will be sent in TransfersocketListenFinished + } + else + { + //Set up an active file transfer + + CString temp; + UINT nPort; + if (//create listen socket (let Windows choose the port) & start listening + !m_pTransferSocket->Listen() || + !m_pTransferSocket->GetSockName(temp, nPort)) + { + bError = TRUE; + break; + } + + CString host; + if (GetFamily() == AF_INET) + { + host = GetOption(OPTION_TRANSFERIP); + if (host != L"") + { + DWORD ip = inet_addr(T2CA(host)); + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + { + hostent *fullname = gethostbyname(T2CA(host)); + if (!fullname) + host = L""; + else + { + DWORD ip = ((LPIN_ADDR)fullname->h_addr)->s_addr; + if (ip != INADDR_NONE) + host.Format(L"%d,%d,%d,%d", ip%256, (ip>>8)%256, (ip>>16)%256, ip>>24); + else + host = L""; + } + } + } + if (host == L"") + { + UINT temp; + + if (!GetSockName(host, temp)) + bError = true; + + host.Replace(L'.', L','); + } + + if (!bError) + { + host.Format(host+L",%d,%d", nPort/256, nPort%256); + if (!Send(L"PORT " + host)) // send PORT cmd to server + bError = TRUE; + } + } + else if (GetFamily() == AF_INET6) + { + host = GetOption(OPTION_TRANSFERIP6); + if (host != L"") + { + USES_CONVERSION; + addrinfo hints, *res; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + if (p_getaddrinfo && !p_getaddrinfo(T2CA(host), "1024", &hints, &res)) + { + host = Inet6AddrToString(((SOCKADDR_IN6 *)res->ai_addr)->sin6_addr); + if (p_freeaddrinfo) p_freeaddrinfo(res); + } + else + host = L""; + } + if (host == L"") + { + UINT temp; + + if(!GetSockName(host, temp)) + bError = true; + } + + if (!bError) + { + // assamble EPRT command + CString cmd; + cmd.Format(L"EPRT |2|" + host + L"|%d|", nPort); + if (!Send(cmd)) + bError = TRUE; + } + } + else + { + LogMessage(FZ_LOG_WARNING, L"Protocol %d not supported", GetFamily()); + bError = true; + } + + if (bError) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + } + } + break; + case FILETRANSFER_REST: + DebugAssert(m_pDataFile); + { + CString command; + __int64 transferoffset = + pData->transferfile.get ? + GetLength64(*m_pDataFile) : + pData->transferdata.transfersize-pData->transferdata.transferleft; + command.Format(L"REST %I64d", transferoffset); + if (!Send(command)) + bError=TRUE; + } + break; + case FILETRANSFER_RETRSTOR: + // send RETR/STOR command to server + if (!m_pTransferSocket) + { + ResetOperation(FZ_REPLY_ERROR); + return; + } + m_pTransferSocket->m_transferdata=pData->transferdata; + // not sure what happens when we active transfer socket immediately while resuming + // it can possibly make transfer socket start reading from a file before a file pointer is advanced + if (GetOptionVal(OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY) || + ((pData->transferfile.get || !pData->transferdata.bResume) && !pData->bPasv)) + { + m_pTransferSocket->SetActive(); + } + CString filename; + + filename = pData->transferfile.remotepath.FormatFilename(pData->transferfile.remotefile, !pData->bUseAbsolutePaths); + if(!Send((pData->transferfile.get?L"RETR ":(pData->transferdata.bResumeAppend)?L"APPE ":L"STOR ")+ filename)) + bError = TRUE; + else + { + if (pData->bPasv) + {// if PASV create the socket & initiate outbound data channel connection + if (!m_pTransferSocket->Connect(pData->host,pData->port)) + { + if (GetLastError()!=WSAEWOULDBLOCK) + bError=TRUE; + } + } + } + break; + } + if (bError) + { //Error transferring the file + ResetOperation(FZ_REPLY_ERROR); + return; + } +} + +void CFtpControlSocket::TransferHandleListError() +{ + if (m_pTransferSocket) + delete m_pTransferSocket; + m_pTransferSocket=0; + m_Operation.nOpState = FILETRANSFER_NOLIST_SIZE; +} + +static int atoui(const wchar_t * s) +{ + wchar_t * endptr; + int result = wcstol(s, &endptr, 10); + if ((*s == L'\0') || (*endptr != L'\0')) + { + result = -1; + } + return result; +} + +bool CFtpControlSocket::HandleMdtm(int code, t_directory::t_direntry::t_date & date) +{ + bool result = false; + if (code==2) + { + CString line = GetReply(); + if ( line.GetLength()>4 && line.Left(4) == L"213 " ) + { + int y=0, M=0, d=0, h=0, m=0, s=0; + bool hasseconds = false; + line=line.Mid(4); + y=atoui(line.Left(4)); + if ((y >= 0) && (line.GetLength() > 4)) + { + line=line.Mid(4); + M=atoui(line.Left(2)); + if ((M >= 0) && (line.GetLength() > 2)) + { + line=line.Mid(2); + d=atoui(line.Left(2)); + if ((d >= 0) && (line.GetLength() > 2)) + { + line=line.Mid(2); + h=atoui(line.Left(2)); + if ((h >= 0) && (line.GetLength() > 2)) + { + line=line.Mid(2); + m=atoui(line.Left(2)); + if ((m >= 0) && (line.GetLength() > 2)) + { + line=line.Mid(2); + s=_ttoi(line.Left(2)); + hasseconds = true; + } + } + } + if (M>0 && M<=12 && d>0 && d<=31 && h>=0 && h<24 && m>=0 && m<60 && s>=0 && s<60) + { + result = true; + date.year = y; + date.month = M; + date.day = d; + date.hour = h; + date.minute = m; + date.second = s; + date.hastime = true; + date.hasseconds = hasseconds; + date.hasdate = true; + date.utc = true; + } + } + } + } + } + return result; +} + +bool CFtpControlSocket::HandleSize(int code, __int64 & size) +{ + bool result = false; + if (code == 2) + { + CString line = GetReply(); + if ((line.GetLength() > 4) && (line.Left(4) == L"213 ")) + { + size = _ttoi64(line.Mid(4)); + result = true; + } + } + return result; +} + +void CFtpControlSocket::TransferFinished(bool preserveFileTimeForUploads) +{ + CFileTransferData *pData=static_cast(m_Operation.pData); + + if (GetOptionVal(OPTION_PRESERVEDOWNLOADFILETIME) && m_pDataFile && + pData->transferfile.get) + { + m_pTools->PreserveDownloadFileTime( + (HANDLE)m_pDataFile->m_hFile, reinterpret_cast(pData->transferfile.nUserData)); + } + if (!pData->transferfile.get && + GetOptionVal(OPTION_MPEXT_PRESERVEUPLOADFILETIME) && preserveFileTimeForUploads && + ((m_serverCapabilities.GetCapability(mfmt_command) == yes) || + (m_serverCapabilities.GetCapability(mdtm_command) == yes))) + { + CString filename = + pData->transferfile.remotepath.FormatFilename(pData->transferfile.remotefile, !pData->bUseAbsolutePaths); + struct tm tm; + if (m_pTools->GetFileModificationTimeInUtc((LPCTSTR)pData->transferfile.localfile, tm)) + { + CString timestr; + timestr.Format(L"%02d%02d%02d%02d%02d%02d", + 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + CString command; + if (m_serverCapabilities.GetCapability(mfmt_command) == yes) + { + command = L"MFMT"; + } + else + { + // Support for MDTM does not necessarily mean + // that the server supportsnon-standard hack + // of setting timestamp using + // MFMT-like (two argument) call to MDTM. + // IIS definitelly does. + command = L"MDTM"; + } + if (Send( command + L" " + timestr + L" " + filename)) + { + m_Operation.nOpState = FILETRANSFER_MFMT; + return; + } + } + } + //Transfer successful + ResetOperation(FZ_REPLY_OK); +} + +void CFtpControlSocket::Cancel(BOOL bQuit/*=FALSE*/) +{ + const int nOpMode = m_Operation.nOpMode; + if (nOpMode==CSMODE_CONNECT) + DoClose(FZ_REPLY_CANCEL); + else if (nOpMode & CSMODE_LIST) + { + if (m_Operation.nOpState == LIST_WAITFINISH) + m_skipReply = true; + ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_CANCEL); + } + else if (nOpMode & CSMODE_TRANSFER) + { + if (m_Operation.nOpState == FILETRANSFER_WAITFINISH || m_Operation.nOpState == FILETRANSFER_LIST_WAITFINISH) + m_skipReply = true; + ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_CANCEL | FZ_REPLY_ABORTED); + } + else if (nOpMode != CSMODE_NONE) + ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_CANCEL); + + if (nOpMode != CSMODE_NONE && !bQuit) + ShowStatus(IDS_ERRORMSG_INTERRUPTED, FZ_LOG_ERROR); + + if (m_awaitsReply) + m_skipReply = true; +} + +void CFtpControlSocket::TransfersocketListenFinished(unsigned int ip, unsigned short port) +{ + if (m_Operation.nOpMode&CSMODE_TRANSFER || m_Operation.nOpMode&CSMODE_LIST) + { + CString host; + host.Format(L"%d,%d,%d,%d,%d,%d",ip%256,(ip>>8)%256,(ip>>16)%256,(ip>>24)%256,port%256,port>>8); + Send(L"PORT "+host); + } +} + +void CFtpControlSocket::ResumeTransfer() +{ + if (m_pTransferSocket && (m_Operation.nOpMode&CSMODE_TRANSFER || m_Operation.nOpMode&CSMODE_LIST)) + { + m_pTransferSocket->OnSend(0); + m_pTransferSocket->OnReceive(0); + } +} + +BOOL CFtpControlSocket::Create() +{ + if (!GetOptionVal(OPTION_LIMITPORTRANGE)) + return CAsyncSocketEx::Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, 0, GetOptionVal(OPTION_ENABLE_IPV6) ? AF_UNSPEC : AF_INET); + else + { + int min=GetOptionVal(OPTION_PORTRANGELOW); + int max=GetOptionVal(OPTION_PORTRANGEHIGH); + if (min>=max) + { + ShowStatus(IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE,FZ_LOG_ERROR); + return FALSE; + } + int startport = static_cast(min+((double)rand()*(max-min))/(RAND_MAX+1)); + int port = startport; + + while (!CAsyncSocketEx::Create(port, SOCK_STREAM, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, 0, GetOptionVal(OPTION_ENABLE_IPV6) ? AF_UNSPEC : AF_INET)) + { + port++; + if (port>max) + port=min; + if (port==startport) + { + ShowStatus(IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE,FZ_LOG_ERROR); + return FALSE; + } + + if (!InitConnect()) + return FALSE; + } + } + return TRUE; +} + + +void CFtpControlSocket::ResetOperation(int nSuccessful /*=FALSE*/) +{ + if (nSuccessful & FZ_REPLY_CRITICALERROR) + nSuccessful |= FZ_REPLY_ERROR; + + if (m_pTransferSocket) + delete m_pTransferSocket; + m_pTransferSocket=0; + + if (m_pDataFile) + delete m_pDataFile; + m_pDataFile=0; + + if (m_Operation.nOpMode) + { + //Unset busy attribute so that new commands can be executed + m_pOwner->SetBusy(FALSE); + + if (m_Operation.nOpMode&CSMODE_CONNECT && nSuccessful&FZ_REPLY_ERROR) + { + nSuccessful|=FZ_REPLY_DISCONNECTED; + ShowStatus(IDS_ERRORMSG_CANTCONNECT, FZ_LOG_ERROR); + } + + if (m_Operation.nOpMode & (CSMODE_LIST|CSMODE_LISTFILE|CSMODE_TRANSFER) && nSuccessful==FZ_REPLY_OK) + m_LastSendTime=CTime::GetCurrentTime(); + + //Update remote file entry + if (m_Operation.pData && + m_Operation.nOpMode&CSMODE_TRANSFER && + (!((CFileTransferData*)m_Operation.pData)->transferfile.get) && + m_pDirectoryListing && + m_Operation.nOpState>=FILETRANSFER_RETRSTOR) + { + CString filename=((CFileTransferData*)m_Operation.pData)->transferfile.remotefile; + CServerPath path=((CFileTransferData*)m_Operation.pData)->transferfile.remotepath; + t_directory dir; + BOOL res=FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path==path) + { + dir=*m_pDirectoryListing; + res=TRUE; + } + } + t_directory WorkingDir; + BOOL bFound=m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path==path) + { + dir=WorkingDir; + res=TRUE; + } + if (res) + { + int i; + for (i=0; itransferfile.get) + dir.direntry[i].size = -1; + if (!GetLength64(((CFileTransferData *)m_Operation.pData)->transferfile.localfile, dir.direntry[i].size)) + dir.direntry[i].size = -1; + } + dir.direntry[i].date.hasdate = false; + break; + } + if (i==dir.num) + { + t_directory::t_direntry *entries=new t_directory::t_direntry[dir.num+1]; + int i; + for (i=0; ipath==dir.path) + { + updated=TRUE; + SetDirectoryListing(&dir, bFound && WorkingDir.path == dir.path); + } + if (!updated) + if (bFound && WorkingDir.path==dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + + if (m_Operation.pData && nSuccessful&FZ_REPLY_ERROR) + { + if (m_Operation.nOpMode&CSMODE_TRANSFER) + if (nSuccessful&FZ_REPLY_ABORTED) + //Transfer aborted by user + ShowStatus((m_Operation.nOpMode&CSMODE_DOWNLOAD)?IDS_ERRORMSG_DOWNLOADABORTED:IDS_ERRORMSG_UPLOADABORTED,FZ_LOG_ERROR); + else + ShowStatus(((CFileTransferData*)m_Operation.pData)->transferfile.get?IDS_ERRORMSG_DOWNLOADFAILED:IDS_ERRORMSG_UPLOADFAILED,FZ_LOG_ERROR); + else if (m_Operation.nOpMode&CSMODE_LIST) + ShowStatus(IDS_ERRORMSG_CANTGETLIST,FZ_LOG_ERROR); + else if (m_Operation.nOpMode&CSMODE_LISTFILE) + ShowStatus(IDS_ERRORMSG_CANTGETLISTFILE,FZ_LOG_ERROR); + } + else if (m_Operation.pData && m_Operation.nOpMode&CSMODE_TRANSFER && nSuccessful==FZ_REPLY_OK) + ShowStatus(((CFileTransferData*)m_Operation.pData)->transferfile.get?IDS_STATUSMSG_DOWNLOADSUCCESSFUL:IDS_STATUSMSG_UPLOADSUCCESSFUL,FZ_LOG_STATUS); + } + else + { + // When control socket is waiting for reply + // to keepalive (!IsReady), while new command comes, + // its execution is postponed + // (CMainThread::m_pPostKeepAliveCommand), + // but the main thread + // is set to busy state already (CMainThread::Command). + // if connection is closed before without receiving reply, + // main thread would stay busy forever. + m_pOwner->SetBusy(FALSE); + //No operation in progress + nSuccessful&=FZ_REPLY_DISCONNECTED|FZ_REPLY_CANCEL; + if (!nSuccessful) + DebugFail(); + } + + if (nSuccessful&FZ_REPLY_DISCONNECTED) + m_pOwner->SetWorkingDir(0); //Disconnected, reset working dir + + if (m_Operation.nOpMode) + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_REPLY, m_pOwner->m_LastCommand.id), nSuccessful); + else + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_REPLY, 0), nSuccessful); + + m_Operation.nOpMode=0; + m_Operation.nOpState=-1; + + if (m_Operation.pData) + delete m_Operation.pData; + m_Operation.pData=0; +} + +void CFtpControlSocket::Delete(const CString & filename, const CServerPath & path) +{ + class CDeleteData : public CFtpControlSocket::t_operation::COpData + { +public: + CDeleteData() {} + virtual ~CDeleteData() {} + CString m_FileName; + CServerPath path; + }; + if (filename!=L"") + { + DebugAssert(!path.IsEmpty()); + DebugAssert(m_Operation.nOpMode==CSMODE_NONE); + DebugAssert(m_Operation.nOpState==-1); + DebugAssert(!m_Operation.pData); + m_Operation.nOpMode=CSMODE_DELETE; + if (!Send(L"DELE " + path.FormatFilename(filename))) + return; + CDeleteData *data=new CDeleteData; + data->m_FileName=filename; + data->path=path; + m_Operation.pData=data; + } + else + { + DebugAssert(path.IsEmpty()); + DebugAssert(m_Operation.nOpMode==CSMODE_DELETE); + DebugAssert(m_Operation.nOpState==-1); + DebugAssert(m_Operation.pData); + int res=GetReplyCode(); + if (res==2 || res==3) + { //Remove file from cached dirs + CDeleteData *pData=(CDeleteData *)m_Operation.pData; + t_directory dir; + BOOL res=FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path==pData->path) + { + dir=*m_pDirectoryListing; + res=TRUE; + } + } + t_directory WorkingDir; + BOOL bFound=m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path==pData->path) + { + dir=WorkingDir; + res=TRUE; + } + if (res) + { + BOOL found=FALSE; + for (int i=0;im_FileName) + { + DebugAssert(!dir.direntry[i].dir || dir.direntry[i].bLink); + found=TRUE; + break; + } + } + if (found) + { + t_directory::t_direntry *direntry=new t_directory::t_direntry[dir.num-1]; + int j=0; + int i; + for (i=0; im_FileName) + continue; + direntry[j]=dir.direntry[i]; + j++; + } + delete [] dir.direntry; + dir.direntry=direntry; + dir.num--; + BOOL updated=FALSE; + if (m_pDirectoryListing) + if (m_pDirectoryListing->path==dir.path) + { + updated=TRUE; + SetDirectoryListing(&dir); + } + if (!updated) + if (WorkingDir.path==dir.path) + { + updated=TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + } + ResetOperation(FZ_REPLY_OK); + } +} + +void CFtpControlSocket::RemoveDir(const CString & dirname, const CServerPath & path) +{ + + class CRemoveDirData : public CFtpControlSocket::t_operation::COpData + { +public: + CRemoveDirData() {} + virtual ~CRemoveDirData() {} + CString m_DirName; + CServerPath path; + }; + if (dirname != L"") + { + DebugAssert(!path.IsEmpty()); + DebugAssert(m_Operation.nOpMode == CSMODE_NONE); + DebugAssert(m_Operation.nOpState == -1); + DebugAssert(!m_Operation.pData); + m_Operation.nOpMode = CSMODE_RMDIR; + CServerPath newPath = path; + if (!newPath.AddSubdir(dirname)) + { + ShowStatus(L"Unable to concatenate path", FZ_LOG_ERROR); + return; + } + if (!Send(L"RMD "+ newPath.GetPath())) + return; + CRemoveDirData *data = new CRemoveDirData(); + data->m_DirName = dirname; + data->path = path; + m_Operation.pData = data; + } + else + { + DebugAssert(path.IsEmpty()); + DebugAssert(m_Operation.nOpMode == CSMODE_RMDIR); + DebugAssert(m_Operation.nOpState == -1); + DebugAssert(m_Operation.pData); + int res = GetReplyCode(); + if (res == 2 || res == 3) + { //Remove dir from cached dirs + CRemoveDirData *pData= (CRemoveDirData *)m_Operation.pData; + t_directory dir; + BOOL res = FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path == pData->path) + { + dir = *m_pDirectoryListing; + res = TRUE; + } + } + t_directory WorkingDir; + BOOL bFound = m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path == pData->path) + { + dir = WorkingDir; + res = TRUE; + } + if (res) + { + BOOL found = FALSE; + for (int i = 0; i < dir.num; i++) + { + if (dir.direntry[i].name == pData->m_DirName) + { + DebugAssert(dir.direntry[i].dir); + found = TRUE; + break; + } + } + if (found) + { + t_directory::t_direntry *direntry = new t_directory::t_direntry[dir.num-1]; + int j = 0; + int i; + for (i = 0; i < dir.num; i++) + { + if (dir.direntry[i].name == pData->m_DirName) + continue; + direntry[j] = dir.direntry[i]; + j++; + } + nb_free(dir.direntry); + dir.direntry = direntry; + dir.num--; + BOOL updated = FALSE; + if (m_pDirectoryListing) + if (m_pDirectoryListing->path == dir.path) + { + updated = TRUE; + SetDirectoryListing(&dir); + } + if (!updated) + if (WorkingDir.path == dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + ResetOperation(FZ_REPLY_OK); + } + else + ResetOperation(FZ_REPLY_ERROR); + } +} + +int CFtpControlSocket::CheckOverwriteFile() +{ + if (!m_Operation.pData) + { + return FZ_REPLY_ERROR; + } + + CFileTransferData *pData = reinterpret_cast(m_Operation.pData); + + int nReplyError = 0; + CFileStatus64 status; + BOOL res = FALSE; + if (pData->transferdata.localFileHandle == INVALID_HANDLE_VALUE) + { + res = GetStatus64(pData->transferfile.localfile, status); + } + if (!res) + { + if (!pData->transferfile.get) + { + ShowStatus(IDS_ERRORMSG_CANTGETLISTFILE,FZ_LOG_ERROR); + nReplyError = FZ_REPLY_CRITICALERROR; //File has to exist when uploading + } + else + { + m_Operation.nOpState = FILETRANSFER_TYPE; + } + } + else + { + if (status.m_attribute & 0x10) + { + nReplyError = FZ_REPLY_CRITICALERROR; //Can't transfer to/from dirs + } + else + { + _int64 localsize; + if (!GetLength64(pData->transferfile.localfile, localsize)) + { + if (!pData->transferfile.get) + nReplyError = FZ_REPLY_CRITICALERROR; + else + m_Operation.nOpState = FILETRANSFER_TYPE; + } + + CTime *localtime = NULL; + TRY + { + if (status.m_has_mtime && status.m_mtime != -1) + localtime = new CTime(status.m_mtime); + } + CATCH_ALL(e) + { + TCHAR buffer[1024]; + CString str =L"Exception creating CTime object: "; + if (e->GetErrorMessage(buffer, 1024, NULL)) + str += buffer; + else + str += L"Unknown exception"; + LogMessageRaw(FZ_LOG_WARNING, str); + localtime = NULL; + } + END_CATCH_ALL; + BOOL bRemoteFileExists = FALSE; + __int64 remotesize = -1; + t_directory::t_direntry::t_date remotetime; + if (m_pDirectoryListing) + { + for (int i=0; inum; i++) + { + CString remotefile = pData->transferfile.remotefile; + if (m_pDirectoryListing->direntry[i].name == remotefile && pData->transferdata.localFileHandle == INVALID_HANDLE_VALUE) + { + remotesize = m_pDirectoryListing->direntry[i].size; + remotetime = m_pDirectoryListing->direntry[i].date; + bRemoteFileExists = TRUE; + break; + } + } + } + if (!bRemoteFileExists && pData->hasRemoteDate) + { + remotetime = pData->remoteDate; + bRemoteFileExists = TRUE; + } + if (remotesize == -1 && pData->pFileSize) + { + remotesize = *pData->pFileSize; + bRemoteFileExists = TRUE; + } + + if (bRemoteFileExists || pData->transferfile.get ) + { + COverwriteRequestData *pOverwriteData = new COverwriteRequestData(); + t_transferfile *pTransferFile = new t_transferfile; + *pTransferFile = pData->transferfile; + pOverwriteData->pTransferFile = pTransferFile; + if (pData->transferfile.get) + { + int pos = pData->transferfile.localfile.ReverseFind(L'\\'); + // pos can be -1 here, e.g. in scripting, the code below still works then + pOverwriteData->FileName1 = pData->transferfile.localfile.Mid(pos+1); + pOverwriteData->FileName2 = pData->transferfile.remotefile; + pOverwriteData->path1 = pData->transferfile.localfile.Left(pos+1); + pOverwriteData->path2 = pData->transferfile.remotepath.GetPath(); + pOverwriteData->size1 = localsize; + pOverwriteData->size2 = remotesize; + } + else + { + int pos = pData->transferfile.localfile.ReverseFind(L'\\'); + // pos can be -1 here, e.g. in scripting, the code below still works then + pOverwriteData->FileName1 = pData->transferfile.remotefile; + pOverwriteData->FileName2 = pData->transferfile.localfile.Mid(pos+1); + pOverwriteData->path1 = pData->transferfile.remotepath.GetPath(); + pOverwriteData->path2 = pData->transferfile.localfile.Left(pos+1); + pOverwriteData->size1 = remotesize; + pOverwriteData->size2 = localsize; + } + pOverwriteData->localtime = localtime; + pOverwriteData->remotetime = remotetime; + pOverwriteData->nRequestID = m_pOwner->GetNextAsyncRequestID(); + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_OVERWRITE), (LPARAM)pOverwriteData)) + { + delete pOverwriteData; + nReplyError = FZ_REPLY_ERROR; + } + else + { + m_bCheckForTimeout = FALSE; + m_Operation.nOpState = FILETRANSFER_WAIT; + } + } + else + { + m_Operation.nOpState = FILETRANSFER_TYPE; + delete localtime; + } + } + } + return nReplyError; +} + +void CFtpControlSocket::SetFileExistsAction(int nAction, COverwriteRequestData *pData) +{ + if (!pData) + return; + if (!(m_Operation.nOpMode & CSMODE_TRANSFER)) + return; + if (m_Operation.nOpState != FILETRANSFER_WAIT) + return; + + CFileTransferData* pTransferData = reinterpret_cast(m_Operation.pData); + if (!pTransferData) + return; + + pTransferData->transferdata.bResume = false; + + m_bCheckForTimeout = TRUE; + int nReplyError = 0; + switch (nAction) + { + case FILEEXISTS_SKIP: + nReplyError = FZ_REPLY_OK; + break; + case FILEEXISTS_OVERWRITE: + pTransferData->nWaitNextOpState = FILETRANSFER_TYPE; + pTransferData->transferdata.localFileHandle = pData->localFileHandle; + break; + case FILEEXISTS_RENAME: + if (pTransferData->transferfile.get) + { + CFileStatus64 status; + if (GetStatus64(pData->FileName1, status)) + { + ShowStatus(IDS_ERRORMSG_NAMEINUSE, FZ_LOG_ERROR); + nReplyError= FZ_REPLY_CRITICALERROR; + } + else + { + pTransferData->transferfile.localfile = pData->path1+pData->FileName1; + //Replace invalid characters in the local filename + int pos = pTransferData->transferfile.localfile.ReverseFind(L'\\'); + for (int i = (pos+1); i < pTransferData->transferfile.localfile.GetLength(); i++) + if (pTransferData->transferfile.localfile[i] == L':') + pTransferData->transferfile.localfile.SetAt(i, L'_'); + + pTransferData->nWaitNextOpState= FILETRANSFER_TYPE; + pTransferData->transferdata.localFileHandle = pData->localFileHandle; + } + } + else + { + DebugAssert(m_pDirectoryListing); + int i; + for (i = 0; i < m_pDirectoryListing->num; i++) + { + if (m_pDirectoryListing->direntry[i].name == pData->FileName1) + { + ShowStatus(IDS_ERRORMSG_NAMEINUSE, FZ_LOG_ERROR); + nReplyError = FZ_REPLY_CRITICALERROR; + break; + } + } + if (i==m_pDirectoryListing->num) + { + pTransferData->transferfile.remotefile = pData->FileName1; + pTransferData->nWaitNextOpState = FILETRANSFER_TYPE; + } + } + break; + case FILEEXISTS_RESUME: + if (pData->size1 >= 0) + { + pTransferData->transferdata.bResume = TRUE; + } + pTransferData->nWaitNextOpState = FILETRANSFER_TYPE; + pTransferData->transferdata.localFileHandle = pData->localFileHandle; + break; + case FILEEXISTS_COMPLETE: + // Simulating transfer finish + m_Operation.nOpState=FILETRANSFER_WAITFINISH; + TransferFinished(true); + return; // Avoid call to FileTransfer below + } + if (nReplyError == FZ_REPLY_OK) + ResetOperation(FZ_REPLY_OK); + else if (nReplyError) + ResetOperation(FZ_REPLY_ERROR | nReplyError); //Error transferring the file + else + FileTransfer(); +} + +void CFtpControlSocket::SendKeepAliveCommand() +{ + ShowStatus(L"Sending dummy command to keep session alive.", FZ_LOG_PROGRESS); + m_bKeepAliveActive=TRUE; + //Choose a random command from the list + TCHAR commands[4][7]={L"PWD",L"REST 0",L"TYPE A",L"TYPE I"}; + int choice=(rand()*4)/(RAND_MAX+1); + Send(commands[choice]); +} + +void CFtpControlSocket::MakeDir(const CServerPath &path) +{ + //Directory creation works like this: + //Find existing parent and create subdirs one by one + if (m_Operation.nOpState == MKD_INIT) + { + DebugAssert(!path.IsEmpty()); + DebugAssert(m_Operation.nOpMode==CSMODE_NONE); + DebugAssert(!m_Operation.pData); + m_Operation.nOpMode = CSMODE_MKDIR; + if (!Send(L"CWD "+path.GetParent().GetPath())) + return; + CMakeDirData *data = new CMakeDirData(); + data->path = path; + data->Current = path.GetParent(); + data->Segments.push_front(path.GetLastSegment()); + m_Operation.pData = data; + m_Operation.nOpState = MKD_FINDPARENT; + } + else if (m_Operation.nOpState==MKD_FINDPARENT) + { + DebugAssert(m_Operation.nOpMode==CSMODE_MKDIR); + DebugAssert(path.IsEmpty()); + DebugAssert(m_Operation.pData); + CMakeDirData *pData=(CMakeDirData *)m_Operation.pData; + int res=GetReplyCode(); + if (res==2 || res==3) + { + m_pOwner->SetCurrentPath(pData->Current); + + m_Operation.nOpState=MKD_MAKESUBDIRS; + pData->Current.AddSubdir(pData->Segments.front()); + CString Segment=pData->Segments.front(); + pData->Segments.pop_front(); + if (Send( L"MKD " + Segment)) + m_Operation.nOpState = MKD_CHANGETOSUBDIR; + else + return; + } + else + { + if (!pData->Current.HasParent()) + ResetOperation(FZ_REPLY_ERROR); + else + { + pData->Segments.push_front(pData->Current.GetLastSegment()); + pData->Current=pData->Current.GetParent(); + if (!Send(L"CWD "+pData->Current.GetPath())) + return; + } + } + } + else if (m_Operation.nOpState==MKD_MAKESUBDIRS) + { + int res=GetReplyCode(); + if (res==2 || res==3) + { //Create dir entry in parent dir + CMakeDirData *pData=(CMakeDirData *)m_Operation.pData; + + DebugAssert(!pData->Segments.empty()); + + m_pOwner->SetCurrentPath(pData->Current); + + pData->Current.AddSubdir(pData->Segments.front()); + CString Segment=pData->Segments.front(); + pData->Segments.pop_front(); + if (Send( L"MKD " + Segment)) + m_Operation.nOpState=MKD_CHANGETOSUBDIR; + else + return; + } + else + ResetOperation(FZ_REPLY_ERROR); + } + else if (m_Operation.nOpState==MKD_CHANGETOSUBDIR) + { + CMakeDirData *pData=(CMakeDirData *)m_Operation.pData; + int res=GetReplyCode(); + if (res==2 || res==3 || //Creation successful + GetReply() == L"550 Directory already exists")//Creation was successful, although someone else did the work for us + { //Create dir entry in parent dir + CServerPath path2=pData->Current; + if (path2.HasParent()) + { + CString name=path2.GetLastSegment(); + path2=path2.GetParent(); + + t_directory dir; + BOOL res = FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path == path2) + { + dir = *m_pDirectoryListing; + res = TRUE; + } + } + t_directory WorkingDir; + BOOL bFound = m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path == path2) + { + dir = WorkingDir; + res = TRUE; + } + if (!res) + { + dir.path = path2; + dir.server = m_CurrentServer; + } + + int i; + for (i=0; ipath == dir.path) + { + updated = TRUE; + SetDirectoryListing(&dir, bFound && WorkingDir.path == dir.path); + } + if (!updated) + if (bFound && WorkingDir.path == dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + } + + //Continue operation even if MKD failed, maybe another thread did create this directory for us + if (pData->Segments.empty()) + ResetOperation(FZ_REPLY_OK); + else + { + if (Send( L"CWD " + pData->Current.GetPath())) + m_Operation.nOpState=MKD_MAKESUBDIRS; + else + return; + } + } + else + DebugFail(); +} + +void CFtpControlSocket::Rename(const CString & oldName, const CString & newName, const CServerPath & path, const CServerPath & newPath) +{ + class CRenameData : public CFtpControlSocket::t_operation::COpData + { + public: + CRenameData() {} + virtual ~CRenameData() {} + CString oldName, newName; + CServerPath path; + CServerPath newPath; + }; + if (oldName != L"") + { + DebugAssert(newName != L""); + DebugAssert(!path.IsEmpty()); + DebugAssert(m_Operation.nOpMode == CSMODE_NONE); + DebugAssert(m_Operation.nOpState == -1); + DebugAssert(!m_Operation.pData); + m_Operation.nOpMode = CSMODE_RENAME; + if (!Send(L"RNFR " + path.FormatFilename(oldName))) + return; + CRenameData *data = new CRenameData(); + data->oldName = oldName; + data->newName = newName; + data->path = path; + data->newPath = newPath; + m_Operation.pData = data; + } + else + { + DebugAssert(oldName == L""); + DebugAssert(path.IsEmpty()); + DebugAssert(m_Operation.nOpMode == CSMODE_RENAME); + DebugAssert(m_Operation.pData); + CRenameData *pData = reinterpret_cast(m_Operation.pData); + + if (m_Operation.nOpState == -1) + { + int res = GetReplyCode(); + if (res == 2 || res == 3) + { + m_Operation.nOpState++; + if (pData->newPath.IsEmpty()) + { + if (!Send(L"RNTO " + pData->path.FormatFilename(((CRenameData *)m_Operation.pData)->newName))) + return; + } + else + if (!Send(L"RNTO " + pData->newPath.FormatFilename(((CRenameData *)m_Operation.pData)->newName))) + return; + } + else + ResetOperation(FZ_REPLY_ERROR); + } + else + { + int res = GetReplyCode(); + if (res == 2 || res == 3) + { //Rename entry in cached directory + CRenameData *pData = reinterpret_cast(m_Operation.pData); + + t_directory dir; + BOOL res = FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path == pData->path) + { + dir = *m_pDirectoryListing; + res = TRUE; + } + } + t_directory WorkingDir; + BOOL bFound = m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path == pData->path) + { + dir = WorkingDir; + res = TRUE; + } + if (res) + { + for (int i=0; ioldName) + { + if (pData->newPath.IsEmpty()) + { + dir.direntry[i].name = pData->newName; + + BOOL updated = FALSE; + if (m_pDirectoryListing && m_pDirectoryListing->path == dir.path) + { + updated = TRUE; + SetDirectoryListing(&dir, WorkingDir.path == dir.path); + } + if (!updated) + if (WorkingDir.path == dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + else + { + t_directory::t_direntry oldentry = dir.direntry[i]; + + for (int j = i+1; j < dir.num; j++) + { + dir.direntry[j-1] = dir.direntry[j]; + } + dir.num--; + + BOOL updated = FALSE; + if (m_pDirectoryListing && m_pDirectoryListing->path == dir.path) + { + updated = TRUE; + SetDirectoryListing(&dir, WorkingDir.path == dir.path); + } + if (!updated) + if (WorkingDir.path == dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + + BOOL res = FALSE; + if (m_pDirectoryListing) + { + if (m_pDirectoryListing->path == pData->newPath) + { + dir = *m_pDirectoryListing; + res = TRUE; + } + } + t_directory WorkingDir; + BOOL bFound = m_pOwner->GetWorkingDir(&WorkingDir); + if (!res && bFound) + if (WorkingDir.path == pData->newPath) + { + dir = WorkingDir; + res = TRUE; + } + if (res) + { + t_directory::t_direntry *direntry = new t_directory::t_direntry[dir.num + 1]; + for (int i = 0; i < dir.num; i++) + direntry[i] = dir.direntry[i]; + direntry[dir.num] = oldentry; + direntry[dir.num].name = pData->newName; + dir.num++; + delete [] dir.direntry; + dir.direntry = direntry; + + BOOL updated = FALSE; + if (m_pDirectoryListing && m_pDirectoryListing->path == dir.path) + { + updated = TRUE; + SetDirectoryListing(&dir, bFound && WorkingDir.path == dir.path); + } + if (!updated) + if (bFound && WorkingDir.path == dir.path) + { + updated = TRUE; + m_pOwner->SetWorkingDir(&dir); + } + } + } + break; + } + } + ResetOperation(FZ_REPLY_OK); + } + else + ResetOperation(FZ_REPLY_ERROR); + } + } +} + +void CFtpControlSocket::SetVerifyCertResult(int nResult, t_SslCertData *pData) +{ + DebugAssert(pData); + if (!m_pSslLayer) + return; + if (!(m_Operation.nOpMode == CSMODE_CONNECT)) + return; + m_bCheckForTimeout = TRUE; + m_pSslLayer->SetNotifyReply(pData->priv_data, SSL_VERIFY_CERT, nResult); + m_LastRecvTime = CTime::GetCurrentTime(); +} + +void CFtpControlSocket::OnTimer() +{ + CheckForTimeout(); + ResumeTransfer(); + if (GetOptionVal(OPTION_KEEPALIVE)) + { + if (!m_pOwner->IsBusy() && m_pOwner->IsConnected() && !m_bKeepAliveActive) + { + //Getting intervals for the Keep Alive feature + int low=GetOptionVal(OPTION_INTERVALLOW); + int diff=GetOptionVal(OPTION_INTERVALHIGH)-low; + + //Choose a new delay + int delay=low+(rand()*diff)/RAND_MAX; + + CTimeSpan span=CTime::GetCurrentTime()-m_LastSendTime; + if (span.GetTotalSeconds()>=delay) + SendKeepAliveCommand(); + } + } +} + +BOOL CFtpControlSocket::IsReady() +{ + return !m_bKeepAliveActive; +} + +void CFtpControlSocket::Chmod(const CString & filename, const CServerPath & path, int nValue) +{ + m_Operation.nOpMode=CSMODE_CHMOD; + CString str; + str.Format( L"SITE CHMOD %03d %s", nValue, (LPCTSTR)path.FormatFilename(filename)); + Send(str); +} + +void CFtpControlSocket::SetAsyncRequestResult(int nAction, CAsyncRequestData *pData) +{ + switch (pData->nRequestType) + { + case FZ_ASYNCREQUEST_OVERWRITE: + SetFileExistsAction(nAction, (COverwriteRequestData *)pData); + break; + case FZ_ASYNCREQUEST_VERIFYCERT: + SetVerifyCertResult(nAction, ((CVerifyCertRequestData *)pData)->pCertData ); + break; + case FZ_ASYNCREQUEST_NEEDPASS: + if (m_Operation.nOpMode!=CSMODE_CONNECT || + m_Operation.nOpState != CONNECT_NEEDPASS) + break; + if (!m_RecvBuffer.empty() && m_RecvBuffer.front() != "") + { + DoClose(); + break; + } + if (!nAction) + { + DoClose(FZ_REPLY_CRITICALERROR|FZ_REPLY_CANCEL); + ShowStatus(IDS_ERRORMSG_INTERRUPTED,FZ_LOG_ERROR); + break; + } + else + { + m_bCheckForTimeout = TRUE; + m_CurrentServer.pass=((CNeedPassRequestData *)pData)->Password; + m_Operation.nOpState=((CNeedPassRequestData *)pData)->nOldOpState; + CLogonData *pLogonData = static_cast(m_Operation.pData); + pLogonData->waitForAsyncRequest = false; + pLogonData->gotPassword = true; + LogOnToServer(TRUE); + } + break; +#ifndef MPEXT_NO_GSS + case FZ_ASYNCREQUEST_GSS_AUTHFAILED: + if (m_Operation.nOpMode!=CSMODE_CONNECT || m_Operation.nOpState!=CONNECT_GSS_FAILED) + break; + if (!m_RecvBuffer.empty() && m_RecvBuffer.front() != "") + { + DoClose(); + break; + } + if (!nAction) + { + DoClose(FZ_REPLY_CRITICALERROR|FZ_REPLY_CANCEL); + ShowStatus(IDS_ERRORMSG_INTERRUPTED,FZ_LOG_ERROR); + break; + } + m_bCheckForTimeout = TRUE; + m_Operation.nOpState=-1; + LogOnToServer(TRUE); + break; + case FZ_ASYNCREQUEST_GSS_NEEDPASS: + if (m_Operation.nOpMode!=CSMODE_CONNECT || + m_Operation.nOpState != CONNECT_GSS_NEEDPASS) + break; + if (!m_RecvBuffer.empty() && m_RecvBuffer.front() != "") + { + DoClose(); + break; + } + if (!nAction) + { + DoClose(FZ_REPLY_CRITICALERROR|FZ_REPLY_CANCEL); + ShowStatus(IDS_ERRORMSG_INTERRUPTED,FZ_LOG_ERROR); + break; + } + else + { + m_bCheckForTimeout = TRUE; + m_CurrentServer.pass=((CGssNeedPassRequestData *)pData)->pass; + m_Operation.nOpState=((CGssNeedPassRequestData *)pData)->nOldOpState; + LogOnToServer(TRUE); + } + break; + case FZ_ASYNCREQUEST_GSS_NEEDUSER: + if (m_Operation.nOpMode != CSMODE_CONNECT || + m_Operation.nOpState != CONNECT_GSS_NEEDUSER) + break; + if (!m_RecvBuffer.empty() && m_RecvBuffer.front() != "") + { + DoClose(); + break; + } + if (!nAction) + { + DoClose(FZ_REPLY_CRITICALERROR | FZ_REPLY_CANCEL); + ShowStatus(IDS_ERRORMSG_INTERRUPTED, FZ_LOG_ERROR); + break; + } + else + { + m_bCheckForTimeout = TRUE; + m_CurrentServer.user = ((CGssNeedUserRequestData *)pData)->user; + m_Operation.nOpState=((CGssNeedUserRequestData *)pData)->nOldOpState; + LogOnToServer(TRUE); + } + break; +#endif + default: + LogMessage(FZ_LOG_WARNING, L"Unknown request reply %d", pData->nRequestType); + break; + } +} + +int CFtpControlSocket::OnLayerCallback(std::list& callbacks) +{ + for (std::list::iterator iter = callbacks.begin(); iter != callbacks.end(); iter++) + { + if (iter->nType == LAYERCALLBACK_STATECHANGE) + { + if (CAsyncSocketEx::LogStateChange(iter->nParam1, iter->nParam2)) + { + const TCHAR * state2Desc = CAsyncSocketEx::GetStateDesc(iter->nParam2); + const TCHAR * state1Desc = CAsyncSocketEx::GetStateDesc(iter->nParam1); + if (iter->pLayer == m_pProxyLayer) + LogMessage(FZ_LOG_INFO, L"Proxy layer changed state from %s to %s", state2Desc, state1Desc); +#ifndef MPEXT_NO_GSS + else if (iter->pLayer == m_pGssLayer) + LogMessage(FZ_LOG_INFO, L"m_pGssLayer changed state from %s to %s", state2Desc, state1Desc); +#endif + else if (iter->pLayer == m_pSslLayer) + { + nb_free(iter->str); + LogMessage(FZ_LOG_INFO, L"TLS layer changed state from %s to %s", (LPCTSTR)CAsyncSocketEx::GetStateDesc(iter->nParam2), (LPCTSTR)CAsyncSocketEx::GetStateDesc(iter->nParam1)); + } + else + LogMessage(FZ_LOG_INFO, L"Layer @ %d changed state from %s to %s", iter->pLayer, state2Desc, state1Desc); + } + } + else if (iter->nType == LAYERCALLBACK_LAYERSPECIFIC) + { + USES_CONVERSION; + if (iter->pLayer == m_pProxyLayer) + { + switch (iter->nParam1) + { + case PROXYERROR_NOERROR: + ShowStatus(IDS_PROXY_CONNECTED, FZ_LOG_STATUS); + break; + case PROXYERROR_NOCONN: + ShowStatus(IDS_ERRORMSG_PROXY_NOCONN, FZ_LOG_ERROR); + break; + case PROXYERROR_REQUESTFAILED: + ShowStatus(IDS_ERRORMSG_PROXY_REQUESTFAILED, FZ_LOG_ERROR); + if (iter->str) + ShowStatus(A2T(iter->str), FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHTYPEUNKNOWN: + ShowStatus(IDS_ERRORMSG_PROXY_AUTHTYPEUNKNOWN, FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHFAILED: + ShowStatus(IDS_ERRORMSG_PROXY_AUTHFAILED, FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHNOLOGON: + ShowStatus(IDS_ERRORMSG_PROXY_AUTHNOLOGON, FZ_LOG_ERROR); + break; + case PROXYERROR_CANTRESOLVEHOST: + ShowStatus(IDS_ERRORMSG_PROXY_CANTRESOLVEHOST, FZ_LOG_ERROR); + break; + default: + LogMessage(FZ_LOG_WARNING, L"Unknown proxy error" ); + } + } +#ifndef MPEXT_NO_GSS + else if (iter->pLayer == m_pGssLayer) + { + switch (iter->nParam1) + { + case GSS_INFO: + LogMessageRaw(FZ_LOG_INFO, A2CT(iter->str)); + break; + case GSS_ERROR: + LogMessageRaw(FZ_LOG_APIERROR, A2CT(iter->str)); + break; + case GSS_COMMAND: + ShowStatus(A2CT(iter->str), FZ_LOG_COMMAND); + break; + case GSS_REPLY: + ShowStatus(A2CT(iter->str), FZ_LOG_REPLY); + break; + } + } +#endif + else if (iter->pLayer == m_pSslLayer) + { + USES_CONVERSION; + + switch (iter->nParam1) + { + case SSL_INFO: + switch (iter->nParam2) + { + case SSL_INFO_ESTABLISHED: + ShowStatus(IDS_STATUSMSG_SSLESTABLISHED, FZ_LOG_STATUS); + if (m_Operation.nOpState == CONNECT_SSL_WAITDONE) + { + LogOnToServer(); + } + break; + } + break; + case SSL_FAILURE: + switch (iter->nParam2) + { + case SSL_FAILURE_UNKNOWN: + ShowStatus(IDS_ERRORMSG_UNKNOWNSSLERROR, FZ_LOG_ERROR); + break; + case SSL_FAILURE_ESTABLISH: + ShowStatus(IDS_ERRORMSG_CANTESTABLISHSSLCONNECTION, FZ_LOG_ERROR); + break; + case SSL_FAILURE_INITSSL: + ShowStatus(IDS_ERRORMSG_CANTINITSSL, FZ_LOG_ERROR); + break; + case SSL_FAILURE_VERIFYCERT: + ShowStatus(IDS_ERRORMSG_SSLCERTIFICATEERROR, FZ_LOG_ERROR); + break; + case SSL_FAILURE_CERTREJECTED: + ShowStatus(IDS_ERRORMSG_CERTREJECTED, FZ_LOG_ERROR); + m_bDidRejectCertificate = TRUE; + break; + } + TriggerEvent(FD_CLOSE); + break; + case SSL_VERIFY_CERT: + t_SslCertData *pData = new t_SslCertData(); + LPCTSTR CertError = NULL; + if (m_pSslLayer->GetPeerCertificateData(*pData, CertError)) + { + CVerifyCertRequestData *pRequestData = new CVerifyCertRequestData; + pRequestData->nRequestID=m_pOwner->GetNextAsyncRequestID(); + + pRequestData->pCertData = pData; + + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_ASYNCREQUEST, FZ_ASYNCREQUEST_VERIFYCERT), (LPARAM)pRequestData)) + { + delete pRequestData->pCertData; + delete pRequestData; + ResetOperation(FZ_REPLY_ERROR); + } + else + { + m_bCheckForTimeout = FALSE; + } + nb_free(iter->str); + continue; + } + else + { + delete pData; + nb_free(iter->str); + CString str; + str.Format(TLS_CERT_DECODE_ERROR, CertError); + ShowStatus(str, FZ_LOG_ERROR); + ResetOperation(FZ_REPLY_ERROR); + continue; + } + break; + } + nb_free(iter->str); + continue; + } +#ifndef MPEXT_NO_GSS + else + if (iter->pLayer == m_pGssLayer) + { + if (iter->nParam1 == GSS_AUTHCOMPLETE || + iter->nParam1 == GSS_AUTHFAILED) + { + LogOnToServer(TRUE); + nb_free(iter->str); + continue; + } + } +#endif + } + nb_free(iter->str); + } + return 0; +} + +_int64 CFtpControlSocket::GetSpeedLimit(CTime &time, int valType, int valValue) +{ + int type = GetOptionVal(valType); + + if ( type == 1) + return ( _int64)GetOptionVal(valValue) * 1024; + + return ( _int64)1000000000000; // I hope that when there will be something with 1000GB/s then I'll change it :) +} + +_int64 CFtpControlSocket::GetSpeedLimit(enum transferDirection direction, CTime &time) +{ + if (direction == download) + return GetSpeedLimit(time, OPTION_SPEEDLIMIT_DOWNLOAD_TYPE, OPTION_SPEEDLIMIT_DOWNLOAD_VALUE); + else + return GetSpeedLimit(time, OPTION_SPEEDLIMIT_UPLOAD_TYPE, OPTION_SPEEDLIMIT_UPLOAD_VALUE); + return ( _int64)1000000000000; +} + +_int64 CFtpControlSocket::GetAbleToUDSize( bool & beenWaiting, CTime & curTime, _int64 & curLimit, std::list::iterator & iter, enum transferDirection direction, int nBufSize) +{ + beenWaiting = false; + + CTime nowTime = CTime::GetCurrentTime(); + _int64 ableToRead = BUFSIZE; + + if ( nowTime == curTime) + { + ableToRead = iter->nBytesAvailable; + + if (ableToRead <= 0) + { + // we should wait till next second + nowTime = CTime::GetCurrentTime(); + + while (nowTime == curTime && !iter->nBytesAvailable) + { + if (beenWaiting) + { + //Check if there are other commands in the command queue. + MSG msg; + if (PeekMessage(&msg, 0, m_pOwner->m_nInternalMessageID, m_pOwner->m_nInternalMessageID, PM_NOREMOVE)) + { + LogMessage(FZ_LOG_INFO, L"Message waiting in queue, resuming later"); + return 0; + } + } + m_SpeedLimitSync.Unlock(); + Sleep(100); + m_SpeedLimitSync.Lock(); + nowTime = CTime::GetCurrentTime(); + beenWaiting = true; + + // Since we didn't hold the critical section for some time, we have to renew the iterator + for (iter = m_InstanceList[direction].begin(); iter != m_InstanceList[direction].end(); iter++) + if (iter->pOwner == this) + break; + if (iter == m_InstanceList[direction].end()) + return 0; + } + } + ableToRead = iter->nBytesAvailable; + } + + if (nowTime != curTime) + { + if (ableToRead > 0) + ableToRead = 0; + + curLimit = GetSpeedLimit(direction, curTime); + __int64 nMax = curLimit / m_InstanceList[direction].size(); + _int64 nLeft = 0; + int nCount = 0; + std::list::iterator iter2; + for (iter2 = m_InstanceList[direction].begin(); iter2 != m_InstanceList[direction].end(); iter2++) + { + if (iter2->nBytesAvailable>0) + { + nLeft += iter2->nBytesAvailable; + iter2->nBytesTransferred = 1; + } + else + { + nCount++; + iter2->nBytesTransferred = 0; + } + iter2->nBytesAvailable = nMax; + } + if (nLeft && nCount) + { + nMax = nLeft / nCount; + for (iter2 = m_InstanceList[direction].begin(); iter2 != m_InstanceList[direction].end(); iter2++) + { + if (!iter2->nBytesTransferred) + iter2->nBytesAvailable += nMax; + else + iter2->nBytesTransferred = 0; + } + } + ableToRead = iter->nBytesAvailable; + } + + curTime = nowTime; + + if (!nBufSize) + nBufSize = BUFSIZE; + if (ableToRead > nBufSize) + ableToRead = nBufSize; + + return ableToRead; +} + +_int64 CFtpControlSocket::GetAbleToTransferSize(enum transferDirection direction, bool &beenWaiting, int nBufSize) +{ + m_SpeedLimitSync.Lock(); + std::list::iterator iter; + for (iter = m_InstanceList[direction].begin(); iter != m_InstanceList[direction].end(); iter++) + if (iter->pOwner == this) + break; + if (iter == m_InstanceList[direction].end()) + { + t_ActiveList item; + CTime time = CTime::GetCurrentTime(); + item.nBytesAvailable = GetSpeedLimit(direction, time) / (m_InstanceList[direction].size() + 1); + item.nBytesTransferred = 0; + item.pOwner = this; + m_InstanceList[direction].push_back(item); + iter = m_InstanceList[direction].end(); + iter--; + } + _int64 limit = GetAbleToUDSize(beenWaiting, m_CurrentTransferTime[direction], m_CurrentTransferLimit[direction], iter, direction, nBufSize); + m_SpeedLimitSync.Unlock(); + return limit; +} + +BOOL CFtpControlSocket::RemoveActiveTransfer() +{ + BOOL bFound = FALSE; + m_SpeedLimitSync.Lock(); + std::list::iterator iter; + for (int i = 0; i < 2; i++) + { + for (iter = m_InstanceList[i].begin(); iter != m_InstanceList[i].end(); iter++) + if (iter->pOwner == this) + { + m_InstanceList[i].erase(iter); + bFound = TRUE; + break; + } + } + m_SpeedLimitSync.Unlock(); + return bFound; +} + +BOOL CFtpControlSocket::SpeedLimitAddTransferredBytes(enum transferDirection direction, _int64 nBytesTransferred) +{ + m_SpeedLimitSync.Lock(); + std::list::iterator iter; + for (iter = m_InstanceList[direction].begin(); iter != m_InstanceList[direction].end(); iter++) + if (iter->pOwner == this) + { + if (iter->nBytesAvailable > nBytesTransferred) + iter->nBytesAvailable -= nBytesTransferred; + else + iter->nBytesAvailable = 0; + iter->nBytesTransferred += nBytesTransferred; + m_SpeedLimitSync.Unlock(); + return TRUE; + } + m_SpeedLimitSync.Unlock(); + return FALSE; +} + +CString CFtpControlSocket::ConvertDomainName(CString domain) +{ + USES_CONVERSION; + + LPCWSTR buffer = T2CW(domain); + + char *utf8 = static_cast(nb_calloc(1, wcslen(buffer) * 2 + 2)); + if (!WideCharToMultiByte(CP_UTF8, 0, buffer, -1, utf8, wcslen(buffer) * 2 + 2, 0, 0)) + { + nb_free(utf8); + LogMessage(FZ_LOG_WARNING, L"Could not convert domain name"); + return domain; + } + + char *output = 0; + output = strdup(utf8); + nb_free(utf8); + + CString result = A2T(output); + free(output); + return result; +} + +void CFtpControlSocket::LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg) +{ + LogMessageRaw(nMessageType, pMsg); +} + +bool CFtpControlSocket::LoggingSocketMessage(int nMessageType) +{ + return LoggingMessageType(nMessageType); +} + +BOOL CFtpControlSocket::ParsePwdReply(CString& rawpwd) +{ + CServerPath realPath; + BOOL Result = ParsePwdReply(rawpwd, realPath); + if (Result) + { + m_pOwner->SetCurrentPath(realPath); + } + return Result; +} + +BOOL CFtpControlSocket::ParsePwdReply(CString& rawpwd, CServerPath & realPath) +{ + CListData *pData = static_cast(m_Operation.pData); + DebugAssert(pData); + + int pos1 = rawpwd.Find(L'"'); + int pos2 = rawpwd.ReverseFind(L'"'); + if (pos1 == -1 || pos2 == -1 || pos1 >= pos2) + { + LogMessage(FZ_LOG_WARNING, L"No quoted path found, try using first token as path"); + pos1 = rawpwd.Find(L' '); + if (pos1 != -1) + { + pos2 = rawpwd.Find(L' ', pos1 + 1); + if (pos2 == -1) + pos2 = rawpwd.GetLength(); + } + + if (pos1 == -1) + { + LogMessage(FZ_LOG_WARNING, L"Can't parse path!"); + ResetOperation(FZ_REPLY_ERROR); + return FALSE; + } + } + rawpwd = rawpwd.Mid(pos1 + 1, pos2 - pos1 - 1); + + realPath = m_pOwner->GetCurrentPath(); + realPath.SetServer(m_CurrentServer); + if (!realPath.SetPath(rawpwd)) + { + LogMessage(FZ_LOG_WARNING, L"Can't parse path!"); + ResetOperation(FZ_REPLY_ERROR); + return FALSE; + } + + return TRUE; +} + +void CFtpControlSocket::DiscardLine(CStringA line) +{ + if (m_Operation.nOpMode == CSMODE_CONNECT && m_Operation.nOpState == CONNECT_FEAT) + { + line.MakeUpper(); + while (line.Left(1) == " ") + { + line = line.Mid(1, line.GetLength() - 1); + } +#ifndef MPEXT_NO_ZLIB + if (line == "MODE Z" || line.Left(7) == "MODE Z ") + m_zlibSupported = true; + else +#endif + if (line == "UTF8" && m_CurrentServer.nUTF8 != 2) + m_bAnnouncesUTF8 = true; + else if (line == "CLNT" || line.Left(5) == "CLNT ") + m_hasClntCmd = true; + else if (line == "MLSD") + { + m_serverCapabilities.SetCapability(mlsd_command, yes); + } + else if (line == "MDTM") + { + m_serverCapabilities.SetCapability(mdtm_command, yes); + } + else if (line == "SIZE") + { + m_serverCapabilities.SetCapability(size_command, yes); + } + else if (line.Left(4) == "MLST") + { + USES_CONVERSION; + std::string facts; + if (line.GetLength() > 5) + { + facts = (LPCSTR)line.Mid(5, line.GetLength() - 5); + } + m_serverCapabilities.SetCapability(mlsd_command, yes, facts); + } + else if (line == "MFMT") + { + m_serverCapabilities.SetCapability(mfmt_command, yes); + } + } + else if (m_Operation.nOpMode == CSMODE_LISTFILE) + { + m_ListFile = line; + } +} + +int CFtpControlSocket::FileTransferListState(bool get) +{ + int Result; + if (GetOptionVal(OPTION_MPEXT_NOLIST) && !get) + { + Result = FILETRANSFER_TYPE; + } + else + { + Result = NeedModeCommand() ? FILETRANSFER_LIST_MODE : (NeedOptsCommand() ? FILETRANSFER_LIST_OPTS : FILETRANSFER_LIST_TYPE); + } + return Result; +} + +bool CFtpControlSocket::NeedModeCommand() +{ +#ifdef MPEXT_NO_ZLIB + return false; +#else + bool useZlib; + if (m_Operation.nOpMode == CSMODE_LIST || (m_Operation.nOpMode == CSMODE_TRANSFER && m_Operation.nOpMode <= FILETRANSFER_TYPE)) + useZlib = GetOptionVal(OPTION_MODEZ_USE) != 0; + else + useZlib = GetOptionVal(OPTION_MODEZ_USE) > 1; + + if (!m_useZlib && !m_zlibSupported) + return false; + + return m_useZlib != useZlib; +#endif +} + +bool CFtpControlSocket::NeedOptsCommand() +{ +#ifndef MPEXT_NO_ZLIB + if (!m_useZlib) +#endif + return false; + +#ifndef MPEXT_NO_ZLIB + return m_zlibLevel != GetOptionVal(OPTION_MODEZ_LEVEL); +#endif +} + +CString CFtpControlSocket::GetReply() +{ + if (m_RecvBuffer.empty()) + return L""; + + USES_CONVERSION; + + LPCSTR line; + + if ((m_Operation.nOpMode&CSMODE_LISTFILE) && (m_Operation.nOpState==LISTFILE_MLST) && + (GetReplyCode() == 2)) + { + // this is probably never used anyway + line = (LPCSTR)m_ListFile; + } + else + { + line = (LPCSTR)m_RecvBuffer.front(); + } + + if (m_bUTF8) + { + // convert from UTF-8 to ANSI + if (DetectUTF8Encoding(RawByteString(line)) == etANSI) + { + if (m_CurrentServer.nUTF8 != 1) + { + LogMessage(FZ_LOG_WARNING, L"Server does not send proper UTF-8, falling back to local charset"); + m_bUTF8 = false; + } + return A2CT(line); + } + + // convert from UTF-8 to ANSI + int len = MultiByteToWideChar(CP_UTF8, 0, line, -1, NULL, 0); + if (!len) + { + m_RecvBuffer.pop_front(); + if (m_RecvBuffer.empty()) + m_RecvBuffer.push_back(""); + return L""; + } + else + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(CP_UTF8, 0, line, -1 , (LPWSTR)p1, len + 1); + CString reply = W2CT(p1); + nb_free(p1); + return reply; + } + } + else if (m_nCodePage) + { + // convert from UTF-8 to ANSI + int len = MultiByteToWideChar(m_nCodePage, 0, line, -1, NULL, 0); + if (!len) + { + m_RecvBuffer.pop_front(); + if (m_RecvBuffer.empty()) + m_RecvBuffer.push_back(""); + return L""; + } + else + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(m_nCodePage, 0, line, -1 , (LPWSTR)p1, len + 1); + CString reply = W2CT(p1); + nb_free(p1); + return reply; + } + } + else + return A2CT(line); +} + +void CFtpControlSocket::OnSend(int nErrorCode) +{ + if (!m_sendBufferLen || !m_sendBuffer || m_awaitsReply) + return; + + int res = CAsyncSocketEx::Send(m_sendBuffer, m_sendBufferLen, 0, m_bUTF8 ? 0 : m_CurrentServer.iDupFF); + if (res == -1) + { + if (GetLastError() != WSAEWOULDBLOCK) + { + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + } + return; + } + if (!res) + { + ShowStatus(IDS_ERRORMSG_CANTSENDCOMMAND, FZ_LOG_ERROR); + DoClose(); + } + + m_awaitsReply = true; + m_LastSendTime = CTime::GetCurrentTime(); + + if (res == m_sendBufferLen) + { + nb_free(m_sendBuffer); + m_sendBuffer = 0; + m_sendBufferLen = 0; + } + else + { + char* tmp = static_cast(nb_calloc(1, m_sendBufferLen - res)); + memcpy(tmp, m_sendBuffer + res, m_sendBufferLen - res); + nb_free(m_sendBuffer); + m_sendBuffer = tmp; + m_sendBufferLen -= res; + } +} + +bool CFtpControlSocket::IsMisleadingListResponse() +{ + // Some servers are broken. Instead of an empty listing, some MVS servers + // for example they return something "550 no members found" + // Other servers return "550 No files found." + + CString retmsg = GetReply(); + if (!retmsg.CompareNoCase(L"550 No members found.")) + return true; + + if (!retmsg.CompareNoCase(L"550 No data sets found.")) + return true; + + if (!retmsg.CompareNoCase(L"550 No files found.")) + return true; + + return false; +} + +bool CFtpControlSocket::IsRoutableAddress(const CString & host) +{ + USES_CONVERSION; + + if (host.Left(3) == L"127" || + host.Left(3) == L"10." || + host.Left(7) == L"192.168" || + host.Left(7) == L"169.254") + { + return false; + } + else if (host.Left(3) == L"172") + { + CString middle = host.Mid(4); + int pos = middle.Find(L"."); + long part = atol(T2CA(middle.Left(pos))); + if ((part >= 16) && (part <= 31)) + { + return false; + } + } + return true; +} + +bool CFtpControlSocket::CheckForcePasvIp(CString & host) +{ + bool result = true; + unsigned int tmpPort; + CString ahost; + switch (m_CurrentServer.iForcePasvIp) + { + case 0: // on + if (!GetPeerName(ahost, tmpPort)) + { + // this should happen with proxy server only + int logontype = GetOptionVal(OPTION_LOGONTYPE); + // do not know what to do, if there's FTP proxy + if (!logontype) + { + // this is a host name, not an IP, but it should not be a problem + ahost = m_CurrentServer.host; + } + } + + if (ahost != host) + { + LogMessage(FZ_LOG_WARNING, L"Using host address %s instead of the one suggested by the server: %s", ahost, host); + host = ahost; + } + break; + + case 1: // off + // noop + break; + + default: // auto + if (!GetPeerName(ahost, tmpPort)) + { + LogMessage(FZ_LOG_PROGRESS, L"Error retrieving server address, cannot test if address is routable"); + } + else if (!IsRoutableAddress(host) && IsRoutableAddress(ahost)) + { + LogMessage(FZ_LOG_WARNING, L"Server sent passive reply with unroutable address %s, using host address instead.", host, ahost); + host = ahost; + } + break; + } + + return result; +} + + +ftp_capabilities_t TFTPServerCapabilities::GetCapability(ftp_capability_names_t Name) +{ + t_cap tcap = FCapabilityMap[Name]; + return tcap.cap; +} + +ftp_capabilities_t TFTPServerCapabilities::GetCapabilityString(ftp_capability_names_t Name, std::string * Option) +{ + t_cap tcap = FCapabilityMap[Name]; + if (Option) + *Option = tcap.option; + return tcap.cap; +} + +void TFTPServerCapabilities::SetCapability(ftp_capability_names_t Name, ftp_capabilities_t Cap) +{ + t_cap tcap = FCapabilityMap[Name]; + tcap.cap = Cap; + tcap.number = 1; + FCapabilityMap[Name] = tcap; +} + +void TFTPServerCapabilities::SetCapability(ftp_capability_names_t Name, ftp_capabilities_t Cap, const std::string & Option) +{ + t_cap tcap = FCapabilityMap[Name]; + tcap.cap = Cap; + tcap.option = Option; + tcap.number = 0; + FCapabilityMap[Name] = tcap; +} diff --git a/netbox/src/filezilla/FtpControlSocket.h b/netbox/src/filezilla/FtpControlSocket.h new file mode 100644 index 000000000..cd3e36a19 --- /dev/null +++ b/netbox/src/filezilla/FtpControlSocket.h @@ -0,0 +1,244 @@ + +#pragma once + +#include "structures.h" +#include "stdafx.h" +#include "FileZillaApi.h" +#include "FileZillaIntf.h" + +class CTransferSocket; +class CMainThread; + +class CAsyncProxySocketLayer; +class CMainThread; + +#define CSMODE_NONE 0x0000 +#define CSMODE_CONNECT 0x0001 +#define CSMODE_COMMAND 0x0002 +#define CSMODE_LIST 0x0004 +#define CSMODE_TRANSFER 0x0008 +#define CSMODE_DOWNLOAD 0x0010 +#define CSMODE_UPLOAD 0x0020 +#define CSMODE_TRANSFERERROR 0x0040 +#define CSMODE_TRANSFERTIMEOUT 0x0080 +#define CSMODE_DELETE 0x0100 +#define CSMODE_RMDIR 0x0200 +#define CSMODE_DISCONNECT 0x0400 +#define CSMODE_MKDIR 0x0800 +#define CSMODE_RENAME 0x1000 +#define CSMODE_CHMOD 0x2000 +#define CSMODE_LISTFILE 0x4000 + +struct t_transferdata +{ + t_transferdata() : + transfersize(0), transferleft(0), + localFileHandle(INVALID_HANDLE_VALUE), + bResume(FALSE), bResumeAppend(FALSE), bType(FALSE) + {} + __int64 transfersize, transferleft; + HANDLE localFileHandle; + BOOL bResume, bResumeAppend, bType; +}; + +class CFtpControlSocket : public CAsyncSocketEx, public CApiLog +{ + friend CTransferSocket; + +public: + CFtpControlSocket(CMainThread * pMainThread, CFileZillaTools * pTools); + virtual ~CFtpControlSocket(); + +public: + virtual void Connect(t_server & server); + virtual void OnTimer(); + virtual BOOL IsReady(); + virtual void List(BOOL bFinish, int nError = 0, CServerPath path = CServerPath(), CString subdir = L"", int nListMode = 0); + virtual void ListFile(const CString & filename, const CServerPath & path); + virtual void FtpCommand(LPCTSTR pCommand); + virtual void Disconnect(); + virtual void FileTransfer(t_transferfile * transferfile = 0, BOOL bFinish = FALSE, int nError = 0); + virtual void Delete(const CString & filename, const CServerPath & path); + virtual void Rename(const CString & oldName, const CString & newName, const CServerPath & path, const CServerPath & newPath); + virtual void MakeDir(const CServerPath & path); + virtual void RemoveDir(const CString & dirname, const CServerPath & path); + virtual void Chmod(const CString & filename, const CServerPath & path, int nValue); + + virtual void ProcessReply(); + virtual void TransferEnd(int nMode); + virtual void Cancel(BOOL bQuit = FALSE); + + virtual void SetAsyncRequestResult(int nAction, CAsyncRequestData * pData); + + int CheckOverwriteFile(); + virtual BOOL Create(); + void TransfersocketListenFinished(unsigned int ip, unsigned short port); + + BOOL m_bKeepAliveActive; + BOOL m_bDidRejectCertificate; + + // Some servers are broken. Instead of an empty listing, some MVS servers + // for example they return something "550 no members found" + // Other servers return "550 No files found." + bool IsMisleadingListResponse(); + + virtual bool UsingMlsd(); + virtual bool UsingUtf8(); + virtual std::string GetTlsVersionStr(); + virtual std::string GetCipherName(); + bool HandleSize(int code, __int64 & size); + bool HandleMdtm(int code, t_directory::t_direntry::t_date & date); + void TransferHandleListError(); + + enum transferDirection + { + download = 0, + upload = 1 + }; + + BOOL RemoveActiveTransfer(); + BOOL SpeedLimitAddTransferredBytes(enum transferDirection direction, _int64 nBytesTransferred); + + _int64 GetSpeedLimit(enum transferDirection direction, CTime & time); + + _int64 GetAbleToTransferSize(enum transferDirection direction, bool &beenWaiting, int nBufSize = 0); + + t_server GetCurrentServer(); + +public: + virtual void OnReceive(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual void OnSend(int nErrorCode); + +protected: + // Called by OnTimer() + void ResumeTransfer(); + void CheckForTimeout(); + void SendKeepAliveCommand(); + + virtual int OnLayerCallback(std::list & callbacks); + void SetFileExistsAction(int nAction, COverwriteRequestData * pData); + void SetVerifyCertResult(int nResult, t_SslCertData * pData); + void ResetOperation(int nSuccessful = -1); + + virtual void DoClose(int nError = 0); + int GetReplyCode(); + CString GetReply(); + void LogOnToServer(BOOL bSkipReply = FALSE); + BOOL Send(CString str); + + BOOL ParsePwdReply(CString & rawpwd); + BOOL ParsePwdReply(CString & rawpwd, CServerPath & realPath); + BOOL SendAuthSsl(); + + void DiscardLine(CStringA line); + int FileTransferListState(bool get); + bool NeedModeCommand(); + bool NeedOptsCommand(); + CString GetListingCmd(); + + bool InitConnect(); + int InitConnectState(); + + bool IsRoutableAddress(const CString & host); + bool CheckForcePasvIp(CString & host); + void TransferFinished(bool preserveFileTimeForUploads); + + virtual void LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg); + virtual bool LoggingSocketMessage(int nMessageType); + + void ShowStatus(UINT nID, int type) const; + void ShowStatus(CString status,int type) const; + void ShowTimeoutError(UINT nID) const; + + void Close(); + BOOL Connect(CString hostAddress, UINT nHostPort); + CString ConvertDomainName(CString domain); + + struct t_ActiveList + { + CFtpControlSocket * pOwner; + __int64 nBytesAvailable; + __int64 nBytesTransferred; + }; + static std::list m_InstanceList[2]; + static CTime m_CurrentTransferTime[2]; + static _int64 m_CurrentTransferLimit[2]; + static CCriticalSection m_SpeedLimitSync; + _int64 GetAbleToUDSize(bool & beenWaiting, CTime & curTime, _int64 & curLimit, std::list::iterator & iter, enum transferDirection direction, int nBufSize); + _int64 GetSpeedLimit(CTime & time, int valType, int valValue); + + void SetDirectoryListing(t_directory * pDirectory, bool bSetWorkingDir = true); + t_directory * m_pDirectoryListing; + + CMainThread * m_pOwner; + CFileZillaTools * m_pTools; + + CFile * m_pDataFile; + CTransferSocket * m_pTransferSocket; + CStringA m_MultiLine; + CTime m_LastSendTime; + + CString m_ServerName; + std::list m_RecvBuffer; + CTime m_LastRecvTime; + class CLogonData; + class CListData; + class CListFileData; + class CFileTransferData; + class CMakeDirData; + +#ifndef MPEXT_NO_ZLIB + bool m_useZlib; + bool m_zlibSupported; + int m_zlibLevel; +#endif + + bool m_bUTF8; + bool m_bAnnouncesUTF8; + int m_nCodePage; + bool m_hasClntCmd; + TFTPServerCapabilities m_serverCapabilities; + CStringA m_ListFile; + __int64 m_ListFileSize; + bool m_isFileZilla; + + bool m_awaitsReply; + bool m_skipReply; + + char * m_sendBuffer; + int m_sendBufferLen; + + bool m_bProtP; + + bool m_mayBeMvsFilesystem; + bool m_mayBeBS2000Filesystem; + + struct t_operation + { + int nOpMode; + int nOpState; + class COpData //Base class which will store operation specific parameters. + { + public: + COpData() {}; + virtual ~COpData() {}; + }; + COpData * pData; + public: + }; + + t_operation m_Operation; + + CAsyncProxySocketLayer * m_pProxyLayer; + CAsyncSslSocketLayer * m_pSslLayer; +#ifndef MPEXT_NO_GSS + CAsyncGssSocketLayer * m_pGssLayer; +#endif + t_server m_CurrentServer; + +private: + BOOL m_bCheckForTimeout; +}; + diff --git a/netbox/src/filezilla/FtpListResult.cpp b/netbox/src/filezilla/FtpListResult.cpp new file mode 100644 index 000000000..b7e3078ce --- /dev/null +++ b/netbox/src/filezilla/FtpListResult.cpp @@ -0,0 +1,3158 @@ + +#include "stdafx.h" +#include "FtpListResult.h" +#include "FileZillaApi.h" +#include + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// +//#define LISTDEBUG +#ifdef LISTDEBUG + //It's the normal UNIX format (or even another nonstandard format) + //Some samples are from http://cr.yp.to/ftpparse/ftpparse.c + /* UNIX-style listing, without inum and without blocks */ + + static char data[][110]={ + "-rw-r--r-- 1 root other 531 Jan 29 03:26 01-unix-std file", + "dr-xr-xr-x 2 root other 512 Apr 8 1994 02-unix-std dir", + "dr-xr-xr-x 2 root 512 Apr 8 1994 03-unix-nogroup dir", + "lrwxrwxrwx 1 root other 7 Jan 25 00:17 04-unix-std link -> usr/bin", + + /* Some listings with uncommon date/time format: */ + "-rw-r--r-- 1 root other 531 09-26 2000 05-unix-date file", + "-rw-r--r-- 1 root other 531 09-26 13:45 06-unix-date file", + "-rw-r--r-- 1 root other 531 2005-06-07 21:22 07-unix-date file", + + /* Unix style with size information in kilobytes */ + "-rw-r--r-- 1 root other 34.5k Oct 5 21:22 08-unix-namedsize file", + + /* Also NetWare: */ + "d [R----F--] supervisor 512 Jan 16 18:53 09-netware dir", + "- [R----F--] rhesus 214059 Oct 20 15:27 10-netware file", + + /* Also NetPresenz for the Mac: */ + "-------r-- 326 1391972 1392298 Nov 22 1995 11-netpresenz file", + "drwxrwxr-x folder 2 May 10 1996 12-netpresenz dir", + + /* A format with domain field some windows servers send */ + "-rw-r--r-- 1 group domain user 531 Jan 29 03:26 13-unix-domain file", + + /* EPLF directory listings */ + "+i8388621.48594,m825718503,r,s280,\t14-eplf file", + "+i8388621.50690,m824255907,/,\t15-eplf dir", + + /* MSDOS type listing used by IIS */ + "04-27-00 12:09PM 16-dos-dateambigious dir", + "04-14-00 03:47PM 589 17-dos-dateambigious file", + + /* Another type of MSDOS style listings */ + "2002-09-02 18:48 18-dos-longyear dir", + "2002-09-02 19:06 9,730 19-dos-longyear file", + + /* Numerical Unix style format */ + "0100644 500 101 12345 123456789 20-unix-numerical file", + + /* This one is used by SSH-2.0-VShell_2_1_2_143, this is the old VShell format */ + "206876 Apr 04, 2000 21:06 21-vshell-old file", + "0 Dec 12, 2002 02:13 22-vshell-old dir/", + + /* This type of directory listings is sent by some newer versions of VShell + * both year and time in one line is uncommon. + */ + "-rwxr-xr-x 1 user group 9 Oct 08, 2002 09:47 23-vshell-new file", + + /* Next ones come from an OS/2 server. The server obviously isn't Y2K aware */ + "36611 A 04-23-103 10:57 24-os2 file", + " 1123 A 07-14-99 12:37 25-os2 file", + " 0 DIR 02-11-103 16:15 26-os2 dir", + " 1123 DIR A 10-05-100 23:38 27-os2 dir", + + /* Some servers send localized date formats, here the German one: */ + "dr-xr-xr-x 2 root other 2235 26. Juli, 20:10 28-datetest-ger dir", + "-r-xr-xr-x 2 root other 2235 2. Okt. 2003 29-datetest-ger file", + "-r-xr-xr-x 2 root other 2235 1999/10/12 17:12 30-datetest file", + "-r-xr-xr-x 2 root other 2235 24-04-2003 17:12 31-datetest file", + + /* Here a Japanese one: */ + "-rw-r--r-- 1 root sys 8473 4\x8c\x8e 18\x93\xfa 2003\x94\x4e 32-datatest-japanese file", + + /* VMS style listings */ + "33-vms-dir.DIR;1 1 19-NOV-2001 21:41 [root,root] (RWE,RWE,RE,RE)", + "34-vms-file;1 155 2-JUL-2003 10:30:13.64", + + /* VMS style listings without time */ + "35-vms-notime-file;1 2/8 15-JAN-2000 [IV2_XXX] (RWED,RWED,RE,)", + "36-vms-notime-file;1 6/8 15-JUI-2002 PRONAS (RWED,RWED,RE,)", + + /* VMS multiline */ + "37-vms-multiline-file;1\r\n170774/170775 24-APR-2003 08:16:15 [FTP_CLIENT,SCOT] (RWED,RWED,RE,)", + "38-vms-multiline-file;1\r\n10 2-JUL-2003 10:30:08.59 [FTP_CLIENT,SCOT] (RWED,RWED,RE,)", + + /* IBM AS/400 style listing */ + "QSYS 77824 02/23/00 15:09:55 *DIR 39-ibm-as400 dir/", + "QSYS 77824 23/02/00 15:09:55 *FILE 40-ibm-as400-date file", + + /* aligned directory listing with too long size */ + "-r-xr-xr-x longowner longgroup123456 Feb 12 17:20 41-unix-concatsize file", + + /* short directory listing with month name */ + "-r-xr-xr-x 2 owner group 4512 01-jun-99 42_unix_shortdatemonth file", + + /* the following format is sent by the Connect:Enterprise server by Sterling Commerce */ + "-C--E-----FTP B BCC3I1 7670 1294495 Jan 13 07:42 43-conent file", + "-C--E-----FTS B BCC3I1 7670 1294495 Jan 13 07:42 44-conent-file", + + "-AR--M----TCP B ceunix 17570 2313708 Mar 29 08:56 45-conent-file", + + /* Nortel wfFtp router */ + "46-nortel-wfftp-file 1014196 06/03/04 Thur. 10:20:03", + + /* VxWorks based server used in Nortel routers */ + "2048 Feb-28-1998 05:23:30 47-nortel-vxworks dir ", + + /* IBM MVS listings */ + // Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname + " WYOSPT 3420 2003/05/21 1 200 FB 80 8053 PS 48-MVS.FILE", + " WPTA01 3290 2004/03/04 1 3 FB 80 3125 PO 49-MVS.DATASET", + " TSO004 3390 VSAM 50-mvs-file", + " TSO005 3390 2005/06/06 213000 U 0 27998 PO 51-mvs-dir", + + /* Dataset members */ + // Name VV.MM Created Changed Size Init Mod Id + // ADATAB /* filenames without data, only check for those on MVS servers */ + " 52-MVSPDSMEMBER 01.01 2004/06/22 2004/06/22 16:32 128 128 0 BOBY12", + + "53-MVSPDSMEMBER2 00B308 000411 00 FO RU 31 ANY", + "54-MVSPDSMEMBER3 00B308 000411 00 FO RU ANY 24", + + // Some asian listing format. Those >127 chars are just examples + "-rwxrwxrwx 1 root staff 0 2003 3\xed\xef 20 55-asian date file", + "-r--r--r-- 1 root root 2096 8\xed 17 08:52 56-asian date file", + + // VMS style listing with a different field order + "57-vms-alternate-field-order-file;1 [SUMMARY] 1/3 2-AUG-2006 13:05 (RWE,RWE,RE,)", + + ""}; + +#endif + +CFtpListResult::CFtpListResult(t_server server, bool *bUTF8, int *nCodePage) +{ + listhead=curpos=0; + + m_server = server; + m_bUTF8 = bUTF8; + m_nCodePage = nCodePage; + + pos=0; + + m_prevline=0; + m_curline=0; + m_curlistaddpos=0; + + //Fill the month names map + + //English month names + m_MonthNamesMap[L"jan"] = 1; + m_MonthNamesMap[L"feb"] = 2; + m_MonthNamesMap[L"mar"] = 3; + m_MonthNamesMap[L"apr"] = 4; + m_MonthNamesMap[L"may"] = 5; + m_MonthNamesMap[L"jun"] = 6; + m_MonthNamesMap[L"june"] = 6; + m_MonthNamesMap[L"jul"] = 7; + m_MonthNamesMap[L"july"] = 7; + m_MonthNamesMap[L"aug"] = 8; + m_MonthNamesMap[L"sep"] = 9; + m_MonthNamesMap[L"sept"] = 9; + m_MonthNamesMap[L"oct"] = 10; + m_MonthNamesMap[L"nov"] = 11; + m_MonthNamesMap[L"dec"] = 12; + + //Numerical values for the month + m_MonthNamesMap[L"1"] = 1; + m_MonthNamesMap[L"01"] = 1; + m_MonthNamesMap[L"2"] = 2; + m_MonthNamesMap[L"02"] = 2; + m_MonthNamesMap[L"3"] = 3; + m_MonthNamesMap[L"03"] = 3; + m_MonthNamesMap[L"4"] = 4; + m_MonthNamesMap[L"04"] = 4; + m_MonthNamesMap[L"5"] = 5; + m_MonthNamesMap[L"05"] = 5; + m_MonthNamesMap[L"6"] = 6; + m_MonthNamesMap[L"06"] = 6; + m_MonthNamesMap[L"7"] = 7; + m_MonthNamesMap[L"07"] = 7; + m_MonthNamesMap[L"8"] = 8; + m_MonthNamesMap[L"08"] = 8; + m_MonthNamesMap[L"9"] = 9; + m_MonthNamesMap[L"09"] = 9; + m_MonthNamesMap[L"10"] = 10; + m_MonthNamesMap[L"11"] = 11; + m_MonthNamesMap[L"12"] = 12; + + //German month names + m_MonthNamesMap[L"mrz"] = 3; + m_MonthNamesMap[L"m\xE4r"] = 3; + m_MonthNamesMap[L"m\xE4rz"] = 3; + m_MonthNamesMap[L"mai"] = 5; + m_MonthNamesMap[L"juni"] = 6; + m_MonthNamesMap[L"m\xC3\xA4r"] = 3; // UTF-8 + m_MonthNamesMap[L"m\xC3\xA4rz"] = 3; // UTF-8 + m_MonthNamesMap[L"juli"] = 7; + m_MonthNamesMap[L"okt"] = 10; + m_MonthNamesMap[L"dez"] = 12; + + //Austrian month names + m_MonthNamesMap[L"j\xE4n"] = 1; + m_MonthNamesMap[L"j\xC3\xA4n"] = 1; // UTF-8 + + //French month names + m_MonthNamesMap[L"janv"] = 1; + m_MonthNamesMap[L"f\xE9" L"b"] = 1; + m_MonthNamesMap[L"f\xE9v"] = 2; + m_MonthNamesMap[L"fev"] = 2; + m_MonthNamesMap[L"f\xE9vr"] = 2; + m_MonthNamesMap[L"fevr"] = 2; + m_MonthNamesMap[L"mars"] = 3; + m_MonthNamesMap[L"mrs"] = 3; + m_MonthNamesMap[L"avr"] = 4; + m_MonthNamesMap[L"juin"] = 6; + m_MonthNamesMap[L"juil"] = 7; + m_MonthNamesMap[L"jui"] = 7; + m_MonthNamesMap[L"ao\xFB"] = 8; + m_MonthNamesMap[L"ao\xFBt"] = 8; + m_MonthNamesMap[L"aout"] = 8; + m_MonthNamesMap[L"d\xE9" L"c"] = 12; + m_MonthNamesMap[L"dec"] = 12; + + //Italian month names + m_MonthNamesMap[L"gen"] = 1; + m_MonthNamesMap[L"mag"] = 5; + m_MonthNamesMap[L"giu"] = 6; + m_MonthNamesMap[L"lug"] = 7; + m_MonthNamesMap[L"ago"] = 8; + m_MonthNamesMap[L"set"] = 9; + m_MonthNamesMap[L"ott"] = 10; + m_MonthNamesMap[L"dic"] = 12; + + //Spanish month names + m_MonthNamesMap[L"ene"] = 1; + m_MonthNamesMap[L"fbro"] = 2; + m_MonthNamesMap[L"mzo"] = 3; + m_MonthNamesMap[L"ab"] = 4; + m_MonthNamesMap[L"abr"] = 4; + m_MonthNamesMap[L"agto"] = 8; + m_MonthNamesMap[L"sbre"] = 9; + m_MonthNamesMap[L"obre"] = 9; + m_MonthNamesMap[L"nbre"] = 9; + m_MonthNamesMap[L"dbre"] = 9; + + //Polish month names + m_MonthNamesMap[L"sty"] = 1; + m_MonthNamesMap[L"lut"] = 2; + m_MonthNamesMap[L"kwi"] = 4; + m_MonthNamesMap[L"maj"] = 5; + m_MonthNamesMap[L"cze"] = 6; + m_MonthNamesMap[L"lip"] = 7; + m_MonthNamesMap[L"sie"] = 8; + m_MonthNamesMap[L"wrz"] = 9; + m_MonthNamesMap[L"pa"] = 10; + m_MonthNamesMap[L"lis"] = 11; + m_MonthNamesMap[L"gru"] = 12; + + //Russian month names + m_MonthNamesMap[L"\xFF\xED\xE2"] = 1; + m_MonthNamesMap[L"\xF4\xE5\xE2"] = 2; + m_MonthNamesMap[L"\xEC\xE0\xF0"] = 3; + m_MonthNamesMap[L"\xE0\xEF\xF0"] = 4; + m_MonthNamesMap[L"\xEC\xE0\xE9"] = 5; + m_MonthNamesMap[L"\xE8\xFE\xED"] = 6; + m_MonthNamesMap[L"\xE8\xFE\xEB"] = 7; + m_MonthNamesMap[L"\xE0\xE2\xE3"] = 8; + m_MonthNamesMap[L"\xF1\xE5\xED"] = 9; + m_MonthNamesMap[L"\xEE\xEA\xF2"] = 10; + m_MonthNamesMap[L"\xED\xEE\xFF"] = 11; + m_MonthNamesMap[L"\xE4\xE5\xEA"] = 12; + + + //Dutch month names + m_MonthNamesMap[L"mrt"] = 3; + m_MonthNamesMap[L"mei"] = 5; + + //Portuguese month names + m_MonthNamesMap[L"out"] = 10; + + //Japanese month names + m_MonthNamesMap[L"1\x8c\x8e"] = 1; + m_MonthNamesMap[L"2\x8c\x8e"] = 2; + m_MonthNamesMap[L"3\x8c\x8e"] = 3; + m_MonthNamesMap[L"4\x8c\x8e"] = 4; + m_MonthNamesMap[L"5\x8c\x8e"] = 5; + m_MonthNamesMap[L"6\x8c\x8e"] = 6; + m_MonthNamesMap[L"7\x8c\x8e"] = 7; + m_MonthNamesMap[L"8\x8c\x8e"] = 8; + m_MonthNamesMap[L"9\x8c\x8e"] = 9; + m_MonthNamesMap[L"10\x8c\x8e"] = 10; + m_MonthNamesMap[L"11\x8c\x8e"] = 11; + m_MonthNamesMap[L"12\x8c\x8e"] = 12; + + //Korean (Unicode) month names + m_MonthNamesMap[L"1\xC6\xD4"] = 1; + m_MonthNamesMap[L"2\xC6\xD4"] = 2; + m_MonthNamesMap[L"3\xC6\xD4"] = 3; + m_MonthNamesMap[L"4\xC6\xD4"] = 4; + m_MonthNamesMap[L"5\xC6\xD4"] = 5; + m_MonthNamesMap[L"6\xC6\xD4"] = 6; + m_MonthNamesMap[L"7\xC6\xD4"] = 7; + m_MonthNamesMap[L"8\xC6\xD4"] = 8; + m_MonthNamesMap[L"9\xC6\xD4"] = 9; + m_MonthNamesMap[L"10\xC6\xD4"] = 10; + m_MonthNamesMap[L"11\xC6\xD4"] = 11; + m_MonthNamesMap[L"12\xC6\xD4"] = 12; + + //Korean (EUC-KR) month names + m_MonthNamesMap[L"1\xBF\xF9"] = 1; + m_MonthNamesMap[L"2\xBF\xF9"] = 2; + m_MonthNamesMap[L"3\xBF\xF9"] = 3; + m_MonthNamesMap[L"4\xBF\xF9"] = 4; + m_MonthNamesMap[L"5\xBF\xF9"] = 5; + m_MonthNamesMap[L"6\xBF\xF9"] = 6; + m_MonthNamesMap[L"7\xBF\xF9"] = 7; + m_MonthNamesMap[L"8\xBF\xF9"] = 8; + m_MonthNamesMap[L"9\xBF\xF9"] = 9; + m_MonthNamesMap[L"10\xBF\xF9"] = 10; + m_MonthNamesMap[L"11\xBF\xF9"] = 11; + m_MonthNamesMap[L"12\xBF\xF9"] = 12; + + //Finnish month names + m_MonthNamesMap[L"tammi"] = 1; + m_MonthNamesMap[L"helmi"] = 2; + m_MonthNamesMap[L"maalis"] = 3; + m_MonthNamesMap[L"huhti"] = 4; + m_MonthNamesMap[L"touko"] = 5; + m_MonthNamesMap[L"kes\xE4"] = 6; + m_MonthNamesMap[L"hein\xE4"] = 7; + m_MonthNamesMap[L"elo"] = 8; + m_MonthNamesMap[L"syys"] = 9; + m_MonthNamesMap[L"loka"] = 10; + m_MonthNamesMap[L"marras"] = 11; + m_MonthNamesMap[L"joulu"] = 12; + + //There are more languages and thus month + //names, but as long nobody reports a + //problem, I won't add them, there are way + //too much languages + + //Some very strange combinations of names and numbers I've seen. + //The developers of those ftp servers must have been dumb. + m_MonthNamesMap[L"jan1"] = 1; + m_MonthNamesMap[L"feb2"] = 2; + m_MonthNamesMap[L"mar3"] = 3; + m_MonthNamesMap[L"apr4"] = 4; + m_MonthNamesMap[L"may5"] = 5; + m_MonthNamesMap[L"jun6"] = 6; + m_MonthNamesMap[L"jul7"] = 7; + m_MonthNamesMap[L"aug8"] = 8; + m_MonthNamesMap[L"sep9"] = 9; + m_MonthNamesMap[L"sept9"] = 9; + m_MonthNamesMap[L"oct0"] = 10; + m_MonthNamesMap[L"nov1"] = 11; + m_MonthNamesMap[L"dec2"] = 12; + + // Slovenian month names + m_MonthNamesMap[L"avg"] = 8; + +#ifdef LISTDEBUG + int i=-1; + while (*data[++i]) + { + char *pData=static_cast(nb_calloc(1, strlen(data[i])+3)); + sprintf(pData, "%s\r\n", data[i]); + AddData(pData, strlen(pData)); + } + TRACE1("%d lines added\n", i); +#endif +} + + +CFtpListResult::~CFtpListResult() +{ + t_list *ptr=listhead; + t_list *ptr2; + while (ptr) + { + nb_free(ptr->buffer); + ptr2=ptr; + ptr=ptr->next; + delete ptr2; + } + if (m_prevline) + nb_free(m_prevline); + if (m_curline) + nb_free(m_curline); +} + +t_directory::t_direntry *CFtpListResult::getList(int &num, bool mlst) +{ + #ifdef _DEBUG + USES_CONVERSION; + #endif + char *line=GetLine(); + m_curline=line; + while (line) + { + int tmp; + char *tmpline = static_cast(nb_calloc(1, strlen(line) + 1)); + strcpy(tmpline, line); + t_directory::t_direntry direntry; + if (parseLine(tmpline, strlen(tmpline), direntry, tmp, mlst)) + { + nb_free(tmpline); + if (tmp) + m_server.nServerType |= tmp; + if (direntry.name!=L"." && direntry.name!=L"..") + { + AddLine(direntry); + } + if (m_prevline) + { + nb_free(m_prevline); + m_prevline=0; + } + if (m_curline!=line) + nb_free(m_curline); + nb_free(line); + line=GetLine(); + m_curline=line; + } + else + { + nb_free(tmpline); + if (m_prevline) + { + if (m_curline!=line) + { + nb_free(m_prevline); + m_prevline=m_curline; + nb_free(line); + line=GetLine(); + m_curline=line; + } + else + { + line=static_cast(nb_calloc(1, strlen(m_prevline)+strlen(m_curline)+2)); + sprintf(line, "%s %s", m_prevline, m_curline); + } + } + else + { + m_prevline=line; + line=GetLine(); + m_curline=line; + } + } + } + if (m_prevline) + { + nb_free(m_prevline); + m_prevline=0; + } + if (m_curline!=line) + nb_free(m_curline); + nb_free(line); + m_curline=0; + + num=(int)m_EntryList.size(); + if (!num) + return 0; + t_directory::t_direntry *res=new t_directory::t_direntry[num]; + int i=0; + for (tEntryList::iterator iter=m_EntryList.begin();iter!=m_EntryList.end();iter++, i++) + { + res[i]=*iter; + } + m_EntryList.clear(); + + return res; +} + +BOOL CFtpListResult::parseLine(const char *lineToParse, const int linelen, t_directory::t_direntry &direntry, int &nFTPServerType, bool mlst) +{ + USES_CONVERSION; + + nFTPServerType = 0; + direntry.ownergroup = L""; + + if (parseAsMlsd(lineToParse, linelen, direntry, mlst)) + return TRUE; + + if (parseAsUnix(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsDos(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsEPLF(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsVMS(lineToParse, linelen, direntry)) + { +#ifndef LISTDEBUG + m_server.nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS; +#endif // LISTDEBUG + return TRUE; + } + + if (parseAsOther(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsIBMMVS(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsIBMMVSPDS(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsIBM(lineToParse, linelen, direntry)) + return TRUE; + + if (parseAsWfFtp(lineToParse, linelen, direntry)) + return TRUE; + + // Should be last + if (parseAsIBMMVSPDS2(lineToParse, linelen, direntry)) + return TRUE; + + // name-only entries + if (strchr(lineToParse, ' ') == NULL) + { + copyStr(direntry.name, 0, lineToParse, strlen(lineToParse)); + return TRUE; + } + + return FALSE; +} + +// Used only with LISTDEBUG +void CFtpListResult::AddData(char *data, int size) +{ + #ifdef _DEBUG + USES_CONVERSION; + #endif + if (!size) + return; + + if (!m_curlistaddpos) + m_curlistaddpos = new t_list(); + else + { + m_curlistaddpos->next = new t_list(); + m_curlistaddpos = m_curlistaddpos->next; + } + if (!listhead) + { + curpos = m_curlistaddpos; + listhead = m_curlistaddpos; + } + m_curlistaddpos->buffer = data; + m_curlistaddpos->len = size; + m_curlistaddpos->next = 0; + + t_list *pOldListPos = curpos; + int nOldListBufferPos = pos; + + //Try if there are already some complete lines + t_directory::t_direntry direntry; + char *line = GetLine(); + m_curline = line; + while (line) + { + if (curpos) + { + pOldListPos = curpos; + nOldListBufferPos = pos; + } + else + { + nb_free(line); + if (m_curline != line) + nb_free(m_curline); + m_curline = 0; + break; + } + int tmp; + char *tmpline = static_cast(nb_calloc(1, strlen(line) + 1)); + strcpy(tmpline, line); + if (parseLine(tmpline, (const int)strlen(tmpline), direntry, tmp, false)) + { + nb_free(tmpline); + if (tmp) + m_server.nServerType |= tmp; + if (direntry.name!=L"." && direntry.name!=L"..") + { + AddLine(direntry); + } + if (m_prevline) + { + nb_free(m_prevline); + m_prevline=0; + } + if (m_curline!=line) + nb_free(m_curline); + nb_free(line); + line = GetLine(); + m_curline = line; + } + else + { + nb_free(tmpline); + if (m_prevline) + { + if (m_curline != line) + { + nb_free(m_prevline); + m_prevline = m_curline; + nb_free(line); + line = GetLine(); + m_curline = line; + } + else + { + line=static_cast(nb_calloc(1, strlen(m_prevline)+strlen(m_curline)+2)); + sprintf(line, "%s %s", m_prevline, m_curline); + } + } + else + { + m_prevline=line; + line=GetLine(); + m_curline=line; + } + } + } + curpos=pOldListPos; + pos=nOldListBufferPos; + +} + +void CFtpListResult::SendToMessageLog() +{ + t_list *oldlistpos = curpos; + int oldbufferpos = pos; + curpos = listhead; + pos=0; + char *line = GetLine(); + // Note that FZ_LOG_INFO here is not checked against debug level, as the direct + // call to PostMessage bypasses check in LogMessage. + // So we get the listing on any logging level, what is actually what we want + if (!line) + { + //Displays a message in the message log + t_ffam_statusmessage *pStatus = new t_ffam_statusmessage(); + pStatus->post = TRUE; + pStatus->status = L""; + pStatus->type = FZ_LOG_INFO; + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_STATUS, 0), (LPARAM)pStatus); + } + while (line) + { + CString status = line; + nb_free(line); + + //Displays a message in the message log + t_ffam_statusmessage *pStatus = new t_ffam_statusmessage(); + pStatus->post = TRUE; + pStatus->status = status; + pStatus->type = FZ_LOG_INFO; + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_STATUS, 0), (LPARAM)pStatus)) + delete pStatus; + + line = GetLine(); + } + curpos = oldlistpos; + pos = oldbufferpos; +} + +char * CFtpListResult::GetLine() +{ + if (!curpos) + return 0; + int len=curpos->len; + while (curpos->buffer[pos]=='\r' || curpos->buffer[pos]=='\n' || curpos->buffer[pos]==' ' || curpos->buffer[pos]=='\t') + { + pos++; + if (pos>=len) + { + curpos=curpos->next; + if (!curpos) + return 0; + len=curpos->len; + pos=0; + } + } + + t_list *startptr=curpos; + int startpos=pos; + int reslen=0; + + int emptylen=0; + + while ((curpos->buffer[pos]!='\n')&&(curpos->buffer[pos]!='\r')) + { + if (curpos->buffer[pos]!=' ' && curpos->buffer[pos]!='\t') + { + reslen+=emptylen+1; + emptylen=0; + } + else + emptylen++; + pos++; + if (pos>=len) + { + curpos=curpos->next; + if (!curpos) + break; + len=curpos->len; + pos=0; + } + } + + char *res = static_cast(nb_calloc(1, reslen+1)); + res[reslen]=0; + int respos=0; + while (startptr!=curpos && reslen) + { + int copylen=startptr->len-startpos; + if (copylen>reslen) + copylen=reslen; + memcpy(&res[respos],&startptr->buffer[startpos], copylen); + reslen-=copylen; + respos+=startptr->len-startpos; + startpos=0; + startptr=startptr->next; + } + if (curpos && reslen) + { + int copylen=pos-startpos; + if (copylen>reslen) + copylen=reslen; + memcpy(&res[respos], &curpos->buffer[startpos], copylen); + } + + return res; +} + +void CFtpListResult::AddLine(t_directory::t_direntry &direntry) +{ + if (m_server.nTimeZoneOffset && + direntry.date.hasdate && direntry.date.hastime && !direntry.date.utc) + { + SYSTEMTIME st = {0}; + st.wYear = direntry.date.year; + st.wMonth = direntry.date.month; + st.wDay = direntry.date.day; + st.wHour = direntry.date.hour; + st.wMinute = direntry.date.minute; + st.wSecond = direntry.date.second; + + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + _int64 nFt = ((_int64)ft.dwHighDateTime << 32) + ft.dwLowDateTime; + _int64 nFt2 = nFt; + nFt += ((_int64)m_server.nTimeZoneOffset) * 10000000 * 60; + ft.dwHighDateTime = static_cast(nFt >> 32); + ft.dwLowDateTime = static_cast(nFt & 0xFFFFFFFF); + FileTimeToSystemTime(&ft, &st); + direntry.date.year = st.wYear; + direntry.date.month = st.wMonth; + direntry.date.day = st.wDay; + direntry.date.hour = st.wHour; + direntry.date.minute = st.wMinute; + direntry.date.second = st.wSecond; + } + + if (m_server.nServerType&FZ_SERVERTYPE_SUB_FTP_VMS && + (!GetOptionVal(OPTION_VMSALLREVISIONS) || direntry.dir)) + { //Remove version information, only keep the latest file + int pos=direntry.name.ReverseFind(L';'); + if (pos<=0 || pos>=(direntry.name.GetLength()-1)) + return;; + int version=_ttoi(direntry.name.Mid(pos+1)); + direntry.name=direntry.name.Left(pos); + + tEntryList::iterator entryiter=m_EntryList.begin(); + tTempData::iterator dataiter=m_TempData.begin(); + BOOL bContinue=FALSE; + while (entryiter!=m_EntryList.end()) + { + DebugAssert(dataiter!=m_TempData.end()); + t_directory::t_direntry dir=*entryiter; + int oldversion=*dataiter; + if (direntry.name==dir.name) + { + bContinue=TRUE; + if (version>oldversion) + { + *entryiter=direntry; + *dataiter=version; + } + break; + } + entryiter++; + dataiter++; + } + if (bContinue) + return; + m_EntryList.push_back(direntry); + m_TempData.push_back(version); + } + else + { + m_EntryList.push_back(direntry); + m_TempData.push_back(0); + } +} + +bool CFtpListResult::IsNumeric(const char *str, int len) const +{ + if (!str) + return false; + if (!*str) + return false; + const char *p=str; + while(*p) + { + if (len != -1) + if ((p - str) >= len) + return true; + + if (*p<'0' || *p>'9') + { + return false; + } + p++; + } + return true; +} + +bool CFtpListResult::ParseShortDate(const char *str, int len, t_directory::t_direntry::t_date &date) const +{ + if (!str) + return false; + + if (len <= 0) + return false; + + int i=0; + + //Extract the date + BOOL bGotYear = FALSE; + BOOL bGotMonth = FALSE; + BOOL bGotDay = FALSE; + int value = 0; + bool numeric = true; + while (str[i] != '-' && str[i] != '.' && str[i] != '/') + { + if (!str[i]) + return false; + + // Left half of token not numeric, check if it's a month name + if (str[i] < '0' || str[i] > '9') + numeric = false; + else + { + value *= 10; + value += str[i] - '0'; + } + i++; + if (i == len) + return false; + } + if (!i) + return false; + + if (!numeric) + { + char *tmpstr = static_cast(nb_calloc(1, i + 1)); + strncpy(tmpstr, str, i); + tmpstr[i] = 0; + strlwr(tmpstr); + + USES_CONVERSION; + std::map::const_iterator iter = const_cast(this)->m_MonthNamesMap.find(A2T(tmpstr)); + nb_free(tmpstr); + if (iter == m_MonthNamesMap.end()) + return false; + + date.month = iter->second; + bGotMonth = true; + } + else if (i == 4) + { //Seems to be yyyy-mm-dd + if (value < 1900) + return false; + date.year = value; + bGotYear = TRUE; + } + else if (i <= 2) + { + if (str[i] == '.') + { + // Maybe dd.mm.yyyy + if (!value || value > 31) + return false; + date.day = value; + bGotDay = TRUE; + } + else + { + // Seems to be mm-dd-yyyy or mm/dd/yyyy (stupid format, though) + if (!value) + return false; + else if (value > 12) // sigh, guess dd/mm/yyyy instead + { + date.day = value; + bGotDay = true; + } + else + { + date.month = value; + bGotMonth = TRUE; + } + } + } + else + return false; + + + //Extract the second date field + const char *p = str + i + 1; + len -= i + 1; + i=0; + value=0; + + if (i >= len) + return false; + + while (p[i]!='-' && p[i]!='.' && p[i]!='/') + { + value *= 10; + value += p[i]-'0'; + i++; + + if (i >= len) + return false; + } + if (bGotYear || bGotDay) + { + // Month field in yyyy-mm-dd or dd-mm-yyyy + if (!value || value > 12) + return false; + date.month = value; + bGotMonth = TRUE; + + } + else + { + // Day field in mm-dd-yyyy + if (!value || value > 31) + return false; + date.day = value; + bGotDay = TRUE; + } + + //Extract the last date field + p += i+1; + len -= i + 1; + i=0; + value=0; + + if (i >= len) + return false; + while (p[i]!='-' && p[i]!='.' && p[i]!='/') + { + value *= 10; + value += p[i]-'0'; + i++; + if (i >= len) + break; + } + + if (bGotYear) + { + // Day field in yyyy-mm-dd + if (!value || value > 31) + return false; + date.day = value; + } + else + { + //Year in dd.mm.yyyy or mm-dd-yyyy + date.year = value; + if (date.year<50) + date.year+=2000; + else if (date.year<1000) + date.year += 1900; + } + + date.hasdate = TRUE; + return true; +} + +BOOL CFtpListResult::parseAsVMS(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int tokenlen = 0; + int pos = 0; + USES_CONVERSION; + + t_directory::t_direntry dir; + + dir.bUnsure = FALSE; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!strnchr(str, tokenlen, ';')) + return FALSE; + + dir.size = -2; + const char *separator = strnchr(str, tokenlen, ';'); + if (!separator) + return FALSE; + + dir.dir = FALSE; + + if ((separator - str) > 4) + if (*(separator - 4) == '.') + if (*(separator - 3) == 'D') + if (*(separator - 2) == 'I') + if (*(separator - 1) == 'R') + { + dir.dir = TRUE; + } + if (dir.dir) + { + int i; + LPTSTR pBuffer = dir.name.GetBuffer(tokenlen - 4); + for (i = 0; i < (separator - str - 4); i++) + pBuffer[i] = str[i]; + for (i = 0; i < (tokenlen - (separator - str)); i++) + pBuffer[i + (separator - str) - 4] = separator[i]; + dir.name.ReleaseBuffer(tokenlen - 4); + } + else + copyStr(dir.name, 0, str, tokenlen); + + // This field is either the size or a username (???) enclosed in []. + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + bool gotSize = false; + const char *p = strnchr(str, tokenlen, '/'); + if (!p && IsNumeric(str, tokenlen)) + { + gotSize = true; + dir.size = strntoi64(str, tokenlen) * 512; + } + else if (p && p > str && IsNumeric(str, p - str)) + { + gotSize = true; + dir.size = strntoi64(str, p - str) * 512; + } + else + { + if (tokenlen < 3 || str[0] != '[' || str[tokenlen - 1] != ']') + return false; + copyStr(dir.ownergroup, 0, str + 1, tokenlen - 2); + } + + if (!gotSize) + { + //Size + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + const char *p = strnchr(str, tokenlen, '/'); + int len; + if (p) + len = p - str; + else + len = tokenlen; + + if (!IsNumeric(str, len)) + return FALSE; + + dir.size = strntoi64(str, len) * 512; + } + + //Get date + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + dir.date.hasdate = TRUE; + + //Day + p = str; + + while (*p != '-') + if ((++p - str) == tokenlen) + return 0; + + dir.date.day = static_cast(strntoi64(str, p-str)); + if (!dir.date.day || dir.date.day > 31) + return FALSE; + + p++; + const char *pMonth = p; + //Month + while (*p != '-') + if ((++p - str) == tokenlen) + return FALSE; + if ((p - pMonth) >= 15) + return FALSE; + char buffer[15] = {0}; + memcpy(buffer, pMonth, p-pMonth); + strlwr(buffer); + std::map::const_iterator iter = m_MonthNamesMap.find(A2T(buffer)); + if (iter == m_MonthNamesMap.end()) + return FALSE; + dir.date.month = iter->second; + p++; + + dir.date.year = static_cast(strntoi64(p, tokenlen - (p - str))); + + //Get time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + + if (str && strnchr(str + 1, tokenlen - 2, ':')) + { + dir.date.hastime = TRUE; + + p = str; + //Hours + while (*p != ':') + if ((++p - str) == tokenlen) + return 0; + + dir.date.hour = static_cast(strntoi64(str, p - str)); + if (dir.date.hour < 0 || dir.date.hour > 23) + return FALSE; + p++; + + const char *pMinute = p; + //Minutes + while (*p && *p != ':' ) + p++; + + dir.date.minute = static_cast(strntoi64(pMinute, p - pMinute)); + if (dir.date.minute < 0 || dir.date.minute > 59) + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + } + else + { + dir.date.hastime = FALSE; + } + + while (str) + { + if (tokenlen > 2 && str[0] == '(' && str[tokenlen - 1] == ')') + { + if (dir.permissionstr != L"") + dir.permissionstr += L" "; + CString tmp; + copyStr(tmp, 0, str + 1, tokenlen - 2); + dir.permissionstr += tmp; + } + else if (tokenlen > 2 && str[0] == '[' && str[tokenlen - 1] == ']') + { + if (dir.ownergroup != L"") + dir.ownergroup += L" "; + CString tmp; + copyStr(tmp, 0, str + 1, tokenlen - 2); + dir.ownergroup += tmp; + } + else + { + if (dir.permissionstr != L"") + dir.permissionstr += L" "; + CString tmp; + copyStr(tmp, 0, str, tokenlen); + dir.permissionstr += tmp; + } + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + } + + direntry = dir; + + return TRUE; +} + +BOOL CFtpListResult::parseAsEPLF(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + t_directory::t_direntry dir; + const char *str = strstr(line, "\t"); + + //Check if directory listing is an EPLF one + if (*line=='+' && str) + { + str++; + if (!*str) + return FALSE; + dir.bLink = FALSE; + dir.bUnsure = FALSE; + dir.date.hasdate = dir.date.hastime = FALSE; + dir.dir = FALSE; + dir.size = -2; + dir.name = str; + const char *fact = line + 1; + const char *nextfact = fact; + nextfact = strstr(nextfact, ","); + //if (nextfact && nextfact < str) + // *nextfact=0; + while (fact<=(str-2)) + { + int len; + if (!nextfact) + len = str - fact - 1; + else + len = nextfact - fact; + if (len == 1 && fact[0] == '/') + dir.dir = TRUE; + else if (*fact=='s') + dir.size = strntoi64(fact+1, len-1); + else if (*fact=='m') + { + __int64 rawtime = strntoi64(fact+1, len-1); + COleDateTime time((time_t)rawtime); + dir.date.hasdate = TRUE; + dir.date.hastime = TRUE; + dir.date.year = time.GetYear(); + dir.date.month = time.GetMonth(); + dir.date.day = time.GetDay(); + dir.date.hour = time.GetHour(); + dir.date.minute = time.GetMinute(); + } + else if (len == 5 && *fact=='u' && *(fact+1)=='p') + { + char buffer[4] = {0}; + memcpy(buffer, fact+2, len-2); + direntry.permissionstr = buffer; + } + if (!nextfact || nextfact>=(str-2)) + break; + fact = nextfact+1; + nextfact = strstr(nextfact+1, ","); +// if (nextfact && nextfact delim) + { + return FALSE; + } + + CString factname = facts.Left(pos); + factname.MakeLower(); + CString value = facts.Mid(pos + 1, delim - pos - 1); + // When adding new facts, update filter in CFtpControlSocket::LogOnToServer + // (CONNECT_FEAT state) + if (factname == L"type") + { + if (!value.CompareNoCase(L"dir")) + { + direntry.dir = TRUE; + } + // This is syntax used by proftpd by default + // http://www.proftpd.org/docs/modules/mod_facts.html + // They claim it's the correct one. + // See also + // http://www.rfc-editor.org/errata_search.php?rfc=3659&eid=1500 + else if (!value.Left(15).CompareNoCase(L"OS.unix=symlink")) + { + direntry.dir = TRUE; + direntry.bLink = TRUE; + // actually symlink target should not be included in this syntax, + // but just in case some servers do. + if ((value.GetLength() > 16) && (value[15] == ':')) + direntry.linkTarget = value.Mid(16); + } + // This is syntax shown in RFC 3659 section 7.7.4 "A More Complex Example" + // Type=OS.unix=slink:/foobar;Perm=;Unique=keVO1+4G4; foobar + // https://tools.ietf.org/html/rfc3659 + else if (!value.Left(13).CompareNoCase(L"OS.unix=slink")) + { + direntry.dir = TRUE; + direntry.bLink = TRUE; + if ((value.GetLength() > 14) && (value[13] == ':')) + direntry.linkTarget = value.Mid(14); + } + else if (!value.CompareNoCase(L"cdir")) + { + // ProFTPD up to 1.3.6rc1 and 1.3.5a incorrectly uses "cdir" for the current working directory. + // So at least in MLST, where this would be the only entry, we treat it like "dir". + if (mlst) + { + direntry.dir = TRUE; + } + else + { + return FALSE; + } + } + else if (!value.CompareNoCase(L"pdir")) + { + return FALSE; + } + } + else if (factname == L"size") + { + direntry.size = 0; + + for (unsigned int i = 0; i < value.GetLength(); ++i) + { + if (value[i] < '0' || value[i] > '9') + { + return FALSE; + } + direntry.size *= 10; + direntry.size += value[i] - '0'; + } + } + else if (factname == L"modify" || + (!direntry.date.hasdate && factname == L"create")) + { + if (!parseMlsdDateTime(value, direntry.date)) + { + return FALSE; + } + } + else if (factname == L"perm") + { + // there's no way we can convert Perm fact to unix-style permissions, + // so we at least present Perm as-is to a user + direntry.humanpermstr = value; + } + else if (factname == L"unix.mode") + { + direntry.permissionstr = value; + } + else if (factname == L"unix.owner" || factname == L"unix.user") + { + owner = value; + } + else if (factname == L"unix.group") + { + group = value; + } + else if (factname == L"unix.uid") + { + uid = value; + } + else if (factname == L"unix.gid") + { + gid = value; + } + + facts = facts.Mid(delim + 1); + } + + // The order of the facts is undefined, so assemble ownerGroup in correct + // order + if (!owner.IsEmpty()) + direntry.ownergroup += owner; + else if (!uid.IsEmpty()) + direntry.ownergroup += uid; + if (!group.IsEmpty()) + direntry.ownergroup += L" " + group; + else if (!gid.IsEmpty()) + direntry.ownergroup += L" " + gid; + + if (line[pos] != L' ') + { + return FALSE; + } + pos++; + CString fileName; + copyStr(fileName, 0, line + pos, linelen - pos, true); + CServerPath path(fileName, false); // do not trim + direntry.name = path.GetLastSegment(); + if (direntry.name.IsEmpty()) + { + direntry.name = fileName; + } + return TRUE; +} + +bool CFtpListResult::parseMlsdDateTime(const CString value, t_directory::t_direntry::t_date &date) const +{ + if (value.IsEmpty()) + { + return FALSE; + } + + bool result = FALSE; + int Year, Month, Day, Hours, Minutes, Seconds; + Year=Month=Day=Hours=Minutes=Seconds=0; + if (swscanf((LPCWSTR)value, L"%4d%2d%2d%2d%2d%2d", &Year, &Month, &Day, &Hours, &Minutes, &Seconds) == 6) + { + date.hasdate = TRUE; + date.hastime = TRUE; + date.hasseconds = TRUE; + result = TRUE; + } + else if (swscanf((LPCWSTR)value, L"%4d%2d%2d", &Year, &Month, &Day) == 3) + { + date.hasdate = TRUE; + date.hastime = FALSE; + result = TRUE; + } + if (result) + { + try + { + date.year = Year; + date.month = Month; + date.day = Day; + date.hour = Hours; + date.minute = Minutes; + date.second = Seconds; + date.utc = TRUE; + } + catch (CAtlException &) + { + date.hasdate = FALSE; + date.hastime = FALSE; + } + } + return result; +} + +BOOL CFtpListResult::parseAsUnix(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + //Check the first token + if (str[0] != 'b' && + str[0] != 'c' && + str[0] != 'd' && + str[0] != 'l' && + str[0] != 'p' && + str[0] != 's' && + str[0] != '-') + { + return FALSE; + } + + //First check if it is a netware server + bool bNetWare = false; + copyStr(direntry.permissionstr, 0, str, tokenlen); + if (tokenlen == 1) + { + //Yes, it's most likely a netware server + //Now get the full permission string + bNetWare = true; + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + direntry.permissionstr += L" "; + copyStr(direntry.permissionstr, direntry.permissionstr.GetLength(), str, tokenlen); + } + + //Set directory and link flags + //Always assume links point directories + //GUI frontend should try to figure out + //to where the link really points + if (direntry.permissionstr[0]==L'd' || direntry.permissionstr[0]==L'l') + direntry.dir = true; + else + direntry.dir = false; + + if (direntry.permissionstr[0]==L'l') + direntry.bLink = true; + else + direntry.bLink = false; + + bool bNetPresenz = false; + if (!bNetWare) //On non-netware servers, expect at least two unused tokens + { + bool groupid = false; + // Unused param + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + if (tokenlen == 6 && !strncmp(str, "folder", tokenlen) && direntry.dir) + bNetPresenz = true; //Assume NetPresenz server + //However, it's possible that we mark a non-NetPresenz + //server if the fileowner is "folder" + else if (!IsNumeric(str, tokenlen)) + { + // Check for Connect:Enterprise server + if (direntry.permissionstr.GetLength() > 3 && + (direntry.permissionstr.Right(3) == L"FTP" || + direntry.permissionstr.Right(3) == L"FTS" || + direntry.permissionstr.Right(3) == L"TCP" || + direntry.permissionstr.Right(3) == L"SSH")) + groupid = TRUE; + + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen); + } + else + groupid = TRUE; + + if (!bNetPresenz && groupid) + { + //Unused param + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen); + } + } + + //Skip param, may be used for size + int skippedlen = 0; + const char *skipped = GetNextToken(line, linelen,skippedlen, pos, 0); + if (!skipped) + { + return FALSE; + } + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + + // Keep parsing the information until we get a numerical string, + // because some broken servers may send 3 tags: domain/network, group and user + const char *prevstr = 0; + int prevstrlen = 0; + + __int64 tmp = 0; + while (str && !ParseSize(str, tokenlen, tmp) && !IsNumeric(skipped, skippedlen)) + { + //Maybe the server has left no space between the group and the size + //because of stupid alignment + char *tmpstr = static_cast(nb_calloc(1, tokenlen + 1)); + strncpy(tmpstr, str, tokenlen); + tmpstr[tokenlen] = 0; + strlwr(tmpstr); + + USES_CONVERSION; + std::map::const_iterator iter = m_MonthNamesMap.find(A2T(tmpstr)); + nb_free(tmpstr); + if (iter != m_MonthNamesMap.end()) + { + BOOL bRightNumeric = true; + if (skipped[skippedlen-1]<'0' || skipped[skippedlen-1]>'9') + bRightNumeric = false; + + if (bRightNumeric) + break; + + bRightNumeric = true; + if (prevstr && prevstrlen) + { + if (prevstr[prevstrlen-1]<'0' || prevstr[prevstrlen-1]>'9') + bRightNumeric = false; + + if (bRightNumeric) + { + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen); + skipped = prevstr; + skippedlen = prevstrlen; + break; + } + } + } + if (prevstr) + { + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), prevstr, prevstrlen); + } + prevstr = str; + prevstrlen = tokenlen; + str = GetNextToken(line, linelen, tokenlen, pos, 0); + } + + if (!str) + { + return FALSE; + } + + const char *size = str; + int sizelen = tokenlen; + if (!ParseSize(str, tokenlen, direntry.size)) + { + //Maybe we've skipped too much tokens + if (!ParseSize(skipped, skippedlen, direntry.size)) + { + //Maybe the server has left no space between the group and the size + //because of stupid alignment + bool bRightNumeric = true; + const char *pos; + for (pos=(str+tokenlen-1); pos > str; pos--) + { + if (*pos<'0' || *pos>'9') + { + if (pos==(str+tokenlen-1)) + bRightNumeric=false; + break; + } + } + if (bRightNumeric && pos>str) + { + size = pos + 1; + sizelen = pos - str; + direntry.ownergroup += L" "; + + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, pos-str); + + } + else + { + for (pos=(skipped+skippedlen-1); pos > skipped; pos--) + { + if (*pos<'0' || *pos>'9') + { + if (pos==(skipped+skippedlen-1)) + { + return false; + } + break; + } + } + size = pos + 1; + sizelen = skippedlen + skipped - size; + + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), skipped, skippedlen - sizelen); + + } + direntry.size = strntoi64(size, sizelen); + } + else + { + //We should've not skipped the last token + //This also fixes the problem with the NetPresenz detection + if (bNetPresenz && direntry.dir && direntry.ownergroup != L"") + { + direntry.ownergroup = L"folder " + direntry.ownergroup; + } + } + } + else + { + if (direntry.ownergroup != L"") + direntry.ownergroup += L" "; + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), skipped, skippedlen); + if (prevstr) + { + direntry.ownergroup += L" "; + copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), prevstr, prevstrlen); + } + str = 0; + } + + //Month + if (!str) + str = GetNextToken(line, linelen, tokenlen, pos, 0); + + if (!str) + { + return FALSE; + } + + const char *smonth = str; + int smonthlen = tokenlen; + direntry.date.year = 0; + + //Day + const char *sday = 0; + int sdaylen = 0; + + // Some VShell server send both the year and the time, try to detect them + BOOL bCouldBeVShell = FALSE; + + //Some servers use the following date formats: + // 26-09 2002, 2002-10-14, 01-jun-99 + const char *p = strnchr(smonth, smonthlen, '-'); + if (p) + { + int plen = smonthlen - (p - smonth); + const char *pos2 = strnchr(p+1, plen - 1, '-'); + if (!pos2) //26-09 2002 + { + sday = p + 1; + sdaylen = plen - 1; + smonthlen = p-smonth; + } + else if (p-smonth == 4) //2002-10-14 + { + direntry.date.year = static_cast(strntoi64(smonth, p-smonth)); + sday = pos2 + 1; + sdaylen = smonthlen - (pos2 - smonth) - 1; + smonthlen = pos2-smonth - (p-smonth) - 1; + smonth = p + 1; + /* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd + * Unfortunately we have to guess which one is the right if + * the month is < 12 + */ + if (strntoi64(smonth, smonthlen) > 12) + { + const char *tmp = smonth; + smonth = sday; + sday = tmp; + int tmplen = smonthlen; + smonthlen = sdaylen; + sdaylen = tmplen; + } + } + else if (p-smonth) //14-10-2002 or 01-jun-99 + { + direntry.date.year = static_cast(strntoi64(pos2+1, tokenlen - (pos2-smonth) - 1)); + sday = smonth; + sdaylen = p - smonth; + smonthlen = pos2-smonth - (p-smonth) - 1; + smonth = p + 1; + /* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd + * Unfortunately we have to guess which one is the right if + * the month is < 12 + */ + if (strntoi64(smonth, smonthlen) > 12) + { + const char *tmp = smonth; + smonth = sday; + sday = tmp; + int tmplen = smonthlen; + smonthlen = sdaylen; + sdaylen = tmplen; + } + } + else + { + return FALSE; + } + } + /* Some servers use the following date formats: + * yyyy/dd/mm, yyyy/mm/dd, dd/mm/yyyy, mm/dd/yyyy + * try to detect them. + */ + else if (strnchr(smonth, smonthlen, '/')) + { + const char *p = strnchr(smonth, smonthlen, '/'); + int plen = smonthlen - (p - smonth); + const char *pos2 = strnchr(p+1, plen - 1, '/'); + if (!pos2) //Assume 26/09 2002 + { + sday = p + 1; + sdaylen = plen - 1; + smonthlen = p-smonth; + } + else if (p-smonth==4) + { + direntry.date.year = static_cast(strntoi64(smonth, p-smonth)); + sday = pos2 + 1; + sdaylen = smonthlen - (pos2 - smonth) - 1; + smonthlen = pos2-smonth - (p-smonth) - 1; + smonth = p + 1; + /* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd + * Unfortunately we have to guess which one is the right if + * the month is < 12 + */ + if (strntoi64(smonth, smonthlen) > 12) + { + const char *tmp = smonth; + smonth = sday; + sday = tmp; + int tmplen = smonthlen; + smonthlen = sdaylen; + sdaylen = tmplen; + } + } + else if (p-smonth==2) + { + direntry.date.year = static_cast(strntoi64(pos2+1, tokenlen - (pos2-smonth) - 1)); + sday = smonth; + sdaylen = p - smonth; + smonthlen = pos2-smonth - (p-smonth) - 1; + smonth = p + 1; + /* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd + * Unfortunately we have to guess which one is the right if + * the month is < 12 + */ + if (strntoi64(smonth, smonthlen) > 12) + { + const char *tmp = smonth; + smonth = sday; + sday = tmp; + int tmplen = smonthlen; + smonthlen = sdaylen; + sdaylen = tmplen; + } + } + else + { + return FALSE; + } + } + else + { + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + + sday = str; + sdaylen = tokenlen; + + if (sdaylen && sday[sdaylen-1] == ',') + { + sdaylen--; + bCouldBeVShell = TRUE; + } + + //Trim trailing characters + while (sdaylen && (sday[sdaylen-1]=='.' || sday[sdaylen-1]==',')) + { + bCouldBeVShell = FALSE; + sdaylen--; + } + + int i; + for (i = 0; i < sdaylen; i++) + if (sday[i] < '0' || sday[i] > '9') + break; + if (i && i < sdaylen) + { + if ((uint8_t)sday[i] > 127) + sdaylen = i; + } + + if (!sdaylen) + { + return FALSE; + } + } + + if (!strntoi64(sday, sdaylen)) //Day field invalid + { //Maybe the server is sending a directory listing with localized date format. + //Try to fix this really bad behavior + bCouldBeVShell = FALSE; + const char *tmp = smonth; + smonth = sday; + sday = tmp; + int tmplen = smonthlen; + smonthlen = sdaylen; + sdaylen = tmplen; + } + + //Time/Year + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + + //Trim trailing characters + while (smonthlen && (smonth[smonthlen-1]=='.' || smonth[smonthlen-1]==',')) + smonthlen--; + + if (!smonthlen) + { + return FALSE; + } + + char *lwr = static_cast(nb_calloc(1, smonthlen + 1)); + memcpy(lwr, smonth, smonthlen); + lwr[smonthlen] = 0; + _strlwr(lwr); + + bool gotYear = false; + int year = (int)strntoi64(smonth, smonthlen); + if (year > 1000) + { + gotYear = true; + direntry.date.year = year; + } + else + { + //Try if we can recognize the month name + USES_CONVERSION; + std::map::const_iterator iter = m_MonthNamesMap.find(A2T(lwr)); + nb_free(lwr); + if (iter == m_MonthNamesMap.end()) + { + int i; + for (i = 0; i < smonthlen; i++) + if (smonth[i] < '0' || smonth[i] > '9') + break; + if (!i || i == smonthlen) + { + return false; + } + if ((uint8_t)smonth[i] < 128) + { + return false; + } + + smonthlen = i; + direntry.date.month = (int)strntoi64(smonth, smonthlen); + if (!direntry.date.month || direntry.date.month > 12) + { + return false; + } + } + else + direntry.date.month = iter->second; + } + + if (!gotYear) + { + direntry.date.day = static_cast(strntoi64(sday, sdaylen)); + if (direntry.date.day < 1 || direntry.date.day > 31) + { + return false; + } + } + else + { + direntry.date.month = static_cast(strntoi64(sday, sdaylen)); + if (direntry.date.month < 1 || direntry.date.month > 12) + { + return false; + } + } + + const char *stimeyear = str; + int stimeyearlen = tokenlen; + + //Parse the time/year token + const char *strpos = strnchr(stimeyear, stimeyearlen, ':'); + if (!strpos) + strpos = strnchr(stimeyear, stimeyearlen, '.'); + if (!strpos) + strpos = strnchr(stimeyear, stimeyearlen, '-'); + if (strpos) + { + //stimeyear has delimiter, so it's a time + direntry.date.hour = static_cast(strntoi64(stimeyear, strpos - stimeyear)); + int stimeyearrem = stimeyearlen - (strpos - stimeyear) - 1; + const char *strpos2 = strnchr(strpos + 1, stimeyearrem, ':'); + if (strpos2 == NULL) + { + direntry.date.minute = static_cast(strntoi64(strpos + 1, stimeyearrem)); + } + else + { + direntry.date.minute = static_cast(strntoi64(strpos + 1, strpos2 - strpos - 1)); + direntry.date.second = static_cast(strntoi64(strpos2 + 1, stimeyearlen - (strpos2 - stimeyear) - 1)); + direntry.date.hasseconds = TRUE; + } + direntry.date.hastime = TRUE; + + //Problem: Some servers use times only for files newer than 6 months, + //others use one year as limit. IIS shows time for files from the current year (jan-dec). + //So there is no support for files with time + //dated in the near future. Under normal conditions there should not be such files. + if (!direntry.date.year) + { + CTime curtime = CTime::GetCurrentTime(); + int curday = curtime.GetDay(); + int curmonth = curtime.GetMonth(); + int curyear = curtime.GetYear(); + int now = curmonth*31+curday; + int file = direntry.date.month*31+direntry.date.day; + if ((now+1)>=file) + direntry.date.year = curyear; + else + direntry.date.year = curyear-1; + } + bCouldBeVShell = FALSE; + } + else + { + if (gotYear) + { + direntry.date.day = static_cast(strntoi64(stimeyear, stimeyearlen)); + if (direntry.date.day < 1 || direntry.date.day > 31) + { + return false; + } + } + else if (!direntry.date.year) + { + //No delimiters -> year + + direntry.date.hastime = FALSE; + direntry.date.year = static_cast(strntoi64(stimeyear, stimeyearlen)); + } + else + { + // File has no time token and short date format + pos -= stimeyearlen; + } + } + + if (!direntry.date.year) //Year 0? Really ancient file, this is invalid! + { + return FALSE; + } + + // Check if server could still be one of the newer VShell servers + if (bCouldBeVShell) + { + int oldpos = pos; + + //Get time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + { + return FALSE; + } + + const char *p = strnchr(str, tokenlen, ':'); + if (pos && (p - str) == 2 && IsNumeric(str, 2) && IsNumeric(str + 3, 2)) + { + //stimeyear has delimiter, so it's a time + int hour = static_cast(strntoi64(str, 2)); + int minute = static_cast(strntoi64(str + 3, 2)); + + if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60) + { + direntry.date.hour = hour; + direntry.date.minute = minute; + direntry.date.hastime = TRUE; + } + else + pos = oldpos; + } + else + pos = oldpos; + } + + //Get filename + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + { + return FALSE; + } + + //Trim link data from filename + if (direntry.bLink) + { + const char *pos = strnstr(str, tokenlen, " -> "); + if (pos) + { + copyStr(direntry.linkTarget, 0, pos + 4, tokenlen - (pos - str) - 4); + tokenlen = pos - str; + } + + if (!tokenlen) + { + return FALSE; + } + } + + //Trim indicators, some server add those to mark special files + if (str[tokenlen - 1] == '*' || + str[tokenlen - 1] == '/' || +// str[tokenlen - 1] == '=' || //Don't trim this char, it would cause problems on certain servers + //This char just marks sockets, so it will never appear as indicator + //However it is valid as character for filenames on some systems + str[tokenlen - 1] == '|') + tokenlen--; + + copyStr(direntry.name, 0, str, tokenlen, true); + + direntry.bUnsure = FALSE; + direntry.date.hasdate = TRUE; + + return TRUE; +} + +BOOL CFtpListResult::parseAsDos(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + //Check the first token + if (str[0]=='b' || + str[0]=='c' || + str[0]=='d' || + str[0]=='l' || + str[0]=='p' || + str[0]=='s' || + str[0]=='-') + return FALSE; + + if (IsNumeric(str, tokenlen)) + return FALSE; + + //It's a NT server with MSDOS directory format + + if (!ParseShortDate(str, tokenlen, direntry.date)) + return FALSE; + + //Extract time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!parseTime(str, tokenlen, direntry.date)) + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (tokenlen == 5 && !memcmp(str, "", 5)) + { + direntry.dir = TRUE; + } + else + { + char * buffer = static_cast(nb_calloc(1, tokenlen)); + int i, j; + for (i = 0, j = 0; i < tokenlen; i++) + { + if (str[i] != ',') + buffer[j++] = str[i]; + } + direntry.dir = FALSE; + direntry.size = strntoi64(buffer, j); + nb_free(buffer); + } + + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + copyStr(direntry.name, 0, str, tokenlen, true); + + direntry.bUnsure = FALSE; + + return TRUE; +} + +BOOL CFtpListResult::parseAsOther(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //Check the first token + if (str[0]=='b' || + str[0]=='c' || + str[0]=='d' || + str[0]=='l' || + str[0]=='p' || + str[0]=='s' || + str[0]=='-') + return FALSE; + + if (!IsNumeric(str, tokenlen)) + return FALSE; + + //Could be numerical Unix style format or VShell format + //or even an OS2 server + + const char *skipped = str; + int skippedtokenlen = tokenlen; + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //If next token is numerical, than it's the numerical Unix style format, + //else it's the VShell or OS/2 format + if (IsNumeric(str, tokenlen)) + { + copyStr(direntry.permissionstr, 0, skipped, skippedtokenlen); + + if (skippedtokenlen >= 2 && skipped[1] == '4') + direntry.dir = TRUE; + else + direntry.dir = FALSE; + + //Unused token + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //Size + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + direntry.size = strntoi64(str, tokenlen); + + //Date/Time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + time_t secsSince1970 = static_cast(strntoi64(str, tokenlen)); + tm *sTime = gmtime(&secsSince1970); + direntry.date.year = sTime->tm_year + 1900; + direntry.date.month = sTime->tm_mon+1; + direntry.date.day = sTime->tm_mday; + direntry.date.hour = sTime->tm_hour; + direntry.date.minute = sTime->tm_min; + direntry.date.hasdate = direntry.date.hastime = TRUE; + + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + copyStr(direntry.name, 0, str, tokenlen, true); + } + else + { + //Get size + direntry.size = strntoi64(skipped, skippedtokenlen); + + //Get date, month first + if (tokenlen >= 15) + return FALSE; + + char buffer[15] = {0}; + memcpy(buffer, str, tokenlen); + strlwr(buffer); + + USES_CONVERSION; + std::map::const_iterator iter = m_MonthNamesMap.find(A2T(buffer)); + if (iter == m_MonthNamesMap.end()) + { + direntry.dir = FALSE; + while (str) + { + //Could be an OS/2 server + if (tokenlen == 3 && !memcmp(str, "dir", 3)) + direntry.dir = TRUE; + else if (tokenlen == 3 && !memcmp(str, "DIR", 3)) + direntry.dir = TRUE; + else if (strnchr(str, tokenlen, '-')) + break; + str = GetNextToken(line, linelen, tokenlen, pos, 0); + } + if (!str) + return FALSE; + + if (!ParseShortDate(str, tokenlen, direntry.date)) + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //Parse the time token + const char *strpos = strnchr(str, tokenlen, ':'); + if (!strpos) + return FALSE; + if (strpos) + { + //stimeyear has delimiter, so it's a time + direntry.date.hour = static_cast(strntoi64(str, strpos - str)); + direntry.date.minute = static_cast(strntoi64(strpos+1, tokenlen - (strpos - str) - 1)); + direntry.date.hastime = TRUE; + + //Problem: Some servers use times only for files newer than 6 months, + //others use one year as limit. So there is no support for files with time + //dated in the near future. Under normal conditions there should not be such files + if (!direntry.date.year) + { + CTime curtime = CTime::GetCurrentTime(); + int curday = curtime.GetDay(); + int curmonth = curtime.GetMonth(); + int curyear = curtime.GetYear(); + int now = curmonth*31+curday; + int file = direntry.date.month*31+direntry.date.day; + if ((now+1)>=file) + direntry.date.year = curyear; + else + direntry.date.year = curyear-1; + } + } + + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + + if (tokenlen > 6) + { + if (!strnicmp(str + tokenlen - 5, "", 5)) + { + direntry.dir = TRUE; + tokenlen -= 5; + while (tokenlen && str[tokenlen - 1] == ' ' || str[tokenlen - 1] == '\t') + tokenlen--; + } + if (!tokenlen) + return FALSE; + } + + + copyStr(direntry.name, 0, str, tokenlen, true); + } + else + { + direntry.date.month = iter->second; + + //Day + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (str[tokenlen-1]==',') + tokenlen--; + if (!tokenlen) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + direntry.date.day = static_cast(strntoi64(str, tokenlen)); + if (direntry.date.day < 1 || direntry.date.day > 31) + return FALSE; + + //Year + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!IsNumeric(str, tokenlen)) + return FALSE; + + direntry.date.year = static_cast(strntoi64(str, tokenlen)); + if (direntry.date.year < 50) + direntry.date.year += 2000; + else if (direntry.date.year < 1000) + direntry.date.year += 1900; + + direntry.date.hasdate = TRUE; + + //Now get the time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + const char *p = strnchr(str, tokenlen, ':'); + if (!p) + return FALSE; + + if (p==str || !IsNumeric(str, p-str) || (p-str + 1) >= tokenlen || !IsNumeric(p+1, tokenlen - (p-str) - 1)) + return FALSE; + direntry.date.hour = static_cast(strntoi64(str, p-str)); + direntry.date.minute = static_cast(strntoi64(p+1, tokenlen - (p-str) - 1)); + + if (direntry.date.hour < 0 || direntry.date.hour > 24) + return FALSE; + if (direntry.date.minute < 0 || direntry.date.minute > 59) + return FALSE; + + direntry.date.hastime = TRUE; + + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + + if (tokenlen > 1 && (str[tokenlen-1] == '\\' || str[tokenlen-1] == '/')) + { + direntry.dir = TRUE; + tokenlen--; + } + else + direntry.dir = FALSE; + + if (!tokenlen) + return FALSE; + copyStr(direntry.name, 0, str, tokenlen, true); + } + } + + direntry.bUnsure = FALSE; + direntry.date.hasdate = TRUE; + + return TRUE; +} + +_int64 CFtpListResult::strntoi64(const char *str, int len) const +{ + _int64 res = 0; + const char *p = str; + while ((p-str) < len) + { + if (*p < '0' || *p > '9') + break; + res *= 10; + res += *p++ - '0'; + } + return res; +} + +const char *CFtpListResult::GetNextToken(const char *line, const int linelen, int &len, int &pos, int type) const +{ + const char *p = line + pos; + if ((p - line) >= linelen) + return NULL; + while ((p - line) < linelen && (!p || *p==' ' || *p=='\t')) + p++; + + if ((p - line) >= linelen) + return NULL; + + const char *res = p; + + if (type) + { + pos = linelen; + len = linelen - (p - line); + } + else + { + while ((p - line) < linelen && *p && *p != ' ' && *p!='\t') + p++; + + len = p - res; + pos = p - line; + } + + return res; +} + +const char * CFtpListResult::strnchr(const char *str, int len, char c) const +{ + if (!str) + return NULL; + + const char *p = str; + while (len > 0) + { + if (!*p) + return NULL; + if (*p == c) + return p; + p++; + len--; + } + return NULL; +} + +const char * CFtpListResult::strnstr(const char *str, int len, const char *c) const +{ + if (!str) + return NULL; + if (!c) + return NULL; + int clen = strlen(c); + + const char *p = str; + while (len > 0) + { + if (!*p) + return NULL; + if (*p == *c) + { + if (clen == 1) + return p; + else if (len >= clen) + { + if (!memcmp(p + 1, c+1, clen-1)) + return p; + } + else + return NULL; + } + p++; + len--; + } + return NULL; +} + +void CFtpListResult::copyStr(CString &target, int pos, const char *source, int len, bool mayInvalidateUTF8 /*=false*/) +{ + USES_CONVERSION; + + char *p = static_cast(nb_calloc(1, len + 1)); + memcpy(p, source, len); + p[len] = '\0'; + if (m_bUTF8 && *m_bUTF8) + { + // convert from UTF-8 to ANSI + if (DetectUTF8Encoding(RawByteString(p, len)) == etANSI) + { + if (mayInvalidateUTF8 && m_server.nUTF8 != 1) + { + LogMessage(FZ_LOG_WARNING, L"Server does not send proper UTF-8, falling back to local charset"); + *m_bUTF8 = false; + } + target = target.Left(pos) + A2CT(p); + } + else + { + // convert from UTF-8 to ANSI + int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)p, -1, NULL, 0); + if (len != 0) + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)p, -1 , (LPWSTR)p1, len + 1); + target = target.Left(pos) + W2CT(p1); + nb_free(p1); + } + else + target = target.Left(pos) + A2CT(p); + } + } + else if (m_nCodePage && *m_nCodePage) + { + // convert to ANSI + int len = MultiByteToWideChar(*m_nCodePage, 0, (LPCSTR)p, -1, NULL, 0); + if (len != 0) + { + LPWSTR p1 = static_cast(nb_calloc(len + 1, sizeof(WCHAR))); + MultiByteToWideChar(*m_nCodePage, 0, (LPCSTR)p, -1 , (LPWSTR)p1, len + 1); + target = target.Left(pos) + W2CT(p1); + nb_free(p1); + } + else + target = target.Left(pos) + A2CT(p); + } + else + target = target.Left(pos) + A2CT(p); + nb_free(p); +} + +BOOL CFtpListResult::parseAsIBM(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + copyStr(direntry.ownergroup , 0, str, tokenlen); + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!IsNumeric(str, tokenlen)) + return FALSE; + + direntry.size = strntoi64(str, tokenlen); + + //Date + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!ParseShortDate(str, tokenlen, direntry.date)) + return FALSE; + + //Time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!parseTime(str, tokenlen, direntry.date)) + return FALSE; + + //Unused Token + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //Name + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + + if (str[tokenlen-1] == '/') + { + direntry.dir = TRUE; + if (!--tokenlen) + return FALSE; + } + else + direntry.dir = FALSE; + + copyStr(direntry.name, 0, str, tokenlen, true); + + direntry.bUnsure = FALSE; + + return true; +} + +BOOL CFtpListResult::parseAsIBMMVS(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + // volume + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + // unit + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + //Referred Date + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!ParseShortDate(str, tokenlen, direntry.date)) + { + // Perhaps of the following type: + // TSO004 3390 VSAM FOO.BAR + if (tokenlen != 4 || strncmp(str, "VSAM", tokenlen)) + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 1); + if (!str) + return FALSE; + + if (strnchr(str, tokenlen, ' ')) + return FALSE; + + copyStr(direntry.name, 0, str, tokenlen, true); + direntry.dir = false; + return true; + } + + // ext + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + int prevLen = tokenlen; + + // used + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (IsNumeric(str, tokenlen)) + { + // recfm + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (IsNumeric(str, tokenlen)) + return false; + } + else + { + if (prevLen < 6) + return false; + } + + // lrecl + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + // blksize + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + // dsorg + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (tokenlen == 2 && !memcmp(str, "PO", 2)) + { + direntry.dir = TRUE; + } + else + { + direntry.dir = FALSE; + direntry.size = 100; + } + + // name of dataset or sequential name + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + copyStr(direntry.name, 0, str, tokenlen, true); + + direntry.bUnsure = FALSE; + + return true; +} + +BOOL CFtpListResult::parseAsIBMMVSPDS(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + // pds member name + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + copyStr(direntry.name, 0, str, tokenlen); + + // vv.mm + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + // creation date + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + // change date + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!ParseShortDate(str, tokenlen, direntry.date)) + return FALSE; + + // change time + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!parseTime(str, tokenlen, direntry.date)) + return FALSE; + + // size + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + direntry.size = strntoi64(str, tokenlen); + + // init + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + // mod + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (!IsNumeric(str, tokenlen)) + return FALSE; + + // id + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + direntry.dir = FALSE; + direntry.bUnsure = FALSE; + + return true; +} + +BOOL CFtpListResult::parseAsIBMMVSPDS2(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + // Use this one only on MVS servers, as it will cause problems on other servers + +#ifndef LISTDEBUG + if (!(m_server.nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_BS2000))) + return false; +#endif + + int pos = 0; + int tokenlen = 0; + + direntry.bUnsure = FALSE; + direntry.dir = FALSE; + direntry.bLink = FALSE; + direntry.ownergroup = L""; + direntry.permissionstr = L""; + direntry.date.hasdate = direntry.date.hastime = FALSE; + + // pds member name + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_BS2000 && + IsNumeric(str, tokenlen)) + { + int prevlen = tokenlen; + const char* prev = str; + int oldpos = pos; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + if (str[0] != ':') + { + str = prev; + tokenlen = prevlen; + pos = oldpos; + } + } + + copyStr(direntry.name, 0, str, tokenlen); + + if (m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_BS2000) + { + int pos = direntry.name.ReverseFind(L':'); + if (pos != -1) + direntry.name = direntry.name.Mid(pos + 1); + if (direntry.name[0] == L'$') + { + int pos = direntry.name.Find(L'.'); + if (pos != -1) + direntry.name = direntry.name.Mid(pos + 1); + } + if (direntry.name == L"") + return FALSE; + } + + // Hexadecimal filesize + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + // We can return true since sometimes MVS servers send pure filenames + return true; + + direntry.size = 0; + const char *end = str + tokenlen; + const char *p = str; + while (p < end) + { + direntry.size *= 16; + if (*p >= '0' && *p <= '9') + direntry.size += *p - '0'; + else if (*p >= 'A' && *p <= 'F') + direntry.size += *p - 'A' + 10; + else + return false; + p++; + } + + // Unused hexadecimal token + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return false; + + end = str + tokenlen; + p = str; + while (p < end) + { + if ((*p < '0' || *p > '9') && (*p < 'A' || *p > 'F')) + return false; + p++; + } + + // Unused token + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return false; + if (!IsNumeric(str, tokenlen)) + return false; + + const char* prevprev = 0; + const char* prev = 0; + int prevprevlen = 0; + int prevlen = 0; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return false; + while (str) + { + if (prevprev) + { + for (int i = 0; i < prevprevlen; i++) + if (prevprev[i] < 'A' || prevprev[i] > 'Z') + return false; + } + prevprev = prev; + prevprevlen = prevlen; + prev = str; + prevlen = tokenlen; + str = GetNextToken(line, linelen, tokenlen, pos, 0); + } + if (!prev || !prevprev) + return false; + + if (!IsNumeric(prev, prevlen) && (prevlen != 3 || memcmp(prev, "ANY", 3))) + return false; + + if (!IsNumeric(prevprev, prevprevlen) && (prevprevlen != 3 || memcmp(prevprev, "ANY", 3))) + return false; + + return true; +} + +bool CFtpListResult::parseTime(const char *str, int len, t_directory::t_direntry::t_date &date) const +{ + int i = 0; + //Extract the hour + date.hastime = TRUE; + date.hour = 0; + while (str[i] != ':') + { + if (str[i] < '0' || str[i] > '9') + return false; + date.hour *= 10; + date.hour += str[i] - '0'; + if (date.hour > 24) + return false; + + i++; + if (i == len) + return false; + } + // Make sure we did read at least one digit + if (!i) + return false; + + i++; + + if (i == len) + return false; + + //Extract the minute + date.minute = 0; + while (str[i] >= '0' && str[i] <= '9') + { + date.minute *= 10; + date.minute += str[i] - '0'; + if (date.minute > 59) + return false; + + i++; + if (i == len) + break; + } + + //Convert to 24h format + //I really wish we would have the following system: + //one year->ten months->ten days->ten hours->ten minutes->ten seconds and so on... + //I should modifiy the earth rotation to force everyone to use this system *g* + if (i != len) + { + if (str[i]=='P') + { + if (date.hour < 12) + date.hour += 12; + } + else + if (date.hour == 12) + date.hour = 0; + } + + date.hastime = TRUE; + + return true; +} + +BOOL CFtpListResult::parseAsWfFtp(const char *line, const int linelen, t_directory::t_direntry &direntry) +{ + int pos = 0; + int tokenlen = 0; + + const char *str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + copyStr(direntry.name, 0, str, tokenlen); + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!IsNumeric(str, tokenlen)) + return FALSE; + + direntry.size = strntoi64(str, tokenlen); + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!ParseShortDate(str, tokenlen, direntry.date)) + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (str[tokenlen - 1] != '.') + return FALSE; + + str = GetNextToken(line, linelen, tokenlen, pos, 0); + if (!str) + return FALSE; + + if (!parseTime(str, tokenlen, direntry.date)) + return FALSE; + + direntry.bUnsure = FALSE; + direntry.dir = false; + direntry.bLink = false; + direntry.permissionstr = L""; + direntry.ownergroup = L""; + + return TRUE; +} + +bool CFtpListResult::ParseSize(const char* str, int len, __int64 &size) const +{ + if (len < 1) + return false; + + if (IsNumeric(str, len)) + { + size = strntoi64(str, len); + return true; + } + + size = 0; + char last = str[--len]; + + int delimiter = -1; + for (int i = 0; i < len; i++) + { + char c = str[i]; + if (c >= '0' && c <= '9') + { + size *= 10; + size += c - '0'; + } + else if (c == '.') + delimiter = len - i; + else + return false; + } + + // Check for digit before or after delimiterr + if (!delimiter || delimiter == len) + return false; + + + switch (last) + { + case 'k': + case 'K': + size *= 1 << 10; + break; + case 'm': + case 'M': + size *= 1 << 20; + break; + case 'g': + case 'G': + size *= 1 << 30; + break; + case 't': + case 'T': + size *= static_cast<__int64>(1) << 40; + break; + default: + return false; + } + + if (delimiter == -1) + return true; + + while (delimiter--) + size /= 10; + + return true; +} diff --git a/netbox/src/filezilla/FtpListResult.h b/netbox/src/filezilla/FtpListResult.h new file mode 100644 index 000000000..c36b5c11c --- /dev/null +++ b/netbox/src/filezilla/FtpListResult.h @@ -0,0 +1,91 @@ + +#pragma once + +/*This class parses the directory listing returned from the server. These formats are supported: +-rw-r--r-- 1 root other 531 Jan 29 03:26 README\r\n +dr-xr-xr-x 2 root other 512 Apr 8 1994 etc\r\n +dr-xr-xr-x 2 root 512 Apr 8 1994 etc\r\n +lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin\r\n +---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z\r\n +d--------- 1 owner group 0 May 9 19:45 Softlib\r\n +-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp\r\n +d [R----F--] supervisor 512 Jan 16 18:53 login\r\n +- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe\r\n +-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit\r\n +drwxrwxr-x folder 2 May 10 1996 network\r\n +00README.TXT;1 2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)\r\n +CORE.DIR;1 1 8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)\r\n +CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)\r\n +04-27-00 09:09PM licensed\r\n +07-18-00 10:16AM pub\r\n +04-14-00 03:47PM 589 readme.htm\r\n + +Multiple spaces are ignored (except within filenames), a single LF character at the end is also supported as well as multiple +CRLF pairs and other variants. +*/ + +#include "ApiLog.h" + +class CFtpListResult : public CApiLog +{ +public: + t_server m_server; + void SendToMessageLog(); + void AddData(char * data, int size); + CFtpListResult(t_server server, bool * bUTF8 = 0, int * nCodePage = 0); + virtual ~CFtpListResult(); + t_directory::t_direntry * getList(int & num, bool mlst); + +private: + typedef std::list tEntryList; + tEntryList m_EntryList; + + BOOL parseLine(const char * lineToParse, const int linelen, t_directory::t_direntry & direntry, int & nFTPServerType, bool mlst); + + BOOL parseAsVMS(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsEPLF(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsMlsd(const char * line, const int linelen, t_directory::t_direntry & direntry, bool mlst); + BOOL parseAsUnix(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsDos(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsOther(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsIBM(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsIBMMVS(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsIBMMVSPDS(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsIBMMVSPDS2(const char * line, const int linelen, t_directory::t_direntry & direntry); + BOOL parseAsWfFtp(const char * line, const int linelen, t_directory::t_direntry & direntry); + + const char * GetNextToken(const char * line, const int linelen, int & len, int & pos, int type) const; + + bool ParseShortDate(const char * str, int len, t_directory::t_direntry::t_date & date) const; + bool parseTime(const char * str, int len, t_directory::t_direntry::t_date & date) const; + bool ParseSize(const char * str, int len, __int64 & size) const; + + bool parseMlsdDateTime(const CString value, t_directory::t_direntry::t_date & date) const; + + int pos; + struct t_list : public TObject + { + char * buffer; + int len; + t_list * next; + } * listhead, * curpos, * m_curlistaddpos; + + typedef std::list tTempData; + tTempData m_TempData; + + // Month names map + std::map m_MonthNamesMap; + +protected: + bool * m_bUTF8; + int * m_nCodePage; + void copyStr(CString & target, int pos, const char * source, int len, bool mayInvalidateUTF8 = false); + const char * strnchr(const char * str, int len, char c) const; + const char * strnstr(const char * str, int len, const char * c) const; + _int64 strntoi64(const char * str, int len) const; + void AddLine(t_directory::t_direntry & direntry); + char * GetLine(); + bool IsNumeric(const char * str, int len) const; + char * m_prevline; + char * m_curline; +}; diff --git a/netbox/src/filezilla/FzApiStructures.cpp b/netbox/src/filezilla/FzApiStructures.cpp new file mode 100644 index 000000000..3a8784de5 --- /dev/null +++ b/netbox/src/filezilla/FzApiStructures.cpp @@ -0,0 +1,87 @@ + +#include "stdafx.h" +#include "FzApiStructures.h" + +t_server::t_server() +{ + port = 0; + nServerType = 0; + nPasv = 0; + nTimeZoneOffset = 0; + nUTF8 = 0; + nCodePage = 0; + iForcePasvIp = -1; + iUseMlsd = -1; + iDupFF = 0; + iUndupFF = 0; + Certificate = NULL; + PrivateKey = NULL; +} + +t_server::~t_server() +{ +} + +const bool operator == (const t_server & a, const t_server & b) +{ + if (a.host!=b.host) + return false; + if (a.port!=b.port) + return false; + if (a.user!=b.user) + return false; + if (a.account != b.account) + return false; + if (a.pass!=b.pass && a.user!=L"anonymous") + return false; + if (a.nServerType!=b.nServerType) + return false; + if (a.nPasv != b.nPasv) + return false; + if (a.nTimeZoneOffset != b.nTimeZoneOffset) + return false; + if (a.nUTF8 != b.nUTF8) + return false; + if (a.nCodePage != b.nCodePage) + return false; + if (a.iForcePasvIp != b.iForcePasvIp) + return false; + if (a.iUseMlsd != b.iUseMlsd) + return false; + return true; +} + +const bool operator != (const t_server & a, const t_server & b) +{ + return !(a == b); +} + +bool t_server::operator<(const t_server & op) const +{ + if (host +#include + +class t_server +{ +CUSTOM_MEM_ALLOCATION_IMPL +public: + t_server(); + ~t_server(); + CString host; + int port; + CString user, pass, account; + CString path; + int nServerType; + int nPasv; + int nTimeZoneOffset; + int nUTF8; + int nCodePage; + int iForcePasvIp; + int iUseMlsd; + int iDupFF; + int iUndupFF; + bool operator<(const t_server &op) const; //Needed by STL map + X509 * Certificate; + EVP_PKEY * PrivateKey; +}; + +const bool operator == (const t_server &a,const t_server &b); +const bool operator != (const t_server &a,const t_server &b); + +#include "ServerPath.h" + +struct t_transferfile +{ +CUSTOM_MEM_ALLOCATION_IMPL + + CString localfile; + CString remotefile; + CServerPath remotepath; + BOOL get; + __int64 size; + t_server server; + int nType; + void * nUserData; +}; + + diff --git a/netbox/src/filezilla/MFC64bitFix.cpp b/netbox/src/filezilla/MFC64bitFix.cpp new file mode 100644 index 000000000..ff93609bb --- /dev/null +++ b/netbox/src/filezilla/MFC64bitFix.cpp @@ -0,0 +1,112 @@ + +#include "stdafx.h" +#include "MFC64bitFix.h" + +__int64 GetLength64(CFile &file) +{ + DWORD low; + DWORD high; + low=GetFileSize((void *)file.m_hFile, &high); + _int64 size=((_int64)high<<32)+low; + return size; +} + +BOOL GetLength64(CString filename, _int64 &size) +{ + WIN32_FIND_DATA findFileData; + HANDLE hFind = FindFirstFile(filename, &findFileData); + if (hFind == INVALID_HANDLE_VALUE) + return FALSE; + DebugCheck(FindClose(hFind)); + + size=((_int64)findFileData.nFileSizeHigh<<32)+findFileData.nFileSizeLow; + + return TRUE; +} + +BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus) +{ + WIN32_FIND_DATA findFileData; + HANDLE hFind = FindFirstFile((LPTSTR)lpszFileName, &findFileData); + if (hFind == INVALID_HANDLE_VALUE) + { + return FALSE; + } + DebugCheck(FindClose(hFind)); + + // strip attribute of NORMAL bit, our API doesn't have a "normal" bit. + rStatus.m_attribute = (BYTE) + (findFileData.dwFileAttributes & ~FILE_ATTRIBUTE_NORMAL); + + rStatus.m_size = ((_int64)findFileData.nFileSizeHigh<<32)+findFileData.nFileSizeLow; + + // convert times as appropriate + TRY + { + rStatus.m_ctime = CTime(findFileData.ftCreationTime); + rStatus.m_has_ctime = true; + } + CATCH_ALL(e) + { + rStatus.m_has_ctime = false; + } + END_CATCH_ALL; + + TRY + { + rStatus.m_atime = CTime(findFileData.ftLastAccessTime); + rStatus.m_has_atime = true; + } + CATCH_ALL(e) + { + rStatus.m_has_atime = false; + } + END_CATCH_ALL; + + TRY + { + rStatus.m_mtime = CTime(findFileData.ftLastWriteTime); + rStatus.m_has_mtime = true; + } + CATCH_ALL(e) + { + rStatus.m_has_mtime = false; + } + END_CATCH_ALL; + + if (!rStatus.m_has_ctime || rStatus.m_ctime.GetTime() == 0) + { + if (rStatus.m_has_mtime) + { + rStatus.m_ctime = rStatus.m_mtime; + rStatus.m_has_ctime = true; + } + else + rStatus.m_has_ctime = false; + } + + + if (!rStatus.m_has_atime || rStatus.m_atime.GetTime() == 0) + { + if (rStatus.m_has_mtime) + { + rStatus.m_atime = rStatus.m_mtime; + rStatus.m_has_atime = true; + } + else + rStatus.m_has_atime = false; + } + + if (!rStatus.m_has_mtime || rStatus.m_mtime.GetTime() == 0) + { + if (rStatus.m_has_ctime) + { + rStatus.m_mtime = rStatus.m_ctime; + rStatus.m_has_mtime = true; + } + else + rStatus.m_has_mtime = false; + } + + return TRUE; +} diff --git a/netbox/src/filezilla/MFC64bitFix.h b/netbox/src/filezilla/MFC64bitFix.h new file mode 100644 index 000000000..dbf112262 --- /dev/null +++ b/netbox/src/filezilla/MFC64bitFix.h @@ -0,0 +1,20 @@ + +#pragma once + +__int64 GetLength64(CFile & file); +BOOL GetLength64(CString filename, _int64 & size); + +struct CFileStatus64 +{ + bool m_has_ctime; + bool m_has_mtime; + bool m_has_atime; + CTime m_ctime; // creation date/time of file + CTime m_mtime; // last modification date/time of file + CTime m_atime; // last access date/time of file + _int64 m_size; // logical size of file in bytes + BYTE m_attribute; // logical OR of CFile::Attribute enum values + BYTE _m_padding; // pad the structure to a WORD +}; + +BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64 & rStatus); diff --git a/netbox/src/filezilla/MainThread.cpp b/netbox/src/filezilla/MainThread.cpp new file mode 100644 index 000000000..30607eba1 --- /dev/null +++ b/netbox/src/filezilla/MainThread.cpp @@ -0,0 +1,428 @@ + +#include "stdafx.h" +#include "MainThread.h" + +#define ECS m_CriticalSection.Lock() +#define LCS m_CriticalSection.Unlock() + +///////////////////////////////////////////////////////////////////////////// +// CMainThread + +CMainThread::CMainThread() +{ + m_LastCommand.id = 0; + m_LastCommand.param4 = 0; + m_pTools = NULL; + m_nInternalMessageID = 0; + m_pPostKeepAliveCommand = 0; + m_nTimerID = 0; + m_pControlSocket = NULL; + m_bBusy = FALSE; + m_bConnected = FALSE; + m_pWorkingDir = 0; + m_nAsyncRequestID = 0; + m_bQuit = FALSE; + m_hThread = 0; + m_dwThreadId = 0; +} + +CMainThread::~CMainThread() +{ + delete m_pWorkingDir; + ::CloseHandle(m_hThread); +} + +BOOL CMainThread::InitInstance() +{ +#ifdef _MSC_VER + AFX_MANAGE_STATE(AfxGetModuleState()); + afxCurrentResourceHandle = ::HInst; +#endif + + m_nTimerID=SetTimer(0,1,1000,0); + m_pPostKeepAliveCommand=0; + + // initialize Winsock library + BOOL res=TRUE; + WSADATA wsaData; + + WORD wVersionRequested = MAKEWORD(1, 1); + int nResult = ::WSAStartup(wVersionRequested, &wsaData); + if (nResult != 0) + res=FALSE; + else if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) + { + ::WSACleanup(); + res=FALSE; + } + + m_pControlSocket=new CFtpControlSocket(this, m_pTools); + m_pControlSocket->InitIntern(GetIntern()); + return TRUE; +} + +DWORD CMainThread::ExitInstance() +{ + KillTimer(0,m_nTimerID); + if (m_pControlSocket) + delete m_pControlSocket; + return 1; +} + +BOOL CMainThread::IsConnected() +{ + BOOL bConnected; + ECS; + bConnected=m_bConnected; + LCS; + return bConnected; +} + +void CMainThread::OnTimer(WPARAM wParam, LPARAM lParam) +{ + if (!m_pControlSocket) + return; + + if (wParam==m_nTimerID) + m_pControlSocket->OnTimer(); + + return; +} + +BOOL CMainThread::OnThreadMessage(UINT Msg, WPARAM wParam, LPARAM lParam) +{ + if (Msg==m_nInternalMessageID) + { + if (wParam==FZAPI_THREADMSG_COMMAND) + { + if (m_pControlSocket && !m_pControlSocket->IsReady()) + m_pPostKeepAliveCommand=(t_command *)lParam; + else + { + t_command *pCommand=(t_command *)lParam; + switch(pCommand->id) + { + case FZ_COMMAND_CONNECT: + DebugAssert(!IsConnected()); + SetCurrentPath(CServerPath()); + m_pControlSocket->Connect(pCommand->server); + break; + case FZ_COMMAND_LIST: + DebugAssert(m_pControlSocket); + m_pControlSocket->List(FALSE, 0, pCommand->path, pCommand->param1); + break; + case FZ_COMMAND_LISTFILE: + DebugAssert(m_pControlSocket); + m_pControlSocket->ListFile(pCommand->param1, pCommand->path); + break; + case FZ_COMMAND_FILETRANSFER: + DebugAssert(m_pControlSocket); + m_pControlSocket->FileTransfer(&pCommand->transferfile); + break; + case FZ_COMMAND_CUSTOMCOMMAND: + DebugAssert(m_pControlSocket); + m_pControlSocket->FtpCommand(pCommand->param1); + break; + case FZ_COMMAND_DELETE: + DebugAssert(m_pControlSocket); + m_pControlSocket->Delete(pCommand->param1, pCommand->path); + break; + case FZ_COMMAND_REMOVEDIR: + DebugAssert(m_pControlSocket); + m_pControlSocket->RemoveDir(pCommand->param1, pCommand->path); + break; + case FZ_COMMAND_MAKEDIR: + DebugAssert(m_pControlSocket); + m_pControlSocket->MakeDir(pCommand->path); + break; + case FZ_COMMAND_RENAME: + DebugAssert(m_pControlSocket); + m_pControlSocket->Rename(pCommand->param1, pCommand->param2, pCommand->path, pCommand->newPath); + break; + case FZ_COMMAND_CHMOD: + DebugAssert(m_pControlSocket); + m_pControlSocket->Chmod(pCommand->param1, pCommand->path, pCommand->param4); + break; + } + delete pCommand; + } + } + else if (wParam==FZAPI_THREADMSG_PROCESSREPLY) + m_pControlSocket->ProcessReply(); + else if (wParam==FZAPI_THREADMSG_TRANSFEREND) + m_pControlSocket->TransferEnd(lParam); + else if (wParam==FZAPI_THREADMSG_CANCEL) + m_pControlSocket->Cancel(lParam); + else if (wParam==FZAPI_THREADMSG_DISCONNECT) + m_pControlSocket->Disconnect(); + else if (wParam==FZAPI_THREADMSG_POSTKEEPALIVE) + { + if (m_pPostKeepAliveCommand) + { + PostThreadMessage(m_nInternalMessageID,FZAPI_THREADMSG_COMMAND,(LPARAM)m_pPostKeepAliveCommand); + m_pPostKeepAliveCommand=0; + } + } + else if (wParam==FZAPI_THREADMSG_ASYNCREQUESTREPLY) + { + CAsyncRequestData *pData=(CAsyncRequestData *)lParam; + if (pData) + { + if (pData->nRequestID!=GetAsyncRequestID()) + LogMessage(FZ_LOG_INFO, L"Ignoring old request ID"); + else + m_pControlSocket->SetAsyncRequestResult(pData->nRequestResult, pData); + delete pData; + } + else + LogMessage(FZ_LOG_WARNING, L"Request reply without data"); + } + return TRUE; + } + else if (Msg==WM_TIMER) + { + OnTimer(wParam, lParam); + } + + return TRUE; +} + +BOOL CMainThread::IsBusy() +{ + BOOL bBusy; + ECS; + bBusy=m_bBusy; + LCS; + return bBusy; +} + +void CMainThread::Command(const t_command &command) +{ + DebugAssert(!IsBusy()); + ECS; + if (m_bQuit) + { + LCS; + return; + } + m_bBusy=TRUE; + t_command *pCommand=new t_command; + *pCommand=command; + DebugCheck(PostThreadMessage(m_nInternalMessageID,FZAPI_THREADMSG_COMMAND,(LPARAM)pCommand)); + m_LastCommand=command; + LCS; +} + +BOOL CMainThread::LastOperationSuccessful() +{ + return TRUE; +} + +void CMainThread::SetBusy(BOOL bBusy) +{ + ECS; + m_bBusy=bBusy; + LCS; +} + +void CMainThread::SetConnected(BOOL bConnected /*=TRUE*/) +{ + ECS; + m_bConnected=bConnected; + if (!bConnected) + { + // when we loose connection + // reset pending commands as we cannot fulfill them anyway + m_pPostKeepAliveCommand = 0; + } + LCS; +} + +bool CMainThread::GetCurrentPath(CServerPath &dir) +{ + if (!IsConnected()) + return false; + ECS; + dir=m_CurrentPath; + LCS; + return true; +} + +CServerPath CMainThread::GetCurrentPath() +{ + CServerPath path; + bool res = GetCurrentPath(path); + if (!res) + return CServerPath(); + + return path; +} + +BOOL CMainThread::GetCurrentServer(t_server &server) +{ + if (!IsConnected()) + return FALSE; + ECS; + server=m_pControlSocket->GetCurrentServer(); + LCS; + return TRUE; +} + +void CMainThread::Quit() +{ + ECS; + m_bQuit=TRUE; + LCS; + if (IsBusy()) + PostThreadMessage(m_nInternalMessageID, FZAPI_THREADMSG_CANCEL, 1); + PostThreadMessage(WM_QUIT, 0, 0); +} + +void CMainThread::SetCurrentPath(CServerPath path) +{ + ECS; + m_CurrentPath=path; + LCS; + return; +} + +bool CMainThread::UsingMlsd() +{ + if (!IsConnected()) + return false; + return m_pControlSocket->UsingMlsd(); +} + +bool CMainThread::UsingUtf8() +{ + if (!IsConnected()) + return false; + return m_pControlSocket->UsingUtf8(); +} + +std::string CMainThread::GetTlsVersionStr() +{ + if (!IsConnected()) + return std::string(); + return m_pControlSocket->GetTlsVersionStr(); +} + +std::string CMainThread::GetCipherName() +{ + if (!IsConnected()) + return std::string(); + return m_pControlSocket->GetCipherName(); +} + +BOOL CMainThread::GetWorkingDir(t_directory *pWorkingDir) +{ + ECS; + if (m_pWorkingDir) + { + *pWorkingDir=*m_pWorkingDir; + LCS; + return TRUE; + } + LCS; + return FALSE; +} + +void CMainThread::SetWorkingDir(t_directory *pWorkingDir) +{ + if (!pWorkingDir) + { + ECS; + delete m_pWorkingDir; + m_pWorkingDir=0; + LCS; + } + else + { + ECS; + if (!m_pWorkingDir) + m_pWorkingDir=new t_directory; + *m_pWorkingDir=*pWorkingDir; + LCS; + } + if (pWorkingDir) + { + t_directory *pDirectoryToSend=new t_directory; + *pDirectoryToSend=*pWorkingDir; + SendDirectoryListing(pDirectoryToSend); + } + + return; +} + +void CMainThread::SendDirectoryListing(t_directory * pDirectoryToSend) +{ + if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_LISTDATA, 0), (LPARAM)pDirectoryToSend)) + { + delete pDirectoryToSend; + } +} + +__int64 CMainThread::GetAsyncRequestID() const +{ + return m_nAsyncRequestID; +} + +__int64 CMainThread::GetNextAsyncRequestID() +{ + return ++m_nAsyncRequestID; +} + +CMainThread* CMainThread::Create(int nPriority, DWORD dwCreateFlags) +{ + CMainThread *pMainThread=new CMainThread(); + pMainThread->m_hThread=CreateThread(0, 0, ThreadProc, pMainThread, dwCreateFlags, &pMainThread->m_dwThreadId); + if (!pMainThread->m_hThread) + { + delete pMainThread; + return NULL; + } + ::SetThreadPriority(pMainThread->m_hThread, nPriority); + return pMainThread; +} + +BOOL CMainThread::PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + return ::PostThreadMessage(m_dwThreadId, message, wParam, lParam); +} + +DWORD CMainThread::ResumeThread() +{ + BOOL res=::ResumeThread(m_hThread); + if (res) + { + m_EventStarted.Lock(); + m_EventStarted.Unlock(); + } + return res; +} + +DWORD WINAPI CMainThread::ThreadProc(LPVOID lpParameter) +{ + return (static_cast(lpParameter))->Run(); +} + +DWORD CMainThread::Run() +{ + ECS; + InitInstance(); + m_EventStarted.SetEvent(); + LCS; + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) + { + TranslateMessage(&msg); + if (!msg.hwnd) + { + OnThreadMessage(msg.message, msg.wParam, msg.lParam); + } + DispatchMessage(&msg); + } + DWORD res = ExitInstance(); + delete this; + return res; +} diff --git a/netbox/src/filezilla/MainThread.h b/netbox/src/filezilla/MainThread.h new file mode 100644 index 000000000..2cd9c7db3 --- /dev/null +++ b/netbox/src/filezilla/MainThread.h @@ -0,0 +1,79 @@ + +#pragma once + +#include "FtpControlSocket.h" +#include "structures.h" +#include "FileZillaApi.h" +#include "ApiLog.h" + +#define FZAPI_THREADMSG_PROCESSREPLY 0 +#define FZAPI_THREADMSG_COMMAND 1 +#define FZAPI_THREADMSG_TRANSFEREND 2 +#define FZAPI_THREADMSG_CANCEL 3 +#define FZAPI_THREADMSG_DISCONNECT 4 +#define FZAPI_THREADMSG_ASYNCREQUESTREPLY 5 +#define FZAPI_THREADMSG_POSTKEEPALIVE 6 + +class CMainThread : public CApiLog +{ +protected: + CMainThread(); + +public: + DWORD m_dwThreadId; + HANDLE m_hThread; + static CMainThread * Create(int nPriority, DWORD dwCreateFlags); + void SetWorkingDir(t_directory * pWorkingDir); + BOOL GetWorkingDir(t_directory * pWorkingDir); + void SendDirectoryListing(t_directory * pDirectoryToSend); + bool UsingMlsd(); + bool UsingUtf8(); + std::string GetTlsVersionStr(); + std::string GetCipherName(); + t_command m_LastCommand; + void SetCurrentPath(CServerPath path); + void Quit(); + BOOL GetCurrentServer(t_server & server); + bool GetCurrentPath(CServerPath & dir); + CServerPath GetCurrentPath(); + void SetConnected(BOOL bConnected = TRUE); + BOOL m_bConnected; + void SetBusy(BOOL bBusy); + BOOL LastOperationSuccessful(); + void Command(const t_command & command); + BOOL IsBusy(); + CFileZillaTools * m_pTools; + BOOL m_bBusy; + unsigned int m_nInternalMessageID; + BOOL IsConnected(); + __int64 GetAsyncRequestID() const; + __int64 GetNextAsyncRequestID(); + virtual int OnThreadMessage(UINT Msg, WPARAM wParam, LPARAM lParam); + DWORD SuspendThread(); + DWORD ResumeThread(); + BOOL PostThreadMessage( UINT message , WPARAM wParam, LPARAM lParam); + +protected: + BOOL InitInstance(); + DWORD ExitInstance(); + DWORD Run(); + static DWORD WINAPI ThreadProc(LPVOID lpParameter); + + CCriticalSection m_CriticalSection; + + CFtpControlSocket * m_pControlSocket; + __int64 m_nAsyncRequestID; + void OnTimer(WPARAM wParam, LPARAM lParam); + +protected: + t_directory * m_pWorkingDir; + std::map m_Options; + BOOL m_bQuit; + t_command * m_pPostKeepAliveCommand; + CServerPath m_CurrentPath; + UINT m_nTimerID; + virtual ~CMainThread(); + CEvent m_EventStarted; +}; + + diff --git a/netbox/src/filezilla/ServerPath.cpp b/netbox/src/filezilla/ServerPath.cpp new file mode 100644 index 000000000..2f47ebf82 --- /dev/null +++ b/netbox/src/filezilla/ServerPath.cpp @@ -0,0 +1,645 @@ + +#include "stdafx.h" +#include "ServerPath.h" +#include "structures.h" + +#define FTP_MVS_DOUBLE_QUOTA (TCHAR)0xDC + +////////////////////////////////////////////////////////////////////// +// Konstruktion/Destruktion +////////////////////////////////////////////////////////////////////// + +CServerPath::CServerPath() +{ + m_nServerType = 0; + m_bEmpty = TRUE; +} + +CServerPath::CServerPath(CString path, bool trim) +{ + m_nServerType = FZ_SERVERTYPE_FTP; + if (trim) + { + path.TrimLeft( L" " ); + path.TrimRight( L" " ); + } + if (path == L"") + { + m_bEmpty = TRUE; + return; + } + else + m_bEmpty = FALSE; + + int pos1 = path.Find( L":[" ); + if (pos1 != -1 && path.Right(1) == L"]" && pos1 != (path.GetLength()-1)) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS; + else if (path.GetLength() >= 3 && _istalpha(path[0]) && path[1] == L':' && (path[2] == L'\\' || path[2] == L'/')) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_WINDOWS; + else if (path[0] == FTP_MVS_DOUBLE_QUOTA && path[path.GetLength() - 1] == FTP_MVS_DOUBLE_QUOTA) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS; + else if (path.GetLength() > 2 && path[0] == L'\'' && path.Right(1) == L"'" && path.Find(L'/') == -1 && path.Find(L'\\') == -1) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS; + + *this = CServerPath(path, m_nServerType, trim); +} + +CServerPath::CServerPath(CString path, int nServerType, bool trim) +{ + m_nServerType = nServerType; + if (trim) + { + path.TrimLeft( L" " ); + path.TrimRight( L" " ); + } + if (path == L"") + { + m_bEmpty = TRUE; + return; + } + else + m_bEmpty = FALSE; + + switch (m_nServerType&FZ_SERVERTYPE_HIGHMASK) + { + case FZ_SERVERTYPE_FTP: + switch(m_nServerType&FZ_SERVERTYPE_SUBMASK) + { + case FZ_SERVERTYPE_SUB_FTP_MVS: + case FZ_SERVERTYPE_SUB_FTP_BS2000: + { + path.TrimLeft(FTP_MVS_DOUBLE_QUOTA); + path.TrimRight(FTP_MVS_DOUBLE_QUOTA); + path.TrimLeft(L'\''); + path.TrimRight(L'\''); + path.TrimLeft(L'.'); + while (path.Replace(L"..", L".")); + + int pos = path.Find(L"."); + while (pos != -1) + { + m_Segments.push_back(path.Left(pos)); + path = path.Mid(pos + 1); + pos = path.Find( L"." ); + } + if (path != L"") + m_Segments.push_back(path); + else + m_Prefix = L"."; + } + break; + case FZ_SERVERTYPE_SUB_FTP_VMS: + { + int pos1 = path.Find( L"[" ); + if (pos1 == -1 || path.Right(1) != L"]") + { + DebugFail(); + m_bEmpty = TRUE; + return; + } + path.TrimRight( L"]" ); + if (pos1) + m_Prefix = path.Left(pos1); + path = path.Mid(pos1 + 1); + int pos = path.Find( L"." ); + while (pos != -1) + { + m_Segments.push_back(path.Left(pos)); + path = path.Mid(pos+1); + pos = path.Find( L"." ); + } + if (path != L"") + m_Segments.push_back(path); + } + break; + default: + path.Replace( L"\\", L"/" ); + while (path.Replace( L"//", L"/" )); + path.TrimLeft( L"/" ); + path.TrimRight( L"/" ); + int pos = path.Find( L"/" ); + while (pos != -1) + { + m_Segments.push_back(path.Left(pos)); + path = path.Mid(pos+1); + pos = path.Find( L"/" ); + } + if (path != L"") + m_Segments.push_back(path); + break; + } + break; + case FZ_SERVERTYPE_LOCAL: + { + path.TrimRight( L"\\" ); + while (path.Replace( L"\\\\", L"\\" )); + int pos = path.Find( L"\\" ); + if (pos == -1) + { + m_Prefix = path; + return; + } + DebugAssert(pos == 2); + m_Prefix = path.Left(pos); + path = path.Mid(pos + 1); + pos = path.Find( L"\\" ); + while (pos != -1) + { + m_Segments.push_back(path.Left(pos)); + path=path.Mid(pos + 1); + pos=path.Find( L"\\" ); + } + if (path != L"") + m_Segments.push_back(path); + } + break; + default: + DebugFail(); + } +} + +CServerPath::CServerPath(const CServerPath &path) +{ + m_nServerType = path.m_nServerType; + m_Prefix = path.m_Prefix; + m_bEmpty = path.m_bEmpty; + m_Segments = path.m_Segments; +} + +CServerPath::~CServerPath() +{ + +} + +void CServerPath::SetServer(const t_server &server) +{ + m_nServerType=server.nServerType; +} + +BOOL CServerPath::SetPath(CString &newpath, BOOL bIsFile /*=FALSE*/) +{ + CString file; + CString path=newpath; + + path.TrimLeft( L" " ); + path.TrimRight( L" " ); + if (path != L"") + m_bEmpty = FALSE; + else + m_bEmpty = TRUE; + if (!(m_nServerType & FZ_SERVERTYPE_HIGHMASK)) + m_nServerType = FZ_SERVERTYPE_FTP; + if (!(m_nServerType&FZ_SERVERTYPE_SUBMASK) && (m_nServerType&FZ_SERVERTYPE_HIGHMASK)==FZ_SERVERTYPE_FTP) + { + int pos1 = path.Find( L":[" ); + if (pos1!=-1 && pos1!=(path.GetLength()-2)) + { + if (!bIsFile && path.Right(1)==L"]") + m_nServerType|=FZ_SERVERTYPE_SUB_FTP_VMS; + else if (bIsFile && path.ReverseFind(']')>(pos1+1)) + m_nServerType|=FZ_SERVERTYPE_SUB_FTP_VMS; + } + if (newpath.GetLength() >= 3 && _istalpha(newpath[0]) && newpath[1] == L':' && (newpath[2] == L'\\' || newpath[2] == L'/')) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_WINDOWS; + else if (path[0] == FTP_MVS_DOUBLE_QUOTA && path[path.GetLength() - 1] == FTP_MVS_DOUBLE_QUOTA) + m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS; + } + m_Segments.clear(); + m_Prefix = L""; + switch (m_nServerType&FZ_SERVERTYPE_HIGHMASK) + { + case FZ_SERVERTYPE_FTP: + switch (m_nServerType&FZ_SERVERTYPE_SUBMASK) + { + case FZ_SERVERTYPE_SUB_FTP_MVS: + case FZ_SERVERTYPE_SUB_FTP_BS2000: + { + path.TrimLeft(FTP_MVS_DOUBLE_QUOTA); + path.TrimRight(FTP_MVS_DOUBLE_QUOTA); + path.TrimLeft(L'\''); + path.TrimRight(L'\''); + path.TrimLeft(L'.'); + while (path.Replace(L"..", L".")); + + int pos = path.Find(L"."); + while (pos != -1) + { + m_Segments.push_back(path.Left(pos)); + path = path.Mid(pos + 1); + pos = path.Find( L"." ); + } + if (path != L"") + m_Segments.push_back(path); + else + m_Prefix = L"."; + + if (bIsFile) + { + if (m_Segments.empty()) + return FALSE; + file = m_Segments.back(); + m_Segments.pop_back(); + + if (file.Right(1) == L".") + return FALSE; + + int pos = file.Find(L'('); + int pos2 = file.Find(L')'); + if (pos != -1) + { + if (!pos || pos2 != file.GetLength() - 2) + return FALSE; + m_Prefix = L""; + m_Segments.push_back(file.Left(pos)); + file = file.Mid(pos + 1, pos2 - pos - 1); + } + else if (pos2 != -1) + return FALSE; + } + } + break; + case FZ_SERVERTYPE_SUB_FTP_VMS: + { + int pos1=path.Find( L"[" ); + if (pos1==-1) + return FALSE; + if (bIsFile) + { + int rpos=path.ReverseFind(L']'); + if (rpos==-1) + return FALSE; + else if (rpos!=(path.GetLength()-1) ) + { + file=file.Mid(rpos+1); + path=path.Left(rpos+1); + } + else + return FALSE; + } + if (path.Right(1)!=L"]") + return FALSE; + path.TrimRight( L"]" ); + if (pos1) + m_Prefix=path.Left(pos1); + path=path.Mid(pos1+1); + int pos=path.Find( L"." ); + while(pos!=-1) + { + m_Segments.push_back(path.Left(pos)); + path=path.Mid(pos+1); + pos=path.Find( L"." ); + } + if (path!=L"") + m_Segments.push_back(path); + } + break; + default: + path.Replace( L"\\", L"/" ); + while(path.Replace( L"//", L"/" )); + path.TrimLeft( L"/" ); + if (bIsFile) + { + if (path.Right(1)!= L"/" ) + { + int rpos=path.ReverseFind(L'/'); + if (rpos==-1) + { + newpath=path; + m_bEmpty=TRUE; + return TRUE; + } + file=path.Mid(rpos+1); + path=path.Left(rpos); + } + else + return FALSE; + } + path.TrimRight( L"/" ); + int pos=path.Find( L"/" ); + while(pos!=-1) + { + m_Segments.push_back(path.Left(pos)); + path=path.Mid(pos+1); + pos=path.Find( L"/" ); + } + if (path!=L"") + m_Segments.push_back(path); + break; + } + break; + case FZ_SERVERTYPE_LOCAL: + { + if (bIsFile) + { + if (path.Right(1)!= L"\\" ) + { + int rpos=path.ReverseFind(L'\\'); + if (rpos==-1) + return FALSE; + + file=path.Mid(rpos+1); + path=path.Left(rpos); + } + else + return FALSE; + } + path.TrimRight( L"\\" ); + while (path.Replace( L"\\\\", L"\\" )); + int pos=path.Find( L":\\" ); + if (pos==-1 || pos!=1) + return FALSE; + else + { + m_Prefix=path.Left(pos+1); + path=path.Mid(pos+2); + } + pos=path.Find( L"\\" ); + while (pos!=-1) + { + m_Segments.push_back(path.Left(pos)); + path=path.Mid(pos+1); + pos=path.Find( L"\\" ); + } + if (path!=L"") + m_Segments.push_back(path); + } + break; + } + if (bIsFile) + newpath = file; + return TRUE; +} + +const CString CServerPath::GetPath() const +{ + if (m_bEmpty) + return L""; + CString path; + tConstIter iter; + switch (m_nServerType&FZ_SERVERTYPE_HIGHMASK) + { + case FZ_SERVERTYPE_FTP: + switch (m_nServerType&FZ_SERVERTYPE_SUBMASK) + { + case FZ_SERVERTYPE_SUB_FTP_MVS: + case FZ_SERVERTYPE_SUB_FTP_BS2000: + path = L"'"; + for (iter = m_Segments.begin(); iter != m_Segments.end(); iter++) + { + if (iter != m_Segments.begin()) + path += L"."; + path += *iter; + } + path += m_Prefix + L"'"; + break; + case FZ_SERVERTYPE_SUB_FTP_VMS: + path = m_Prefix + L"["; + for (iter = m_Segments.begin(); iter != m_Segments.end(); iter++) + path += *iter + L"."; + path.TrimRight( L"." ); + path += L"]"; + break; + default: + if (!(m_nServerType & FZ_SERVERTYPE_SUB_FTP_WINDOWS)) + path=L"/"; + for (iter=m_Segments.begin(); iter!=m_Segments.end(); iter++) + path+=*iter + L"/"; + break; + } + break; + case FZ_SERVERTYPE_LOCAL: + path=m_Prefix; + if (!m_Segments.empty()) + path+=L"\\"; + for (iter=m_Segments.begin(); iter!=m_Segments.end(); iter++) + path+=*iter + L"\\"; + + break; + default: + DebugFail(); + } + return path; +} + +CServerPath& CServerPath::operator=(const CServerPath &op) +{ + if (this == &op) + return *this; + m_Segments.clear(); + + m_nServerType = op.m_nServerType; + + m_Prefix = op.m_Prefix; + m_bEmpty = op.m_bEmpty; + + m_Segments = op.m_Segments; + + return *this; +} + +const bool CServerPath::operator==(const CServerPath &op) const +{ + if (this == &op) + return true; + + if (m_bEmpty != op.m_bEmpty) + return false; + if (m_Prefix != op.m_Prefix) + return false; + // excluding FZ_SERVERTYPE_LAYERMASK from comparison, + // as this part of server type is not set in TFileZillaIntf + const int CompareMask = FZ_SERVERTYPE_HIGHMASK | FZ_SERVERTYPE_SUBMASK; + if ((m_nServerType & CompareMask) != (op.m_nServerType & CompareMask)) + return false; + tConstIter iter1 = m_Segments.begin(); + tConstIter iter2 = op.m_Segments.begin(); + while (iter1 != m_Segments.end()) + { + if (iter2 == op.m_Segments.end()) + return false; + if (*iter1 != *iter2) + return false; + iter1++; + iter2++; + } + if (iter2 != op.m_Segments.end()) + return false; + return true; +} + +const BOOL operator==(const CServerPath &a, const CString &b) +{ + CServerPath path(b); + return a==path; +} + +const bool CServerPath::operator!=(const CServerPath &op) const +{ + if (!this) + return false; + + if (*this == op) + return false; + else + return true; +} + +CString CServerPath::GetLastSegment() const +{ + if (!HasParent()) + return L""; + if (m_Segments.empty()) + return L""; + else + return m_Segments.back(); +} + +CServerPath CServerPath::GetParent() const +{ + DebugAssert(HasParent()); + CServerPath path; + path = *this; + path.m_Segments.pop_back(); + if (m_nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_BS2000)) + path.m_Prefix = L"."; + return path; +} + +BOOL CServerPath::HasParent() const +{ + if (!m_Segments.empty()) + return TRUE; + else + return FALSE; +} + +const BOOL CServerPath::IsEmpty() const +{ + return m_bEmpty; +} + +CString CServerPath::GetSafePath() const +{ + if (m_bEmpty) + return L""; + + CString safepath; + safepath.Format(L"%d %d ", m_nServerType, m_Prefix.GetLength()); + if (m_Prefix!=L"") + safepath+=m_Prefix+L" "; + tConstIter iter = m_Segments.begin(); + while(iter!=m_Segments.end()) + { + CString len; + len.Format(L"%d ", iter->GetLength()); + safepath+=len; + safepath+=*iter; + iter++; + if (iter!=m_Segments.end()) + safepath+=L" "; + } + return safepath; +} + +BOOL CServerPath::AddSubdir(CString subdir) +{ + subdir.TrimLeft( L" " ); + subdir.TrimRight( L" " ); + if (subdir == L"") + return FALSE; + + if (m_nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_BS2000) && m_Prefix != L".") + return FALSE; + + m_Segments.push_back(subdir); + + if (m_nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_BS2000) && !m_Segments.empty()) + { + if (m_Segments.back().Right(1) == L".") + { + m_Segments.back().TrimRight(L'.'); + m_Prefix = L"."; + } + else + m_Prefix = L""; + } + + m_bEmpty = FALSE; + + return TRUE; +} + +BOOL CServerPath::SetPath(CString newpath) +{ + return SetPath(newpath, FALSE); +} + +CString CServerPath::FormatFilename(CString fn, bool omitPath /*=false*/) const +{ + if (m_bEmpty) + return fn; + + if (fn == L"") + return L""; + + CString path; + tConstIter iter; + switch (m_nServerType&FZ_SERVERTYPE_HIGHMASK) + { + case FZ_SERVERTYPE_FTP: + switch (m_nServerType&FZ_SERVERTYPE_SUBMASK) + { + case FZ_SERVERTYPE_SUB_FTP_MVS: + case FZ_SERVERTYPE_SUB_FTP_BS2000: + if (omitPath && m_Prefix == L".") + return fn; + + path = L"'"; + for (iter = m_Segments.begin(); iter != m_Segments.end(); iter++) + path += *iter + L"."; + if (m_Prefix != L".") + { + path.TrimRight(L'.'); + path += L"(" + fn + L")"; + } + else + path += fn; + path += L"'"; + break; + case FZ_SERVERTYPE_SUB_FTP_VMS: + if (omitPath) + return fn; + + path = m_Prefix + L"["; + for (iter = m_Segments.begin(); iter != m_Segments.end(); iter++) + path += *iter + L"."; + path.TrimRight( L"." ); + path += L"]"; + path += fn; + break; + default: + if (omitPath) + return fn; + if (!(m_nServerType & FZ_SERVERTYPE_SUB_FTP_WINDOWS)) + path=L"/"; + for (iter = m_Segments.begin(); iter != m_Segments.end(); iter++) + path+=*iter + L"/"; + path += fn; + break; + } + break; + case FZ_SERVERTYPE_LOCAL: + if (omitPath) + return fn; + path=m_Prefix; + if (!m_Segments.empty()) + path+=L"\\"; + for (iter=m_Segments.begin(); iter!=m_Segments.end(); iter++) + path+=*iter + L"\\"; + path += fn; + break; + default: + DebugFail(); + } + return path; +} diff --git a/netbox/src/filezilla/ServerPath.h b/netbox/src/filezilla/ServerPath.h new file mode 100644 index 000000000..ac75fbcba --- /dev/null +++ b/netbox/src/filezilla/ServerPath.h @@ -0,0 +1,44 @@ + +#pragma once + +class CServerPath +{ +public: + BOOL AddSubdir(CString subdir); + CString GetSafePath() const; + const BOOL IsEmpty() const; + CServerPath GetParent() const; + BOOL HasParent() const; + CString GetLastSegment() const; + CServerPath(); + explicit CServerPath(CString path, bool trim = true); + CServerPath(CString path, int nServerType, bool trim = true); + CServerPath(const CServerPath &path); + + virtual ~CServerPath(); + + CServerPath & operator=(CString path) { SetPath(path); return *this; } + void SetServer(const t_server & server); + BOOL SetPath(CString & newpath, BOOL bIsFile); + BOOL SetPath(CString newpath); + const CString GetPath() const; + + CServerPath & operator=(const CServerPath & op); + + const bool operator == (const CServerPath & op) const; + const bool operator != (const CServerPath & op) const; + + CString FormatFilename(CString fn, bool omitPath = false) const; + +protected: + BOOL m_bEmpty; + std::list m_Segments; + typedef std::list::iterator tIter; + typedef std::list::const_iterator tConstIter; + CString m_Prefix; + int m_nServerType; +}; + +const BOOL operator == (const CServerPath & a, const CString & b); + + diff --git a/netbox/src/filezilla/TransferSocket.cpp b/netbox/src/filezilla/TransferSocket.cpp new file mode 100644 index 000000000..7c9d3f8ee --- /dev/null +++ b/netbox/src/filezilla/TransferSocket.cpp @@ -0,0 +1,1178 @@ + +#include "stdafx.h" +#include "TransferSocket.h" +#include "mainthread.h" +#include "AsyncProxySocketLayer.h" +#include "TextsFileZilla.h" +#ifndef MPEXT_NO_GSS +#include "AsyncGssSocketLayer.h" +#endif + +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY _IOR('t', 123, ULONG) +#define SIO_IDEAL_SEND_BACKLOG_CHANGE _IO('t', 122) +#endif + +#define BUFSIZE 16384 + +#define STATE_WAITING 0 +#define STATE_STARTING 1 +#define STATE_STARTED 2 + +///////////////////////////////////////////////////////////////////////////// +// CTransferSocket + +CTransferSocket::CTransferSocket(CFtpControlSocket *pOwner, int nMode) +{ + DebugAssert(pOwner); + InitIntern(pOwner->GetIntern()); + m_pOwner = pOwner; + m_nMode = nMode; + m_nTransferState = STATE_WAITING; + m_bCheckTimeout = FALSE; + m_pBuffer = 0; +#ifndef MPEXT_NO_ZLIB + m_pBuffer2 = 0; +#endif + m_bufferpos = 0; + m_pFile = 0; + m_bListening = FALSE; + m_bSentClose = FALSE; + m_nInternalMessageID = 0; + m_transferdata.transfersize = 0; + m_transferdata.transferleft = 0; + m_nNotifyWaiting = 0; + m_bActivationPending = false; + m_LastSendBufferUpdate = 0; + + UpdateStatusBar(true); + + m_pProxyLayer = NULL; + m_pSslLayer = NULL; +#ifndef MPEXT_NO_GSS + m_pGssLayer = NULL; +#endif + + if (m_nMode & CSMODE_LIST) + { + m_pListResult = new CFtpListResult(pOwner->m_CurrentServer, &pOwner->m_bUTF8, &pOwner->m_nCodePage); + m_pListResult->InitIntern(GetIntern()); + } + else + m_pListResult = 0; + m_LastUpdateTime.QuadPart = 0; + +#ifndef MPEXT_NO_ZLIB + memset(&m_zlibStream, 0, sizeof(m_zlibStream)); + m_useZlib = false; +#endif +} + +CTransferSocket::~CTransferSocket() +{ + nb_free(m_pBuffer); +#ifndef MPEXT_NO_ZLIB + nb_free(m_pBuffer2); +#endif + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_TRANSFERSTATUS, 0), 0); + Close(); + RemoveAllLayers(); + delete m_pProxyLayer; + delete m_pSslLayer; +#ifndef MPEXT_NO_GSS + delete m_pGssLayer; +#endif + m_pOwner->RemoveActiveTransfer(); + + delete m_pListResult; + +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (m_nMode & CSMODE_UPLOAD) + deflateEnd(&m_zlibStream); + else + inflateEnd(&m_zlibStream); + } +#endif +} + +///////////////////////////////////////////////////////////////////////////// +// Member-Funktion CTransferSocket +void CTransferSocket::OnReceive(int nErrorCode) +{ + if (GetState() != connected && GetState() != attached && GetState() != closed) + return; + if (m_nTransferState == STATE_WAITING) + { + m_nNotifyWaiting |= FD_READ; + return; + } + + if (m_bSentClose) + return; + if (m_bListening) + return; + + if (m_nMode&CSMODE_LIST) + { + if (m_nTransferState == STATE_STARTING) + OnConnect(0); + + char *buffer = static_cast(nb_calloc(1, BUFSIZE)); + int numread = CAsyncSocketEx::Receive(buffer, BUFSIZE); + if (numread != SOCKET_ERROR && numread) + { + m_LastActiveTime = CTime::GetCurrentTime(); + +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + m_zlibStream.next_in = (Bytef *)buffer; + m_zlibStream.avail_in = numread; + char *out = static_cast(nb_calloc(1, BUFSIZE)); + m_zlibStream.next_out = (Bytef *)out; + m_zlibStream.avail_out = BUFSIZE; + int res = inflate(&m_zlibStream, 0); + while (res == Z_OK) + { + m_pListResult->AddData(out, BUFSIZE - m_zlibStream.avail_out); + out = static_cast(nb_calloc(1, BUFSIZE)); + m_zlibStream.next_out = (Bytef *)out; + m_zlibStream.avail_out = BUFSIZE; + res = inflate(&m_zlibStream, 0); + } + nb_free(buffer); + if (res == Z_STREAM_END) + m_pListResult->AddData(out, BUFSIZE - m_zlibStream.avail_out); + else if (res != Z_OK && res != Z_BUF_ERROR) + { + nb_free(out); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + return; + } + else + nb_free(out); + } + else +#endif + m_pListResult->AddData(buffer, numread); + m_transferdata.transfersize += numread; + t_ffam_transferstatus *status = new t_ffam_transferstatus(); + status->bFileTransfer = FALSE; + status->transfersize = -1; + status->bytes = m_transferdata.transfersize; + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_TRANSFERSTATUS, 0), (LPARAM)status); + } + else + nb_free(buffer); + if (!numread) + { + CloseAndEnsureSendClose(0); + } + if (numread == SOCKET_ERROR) + { + int nError = GetLastError(); + if (nError == WSAENOTCONN) + { + //Not yet connected + return; + } + else if (m_pSslLayer && nError == WSAESHUTDOWN) + { + // Do nothing, wait for shutdown complete notification. + return; + } + else if (nError != WSAEWOULDBLOCK) + { + LogError(nError); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + } + } + } + else if (m_nMode & CSMODE_DOWNLOAD) + { + if (m_nTransferState == STATE_STARTING) + OnConnect(0); + + bool beenWaiting = false; + _int64 ableToRead; + if (GetState() != closed) + ableToRead = m_pOwner->GetAbleToTransferSize(CFtpControlSocket::download, beenWaiting); + else + ableToRead = BUFSIZE; + + if (!beenWaiting) + DebugAssert(ableToRead); + else if (!ableToRead) + { + TriggerEvent(FD_READ); + return; + } + + if (!m_pBuffer) + m_pBuffer = static_cast(nb_calloc(1, BUFSIZE)); + + int numread = CAsyncSocketEx::Receive(m_pBuffer, static_cast(ableToRead)); + if (numread!=SOCKET_ERROR) + { + m_pOwner->SpeedLimitAddTransferredBytes(CFtpControlSocket::download, numread); + } + + if (!numread) + { + CloseAndEnsureSendClose(0); + return; + } + + if (numread == SOCKET_ERROR) + { + int nError = GetLastError(); + if (nError == WSAENOTCONN) + { + //Not yet connected + return; + } + else if (m_pSslLayer && nError == WSAESHUTDOWN) + { + // Do nothing, wait for shutdown complete notification. + return; + } + else if (nError != WSAEWOULDBLOCK) + { + LogError(nError); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + } + + UpdateStatusBar(false); + return; + } + + int written = 0; + m_LastActiveTime = CTime::GetCurrentTime(); + TRY + { +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pBuffer2) + m_pBuffer2 = static_cast(nb_calloc(1, BUFSIZE)); + + m_zlibStream.next_in = (Bytef *)m_pBuffer; + m_zlibStream.avail_in = numread; + m_zlibStream.next_out = (Bytef *)m_pBuffer2; + m_zlibStream.avail_out = BUFSIZE; + int res = inflate(&m_zlibStream, 0); + while (res == Z_OK) + { + m_pFile->Write(m_pBuffer2, BUFSIZE - m_zlibStream.avail_out); + written += BUFSIZE - m_zlibStream.avail_out; + m_zlibStream.next_out = (Bytef *)m_pBuffer2; + m_zlibStream.avail_out = BUFSIZE; + res = inflate(&m_zlibStream, 0); + } + if (res == Z_STREAM_END) + { + m_pFile->Write(m_pBuffer2, BUFSIZE - m_zlibStream.avail_out); + written += BUFSIZE - m_zlibStream.avail_out; + } + else if (res != Z_OK && res != Z_BUF_ERROR) + { + m_pOwner->ShowStatus(L"Compression error", FZ_LOG_ERROR); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + return; + } + } + else +#endif + { + m_pFile->Write(m_pBuffer, numread); + written = numread; + } + } + CATCH(CFileException,e) + { + LPTSTR msg = static_cast(nb_calloc(BUFSIZE, sizeof(TCHAR))); + if (e->GetErrorMessage(msg, BUFSIZE)) + m_pOwner->ShowStatus(msg, FZ_LOG_ERROR); + nb_free(msg); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + return; + } + END_CATCH; + m_transferdata.transferleft -= written; + + UpdateStatusBar(false); + } +} + +void CTransferSocket::SetBuffers() +{ + /* Set internal socket send buffer + * this should fix the speed problems some users have reported + */ + m_SendBuf = GetOptionVal(OPTION_MPEXT_SNDBUF); + if (m_SendBuf > 0) + { + DWORD value; + int len = sizeof(value); + GetSockOpt(SO_SNDBUF, &value, &len); + if (value < m_SendBuf) + { + SetSockOpt(SO_SNDBUF, &m_SendBuf, sizeof(m_SendBuf)); + } + + // For now we increase receive buffer, whenever send buffer is set. + // The size is not configurable. The constant taken from FZ. + value = 0; + len = sizeof(value); + GetSockOpt(SO_RCVBUF, &value, &len); + int rcvbuf = 4 * 1024 * 1024; + if (value < rcvbuf) + { + value = rcvbuf; + SetSockOpt(SO_RCVBUF, &value, sizeof(value)); + } + } +} + +void CTransferSocket::OnAccept(int nErrorCode) +{ + m_bListening=FALSE; + CAsyncSocketEx tmp; + Accept(tmp); + SOCKET socket=tmp.Detach(); + CAsyncSocketEx::Close(); + + Attach(socket); + + SetBuffers(); + + if (m_nTransferState == STATE_STARTING) + { + Start(); + } +} + +void CTransferSocket::ConfigureSocket() +{ + // Note that FileZilla re-enables Nagle's alg during TLS negotiation. + + // Following post claims that TCP_NODELAY + // has to be set before connect() + // http://stackoverflow.com/questions/22583941/what-is-the-workaround-for-tcp-delayed-acknowledgment/25871250#25871250 + + int nodelay = GetOptionVal(OPTION_MPEXT_NODELAY); + if (nodelay != 0) + { + BOOL bvalue = TRUE; + SetSockOpt(TCP_NODELAY, &bvalue, sizeof(bvalue), IPPROTO_TCP); + } + + CAsyncSocketEx::ConfigureSocket(); +} + +void CTransferSocket::OnConnect(int nErrorCode) +{ + if (nErrorCode) + { + TCHAR buffer[1000]; + memset(buffer, 0, sizeof(buffer)); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, nErrorCode, 0, buffer, 999, 0); + CString str; + str.Format(IDS_ERRORMSG_CANTOPENTRANSFERCHANNEL,buffer); + str.Replace( L"\n", L"\0" ); + str.Replace( L"\r", L"\0" ); + m_pOwner->ShowStatus(str, FZ_LOG_ERROR); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + } + else + { + SetBuffers(); + m_pOwner->ShowStatus(L"Data connection opened", FZ_LOG_INFO); + } + if (m_nTransferState == STATE_WAITING) + { + // OnReceive (invoked by m_nNotifyWaiting including FD_READ) + // will call back to OnConnected (as we won't be connected yet). + // This is needed for file transfers only, where SetActive is + // called only after 1xx response to RETR (and similar) arrives. + // But we get FD_CONNECT earlier, hence we get to this branch. + // With directory listing, SetActive is called before Connect, + // so we are already STATE_STARTING on FD_CONNECT. + // It should probably behave the same in both scenarios. + m_nNotifyWaiting |= FD_READ; + } + else if (m_nTransferState == STATE_STARTING) + { + Start(); + } +} + +void CTransferSocket::Start() +{ + m_nTransferState = STATE_STARTED; + + m_LastActiveTime=CTime::GetCurrentTime(); + + if (m_pSslLayer) + { + AddLayer(m_pSslLayer); + int res = m_pSslLayer->InitSSLConnection(true, m_pOwner->m_pSslLayer, + GetOptionVal(OPTION_MPEXT_SSLSESSIONREUSE), + GetOptionVal(OPTION_MPEXT_MIN_TLS_VERSION), + GetOptionVal(OPTION_MPEXT_MAX_TLS_VERSION)); + if (res == SSL_FAILURE_INITSSL) + { + m_pOwner->ShowStatus(IDS_ERRORMSG_CANTINITSSL, FZ_LOG_ERROR); + } + + if (res) + { + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + return; + } + } + +#ifndef MPEXT_NO_GSS + if (m_pGssLayer) + { + AddLayer(m_pGssLayer); + } +#endif +} + +void CTransferSocket::OnClose(int nErrorCode) +{ + if (m_nTransferState == STATE_WAITING) + { + m_nNotifyWaiting |= FD_CLOSE; + return; + } + + m_pOwner->ShowStatus(L"Data connection closed", FZ_LOG_INFO); + + OnReceive(0); + CloseAndEnsureSendClose(0); +} + +int CTransferSocket::CheckForTimeout(int delay) +{ + UpdateStatusBar(false); + if (!m_bCheckTimeout) + { + // we are closed, so make sure the FTP control socket is itself checking for + // timeout as we are not + return 0; + } + CTimeSpan span = CTime::GetCurrentTime()-m_LastActiveTime; + if ((delay > 0) && (span.GetTotalSeconds()>=delay)) + { + m_pOwner->ShowTimeoutError(IDS_DATA_CONNECTION); + CloseAndEnsureSendClose(CSMODE_TRANSFERTIMEOUT); + return 2; + } + return 1; +} + +void CTransferSocket::SetState(int nState) +{ + CAsyncSocketEx::SetState(nState); + if (m_bActivationPending && Activate()) + { + m_bActivationPending = false; + } +} + +bool CTransferSocket::Activate() +{ + // Activation (OnSend => OnConnect) indirectly causes adding + // of TLS layer, which needs connected underlying layers. + // The code should be generic, but we particularly need it for this (TLS over proxy) + // scenario only. So for a safety, we use it for the scenario only. + bool Result = + (GetState() == connected) || (GetState() == attached) || + (m_pSslLayer == NULL) || (m_pProxyLayer == NULL); + if (Result) + { + if (m_nTransferState == STATE_WAITING) + m_nTransferState = STATE_STARTING; + m_bCheckTimeout = TRUE; + m_LastActiveTime = CTime::GetCurrentTime(); + + if (m_nNotifyWaiting & FD_READ) + OnReceive(0); + if (m_nNotifyWaiting & FD_WRITE) + OnSend(0); + if (m_nNotifyWaiting & FD_CLOSE) + OnClose(0); + } + return Result; +} + +void CTransferSocket::SetActive() +{ + if (!Activate()) + { + m_bActivationPending = true; + } +} + +void CTransferSocket::OnSend(int nErrorCode) +{ + if (m_nTransferState == STATE_WAITING) + { + m_nNotifyWaiting |= FD_WRITE; + return; + } + + if (m_bSentClose) + { + return; + } + if (m_bListening) + { + return; + } + + if (!(m_nMode&CSMODE_UPLOAD)) + { + return; + } + + if (m_nTransferState == STATE_STARTING) + { + OnConnect(0); + } + + if (m_SendBuf > 0) + { + unsigned int Ticks = GetTickCount(); + if (Ticks - m_LastSendBufferUpdate >= 1000) + { + DWORD BufferLen = 0; + DWORD OutLen = 0; + if (WSAIoctl(m_SocketData.hSocket, SIO_IDEAL_SEND_BACKLOG_QUERY, NULL, 0, &BufferLen, sizeof(BufferLen), &OutLen, 0, 0) == 0) + { + DebugAssert(OutLen == sizeof(BufferLen)); + if (m_SendBuf < BufferLen) + { + LogMessage(FZ_LOG_PROGRESS, L"Increasing send buffer from %d to %d", m_SendBuf, BufferLen); + m_SendBuf = BufferLen; + SetSockOpt(SO_SNDBUF, &m_SendBuf, sizeof(m_SendBuf)); + } + } + m_LastSendBufferUpdate = Ticks; + } + } + +#ifndef MPEXT_NO_ZLIB + if (m_useZlib) + { + if (!m_pBuffer) + { + m_pBuffer = static_cast(nb_calloc(1, BUFSIZE)); + m_bufferpos = 0; + + m_zlibStream.next_out = (Bytef *)m_pBuffer; + m_zlibStream.avail_out = BUFSIZE; + } + if (!m_pBuffer2) + { + m_pBuffer2 = static_cast(nb_calloc(1, BUFSIZE)); + + m_zlibStream.next_in = (Bytef *)m_pBuffer2; + } + + bool beenWaiting = false; + while (true) + { + int numsend; + if (!m_zlibStream.avail_in) + { + if (m_pFile) + { + DWORD numread; + numread = ReadDataFromFile(m_pBuffer2, BUFSIZE); + if (numread < 0) + { + return; + } + + m_transferdata.transferleft -= numread; + m_zlibStream.next_in = (Bytef *)m_pBuffer2; + m_zlibStream.avail_in = numread; + + if (numread < BUFSIZE) + m_pFile = 0; + } + } + if (!m_zlibStream.avail_out) + { + if (m_bufferpos >= BUFSIZE) + { + m_bufferpos = 0; + m_zlibStream.next_out = (Bytef *)m_pBuffer; + m_zlibStream.avail_out = BUFSIZE; + } + } + + int res = Z_OK; + if (m_zlibStream.avail_out) + { + res = deflate(&m_zlibStream, m_pFile ? 0 : Z_FINISH); + if (res != Z_OK && (!m_pFile && res != Z_STREAM_END)) + { + m_pOwner->ShowStatus("Decompression error", FZ_LOG_ERROR); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + return; + } + } + + numsend = BUFSIZE; + int len = BUFSIZE - m_bufferpos - m_zlibStream.avail_out; + if (!len && !m_pFile) + { + break; + } + + if (len < BUFSIZE) + numsend = len; + + int nLimit = (int)m_pOwner->GetAbleToTransferSize(CFtpControlSocket::upload, beenWaiting); + if (nLimit != -1 && GetState() != closed && numsend > nLimit) + numsend = nLimit; + + if (!numsend) + { + TriggerEvent(FD_WRITE); + return; + } + + int numsent = Send(m_pBuffer + m_bufferpos, numsend); + if (numsent == SOCKET_ERROR) + { + int nError = GetLastError(); + if (nError == WSAENOTCONN) + { + //Not yet connected + return; + } + else if (m_pSslLayer && nError == WSAESHUTDOWN) + { + // Do nothing, wait for shutdown complete notification. + return; + } + else if (nError != WSAEWOULDBLOCK) + { + CloseOnShutDownOrError(CSMODE_TRANSFERERROR); + } + UpdateStatusBar(false); + return; + } + + m_pOwner->SpeedLimitAddTransferredBytes(CFtpControlSocket::upload, numsent); + m_LastActiveTime = CTime::GetCurrentTime(); + + m_bufferpos += numsent; + + UpdateStatusBar(false); + + if (!m_zlibStream.avail_in && !m_pFile && m_zlibStream.avail_out && + m_zlibStream.avail_out + m_bufferpos == BUFSIZE && res == Z_STREAM_END) + { + CloseOnShutDownOrError(0); + return; + } + + //Check if there are other commands in the command queue. + MSG msg; + if (PeekMessage(&msg,0, 0, 0, PM_NOREMOVE)) + { + TriggerEvent(FD_WRITE); + return; + } + } + } + else +#endif + { + if (!m_pFile) + { + return; + } + if (!m_pBuffer) + m_pBuffer = static_cast(nb_calloc(1, BUFSIZE)); + + int numread; + + bool beenWaiting = false; + _int64 currentBufferSize; + if (GetState() != closed) + currentBufferSize = m_pOwner->GetAbleToTransferSize(CFtpControlSocket::upload, beenWaiting); + else + currentBufferSize = BUFSIZE; + + if (!currentBufferSize && !m_bufferpos) + { + // Not allowed to send yet, try later + TriggerEvent(FD_WRITE); + return; + } + else if (m_bufferpos < currentBufferSize) + { + numread = ReadDataFromFile(m_pBuffer + m_bufferpos, static_cast(currentBufferSize - m_bufferpos)); + if (numread < 0 ) + { + return; + } + else if (!numread && !m_bufferpos) + { + CloseOnShutDownOrError(0); + return; + } + } + else + numread = 0; + + DebugAssert((numread+m_bufferpos) <= BUFSIZE); + DebugAssert(numread>=0); + DebugAssert(m_bufferpos>=0); + + if (numread+m_bufferpos <= 0) + { + CloseOnShutDownOrError(0); + return; + } + + int numsent = Send(m_pBuffer, numread + m_bufferpos); + + while (TRUE) + { + if (numsent != SOCKET_ERROR) + { + m_pOwner->SpeedLimitAddTransferredBytes(CFtpControlSocket::upload, numsent); + m_LastActiveTime = CTime::GetCurrentTime(); + m_transferdata.transferleft -= numsent; + } + + if (numsent==SOCKET_ERROR || !numsent) + { + int nError = GetLastError(); + if (nError == WSAENOTCONN) + { + //Not yet connected + m_bufferpos += numread; + return; + } + else if (nError == WSAEWOULDBLOCK) + { + m_bufferpos += numread; + } + else if (m_pSslLayer && nError == WSAESHUTDOWN) + { + m_bufferpos += numread; + // Do nothing, wait for shutdown complete notification. + return; + } + else + { + CloseOnShutDownOrError(CSMODE_TRANSFERERROR); + } + UpdateStatusBar(false); + return; + } + else + { + int pos = numread + m_bufferpos - numsent; + + if (pos < 0 || (numsent + pos) > BUFSIZE) + { + LogMessage(FZ_LOG_WARNING, L"Index out of range"); + CloseOnShutDownOrError(CSMODE_TRANSFERERROR); + return; + } + else if (!pos && numread < (currentBufferSize-m_bufferpos) && m_bufferpos != currentBufferSize) + { + CloseOnShutDownOrError(0); + return; + } + else if (!pos) + { + m_bufferpos = 0; + } + else + { + memmove(m_pBuffer, m_pBuffer+numsent, pos); + m_bufferpos=pos; + } + } + //Check if there are other commands in the command queue. + MSG msg; + if (PeekMessage(&msg, 0, m_nInternalMessageID, m_nInternalMessageID, PM_NOREMOVE)) + { + //Send resume message + LogMessage(FZ_LOG_DEBUG, L"Message waiting in queue, resuming later"); + TriggerEvent(FD_WRITE); + UpdateStatusBar(false); + return; + } + UpdateStatusBar(false); + + if (GetState() != closed) + currentBufferSize = m_pOwner->GetAbleToTransferSize(CFtpControlSocket::upload, beenWaiting); + else + currentBufferSize = BUFSIZE; + + if (m_bufferpos < currentBufferSize) + { + numread = ReadDataFromFile(m_pBuffer + m_bufferpos, static_cast(currentBufferSize - m_bufferpos)); + if (numread < 0 ) + { + return; + } + else if (!numread && !m_bufferpos) + { + CloseOnShutDownOrError(0); + return; + } + } + else + { + numread = 0; + } + + if (!currentBufferSize && !m_bufferpos) + { + // Not allowed to send yet, try later + TriggerEvent(FD_WRITE); + return; + } + + DebugAssert(numread>=0); + DebugAssert(m_bufferpos>=0); + numsent = Send(m_pBuffer, numread+m_bufferpos); + } + } +} + +void CTransferSocket::UpdateStatusBar(bool forceUpdate) +{ + if (m_nTransferState != STATE_STARTED) + return; + + if (!forceUpdate) + { + //Don't flood the main window with messages + //Else performance would be really low + LARGE_INTEGER curtime; + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&curtime); + if (((curtime.QuadPart-m_LastUpdateTime.QuadPart) < (freq.QuadPart/15) ) ) + return; + m_LastUpdateTime = curtime; + } + + //Update the statusbar + t_ffam_transferstatus *status=new t_ffam_transferstatus; + status->bFileTransfer = m_nMode & (CSMODE_DOWNLOAD | CSMODE_UPLOAD); + status->transfersize = m_transferdata.transfersize; + status->bytes=m_transferdata.transfersize-m_transferdata.transferleft; + + GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_TRANSFERSTATUS, 0), (LPARAM)status); +} + +BOOL CTransferSocket::Create(BOOL bUseSsl) +{ + if (bUseSsl) + { + m_pSslLayer = new CAsyncSslSocketLayer(); + + m_pSslLayer->SetClientCertificate(m_pOwner->m_CurrentServer.Certificate, m_pOwner->m_CurrentServer.PrivateKey); + } + + int nProxyType = GetOptionVal(OPTION_PROXYTYPE); + if (nProxyType != PROXYTYPE_NOPROXY) + { + USES_CONVERSION; + m_pProxyLayer = new CAsyncProxySocketLayer; + m_pProxyLayer->SetProxy( + nProxyType, T2CA(GetOption(OPTION_PROXYHOST)), GetOptionVal(OPTION_PROXYPORT), + GetOptionVal(OPTION_PROXYUSELOGON), T2CA(GetOption(OPTION_PROXYUSER)), T2CA(GetOption(OPTION_PROXYPASS))); + AddLayer(m_pProxyLayer); + } + + if (!GetOptionVal(OPTION_LIMITPORTRANGE)) + { + if (!CAsyncSocketEx::Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, 0, GetFamily())) + return FALSE; + + return TRUE; + } + else + { + int min=GetOptionVal(OPTION_PORTRANGELOW); + int max=GetOptionVal(OPTION_PORTRANGEHIGH); + if (min>=max) + { + m_pOwner->ShowStatus(IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE,FZ_LOG_ERROR); + return FALSE; + } + int startport=static_cast(min+((double)rand()*(max-min))/(RAND_MAX+1)); + int port=startport; + while (!CAsyncSocketEx::Create(port, SOCK_STREAM, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, 0, GetFamily())) + { + port++; + if (port>max) + port=min; + if (port==startport) + { + m_pOwner->ShowStatus(IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE,FZ_LOG_ERROR); + return FALSE; + } + } + } + + return TRUE; +} + +void CTransferSocket::Close() +{ + m_bCheckTimeout = FALSE; + CAsyncSocketEx::Close(); +} + +int CTransferSocket::OnLayerCallback(std::list& callbacks) +{ + for (std::list::iterator iter = callbacks.begin(); iter != callbacks.end(); ++iter) + { + if (iter->nType == LAYERCALLBACK_STATECHANGE) + { + if (CAsyncSocketEx::LogStateChange(iter->nParam1, iter->nParam2)) + { + const TCHAR * state2Desc = CAsyncSocketEx::GetStateDesc(iter->nParam2); + const TCHAR * state1Desc = CAsyncSocketEx::GetStateDesc(iter->nParam1); + if (iter->pLayer == m_pProxyLayer) + LogMessage(FZ_LOG_INFO, L"Proxy layer changed state from %s to %s", state2Desc, state1Desc); + else if (iter->pLayer == m_pSslLayer) + LogMessage(FZ_LOG_INFO, L"TLS layer changed state from %s to %s", state2Desc, state1Desc); +#ifndef MPEXT_NO_GSS + else if (iter->pLayer == m_pGssLayer) + LogMessage(FZ_LOG_INFO, L"GSS layer changed state from %s to %s", state2Desc, state1Desc); +#endif + else + LogMessage(FZ_LOG_INFO, L"Layer @ %d changed state from %s to %s", iter->pLayer, state2Desc, state1Desc); + } + } + else if (iter->nType == LAYERCALLBACK_LAYERSPECIFIC) + { + if (iter->pLayer == m_pProxyLayer) + { + switch (iter->nParam1) + { + case PROXYERROR_NOERROR: + m_pOwner->ShowStatus(IDS_PROXY_CONNECTED, FZ_LOG_STATUS); + break; + case PROXYERROR_NOCONN: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_NOCONN, FZ_LOG_ERROR); + break; + case PROXYERROR_REQUESTFAILED: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_REQUESTFAILED, FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHTYPEUNKNOWN: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_AUTHTYPEUNKNOWN, FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHFAILED: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_AUTHFAILED, FZ_LOG_ERROR); + break; + case PROXYERROR_AUTHNOLOGON: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_AUTHNOLOGON, FZ_LOG_ERROR); + break; + case PROXYERROR_CANTRESOLVEHOST: + m_pOwner->ShowStatus(IDS_ERRORMSG_PROXY_CANTRESOLVEHOST, FZ_LOG_ERROR); + break; + default: + LogMessage(FZ_LOG_WARNING, L"Unknown proxy error"); + } + } + else if (iter->pLayer == m_pSslLayer) + { + switch (iter->nParam1) + { + case SSL_INFO: + switch(iter->nParam2) + { + case SSL_INFO_SHUTDOWNCOMPLETE: + CloseAndEnsureSendClose(0); + break; + case SSL_INFO_ESTABLISHED: + m_pOwner->ShowStatus(IDS_STATUSMSG_SSLESTABLISHEDTRANSFER, FZ_LOG_STATUS); + TriggerEvent(FD_FORCEREAD); + break; + } + break; + case SSL_FAILURE: + switch (iter->nParam2) + { + case SSL_FAILURE_ESTABLISH: + m_pOwner->ShowStatus(IDS_ERRORMSG_CANTESTABLISHSSLCONNECTION, FZ_LOG_ERROR); + break; + case SSL_FAILURE_INITSSL: + m_pOwner->ShowStatus(IDS_ERRORMSG_CANTINITSSL, FZ_LOG_ERROR); + break; + } + EnsureSendClose(CSMODE_TRANSFERERROR); + break; + case SSL_VERIFY_CERT: + t_SslCertData data; + LPCTSTR CertError = NULL; + if (m_pSslLayer->GetPeerCertificateData(data, CertError)) + m_pSslLayer->SetNotifyReply(data.priv_data, SSL_VERIFY_CERT, 1); + else + { + CString str; + str.Format(TLS_CERT_DECODE_ERROR, CertError); + m_pOwner->ShowStatus(str, FZ_LOG_ERROR); + CloseAndEnsureSendClose(CSMODE_TRANSFERERROR); + } + break; + } + } +#ifndef MPEXT_NO_GSS + else if (iter->pLayer == m_pGssLayer) + { + USES_CONVERSION; + switch (iter->nParam1) + { + case GSS_INFO: + LogMessageRaw(FZ_LOG_INFO, A2CT(iter->str)); + break; + case GSS_ERROR: + LogMessageRaw(FZ_LOG_APIERROR, A2CT(iter->str)); + break; + case GSS_SHUTDOWN_COMPLETE: + CloseAndEnsureSendClose(0); + break; + } + } +#endif + } + nb_free(iter->str); + } + return 0; +} + +#ifndef MPEXT_NO_GSS +void CTransferSocket::UseGSS(CAsyncGssSocketLayer *pGssLayer) +{ + m_pGssLayer = new CAsyncGssSocketLayer(); + m_pGssLayer->InitTransferChannel(pGssLayer); +} +#endif + +#ifndef MPEXT_NO_ZLIB +bool CTransferSocket::InitZlib(int level) +{ + int res; + if (m_nMode & CSMODE_UPLOAD) + res = deflateInit2(&m_zlibStream, level, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY); + else + res = inflateInit2(&m_zlibStream, 15); + + if (res == Z_OK) + m_useZlib = true; + + return res == Z_OK; +} +#endif + +int CTransferSocket::ReadDataFromFile(char *buffer, int len) +{ + TRY + { + // Comparing to Filezilla 2, we do not do any translation locally, + // leaving it onto the server (what Filezilla 3 seems to do too) + const char Bom[4] = "\xEF\xBB\xBF"; + int read = m_pFile->Read(buffer, len); + if (GetOptionVal(OPTION_MPEXT_REMOVE_BOM) && + m_transferdata.bType && (read >= sizeof(Bom)) && (memcmp(buffer, Bom, sizeof(Bom)) == 0)) + { + memmove(buffer, buffer + 3, read - 3); + read -= 3; + int read2 = m_pFile->Read(buffer + read, 3); + if (read2 > 0) + { + read += read2; + } + } + return read; + } + CATCH_ALL(e) + { + TCHAR error[BUFSIZE]; + if (e->GetErrorMessage(error, BUFSIZE)) + m_pOwner->ShowStatus(error, FZ_LOG_ERROR); + CloseOnShutDownOrError(CSMODE_TRANSFERERROR); + return -1; + } + END_CATCH_ALL; +} + +void CTransferSocket::LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg) +{ + LogMessageRaw(nMessageType, pMsg); +} + +void CTransferSocket::EnsureSendClose(int Mode) +{ + if (!m_bSentClose) + { + if (Mode != 0) + { + m_nMode |= Mode; + } + m_bSentClose = TRUE; + DebugCheck(m_pOwner->m_pOwner->PostThreadMessage(m_nInternalMessageID, FZAPI_THREADMSG_TRANSFEREND, m_nMode)); + } +} + +void CTransferSocket::CloseAndEnsureSendClose(int Mode) +{ + Close(); + EnsureSendClose(Mode); +} + +void CTransferSocket::CloseOnShutDownOrError(int Mode) +{ + if (ShutDown()) + { + CloseAndEnsureSendClose(Mode); + } + else + { + int Error = GetLastError(); + if (Error != WSAEWOULDBLOCK) + { + // Log always or only when (Mode & CSMODE_TRANSFERERROR)? + // Does it anyway make sense at all to call this with Mode == 0? + LogError(Error); + CloseAndEnsureSendClose(Mode); + } + } +} + +void CTransferSocket::LogError(int Error) +{ + wchar_t * Buffer; + int Len = FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, Error, 0, (LPTSTR)&Buffer, 0, NULL); + if (Len > 0) + { + m_pOwner->ShowStatus(Buffer, FZ_LOG_ERROR); + LocalFree(Buffer); + } +} diff --git a/netbox/src/filezilla/TransferSocket.h b/netbox/src/filezilla/TransferSocket.h new file mode 100644 index 000000000..175c6c2f5 --- /dev/null +++ b/netbox/src/filezilla/TransferSocket.h @@ -0,0 +1,95 @@ +#pragma once + +#include "FtpListResult.h" + +#include "FtpControlSocket.h" +#include "ApiLog.h" + +#ifndef MPEXT_NO_ZLIB +#include +#endif + +class CFtpControlSocket; +class CAsyncProxySocketLayer; +class CAsyncSslSocketLayer; +#ifndef MPEXT_NO_GSS +class CAsyncGssSocketLayer; +#endif + +class CTransferSocket : public CAsyncSocketEx, public CApiLog +{ +public: + CFtpListResult * m_pListResult; + +public: + CTransferSocket(CFtpControlSocket * pOwner, int nMode); + virtual ~CTransferSocket(); + +public: + int m_nInternalMessageID; + virtual void Close(); + virtual BOOL Create(BOOL bUseSsl); + BOOL m_bListening; + CFile * m_pFile; + t_transferdata m_transferdata; + void SetActive(); + int CheckForTimeout(int delay); +#ifndef MPEXT_NO_GSS + void UseGSS(CAsyncGssSocketLayer * pGssLayer); +#endif +#ifndef MPEXT_NO_ZLIB + bool InitZlib(int level); +#endif + +public: + virtual void OnReceive(int nErrorCode); + virtual void OnAccept(int nErrorCode); + virtual void OnConnect(int nErrorCode); + virtual void OnClose(int nErrorCode); + virtual void OnSend(int nErrorCode); + virtual void SetState(int nState); + +protected: + virtual int OnLayerCallback(std::list & callbacks); + int ReadDataFromFile(char * buffer, int len); + virtual void LogSocketMessageRaw(int nMessageType, LPCTSTR pMsg); + virtual void ConfigureSocket(); + bool Activate(); + void Start(); + + CFtpControlSocket * m_pOwner; + CAsyncProxySocketLayer * m_pProxyLayer; + CAsyncSslSocketLayer * m_pSslLayer; +#ifndef MPEXT_NO_GSS + CAsyncGssSocketLayer * m_pGssLayer; +#endif + void UpdateStatusBar(bool forceUpdate); + BOOL m_bSentClose; + int m_bufferpos; + char * m_pBuffer; +#ifndef MPEXT_NO_ZLIB + char * m_pBuffer2; // Used by zlib transfers +#endif + BOOL m_bCheckTimeout; + CTime m_LastActiveTime; + int m_nTransferState; + int m_nMode; + int m_nNotifyWaiting; + bool m_bActivationPending; + + void CloseAndEnsureSendClose(int Mode); + void EnsureSendClose(int Mode); + void CloseOnShutDownOrError(int Mode); + void LogError(int Error); + void SetBuffers(); + + LARGE_INTEGER m_LastUpdateTime; + unsigned int m_LastSendBufferUpdate; + DWORD m_SendBuf; + +#ifndef MPEXT_NO_ZLIB + z_stream m_zlibStream; + bool m_useZlib; +#endif +}; + diff --git a/netbox/src/filezilla/afxdll.cpp b/netbox/src/filezilla/afxdll.cpp new file mode 100644 index 000000000..08fba2ee1 --- /dev/null +++ b/netbox/src/filezilla/afxdll.cpp @@ -0,0 +1,15 @@ +#include "stdafx.h" +#include "afxdll.h" + +HINSTANCE HInst = NULL; + +void InitExtensionModule(HINSTANCE HInst) +{ + ::HInst = HInst; + AFX_MANAGE_STATE(AfxGetModuleState()); + afxCurrentResourceHandle = ::HInst; +} + +void TermExtensionModule() +{ +} diff --git a/netbox/src/filezilla/afxdll.h b/netbox/src/filezilla/afxdll.h new file mode 100644 index 000000000..9ee6659bd --- /dev/null +++ b/netbox/src/filezilla/afxdll.h @@ -0,0 +1,8 @@ +#pragma once + +#include "stdafx.h" + +extern HINSTANCE HInst; + +void InitExtensionModule(HINSTANCE HInst); +void TermExtensionModule(); diff --git a/netbox/src/filezilla/license-filezilla.txt b/netbox/src/filezilla/license-filezilla.txt new file mode 100644 index 000000000..d4ceadc8d --- /dev/null +++ b/netbox/src/filezilla/license-filezilla.txt @@ -0,0 +1,17 @@ +This code is based on FileZilla - a Windows ftp client + +Copyright (C) 2002-2004 - Tim Kosse + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. diff --git a/netbox/src/filezilla/misc/CBase64Coding.cpp b/netbox/src/filezilla/misc/CBase64Coding.cpp new file mode 100644 index 000000000..cc94159d3 --- /dev/null +++ b/netbox/src/filezilla/misc/CBase64Coding.cpp @@ -0,0 +1,263 @@ +#include "stdafx.h" +#include "CBase64Coding.hpp" +#pragma hdrstop + +#define CARRIAGE_RETURN (13) +#define LINE_FEED (10) + +/* +** Author: Samuel R. Blackburn +** Internet: wfc@pobox.com +** +** You can use it any way you like as long as you don't try to sell it. +** +** Any attempt to sell WFC in source code form must have the permission +** of the original author. You can produce commercial executables with +** WFC but you can't sell WFC. +** +** Copyright, 2000, Samuel R. Blackburn +** +** Workfile: CBase64Coding.cpp +** Revision: 1.3 +** Modtime: 5/12/00 3:39p +** Reuse Tracing Code: 1 +*/ + +//Modified for use with CAsyncProxySocket, removed tracing code + +#define END_OF_BASE64_ENCODED_DATA ('=') +#define BASE64_END_OF_BUFFER (0xFD) +#define BASE64_IGNORABLE_CHARACTER (0xFE) +#define BASE64_UNKNOWN_VALUE (0xFF) +#define BASE64_NUMBER_OF_CHARACTERS_PER_LINE (72) + +static inline BYTE __get_character( const BYTE * buffer, const BYTE * decoder_table, int& index, int size_of_buffer ) +{ + BYTE return_value = 0; + + do + { + if ( index >= size_of_buffer ) + { + return( BASE64_END_OF_BUFFER ); + } + + return_value = buffer[ index ]; + ++index; + } + while( return_value != END_OF_BASE64_ENCODED_DATA && + decoder_table[ return_value ] == BASE64_IGNORABLE_CHARACTER ); + + return( return_value ); +} + +CBase64Coding::CBase64Coding() +{ +} + +CBase64Coding::~CBase64Coding() +{ +} + +BOOL CBase64Coding::Encode( const char * source, int len, char * destination_string ) +{ + + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + int loop_index = 0; + int number_of_bytes_to_encode = len; + + BYTE byte_to_add = 0; + BYTE byte_1 = 0; + BYTE byte_2 = 0; + BYTE byte_3 = 0; + + DWORD number_of_bytes_encoded = (DWORD) ( (double) number_of_bytes_to_encode / (double) 0.75 ) + 1; + + // Now add in the CR/LF pairs, each line is truncated at 72 characters + + // 2000-05-12 + // Thanks go to Ilia Golubev (ilia@varicom.co.il) for finding a bug here. + // I was using number_of_bytes_to_encode rather than number_of_bytes_encoded. + + number_of_bytes_encoded += (DWORD)( ( ( number_of_bytes_encoded / BASE64_NUMBER_OF_CHARACTERS_PER_LINE ) + 1 ) * 2 ); + + char * destination = destination_string; + + number_of_bytes_encoded = 0; + + while( loop_index < number_of_bytes_to_encode ) + { + // Output the first byte + + byte_1 = source[ loop_index ]; + byte_to_add = alphabet[ ( byte_1 >> 2 ) ]; + + destination[number_of_bytes_encoded ] = static_cast( byte_to_add ); + number_of_bytes_encoded++; + + ++loop_index; + + if ( loop_index >= number_of_bytes_to_encode ) + { + // We're at the end of the data to encode + + byte_2 = 0; + byte_to_add = alphabet[ ( ( ( byte_1 & 0x03 ) << 4 ) | ( ( byte_2 & 0xF0 ) >> 4 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded + 1 ] = 0; + + return( TRUE ); + } + else + { + byte_2 = source[ loop_index ]; + } + + byte_to_add = alphabet[ ( ( ( byte_1 & 0x03 ) << 4 ) | ( ( byte_2 & 0xF0 ) >> 4 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + ++loop_index; + + if ( loop_index >= number_of_bytes_to_encode ) + { + // We ran out of bytes, we need to add the last half of byte_2 and pad + byte_3 = 0; + + byte_to_add = alphabet[ ( ( ( byte_2 & 0x0F ) << 2 ) | ( ( byte_3 & 0xC0 ) >> 6 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = END_OF_BASE64_ENCODED_DATA; + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded + 1 ] = 0; + + return( TRUE ); + } + else + { + byte_3 = source[ loop_index ]; + } + + ++loop_index; + + byte_to_add = alphabet[ ( ( ( byte_2 & 0x0F ) << 2 ) | ( ( byte_3 & 0xC0 ) >> 6 ) ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + byte_to_add = alphabet[ ( byte_3 & 0x3F ) ]; + + destination[ number_of_bytes_encoded ] = byte_to_add; + number_of_bytes_encoded++; + + if ( ( number_of_bytes_encoded % BASE64_NUMBER_OF_CHARACTERS_PER_LINE ) == 0 ) + { + destination[ number_of_bytes_encoded ] = CARRIAGE_RETURN; + number_of_bytes_encoded++; + + destination[ number_of_bytes_encoded ] = LINE_FEED; + number_of_bytes_encoded++; + } + } + + // 1999-09-01 + // Thanks go to Yurong Lin (ylin@dial.pipex.com) for finding a bug here. + // We must NULL terminate the string before letting CString have the buffer back. + + destination[ number_of_bytes_encoded ] = 0; + + return( TRUE ); +} + +// End of source + +#if 0 + + + +WFC - CBase64Coding + + + + + + +

CBase64Coding

+ +Revision: 1.3

+ +

Description

+ +This class gives you the ability to encode/decode data using base64. + +

Constructors

+ +
+ +
CBase64Coding()
+Constructs this object. + +
+ +

Methods

+ +
+ +
BOOL Decode( const CByteArray& source, CByteArray& destination )
+BOOL Decode( const CString&    source, CByteArray& destination )
+This method takes base64 encoded text and produces the bytes. It decodes +the base64 encoding. + +
BOOL Encode( const CByteArray& source, CByteArray& destination )
+BOOL Encode( const CByteArray& source, CString&    destination )
+This method takes bytes and turns them into base64 text. + +
+ +

Example

+
#include <wfc.h>
+
+int _tmain( int number_of_command_line_arguments, LPCTSTR command_line_arguments[] )
+{
+   WFCTRACEINIT( TEXT( "_tmain()" ) );
+
+   CByteArray bytes;
+
+   get_file_contents( command_line_arguments[ 0 ], bytes );
+
+   CBase64Coding encoder;
+
+   CString encoded_data;
+
+   if ( encoder.Encode( bytes, encoded_data ) != FALSE )
+   {
+      _tprintf( TEXT( "%s\n", (LPCTSTR) encoded_data );
+   }
+}
+
Copyright, 2000, Samuel R. Blackburn
+$Workfile: CBase64Coding.cpp $
+$Modtime: 5/12/00 3:39p $ + + + +#endif diff --git a/netbox/src/filezilla/misc/CBase64Coding.hpp b/netbox/src/filezilla/misc/CBase64Coding.hpp new file mode 100644 index 000000000..ba6aa6b5e --- /dev/null +++ b/netbox/src/filezilla/misc/CBase64Coding.hpp @@ -0,0 +1,48 @@ +#if ! defined( BASE_64_CODING_CLASS_HEADER ) + +/* +** Author: Samuel R. Blackburn +** Internet: wfc@pobox.com +** +** You can use it any way you like as long as you don't try to sell it. +** +** Any attempt to sell WFC in source code form must have the permission +** of the original author. You can produce commercial executables with +** WFC but you can't sell WFC. +** +** Copyright, 2000, Samuel R. Blackburn +** +** Workfile: CBase64Coding.hpp +** Revision: 1.2 +** Modtime: 1/04/00 4:39a +*/ + +#define BASE_64_CODING_CLASS_HEADER + +class CBase64Coding +{ + private: + + // Don't allow canonical behavior (i.e. don't allow this class + // to be passed by value) + + CBase64Coding( const CBase64Coding& ) {}; + CBase64Coding& operator=( const CBase64Coding& ) { return( *this ); } + + public: + + // Construction + + CBase64Coding(); + + /* + ** Destructor should be virtual according to MSJ article in Sept 1992 + ** "Do More with Less Code:..." + */ + + virtual ~CBase64Coding(); + + virtual BOOL Encode( const char * source, int len, char * destination ); +}; + +#endif // BASE_64_CODING_CLASS_HEADER diff --git a/netbox/src/filezilla/stdafx.cpp b/netbox/src/filezilla/stdafx.cpp new file mode 100644 index 000000000..fd4f341c7 --- /dev/null +++ b/netbox/src/filezilla/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/netbox/src/filezilla/stdafx.h b/netbox/src/filezilla/stdafx.h new file mode 100644 index 000000000..9571082e7 --- /dev/null +++ b/netbox/src/filezilla/stdafx.h @@ -0,0 +1,541 @@ + +#pragma once + + +#define _int64 __int64 + +#define MPEXT_NO_ZLIB +#define MPEXT_NO_GSS +#define _AFX_ENABLE_INLINES +#define _AFX_NOFORCE_LIBS + +#ifndef LENOF +#define LENOF(x) (_countof(X)) +#endif + +#define _ATL_MIN_CRT +#define _ATL_NO_DEFAULT_LIBS + +#include "afxpriv.h" +#include "afxole.h" +#include "../src/mfc/oleimpl2.h" +#include "../src/mfc/afximpl.h" + +#include +#include +#include + +// STL includes +#include +#include +#include +#include +#include + +class CFileFix; +#define CFile CFileFix + +#include +// these create conflict with afxwin.h +#undef BEGIN_MESSAGE_MAP +#undef END_MESSAGE_MAP + +#include + +#include +#include +#include +#include + +#include "FileZillaApi.h" +#include "afxdll.h" + +#define _strlwr strlwr + +const int FILEEXISTS_OVERWRITE = 0; +const int FILEEXISTS_RESUME = 1; +const int FILEEXISTS_RENAME = 2; +const int FILEEXISTS_SKIP = 3; +const int FILEEXISTS_COMPLETE = 4; + +class t_ffam_statusmessage +{ +CUSTOM_MEM_ALLOCATION_IMPL +public: + CString status; + int type; + BOOL post; +}; + +struct t_ffam_transferstatus +{ +CUSTOM_MEM_ALLOCATION_IMPL + __int64 bytes; + __int64 transfersize; + BOOL bFileTransfer; +}; + +#undef CFile + +class CFileFix : public CFile +{ +CUSTOM_MEM_ALLOCATION_IMPL +public: + explicit CFileFix() : CFile() {} + explicit CFileFix(HANDLE hFile) : CFile(hFile) {} + void SetCloseOnDelete(BOOL Value) { m_bCloseOnDelete = Value; } + // MFC CFile::Read does not include file name into error message + UINT Read(void * lpBuf, UINT nCount) + { + ASSERT_VALID(this); + DebugAssert(m_hFile != hFileNull); + + if (nCount == 0) + { + return 0; // avoid Win32 "null-read" + } + + DebugAssert(lpBuf != NULL); + DebugAssert(AfxIsValidAddress(lpBuf, nCount)); + + DWORD dwRead; + if (!::ReadFile(static_cast(m_hFile), lpBuf, nCount, &dwRead, NULL)) + { + // The only change from MFC CFile::Read is m_strFileName + CFileException::ThrowOsError(static_cast(::GetLastError()), m_strFileName); + } + + return static_cast(dwRead); + } + + // MFC allocates CObject (ancestor of CFile) with new, but deallocates with free, + // what codeguard dislikes, this is fix, not sure if it is necessary for + // release version, but probably causes no harm + /*void operator delete(void* p) + { + delete p; + }*/ +}; + +#define CFile CFileFix + +struct CStringDataA +{ + long nRefs; // reference count + int nDataLength; // length of data (including terminator) + int nAllocLength; // length of allocation + // char data[nAllocLength]; + + CHAR * data() // CHAR* to managed data + { + return (CHAR *)(this+1); + } +}; + +extern LPCSTR _afxPchNilA; +extern CStringDataA* _afxDataNilA; +#define afxEmptyStringA ((CStringA&)*(CStringA*)&_afxPchNilA) + +#if 0 +class CStringA +{ +public: + CStringA() + { + m_pchData = afxEmptyStringA.m_pchData; + } + + CStringA(const CStringA& stringSrc) + { + DebugAssert(stringSrc.GetData()->nRefs != 0); + if (stringSrc.GetData()->nRefs >= 0) + { + DebugAssert(stringSrc.GetData() != _afxDataNilA); + m_pchData = stringSrc.m_pchData; + InterlockedIncrement(&GetData()->nRefs); + } + else + { + Init(); + *this = stringSrc.m_pchData; + } + } + + CStringA(LPCSTR lpsz) + { + Init(); + if (lpsz != NULL && HIWORD(lpsz) == NULL) + { + DebugFail(); + } + else + { + int nLen = SafeStrlen(lpsz); + if (nLen != 0) + { + AllocBuffer(nLen); + memcpy(m_pchData, lpsz, nLen*sizeof(char)); + } + } + } + + ~CStringA() + { + if (GetData() != _afxDataNilA) + { + if (InterlockedDecrement(&GetData()->nRefs) <= 0) + { + FreeData(GetData()); + } + } + } + + int GetLength() const + { + return GetData()->nDataLength; + } + + char operator[](int nIndex) const + { + // same as GetAt + DebugAssert(nIndex >= 0); + DebugAssert(nIndex < GetData()->nDataLength); + return m_pchData[nIndex]; + } + + // ref-counted copy from another CString + CStringA& operator=(const CStringA& stringSrc) + { + if (m_pchData != stringSrc.m_pchData) + { + if ((GetData()->nRefs < 0 && GetData() != _afxDataNilA) || + stringSrc.GetData()->nRefs < 0) + { + // actual copy necessary since one of the strings is locked + AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData); + } + else + { + // can just copy references around + Release(); + DebugAssert(stringSrc.GetData() != _afxDataNilA); + m_pchData = stringSrc.m_pchData; + InterlockedIncrement(&GetData()->nRefs); + } + } + return *this; + } + + const CStringA & operator=(LPCSTR lpsz) + { + DebugAssert(lpsz == NULL || AfxIsValidString(lpsz)); + AssignCopy(SafeStrlen(lpsz), lpsz); + return *this; + } + + const CStringA & operator+=(char ch) + { + ConcatInPlace(1, &ch); + return *this; + } + + friend CStringA AFXAPI operator+(const CStringA & string, char ch); + + operator LPCSTR() const + { + return m_pchData; + } + + int Compare(LPCSTR lpsz) const + { + DebugAssert(AfxIsValidString(lpsz)); + return strcmp(m_pchData, lpsz); + } + + CStringA Mid(int nFirst, int nCount) const + { + // out-of-bounds requests return sensible things + if (nFirst < 0) + { + nFirst = 0; + } + if (nCount < 0) + { + nCount = 0; + } + + if (nFirst + nCount > GetData()->nDataLength) + { + nCount = GetData()->nDataLength - nFirst; + } + if (nFirst > GetData()->nDataLength) + { + nCount = 0; + } + + DebugAssert(nFirst >= 0); + DebugAssert(nFirst + nCount <= GetData()->nDataLength); + + // optimize case of returning entire string + if (nFirst == 0 && nFirst + nCount == GetData()->nDataLength) + { + return *this; + } + + CStringA dest; + AllocCopy(dest, nCount, nFirst, 0); + return dest; + } + + CStringA Left(int nCount) const + { + if (nCount < 0) + { + nCount = 0; + } + if (nCount >= GetData()->nDataLength) + { + return *this; + } + + CStringA dest; + AllocCopy(dest, nCount, 0, 0); + return dest; + } + + int Find(char ch) const + { + return Find(ch, 0); + } + + int Find(char ch, int nStart) const + { + int nLength = GetData()->nDataLength; + if (nStart >= nLength) + { + return -1; + } + + // find first single character + LPSTR lpsz = strchr(m_pchData + nStart, (unsigned char)ch); + + // return -1 if not found and index otherwise + return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); + } + + // find a sub-string (like strstr) + int Find(LPCSTR lpszSub) const + { + return Find(lpszSub, 0); + } + + int Find(LPCSTR lpszSub, int nStart) const + { + DebugAssert(AfxIsValidString(lpszSub)); + + int nLength = GetData()->nDataLength; + if (nStart > nLength) + { + return -1; + } + + // find first matching substring + LPSTR lpsz = strstr(m_pchData + nStart, lpszSub); + + // return -1 for not found, distance from beginning otherwise + return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); + } + + void MakeUpper() + { + CopyBeforeWrite(); + strupr(m_pchData); + } + +protected: + LPSTR m_pchData; // pointer to ref counted string data + + CStringDataA * GetData() const + { + DebugAssert(m_pchData != NULL); return ((CStringDataA*)m_pchData)-1; + } + + void Init() + { + m_pchData = afxEmptyStringA.m_pchData; + } + + void AllocCopy(CStringA & dest, int nCopyLen, int nCopyIndex, int nExtraLen) const + { + // will clone the data attached to this string + // allocating 'nExtraLen' characters + // Places results in uninitialized string 'dest' + // Will copy the part or all of original data to start of new string + + int nNewLen = nCopyLen + nExtraLen; + if (nNewLen == 0) + { + dest.Init(); + } + else + { + dest.AllocBuffer(nNewLen); + memcpy(dest.m_pchData, m_pchData+nCopyIndex, nCopyLen*sizeof(char)); + } + } + + void AllocBuffer(int nLen) + // always allocate one extra character for '\0' termination + // assumes [optimistically] that data length will equal allocation length + { + DebugAssert(nLen >= 0); + DebugAssert(nLen <= INT_MAX-1); // max size (enough room for 1 extra) + + if (nLen == 0) + { + Init(); + } + else + { + CStringDataA* pData; + { + pData = (CStringDataA*) + new BYTE[sizeof(CStringDataA) + (nLen+1)*sizeof(char)]; + pData->nAllocLength = nLen; + } + pData->nRefs = 1; + pData->data()[nLen] = '\0'; + pData->nDataLength = nLen; + m_pchData = pData->data(); + } + } + + void AssignCopy(int nSrcLen, LPCSTR lpszSrcData) + { + AllocBeforeWrite(nSrcLen); + memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(char)); + GetData()->nDataLength = nSrcLen; + m_pchData[nSrcLen] = '\0'; + } + + void FASTCALL FreeData(CStringDataA * pData) + { + delete[] (BYTE*)pData; + } + + void PASCAL Release(CStringDataA * pData) + { + if (pData != _afxDataNilA) + { + DebugAssert(pData->nRefs != 0); + if (InterlockedDecrement(&pData->nRefs) <= 0) + { + FreeData(pData); + } + } + } + + void Release() + { + if (GetData() != _afxDataNilA) + { + DebugAssert(GetData()->nRefs != 0); + if (InterlockedDecrement(&GetData()->nRefs) <= 0) + { + FreeData(GetData()); + } + Init(); + } + } + + void ConcatCopy(int nSrc1Len, LPCSTR lpszSrc1Data, int nSrc2Len, LPCSTR lpszSrc2Data) + { + // -- master concatenation routine + // Concatenate two sources + // -- assume that 'this' is a new CString object + + int nNewLen = nSrc1Len + nSrc2Len; + if (nNewLen != 0) + { + AllocBuffer(nNewLen); + memcpy(m_pchData, lpszSrc1Data, nSrc1Len*sizeof(char)); + memcpy(m_pchData+nSrc1Len, lpszSrc2Data, nSrc2Len*sizeof(char)); + } + } + + void ConcatInPlace(int nSrcLen, LPCSTR lpszSrcData) + { + // -- the main routine for += operators + + // concatenating an empty string is a no-op! + if (nSrcLen == 0) + { + return; + } + + // if the buffer is too small, or we have a width mis-match, just + // allocate a new buffer (slow but sure) + if (GetData()->nRefs > 1 || GetData()->nDataLength + nSrcLen > GetData()->nAllocLength) + { + // we have to grow the buffer, use the ConcatCopy routine + CStringDataA* pOldData = GetData(); + ConcatCopy(GetData()->nDataLength, m_pchData, nSrcLen, lpszSrcData); + DebugAssert(pOldData != NULL); + CStringA::Release(pOldData); + } + else + { + // fast concatenation when buffer big enough + memcpy(m_pchData+GetData()->nDataLength, lpszSrcData, nSrcLen*sizeof(char)); + GetData()->nDataLength += nSrcLen; + DebugAssert(GetData()->nDataLength <= GetData()->nAllocLength); + m_pchData[GetData()->nDataLength] = '\0'; + } + } + + void CopyBeforeWrite() + { + if (GetData()->nRefs > 1) + { + CStringDataA* pData = GetData(); + Release(); + AllocBuffer(pData->nDataLength); + memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(char)); + } + DebugAssert(GetData()->nRefs <= 1); + } + + void AllocBeforeWrite(int nLen) + { + if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength) + { + Release(); + AllocBuffer(nLen); + } + DebugAssert(GetData()->nRefs <= 1); + } + + static int PASCAL SafeStrlen(LPCSTR lpsz) + { + return (lpsz == NULL) ? 0 : strlen(lpsz); + } +}; + +inline bool AFXAPI operator==(const CStringA & s1, LPCSTR s2) +{ + return s1.Compare(s2) == 0; +} + +inline bool AFXAPI operator!=(const CStringA & s1, LPCSTR s2) +{ + return s1.Compare(s2) != 0; +} + +inline CStringA AFXAPI operator+(const CStringA & string1, char ch) +{ + CStringA s; + s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, 1, &ch); + return s; +} + +#endif diff --git a/netbox/src/filezilla/structures.cpp b/netbox/src/filezilla/structures.cpp new file mode 100644 index 000000000..375e3efd3 --- /dev/null +++ b/netbox/src/filezilla/structures.cpp @@ -0,0 +1,60 @@ + +#include "stdafx.h" + + +#if defined(__BORLANDC__) +AFX_COMDAT int _afxInitDataA[] = { -1, 0, 0, 0 }; +AFX_COMDAT CStringDataA* _afxDataNilA = (CStringDataA*)&_afxInitDataA; +AFX_COMDAT LPCSTR _afxPchNilA = (LPCSTR)(((BYTE*)&_afxInitDataA)+sizeof(CStringDataA)); +#endif + +t_directory::t_directory() : + num(0), + direntry(0) +{ +} + +t_directory::t_directory(const t_directory & a) : + num(0), + direntry(0) +{ + this->operator=(a); +} + +t_directory::~t_directory() +{ + if (direntry) + delete [] direntry; +} + +t_directory& t_directory::operator=(const t_directory &a) +{ + if (&a==this) + return *this; + + if (direntry) + delete [] direntry; + direntry=0; + path=a.path; + num=a.num; + server=a.server; + if (num) + direntry=new t_directory::t_direntry[num]; + for (int i=0;i + +class CServerPath; + +class t_directory : public TObject +{ +public: + t_directory(); + t_directory(const t_directory &a); + ~t_directory(); + t_server server; + CServerPath path; + int num; + class t_direntry : public TObject + { + public: + t_direntry(); + CString linkTarget; + CString name; + CString permissionstr; + CString humanpermstr; // RFC format + CString ownergroup; + __int64 size; + bool bUnsure; // Set by CFtpControlSocket::FileTransfer when uploads fail after sending STOR/APPE + bool dir; + bool bLink; + class t_date : public TObject + { + public: + t_date(); + int year,month,day,hour,minute,second; + bool hastime; + bool hasseconds; + bool hasdate; + bool utc; + } date; + } * direntry; + t_directory & operator=(const t_directory & a); +}; + diff --git a/netbox/src/resource/HelpCore.h b/netbox/src/resource/HelpCore.h new file mode 100644 index 000000000..bd768bb3d --- /dev/null +++ b/netbox/src/resource/HelpCore.h @@ -0,0 +1,36 @@ +#pragma once + +#define HELP_UNKNOWN_KEY "message_host_key" +#define HELP_DIFFERENT_KEY "message_security_breach" +#define HELP_RESUME_TRANSFER "resume" +#define HELP_APPEND_OR_RESUME "resume#alternate_usage" +#define HELP_PARTIAL_BIGGER_THAN_SOURCE HELP_RESUME_TRANSFER +#define HELP_SYNCHRONIZE "task_synchronize_full" +#define HELP_VERIFY_CERTIFICATE "ftps" +#define HELP_FTP_CANNOT_OPEN_ACTIVE_CONNECTION "message_cannot_open_active_connection" +#define HELP_MESSAGE_HOST_IS_NOT_COMMUNICATING "message_host_is_not_communicating" +#define HELP_ERRORMSG_TIMEOUT "message_timeout_detected" +#define HELP_OVERWRITE "ui_overwrite" +#define HELP_NET_TRANSL_REFUSED "message_connection_refused" +#define HELP_NET_TRANSL_TIMEOUT "message_connection_timed_out" +#define HELP_NET_TRANSL_NO_ROUTE "message_no_route_to_host" +#define HELP_NET_TRANSL_CONN_ABORTED "message_software_caused_connection_abort" +#define HELP_NET_TRANSL_HOST_NOT_EXIST "message_host_does_not_exist" +#define HELP_SFTP_STATUS_FAILURE "sftp_codes#code_4" +#define HELP_UNEXPECTED_CLOSE_ERROR "message_unexpected_close" +#define HELP_NET_TRANSL_PACKET_GARBLED "message_incoming_packet_was_garbled_on_decryption" +#define HELP_NOT_CONNECTED "message_exit_status_1" +#define HELP_SFTP_STATUS_PERMISSION_DENIED "message_permission_denied" +#define HELP_NET_TRANSL_RESET "message_reset_by_peer" +#define HELP_LIST_LINE_ERROR "message_unexpected_directory_listing" +#define HELP_SFTP_PACKET_TOO_BIG "message_large_packet" +#define HELP_SKIP_STARTUP_MESSAGE_ERROR "message_startup_message" +#define HELP_EMPTY_DIRECTORY "message_empty_listing_for_directory" +#define HELP_RENAME_AFTER_RESUME_ERROR "message_transfer_finished_could_not_rename" +#define HELP_INTERNAL_ERROR "internal_error" +#define HELP_PRESERVE_TIME_PERM_ERROR "message_preserve_time_perm" +#define HELP_FTP_SUGGESTION "message_server_rejected_sftp_listens_for_ftp" +#define HELP_STATUSMSG_DISCONNECTED "message_disconnected" +#define HELP_SFTP_INITIALIZE_ERROR "message_cannot_initialize_sftp_protocol" +#define HELP_AUTH_TRANSL_KEY_REFUSED "message_key_refused" + diff --git a/netbox/src/resource/TestTexts.h b/netbox/src/resource/TestTexts.h new file mode 100644 index 000000000..5b3b16c79 --- /dev/null +++ b/netbox/src/resource/TestTexts.h @@ -0,0 +1,4 @@ +#pragma once + +#define CONST_TEST_STRING 1100 +#define CONST_TEST_STRING2 1101 diff --git a/netbox/src/resource/TextsCore.h b/netbox/src/resource/TextsCore.h new file mode 100644 index 000000000..f5906afb0 --- /dev/null +++ b/netbox/src/resource/TextsCore.h @@ -0,0 +1,493 @@ +#pragma once + +#define UNKNOWN_KEY3 1 +#define DIFFERENT_KEY4 2 +#define OLD_KEY 3 + +//#define SCRIPT_HELP_HELP 4 +//#define SCRIPT_EXIT_HELP 5 +//#define SCRIPT_OPEN_HELP6 6 +//#define SCRIPT_CLOSE_HELP 7 +//#define SCRIPT_SESSION_HELP 8 +//#define SCRIPT_PWD_HELP 9 +//#define SCRIPT_CD_HELP 10 +//#define SCRIPT_LS_HELP 11 +//#define SCRIPT_LPWD_HELP 12 +//#define SCRIPT_LCD_HELP 13 +//#define SCRIPT_LLS_HELP 14 +//#define SCRIPT_RM_HELP 15 +//#define SCRIPT_RMDIR_HELP 16 +//#define SCRIPT_MV_HELP 17 +//#define SCRIPT_CHMOD_HELP 18 +//#define SCRIPT_LN_HELP 19 +//#define SCRIPT_MKDIR_HELP 20 +//#define SCRIPT_GET_HELP7 21 +//#define SCRIPT_PUT_HELP7 22 +//#define SCRIPT_OPTION_HELP7 23 +//#define SCRIPT_SYNCHRONIZE_HELP7 24 +//#define SCRIPT_KEEPUPTODATE_HELP4 25 +//#define SCRIPT_CALL_HELP2 26 +//#define SCRIPT_ECHO_HELP 27 +//#define SCRIPT_STAT_HELP 28 +//#define SCRIPT_CHECKSUM_HELP 29 + +#define CORE_ERROR_STRINGS 100 +#define KEY_NOT_VERIFIED 101 +#define CONNECTION_FAILED 102 +#define USER_TERMINATED 103 +#define LOST_CONNECTION 104 +#define CANT_DETECT_RETURN_CODE 105 +#define COMMAND_FAILED 106 +#define COMMAND_FAILED_CODEONLY 107 +#define INVALID_OUTPUT_ERROR 108 +#define READ_CURRENT_DIR_ERROR 109 +#define SKIP_STARTUP_MESSAGE_ERROR 110 +#define CHANGE_DIR_ERROR 111 +#define LIST_DIR_ERROR 113 +#define LIST_LINE_ERROR 114 +#define RIGHTS_ERROR 115 +#define CLEANUP_CONFIG_ERROR 116 +#define CLEANUP_HOSTKEYS_ERROR 117 +#define CLEANUP_SEEDFILE_ERROR 118 +#define CLEANUP_SESSIONS_ERROR 119 +#define DETECT_RETURNVAR_ERROR 120 +#define LOOKUP_GROUPS_ERROR 121 +#define FILE_NOT_EXISTS 122 +#define CANT_GET_ATTRS 123 +#define OPENFILE_ERROR 124 +#define READ_ERROR 125 +#define COPY_FATAL 126 +#define TOREMOTE_COPY_ERROR 127 +#define TOLOCAL_COPY_ERROR 128 +#define SCP_EMPTY_LINE 129 +#define SCP_ILLEGAL_TIME_FORMAT 130 +#define SCP_INVALID_CONTROL_RECORD 131 +#define COPY_ERROR 132 +#define SCP_ILLEGAL_FILE_DESCRIPTOR 133 +#define NOT_DIRECTORY_ERROR 134 +#define CREATE_DIR_ERROR 135 +#define CREATE_FILE_ERROR 136 +#define WRITE_ERROR 137 +#define CANT_SET_ATTRS 138 +#define REMOTE_ERROR 139 +#define DELETE_FILE_ERROR 140 +#define LOG_GEN_ERROR 141 +#define LOG_OPENERROR 142 +#define RENAME_FILE_ERROR 143 +#define RENAME_CREATE_FILE_EXISTS 144 +#define RENAME_CREATE_DIR_EXISTS 145 +#define CHANGE_HOMEDIR_ERROR 146 +#define UNALIAS_ALL_ERROR 147 +#define UNSET_NATIONAL_ERROR 149 +#define FIRST_LINE_EXPECTED 150 +#define CLEANUP_INIFILE_ERROR 151 +#define AUTHENTICATION_LOG 153 +#define AUTHENTICATION_FAILED 154 +#define NOT_CONNECTED 155 +#define SAVE_KEY_ERROR 156 +#define SSH_EXITCODE 158 +#define SFTP_INVALID_TYPE 159 +#define SFTP_VERSION_NOT_SUPPORTED 160 +#define SFTP_MESSAGE_NUMBER 161 +#define SFTP_STATUS_OK 162 +#define SFTP_STATUS_EOF 163 +#define SFTP_STATUS_NO_SUCH_FILE 164 +#define SFTP_STATUS_PERMISSION_DENIED 165 +#define SFTP_STATUS_FAILURE 166 +#define SFTP_STATUS_BAD_MESSAGE 167 +#define SFTP_STATUS_NO_CONNECTION 168 +#define SFTP_STATUS_CONNECTION_LOST 169 +#define SFTP_STATUS_OP_UNSUPPORTED 170 +#define SFTP_ERROR_FORMAT3 171 +#define SFTP_STATUS_UNKNOWN 172 +#define READ_SYMLINK_ERROR 173 +#define EMPTY_DIRECTORY 174 +#define SFTP_NON_ONE_FXP_NAME_PACKET 175 +#define SFTP_REALPATH_ERROR 176 +#define CHANGE_PROPERTIES_ERROR 177 +#define SFTP_INITIALIZE_ERROR 178 +#define TIMEZONE_ERROR 179 +#define SFTP_CREATE_FILE_ERROR 180 +#define SFTP_OPEN_FILE_ERROR 181 +#define SFTP_CLOSE_FILE_ERROR 182 +#define NOT_FILE_ERROR 183 +#define RENAME_AFTER_RESUME_ERROR 184 +#define CREATE_LINK_ERROR 185 +#define INVALID_SHELL_COMMAND 186 +#define SFTP_SERVER_MESSAGE_UNSUPPORTED 187 +#define INVALID_OCTAL_PERMISSIONS 188 +#define SFTP_INVALID_EOL 189 +#define SFTP_UNKNOWN_FILE_TYPE 190 +#define SFTP_STATUS_INVALID_HANDLE 191 +#define SFTP_STATUS_NO_SUCH_PATH 192 +#define SFTP_STATUS_FILE_ALREADY_EXISTS 193 +#define SFTP_STATUS_WRITE_PROTECT 194 +#define SFTP_STATUS_NO_MEDIA 195 +#define DECODE_UTF_ERROR 196 +#define CUSTOM_COMMAND_ERROR 197 +#define LOCALE_LOAD_ERROR 198 +#define SFTP_INCOMPLETE_BEFORE_EOF 199 +#define CALCULATE_SIZE_ERROR 200 +#define SFTP_PACKET_TOO_BIG 201 +#define SCP_INIT_ERROR 202 +#define DUPLICATE_BOOKMARK 203 +#define MOVE_FILE_ERROR 204 +#define SFTP_PACKET_TOO_BIG_INIT_EXPLAIN 205 +#define PRESERVE_TIME_PERM_ERROR3 206 +#define ACCESS_VIOLATION_ERROR3 207 +#define SFTP_STATUS_NO_SPACE_ON_FILESYSTEM 208 +#define SFTP_STATUS_QUOTA_EXCEEDED 209 +#define SFTP_STATUS_UNKNOWN_PRINCIPAL 210 +#define COPY_FILE_ERROR 211 +#define CUSTOM_COMMAND_UNTERMINATED 212 +#define CUSTOM_COMMAND_UNKNOWN 213 +#define CUSTOM_COMMAND_FILELIST_ERROR 214 +#define SCRIPT_COMMAND_UNKNOWN 215 +#define SCRIPT_COMMAND_AMBIGUOUS 216 +#define SCRIPT_MISSING_PARAMS 217 +#define SCRIPT_TOO_MANY_PARAMS 218 +#define SESSION_NOT_EXISTS_ERROR 219 +#define SCRIPT_NO_SESSION 223 +#define SCRIPT_SESSION_INDEX_INVALID 224 +#define SCRIPT_OPTION_UNKNOWN 225 +#define SCRIPT_VALUE_UNKNOWN 226 +#define UNKNOWN_SOCKET_STATUS 227 +#define DELETE_ON_RESUME_ERROR 228 +#define SFTP_PACKET_ERROR 229 +#define ITEM_NAME_INVALID 230 +#define SFTP_STATUS_LOCK_CONFLICT 231 +#define SFTP_STATUS_DIR_NOT_EMPTY 232 +#define SFTP_STATUS_NOT_A_DIRECTORY 233 +#define SFTP_STATUS_INVALID_FILENAME 234 +#define SFTP_STATUS_LINK_LOOP 235 +#define SFTP_STATUS_CANNOT_DELETE 236 +#define SFTP_STATUS_INVALID_PARAMETER 237 +#define SFTP_STATUS_FILE_IS_A_DIRECTORY 238 +#define SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT 239 +#define SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED 240 +#define SFTP_STATUS_DELETE_PENDING 241 +#define SFTP_STATUS_FILE_CORRUPT 242 +#define KEY_TYPE_UNKNOWN2 243 +#define KEY_TYPE_UNSUPPORTED2 244 +#define KEY_TYPE_DIFFERENT_SSH 245 +#define SFTP_OVERWRITE_FILE_ERROR2 246 +#define SFTP_OVERWRITE_DELETE_BUTTON 247 +#define SPACE_AVAILABLE_ERROR 248 +#define TUNNEL_NO_FREE_PORT 249 +#define EVENT_SELECT_ERROR 250 +#define UNEXPECTED_CLOSE_ERROR 251 +#define TUNNEL_ERROR 252 +#define CHECKSUM_ERROR 253 +#define INTERNAL_ERROR 254 +#define FZ_NOTSUPPORTED 255 +#define FTP_ACCESS_DENIED 256 +#define FTP_CREDENTIAL_PROMPT 257 +#define FTP_RESPONSE_ERROR 258 +#define FTP_UNSUPPORTED 259 +#define SCRIPT_UNKNOWN_SWITCH 260 +#define TRANSFER_ERROR 261 +#define EXECUTE_APP_ERROR 262 +#define FILE_NOT_FOUND 263 +#define DOCUMENT_WAIT_ERROR 264 +#define SPEED_INVALID 265 +#define CERT_ERR_CERT_CHAIN_TOO_LONG 266 +#define CERT_ERR_CERT_HAS_EXPIRED 267 +#define CERT_ERR_CERT_NOT_YET_VALID 268 +#define CERT_ERR_CERT_REJECTED 269 +#define CERT_ERR_CERT_SIGNATURE_FAILURE 270 +#define CERT_ERR_CERT_UNTRUSTED 271 +#define CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT 272 +#define CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD 273 +#define CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD 274 +#define CERT_ERR_INVALID_CA 275 +#define CERT_ERR_INVALID_PURPOSE 276 +#define CERT_ERR_KEYUSAGE_NO_CERTSIGN 277 +#define CERT_ERR_PATH_LENGTH_EXCEEDED 278 +#define CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN 279 +#define CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY 280 +#define CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE 281 +#define CERT_ERR_UNABLE_TO_GET_ISSUER_CERT 282 +#define CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY 283 +#define CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE 284 +#define CERT_ERR_UNKNOWN 285 +#define CERT_ERRDEPTH 286 +#define MASK_ERROR 288 +#define FTP_CANNOT_OPEN_ACTIVE_CONNECTION2 289 +#define CORE_DELETE_LOCAL_FILE_ERROR 290 +#define URL_OPTION_BOOL_VALUE_ERROR 291 +#define FTP_ACCESS_DENIED_EMPTY_PASSWORD 292 +#define CANNOT_OPEN_SESSION_FOLDER 293 +#define NET_TRANSL_NO_ROUTE2 294 +#define NET_TRANSL_CONN_ABORTED 295 +#define NET_TRANSL_HOST_NOT_EXIST2 296 +#define NET_TRANSL_PACKET_GARBLED 297 +#define REPORT_ERROR 298 +#define TLS_CERT_DECODE_ERROR 299 +#define FIND_FILE_ERROR 700 +#define CERT_NAME_MISMATCH 701 +#define SCRIPT_MATCH_NO_MATCH 702 +#define CERT_ERR_BAD_CHAIN 703 +#define CERT_OK 704 +#define REQUEST_REDIRECTED 705 +#define TOO_MANY_REDIRECTS 706 +#define REDIRECT_LOOP 707 +#define INVALID_URL 708 +#define PROXY_AUTHENTICATION_FAILED 709 +#define CONFIGURED_KEY_NOT_MATCH 710 +#define SFTP_STATUS_OWNER_INVALID 711 +#define SFTP_STATUS_GROUP_INVALID 712 +#define SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK 713 +#define KEY_TYPE_UNOPENABLE 714 +#define UNKNOWN_CHECKSUM 715 +#define CIPHER_NOT_VERIFIED 716 +#define KEX_NOT_VERIFIED 717 +#define SFTP_STATUS_4 718 +#define CERTIFICATE_OPEN_ERROR 719 +#define CERTIFICATE_READ_ERROR 720 +#define CERTIFICATE_DECODE_ERROR_INFO 721 +#define CERTIFICATE_DECODE_ERROR 722 +#define CERTIFICATE_PUBLIC_KEY_NOT_FOUND 723 +#define LOCK_FILE_ERROR 724 +#define UNLOCK_FILE_ERROR 725 +#define NOT_LOCKED 726 +#define KEY_SAVE_ERROR 727 +#define NEON_INIT_FAILED 728 +#define SCRIPT_AMBIGUOUS_SLASH_IN_PATH 729 +#define CERT_IP_CANNOT_VERIFY 730 +#define HOSTKEY_NOT_CONFIGURED 731 +#define UNENCRYPTED_REDIRECT 732 +#define HTTP_ERROR2 733 +#define FILEZILLA_SITE_MANAGER_NOT_FOUND 734 +#define FILEZILLA_NO_SITES 735 +#define FILEZILLA_SITE_NOT_EXIST 736 +#define SFTP_AS_FTP_ERROR 737 + +#define CORE_CONFIRMATION_STRINGS 300 +#define CONFIRM_PROLONG_TIMEOUT3 301 +#define PROMPT_KEY_PASSPHRASE 303 +#define PROMPT_FILE_OVERWRITE 304 +#define DIRECTORY_OVERWRITE 305 +#define CIPHER_BELOW_TRESHOLD 306 +#define CIPHER_TYPE_BOTH 307 +#define CIPHER_TYPE_CS 308 +#define CIPHER_TYPE_SC 309 +#define RESUME_TRANSFER2 310 +#define PARTIAL_BIGGER_THAN_SOURCE 311 +#define APPEND_OR_RESUME2 312 +#define FILE_OVERWRITE_DETAILS 313 +#define READ_ONLY_OVERWRITE 314 +#define LOCAL_FILE_OVERWRITE2 315 +#define REMOTE_FILE_OVERWRITE2 316 +#define TIMEOUT_STILL_WAITING3 321 +#define KEX_BELOW_TRESHOLD 322 +#define RECONNECT_BUTTON 323 +#define RENAME_BUTTON 324 +#define TUNNEL_SESSION_NAME 327 +#define PASSWORD_TITLE 328 +#define PASSPHRASE_TITLE 329 +#define SERVER_PROMPT_TITLE 330 +#define USERNAME_TITLE 331 +#define USERNAME_PROMPT2 332 +#define SERVER_PROMPT_TITLE2 333 +#define NEW_PASSWORD_TITLE 334 +#define PROMPT_PROMPT 335 +#define TIS_INSTRUCTION 336 +#define CRYPTOCARD_INSTRUCTION 337 +#define PASSWORD_PROMPT 338 +#define KEYBINTER_INSTRUCTION 339 +#define NEW_PASSWORD_CURRENT_PROMPT 340 +#define NEW_PASSWORD_NEW_PROMPT 341 +#define NEW_PASSWORD_CONFIRM_PROMPT 342 +#define TUNNEL_INSTRUCTION 343 +#define RENAME_TITLE 344 +#define RENAME_PROMPT2 345 +#define VERIFY_CERT_PROMPT3 346 +#define VERIFY_CERT_CONTACT 347 +#define VERIFY_CERT_CONTACT_LIST 348 +#define CERT_TEXT 349 +#define CERTIFICATE_PASSPHRASE_PROMPT 350 +#define CERTIFICATE_PASSPHRASE_TITLE 351 +#define KEY_TYPE_CONVERT3 352 +#define MULTI_FILES_TO_ONE 353 + +#define CORE_INFORMATION_STRINGS 400 +#define YES_STR 401 +#define NO_STR 402 +#define SESSION_INFO_TIP2 403 +#define VERSION2 404 +#define CLOSED_ON_COMPLETION 405 +#define SFTP_PROTOCOL_NAME2 406 +#define FS_RENAME_NOT_SUPPORTED 407 +#define SFTP_NO_EXTENSION_INFO 408 +#define SFTP_EXTENSION_INFO 409 +#define APPEND_BUTTON 412 +#define YES_TO_NEWER_BUTTON 413 +//#define SCRIPT_HELP_DESC 414 +//#define SCRIPT_EXIT_DESC 415 +//#define SCRIPT_OPEN_DESC 416 +//#define SCRIPT_CLOSE_DESC 417 +//#define SCRIPT_SESSION_DESC 418 +//#define SCRIPT_PWD_DESC 419 +//#define SCRIPT_CD_DESC 420 +//#define SCRIPT_LS_DESC 421 +//#define SCRIPT_LPWD_DESC 422 +//#define SCRIPT_LCD_DESC 423 +//#define SCRIPT_LLS_DESC 424 +//#define SCRIPT_RM_DESC 425 +//#define SCRIPT_RMDIR_DESC 426 +//#define SCRIPT_MV_DESC 427 +//#define SCRIPT_CHMOD_DESC 428 +//#define SCRIPT_LN_DESC 429 +//#define SCRIPT_MKDIR_DESC 430 +//#define SCRIPT_GET_DESC 431 +//#define SCRIPT_PUT_DESC 432 +//#define SCRIPT_OPTION_DESC 433 +//#define SCRIPT_SYNCHRONIZE_DESC 434 +//#define SCRIPT_KEEPUPTODATE_DESC 435 +//#define SCRIPT_HOST_PROMPT 436 +//#define SCRIPT_ACTIVE_SESSION 438 +//#define SCRIPT_SESSION_CLOSED 439 +//#define SCRIPT_SYNCHRONIZE 440 +//#define SCRIPT_SYNCHRONIZE_DELETED 441 +//#define SCRIPT_KEEPING_UP_TO_DATE 442 +#define SKIP_ALL_BUTTON 443 +//#define SCRIPT_CALL_DESC2 444 +#define COPY_PARAM_PRESET_ASCII 445 +#define COPY_PARAM_PRESET_BINARY 446 +#define COPY_INFO_TRANSFER_TYPE2 448 +#define COPY_INFO_FILENAME 449 +#define COPY_INFO_PERMISSIONS 450 +#define COPY_INFO_ADD_X_TO_DIRS 451 +#define COPY_INFO_TIMESTAMP 452 +#define COPY_INFO_FILE_MASK 454 +#define COPY_INFO_CLEAR_ARCHIVE 455 +#define COPY_INFO_DONT_REPLACE_INV_CHARS 456 +#define COPY_INFO_DONT_PRESERVE_TIME 458 +#define COPY_INFO_DONT_CALCULATE_SIZE 459 +#define COPY_INFO_DEFAULT 460 +#define COPY_RULE_HOSTNAME 461 +#define COPY_RULE_USERNAME 462 +#define COPY_RULE_REMOTE_DIR 463 +#define COPY_RULE_LOCAL_DIR 464 +#define SYNCHRONIZE_SCAN 465 +#define SYNCHRONIZE_START 466 +#define SYNCHRONIZE_CHANGE 467 +#define SYNCHRONIZE_UPLOADED 468 +#define SYNCHRONIZE_DELETED 469 +#define COPY_INFO_NOT_USABLE 470 +#define COPY_INFO_IGNORE_PERM_ERRORS 472 +#define AUTH_TRANSL_USERNAME 473 +#define AUTH_TRANSL_KEYB_INTER 474 +#define AUTH_TRANSL_PUBLIC_KEY 475 +#define AUTH_TRANSL_WRONG_PASSPHRASE 476 +#define AUTH_TRANSL_ACCESS_DENIED 477 +#define AUTH_TRANSL_PUBLIC_KEY_AGENT 478 +#define AUTH_TRANSL_TRY_PUBLIC_KEY 479 +#define AUTH_PASSWORD 480 +#define OPEN_TUNNEL 481 +#define NETBOX_STATUS_CLOSED 482 +#define STATUS_LOOKUPHOST 484 +#define STATUS_CONNECT 485 +#define STATUS_AUTHENTICATE 486 +#define STATUS_AUTHENTICATED 487 +#define STATUS_STARTUP 488 +#define STATUS_OPEN_DIRECTORY 489 +#define STATUS_READY 490 +#define USING_TUNNEL 491 +#define AUTH_TRANSL_KEY_REFUSED 492 +#define PFWD_TRANSL_ADMIN 493 +#define PFWD_TRANSL_CONNECT 494 +#define NET_TRANSL_REFUSED2 495 +#define NET_TRANSL_RESET 496 +#define NET_TRANSL_TIMEOUT2 497 +#define SESSION_INFO_TIP_NO_SSH 498 +#define RESUME_BUTTON 499 +#define FTP_NO_FEATURE_INFO 500 +#define FTP_FEATURE_INFO 501 +#define COPY_INFO_CPS_LIMIT2 502 +#define COPY_KEY_BUTTON 503 +#define UPDATE_KEY_BUTTON 504 +#define ADD_KEY_BUTTON 505 +#define COPY_INFO_PRESERVE_READONLY 506 +#define SCRIPT_SYNCHRONIZE_COLLECTING 507 +#define SCRIPT_SYNCHRONIZE_SYNCHRONIZING 508 +#define SCRIPT_SYNCHRONIZE_NODIFFERENCE 509 +#define SPEED_UNLIMITED 510 +#define FTPS_IMPLICIT 511 +#define FTPS_EXPLICIT 512 +//#define FTPS_EXPLICIT_TLS 513 +#define SCRIPT_ECHO_DESC 514 +#define SCRIPT_STAT_DESC 515 +#define HOSTKEY 516 +#define SCRIPT_FILEMASK_INCLUDE_EXCLUDE 517 +#define COPY_PARAM_NEWER_ONLY 518 +#define FTP_SUGGESTION 519 +#define SCRIPT_CMDLINE_SESSION 520 +#define ANY_HOSTKEY 521 +#define ANY_CERTIFICATE 522 +#define SCRIPT_SYNC_UPLOAD_NEW 523 +#define SCRIPT_SYNC_DOWNLOAD_NEW 524 +#define SCRIPT_SYNC_UPLOAD_UPDATE 525 +#define SCRIPT_SYNC_DOWNLOAD_UPDATE 526 +#define SCRIPT_SYNC_DELETE_REMOTE 527 +#define SCRIPT_SYNC_DELETE_LOCAL 528 +#define SCRIPT_SYNCHRONIZE_CHECKLIST 529 +#define COPY_INFO_REMOVE_CTRLZ 530 +#define COPY_INFO_REMOVE_BOM 531 +#define SCRIPT_NON_DEFAULT_COPY_PARAM 532 +#define SCRIPT_NON_DEFAULT_SYNC_PARAM 533 +#define VERSION_BUILD 535 +#define VERSION_DEV_BUILD 536 +#define VERSION_DEBUG_BUILD 537 +#define VERSION_DONT_DISTRIBUTE 538 +#define WEBDAV_EXTENSION_INFO 539 +#define COPY_PARAM_PRESET_EXCLUDE_ALL_DIR 540 +#define SCRIPT_CHECKSUM_DESC 541 +#define CLIENT_CERTIFICATE_LOADING 543 +#define NEED_CLIENT_CERTIFICATE 544 +#define LOCKED 545 +#define EXECUTABLE 546 +#define SCRIPT_CMDLINE_PARAMETERS 547 +#define SCRIPTING_USE_HOSTKEY 548 +#define SCRIPT_SITE_WARNING 549 +#define CODE_SESSION_OPTIONS 550 +#define CODE_CONNECT 551 +#define CODE_YOUR_CODE 552 +#define CODE_PS_ADD_TYPE 553 +#define COPY_INFO_PRESERVE_TIME_DIRS 554 + +#define CORE_VARIABLE_STRINGS 600 +#define PUTTY_BASED_ON 601 +#define PUTTY_VERSION 602 +#define PUTTY_COPYRIGHT 603 +#define PUTTY_URL 604 +#define FILEZILLA_BASED_ON2 605 +#define FILEZILLA_VERSION 606 +#define FILEZILLA_COPYRIGHT2 607 +#define FILEZILLA_URL 608 +#define OPENSSL_BASED_ON 609 +#define OPENSSL_COPYRIGHT2 610 +#define OPENSSL_VERSION 611 +#define OPENSSL_URL 612 +#define NEON_BASED_ON 613 +#define NEON_COPYRIGHT 614 +#define NEON_URL 615 +#define EXPAT_BASED_ON 616 +#define EXPAT_COPYRIGHT 617 +#define EXPAT_URL 618 +#define PUTTY_LICENSE_URL 625 +#define MAIN_MSG_TAG 631 +#define INTERACTIVE_MSG_TAG 632 + +// 7xxx used by errors as secondary sequence + +#define WINSCPFAR_NAME 740 +#define WINSCP_VERSION 741 +#define WINSCPFAR_VERSION 742 +#define WINSCPFAR_BASED_ON 743 +#define WINSCPFAR_BASED_VERSION 744 +#define WINSCPFAR_BASED_COPYRIGHT 745 + +#define CONVERTKEY_SAVE_TITLE 1945 +#define CONVERTKEY_SAVE_FILTER 1946 +#define CONVERTKEY_SAVED 1947 diff --git a/netbox/src/resource/TextsFileZilla.h b/netbox/src/resource/TextsFileZilla.h new file mode 100644 index 000000000..299488728 --- /dev/null +++ b/netbox/src/resource/TextsFileZilla.h @@ -0,0 +1,814 @@ +#include "TextsCore.h" +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by FileZilla.rc +// +#if 0 +#define ID_INDICATOR_SECURESERVER 610 +#define ID_INDICATOR_ELAPSEDTIME 611 +#define ID_INDICATOR_TIMELEFT 612 +#define ID_INDICATOR_PROGRESS_PANE 613 +#define ID_INDICATOR_BYTES 614 +#define ID_INDICATOR_QUEUESIZE 615 +#define ID_INDICATOR_RECVLED 616 +#define ID_INDICATOR_SENDLED 617 +#define IDR_MAINFRAME 620 +#define IDS_DIRINFO_DIRS 629 +#define IDB_DONATE 630 +#define IDB_FTPTREE_STATE 631 +#define IDB_LEDS 632 +#define IDB_SITEMANAGERTREE 633 +#define IDI_CERT 634 +#define IDI_DOWN 635 +#define IDI_EMPTY 636 +#define IDI_SECURE 637 +#define IDI_TRAYICON 638 +#define IDI_UP 639 +#define IDD_ABOUTBOX 700 +#define IDD_CHMOD 701 +#define IDD_CONNECTION2 702 +#define IDD_CONNECTION4 703 +#define IDD_ENTERSOMETHING 704 +#define IDD_FILEEXISTS 705 +#define IDD_MANUALTRANSFER 706 +#define IDD_OPTIONS_DEBUG 707 +#define IDD_OPTIONS_DIRCACHE 708 +#define IDD_OPTIONS_FIREWALL 709 +#define IDD_OPTIONS_FTPPROXY 710 +#define IDD_OPTIONS_GSSPAGE 711 +#define IDD_OPTIONS_IDENT 712 +#define IDD_OPTIONS_INTERFACE 713 +#define IDD_OPTIONS_LANGUAGE 714 +#define IDD_OPTIONS_LOCALVIEW 715 +#define IDD_OPTIONS_MISC 716 +#define IDD_OPTIONS_REMOTEVIEW 717 +#define IDD_OPTIONS_SPEEDLIMIT 718 +#define IDD_OPTIONS_TRANSFER 719 +#define IDD_OPTIONS_TYPEPAGE 720 +#define IDD_OPTIONS_VIEWEDIT 721 +#define IDD_SAPREFS 722 +#define IDD_SERVER_CHANGEPASS 723 +#define IDD_SITEMANAGER 724 +#define IDD_SPEEDLIMIT_RULE_DIALOG 725 +#define IDD_TRANSFERASDLG 726 +#define IDD_VERIFY_CERT_DLG 727 +#define IDD_OPTIONS_SSHPAGE 728 +#define IDD_SITEMANAGER_ADVANCED 729 +#define IDD_OPTIONS_PANELAYOUT 730 +#define IDD_OPTIONS_LOGGING 731 +#define IDD_OPTIONS_TRANSFER_COMPRESSION 732 +#define IDR_FTPCONTEXTMENU 800 +#define IDR_LOCALCONTEXTMENU 801 +#define IDR_LOCALTREECONTEXTMENU 802 +#define IDR_OUTPUTCONTEXT 803 +#define IDR_QUEUECONTEXTMENU 804 +#define IDR_SITEMANAGER 805 +#define IDR_SYSTRAY_MENU 806 +#define IDC_CAPTION_BAR 1000 +#define IDC_CHECK1 1001 +#define IDC_CHECK_DAY1 1002 +#define IDC_CHECK_DAY2 1003 +#define IDC_CHECK_DAY3 1004 +#define IDC_CHECK_DAY4 1005 +#define IDC_CHECK_DAY5 1006 +#define IDC_CHECK_DAY6 1007 +#define IDC_CHECK_DAY7 1008 +#define IDC_DATE_CHECK 1009 +#define IDC_DATE_VALUE 1010 +#define IDC_DEBUG_DEBUGMENU 1011 +#define IDC_DEBUG_ENGINETRACE 1012 +#define IDC_OPTIONS_LOGGING_FILENAME 1013 +#define IDC_DEBUG_SHOWRAWLISTING 1015 +#define IDC_DEFAULT 1016 +#define IDC_DEFAULTFOLDER 1017 +#define IDC_DELAY 1018 +#define IDC_DELETE 1019 +#define IDC_DLG_FRAME 1020 +#define IDC_EDIT1 1021 +#define IDC_EDIT5 1022 +#define IDC_EDIT6 1023 +#define IDC_EDIT7 1024 +#define IDC_EDIT8 1025 +#define IDC_EXIT 1026 +#define IDC_FILEEXISTS_ALWAYS 1027 +#define IDC_FILEEXISTS_DATE1 1028 +#define IDC_FILEEXISTS_DATE2 1029 +#define IDC_FILEEXISTS_FILE1 1030 +#define IDC_FILEEXISTS_FILE2 1031 +#define IDC_FILEEXISTS_ICON1 1032 +#define IDC_FILEEXISTS_ICON2 1033 +#define IDC_FILEEXISTS_RADIO1 1034 +#define IDC_FILEEXISTS_RADIO2 1035 +#define IDC_FILEEXISTS_RADIO3 1036 +#define IDC_FILEEXISTS_RADIO4 1037 +#define IDC_FILEEXISTS_SIZE1 1038 +#define IDC_FILEEXISTS_SIZE2 1039 +#define IDC_FILEEXISTS_SKIP 1040 +#define IDC_FROM_CHECK 1041 +#define IDC_FROM_TIME 1042 +#define IDC_FWBYPASS 1043 +#define IDC_FZICON 1044 +#define IDC_GROUPEXECUTE 1045 +#define IDC_GROUPREAD 1046 +#define IDC_GROUPWRITE 1047 +#define IDC_GSSSERVERNAME 1048 +#define IDC_HOMEPAGE 1049 +#define IDC_HOST 1050 +#define IDC_INTERVALHIGH 1051 +#define IDC_INTERVALLOW 1052 +#define IDC_LANGUAGELIST 1053 +#define IDC_LOCALDIR 1054 +#define IDC_MAIL 1055 +#define IDC_MAIL2 1056 +#define IDC_MANUALTRANSFER_BYPASSPROXY 1057 +#define IDC_MANUALTRANSFER_HOST 1058 +#define IDC_NUMERIC 1059 +#define IDC_MANUALTRANSFER_LOCALFILE 1060 +#define IDC_MANUALTRANSFER_LOGONTYPE1 1061 +#define IDC_MANUALTRANSFER_LOGONTYPE2 1062 +#define IDC_MANUALTRANSFER_PASS 1063 +#define IDC_MANUALTRANSFER_PORT 1064 +#define IDC_MANUALTRANSFER_REMOTEFILE 1065 +#define IDC_MANUALTRANSFER_SERVER1 1066 +#define IDC_MANUALTRANSFER_SERVER2 1067 +#define IDC_MANUALTRANSFER_SERVERTYPE 1068 +#define IDC_MANUALTRANSFER_STARTNOW 1069 +#define IDC_MANUALTRANSFER_UPDOWN1 1070 +#define IDC_MANUALTRANSFER_UPDOWN2 1071 +#define IDC_MANUALTRANSFER_USER 1072 +#define IDC_NEW 1073 +#define IDC_SITEMANAGER_COPY 1074 +#define IDC_NUMRETRY 1075 +#define IDC_SITEMANAGER_RENAME 1076 +#define IDC_OPTIONS_CACHE_CACHETYPE2 1077 +#define IDC_OPTIONS_CACHE_HOURS 1078 +#define IDC_OPTIONS_CACHE_MINUTES 1079 +#define IDC_OPTIONS_CACHE_SECONDS 1080 +#define IDC_OPTIONS_CACHE_USECACHE 1081 +#define IDC_OPTIONS_CACHE_USECACHE2 1082 +#define IDC_OPTIONS_FIREWALL_LIMITPORTS 1083 +#define IDC_OPTIONS_FIREWALL_PASV 1084 +#define IDC_OPTIONS_FIREWALL_RANGEHIGH 1085 +#define IDC_OPTIONS_FIREWALL_RANGELOW 1086 +#define IDC_OPTIONS_IDENT_IDENT 1087 +#define IDC_OPTIONS_IDENT_IDENTCONNECT 1088 +#define IDC_OPTIONS_IDENT_SAMEIP 1089 +#define IDC_OPTIONS_IDENT_SYSTEM 1090 +#define IDC_OPTIONS_IDENT_USERID 1091 +#define IDC_OPTIONS_INTERFACE_MINIMIZETOTRAY 1092 +#define IDC_OPTIONS_CACHE_CACHETYPE 1093 +#define IDC_SITEMANAGER_ADVANCED 1094 +#define IDC_OPTIONS_LOCALVIEW_REMEMBERCOLUMNWIDTHS 1095 +#define IDC_OPTIONS_LOCALVIEW_SIZE 1096 +#define IDC_OPTIONS_LOCALVIEW_SIZEFORMAT 1097 +#define IDC_OPTIONS_LOCALVIEW_STYLE 1098 +#define IDC_OPTIONS_LOCALVIEW_TIME 1099 +#define IDC_OPTIONS_LOCALVIEW_TYPE 1100 +#define IDC_OPTIONS_REMOTEVIEW_ALWAYS 1101 +#define IDC_OPTIONS_REMOTEVIEW_ALWAYSSHOWHIDDEN 1102 +#define IDC_OPTIONS_REMOTEVIEW_DATE 1103 +#define IDC_OPTIONS_REMOTEVIEW_PERMISSIONS 1104 +#define IDC_OPTIONS_REMOTEVIEW_REMEMBER 1105 +#define IDC_OPTIONS_REMOTEVIEW_REMEMBERCOLUMNWIDTHS 1106 +#define IDC_OPTIONS_REMOTEVIEW_SIZE 1107 +#define IDC_OPTIONS_REMOTEVIEW_SIZEFORMAT 1108 +#define IDC_OPTIONS_REMOTEVIEW_STYLE 1109 +#define IDC_OPTIONS_REMOTEVIEW_TIME 1110 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADIO1 1111 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADIO2 1112 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADIO3 1113 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADIO4 1114 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADIO5 1115 +#define IDC_OPTIONS_TRANSFER_MAXPRIMARYSIZE 1116 +#define IDC_OPTIONS_TRANSFER_MAXTRANSFERCONNECTIONS 1117 +#define IDC_OPTIONS_TRANSFER_PRESERVEDATETIME 1118 +#define IDC_OPTIONS_TRANSFER_USEMULTIPLE 1119 +#define IDC_OPTIONS_VIEWEDIT_CUSTOM 1120 +#define IDC_OPTIONS_TRANSFER_PRESERVEDATETIME2 1120 +#define IDC_OPTIONS_TRANSFER_VMSALLREVISIONS 1120 +#define IDC_OPTIONS_VIEWEDIT_DEFAULT 1121 +#define IDC_OPTIONS_VIEWEDIT_DEFAULT_BROWSE 1122 +#define IDC_OWNEREXECUTE 1123 +#define IDC_OWNERREAD 1124 +#define IDC_OWNERWRITE 1125 +#define IDC_PAGE_TREE 1126 +#define IDC_PASS 1127 +#define IDC_PASSDONTSAVE 1128 +#define IDC_PHELP 1129 +#define IDC_PORT 1130 +#define IDC_PROXYHOST 1131 +#define IDC_PROXYPASS 1132 +#define IDC_PROXYPORT 1133 +#define IDC_PROXYUSER 1134 +#define IDC_PUBLICEXECUTE 1135 +#define IDC_PUBLICREAD 1136 +#define IDC_PUBLICWRITE 1137 +#define IDC_RADIO1 1138 +#define IDC_RADIO2 1139 +#define IDC_RADIO3 1140 +#define IDC_RADIO4 1141 +#define IDC_RADIO5 1142 +#define IDC_RADIO6 1143 +#define IDC_RADIO7 1144 +#define IDC_RADIO8 1145 +#define IDC_RADIO9 1146 +#define IDC_RADIO10 1147 +#define IDC_RADIO11 1148 +#define IDC_REMEMBERWINDOWPOS 1149 +#define IDC_REMOTEDIR 1150 +#define IDC_SELECTDEFAULTFOLDERTYPE 1152 +#define IDC_SELECTDEFAULTFOLDERTYPE2 1153 +#define IDC_SERVER_CHANGEPASS_NEW 1154 +#define IDC_SERVER_CHANGEPASS_NEW2 1155 +#define IDC_SERVER_CHANGEPASS_OLD 1156 +#define IDC_SERVERADD 1157 +#define IDC_SERVERLIST 1158 +#define IDC_SERVERREMOVE 1159 +#define IDC_SERVERTYPE 1160 +#define IDC_SHOWLOCALTREEVIEW 1161 +#define IDC_SHOWMESSAGELOG 1162 +#define IDC_SHOWQUICKCONNECTBAR 1163 +#define IDC_SHOWREMOTETREEVIEW 1164 +#define IDC_SHOWSTATUSBAR 1165 +#define IDC_SHOWTOOLBAR 1166 +#define IDC_SHOWTRANSFERQUEUE 1167 +#define IDC_SITEMANAGER_NEWFOLDER 1168 +#define IDC_SITEMANAGEREXPANDFOLDERS 1169 +#define IDC_SITEMANAGERONSTART 1170 +#define IDC_SITEMANAGERSORTFOLDERSFIRST 1171 +#define IDC_SITEMANAGERTREE 1172 +#define IDC_SPEED 1173 +#define IDC_SPEEDLIMIT_DOWNLOAD_ADD 1174 +#define IDC_SPEEDLIMIT_DOWNLOAD_CONSTANT 1175 +#define IDC_SPEEDLIMIT_DOWNLOAD_DOWN 1176 +#define IDC_SPEEDLIMIT_DOWNLOAD_NOLIMIT 1177 +#define IDC_SPEEDLIMIT_DOWNLOAD_REMOVE 1178 +#define IDC_SPEEDLIMIT_DOWNLOAD_RULES 1179 +#define IDC_SPEEDLIMIT_DOWNLOAD_RULES_LIST 1180 +#define IDC_SPEEDLIMIT_DOWNLOAD_UP 1181 +#define IDC_SPEEDLIMIT_DOWNLOAD_VALUE 1182 +#define IDC_SPEEDLIMIT_UPLOAD_ADD 1183 +#define IDC_SPEEDLIMIT_UPLOAD_CONSTANT 1184 +#define IDC_SPEEDLIMIT_UPLOAD_DOWN 1185 +#define IDC_SPEEDLIMIT_UPLOAD_NOLIMIT 1186 +#define IDC_SPEEDLIMIT_UPLOAD_REMOVE 1187 +#define IDC_SPEEDLIMIT_UPLOAD_RULES 1188 +#define IDC_SPEEDLIMIT_UPLOAD_RULES_LIST 1189 +#define IDC_SPEEDLIMIT_UPLOAD_UP 1190 +#define IDC_SPEEDLIMIT_UPLOAD_VALUE 1191 +#define IDC_TEXT 1192 +#define IDC_TIMEOUT 1193 +#define IDC_TO_CHECK 1194 +#define IDC_TO_TIME 1195 +#define IDC_TRANSFERNOW 1196 +#define IDC_TRANSLATEDBY 1197 +#define IDC_TYPEADD 1198 +#define IDC_TYPELIST 1199 +#define IDC_TYPEMODE 1200 +#define IDC_TYPEMODE2 1201 +#define IDC_TYPEMODE3 1202 +#define IDC_TYPENAME 1203 +#define IDC_TYPEREMOVE 1204 +#define IDC_USEGSS 1205 +#define IDC_USER 1206 +#define IDC_VERIFY_CERT_ALWAYS 1207 +#define IDC_USER2 1207 +#define IDC_VERIFY_CERT_ISSUER_COMMONNAME 1208 +#define IDC_VERIFY_CERT_ISSUER_COUNTRY 1209 +#define IDC_VERIFY_CERT_ISSUER_MAIL 1210 +#define IDC_VERIFY_CERT_ISSUER_ORGANIZATION 1211 +#define IDC_VERIFY_CERT_ISSUER_OTHER 1212 +#define IDC_VERIFY_CERT_ISSUER_STATEPROVINCE 1213 +#define IDC_VERIFY_CERT_ISSUER_TOWN 1214 +#define IDC_VERIFY_CERT_ISSUER_UNIT 1215 +#define IDC_VERIFY_CERT_SUBJECT_COMMONNAME 1216 +#define IDC_VERIFY_CERT_SUBJECT_COUNTRY 1217 +#define IDC_VERIFY_CERT_SUBJECT_MAIL 1218 +#define IDC_VERIFY_CERT_SUBJECT_ORGANIZATION 1219 +#define IDC_VERIFY_CERT_SUBJECT_OTHER 1220 +#define IDC_VERIFY_CERT_SUBJECT_STATEPROVINCE 1221 +#define IDC_VERIFY_CERT_SUBJECT_TOWN 1222 +#define IDC_VERIFY_CERT_SUBJECT_UNIT 1223 +#define IDC_VERIFY_CERT_SUMMARY 1224 +#define IDC_VERIFY_CERT_VALIDFROM 1225 +#define IDC_VERIFY_CERT_VALIDUNTIL 1226 +#define IDC_VERSION 1227 +#define IDC_VIEWLABELS 1228 +#define IDC_VIEWMODE 1229 +#define IDC_VIEWMODE2 1230 +#define IDC_SITEMANAGER_LOCALDIR_BROWSE 1231 +#define IDC_OPTIONS_LOCALVIEW_SHOWSTATUSBAR 1232 +#define IDC_OPTIONS_REMOTEVIEW_SHOWSTATUSBAR 1233 +#define IDC_OPTIONS_LOCALVIEW_REMEMBERCOLUMNSORT 1234 +#define IDC_OPTIONS_REMOTEVIEW_REMEMBERCOLUMNSORT 1235 +#define IDC_OPTIONS_SSHPAGE_COMPRESSION1 1236 +#define IDC_OPTIONS_SSHPAGE_COMPRESSION2 1237 +#define IDC_OPTIONS_INTERFACE_MINIMIZETOTRAY2 1238 +#define IDC_OPTIONS_LOCALVIEW_REMEMBER 1239 +#define IDC_OPTIONS_SSHPAGE_PROTOCOL2 1240 +#define IDC_OPTIONS_SSHPAGE_PROTOCOL3 1241 +#define IDC_SITEMANAGER_ADVANCED_SITENAME 1242 +#define IDC_SITEMANAGER_ADVANCED_PASSIVE1 1243 +#define IDC_SITEMANAGER_ADVANCED_PASSIVE2 1244 +#define IDC_SITEMANAGER_ADVANCED_PASSIVE3 1245 +#define IDC_OPTIONS_INTERFACE_MINIMIZETOTRAY3 1246 +#define IDC_SITEMANAGER_ADVANCED_TZSPIN 1247 +#define IDC_SITEMANAGER_ADVANCED_TZOFFSET 1248 +#define IDC_OPTIONS_TRANSFER_FILEEXISTS_RADEIO6 1249 +#define IDC_SITEMANAGER_ADVANCED_TZOFFSET2 1250 +#define IDC_DONATE 1251 +#define IDC_SITEMANAGER_ADVANCED_TZSPIN2 1252 +#define IDC_OPTIONS_REMOTEVIEW_OWNERGROUP 1253 +#define IDC_OPTIONS_SSHPAGE_COMPRESSION3 1254 +#define IDC_OPTIONS_FIREWALL_USETRANSFERIP 1255 +#define IDC_OPTIONS_SSHPAGE_PROTOCOL1 1256 +#define IDC_OPTIONS_FIREWALL_USETRANSFERIP_IPV6 1256 +#define IDC_OPTIONS_FIREWALL_TRANSFERIP 1257 +#define IDC_OPTIONS_INTERFACE_LOCAL_DOUBLECLICK1 1258 +#define IDC_OPTIONS_FIREWALL_TRANSFERIP_IPV6 1258 +#define IDC_OPTIONS_INTERFACE_LOCAL_DOUBLECLICK2 1259 +#define IDC_OPTIONS_INTERFACE_REMOTE_DOUBLECLICK1 1260 +#define IDC_OPTIONS_INTERFACE_LOCAL_DOUBLECLICK3 1261 +#define IDC_OPTIONS_INTERFACE_REMOTE_DOUBLECLICK2 1262 +#define IDC_OPTIONS_INTERFACE_REMOTE_DOUBLECLICK3 1263 +#define IDC_VERIFY_CERT_ICON 1264 +#define IDC_OPTIONS_PANELAYOUT_LOCALTREE1 1265 +#define IDC_OPTIONS_PANELAYOUT_LOCALTREE2 1266 +#define IDC_SITEMANAGER_COMMENTS 1267 +#define IDC_OPTIONS_PANELAYOUT_REMOTETREE1 1268 +#define IDC_VERIFY_CERT_HASH 1269 +#define IDC_OPTIONS_PANELAYOUT_REMOTETREE2 1270 +#define IDC_OPTIONS_LOCALVIEW_ALWAYS 1271 +#define IDC_OPTIONS_REMOTEVIEW_FILETYPE 1272 +#define IDC_OPTIONS_LOGGING_LOGTOFILE 1273 +#define IDC_OPTIONS_LOGGING_TIMESTAMP 1274 +#define IDC_OPTIONS_LOGGING_FONTDATA 1276 +#define IDC_OPTIONS_LOGGING_CHANGEFONT 1278 +#define IDC_OPTIONS_LOGGING_USECUSTOMFONT 1279 +#define IDC_OPTIONS_PANELAYOUT_SWITCHREMOTE 1280 +#define IDC_OPTIONS_TRANSFER_COMPRESSION_USECOMPRESSION 1281 +#define IDC_OPTIONS_TRANSFER_COMPRESSION_USECOMPRESSION2 1282 +#define IDC_OPTIONS_TRANSFER_COMPRESSION_USECOMPRESSION3 1283 +#define IDC_OPTIONS_TRANSFER_COMPRESSION_LEVEL 1284 +#define IDC_OPTIONS_TRANSFER_COMPRESSION_FILETYPES 1285 +#define IDC_OPTIONS_CONNECTION2_ENABLEIPV6 1287 +#define IDC_COMBO1 1288 +#define IDC_SITEMANAGER_ADVANCED_UTF8 1288 +#define IDC_ANONPWD 1289 +#define IDC_CHECK2 1290 +#endif +#define IDS_STATUSMSG_CONNECTED 22000 +#define IDS_STATUSMSG_CONNECTEDWITH 22001 +#if 0 +#define IDS_STATUSMSG_CONNECTEDWITHSFTP 2002 +#endif +#define IDS_STATUSMSG_CONNECTEDWITHSSL 22003 +#define IDS_STATUSMSG_CONNECTING 22004 +#define IDS_STATUSMSG_DIRLISTSUCCESSFUL 22005 +#define IDS_STATUSMSG_DISCONNECTED 22006 +#if 0 +#define IDS_STATUSMSG_DISCONNECTING 22007 +#endif +#define IDS_STATUSMSG_DOWNLOADSTART 22008 +#define IDS_STATUSMSG_DOWNLOADSUCCESSFUL 22009 +#if 0 +#define IDS_STATUSMSG_FILEIMPORTED 22010 +#endif +#define IDS_STATUSMSG_FWCONNECT 22011 + +#if 0 +#define IDS_STATUSMSG_IDENT_REQUEST 2012 +#define IDS_STATUSMSG_IDENT_START 2013 +#define IDS_STATUSMSG_IDENT_STOP 2014 +#define IDS_STATUSMSG_LANGUAGEVERSIONDIFFERENT 2015 +#define IDS_STATUSMSG_PREFIX 2016 +#define IDS_STATUSMSG_RECONNECTWAIT 2017 +#endif +#define IDS_STATUSMSG_RETRIEVINGDIRLIST 22018 +#define IDS_STATUSMSG_SSLESTABLISHED 22019 +#define IDS_STATUSMSG_SSLESTABLISHEDTRANSFER 22020 +#define IDS_STATUSMSG_UPLOADSTART 22021 +#define IDS_STATUSMSG_UPLOADSUCCESSFUL 22022 +#if 0 +#define IDS_STATUSMSG_WAITINGTORETRY 2023 +#define IDS_STATUSMSG_WAITINGTORETRY_LEFT 2024 +#endif +#define IDS_STATUSMSG_LISTFILESUCCESSFUL 22025 +#define IDS_STATUSMSG_RETRIEVINGLISTFILE 22026 +#define IDS_ERRORMSG_CANTGETLISTFILE 22027 + +#define IDS_ERRORMSG_CANTCONNECT CONNECTION_FAILED +#define IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE 22101 +#if 0 +#define IDS_ERRORMSG_CANTCREATEFILE 22102 +#endif +#define IDS_ERRORMSG_CANTESTABLISHSSLCONNECTION 22103 +#define IDS_ERRORMSG_CANTGETFILETYPE 22104 +#define IDS_ERRORMSG_CANTGETLIST 22105 +#if 0 +#define IDS_ERRORMSG_CANTGETRESPONSE 22106 +#define IDS_ERRORMSG_CANTIMPORTFILE 22107 +#endif +#define IDS_ERRORMSG_CANTINITSSL 22108 +#define IDS_ERRORMSG_CANTOPENFILE 22110 +#define IDS_ERRORMSG_CANTOPENLOGFILE 22111 +#define IDS_ERRORMSG_CANTOPENTRANSFERCHANNEL 2112 +#if 0 +#define IDS_ERRORMSG_CANTPARSECMDLINE 22113 +#endif +#define IDS_ERRORMSG_CANTRESOLVEHOST2 22114 +#define IDS_ERRORMSG_CANTRESUME 22115 +#define IDS_ERRORMSG_CANTRESUME_FINISH 22116 +#define IDS_ERRORMSG_CANTSENDCOMMAND 22117 +#if 0 +#define IDS_ERRORMSG_CANTWRITETOFILE 22118 +#endif +#define IDS_ERRORMSG_CERTREJECTED 22119 +#if 0 +#define IDS_ERRORMSG_CRITICALINTERRUPTED 2120 +#endif +#define IDS_ERRORMSG_DOWNLOADABORTED 22121 +#define IDS_ERRORMSG_DOWNLOADFAILED TOLOCAL_COPY_ERROR +#if 0 +#define IDS_ERRORMSG_ENTERSTRING 22123 +#define IDS_ERRORMSG_FILENAMEINVALID 22124 +#endif +#define IDS_ERRORMSG_FILEOPENFAILED OPENFILE_ERROR +#if 0 +#define IDS_ERRORMSG_HOSTNEEDED 22126 +#define IDS_ERRORMSG_IDENT_CANTSTART 22127 +#endif +#define IDS_ERRORMSG_INTERRUPTED USER_TERMINATED +#if 0 +#define IDS_ERRORMSG_INVALIDPORT 22129 +#define IDS_ERRORMSG_LANGUAGEDLLNOTFOUND 2130 +#define IDS_ERRORMSG_LANGUAGEDLLVERSIONINVALID 2131 +#endif +#define IDS_ERRORMSG_NAMEINUSE 22132 +#if 0 +#define IDS_ERRORMSG_PATHNOTFOUND 22133 +#define IDS_ERRORMSG_PORTNEEDED 22134 +#define IDS_ERRORMSG_PREFIX 22135 +#endif +#define IDS_ERRORMSG_PROXY_AUTHFAILED AUTHENTICATION_FAILED +#define IDS_ERRORMSG_PROXY_AUTHNOLOGON 22137 +#define IDS_ERRORMSG_PROXY_AUTHTYPEUNKNOWN 2138 +#define IDS_ERRORMSG_PROXY_CANTRESOLVEHOST 2139 +#define IDS_ERRORMSG_PROXY_NOCONN 22140 +#define IDS_ERRORMSG_PROXY_REQUESTFAILED 2141 +#if 0 +#define IDS_ERRORMSG_SETTINGS_XMLFILE_INVALID 2142 +#define IDS_ERRORMSG_SFTP_CANTCREATESFTPIPC 2143 +#define IDS_ERRORMSG_SFTP_CANTCREATESFTPPROCESS 2144 +#define IDS_ERRORMSG_SFTP_CLOSED 22145 +#define IDS_ERRORMSG_SFTP_INVALIDDATA 22146 +#define IDS_ERRORMSG_SFTP_NORESPONSE 22147 +#define IDS_ERRORMSG_SITEMANAGERNAMEINUSE 2148 +#define IDS_ERRORMSG_SITEMANAGERNEEDLOGONTYPE 2149 +#define IDS_ERRORMSG_SITEMANAGERNEEDNAME 2150 +#define IDS_ERRORMSG_SITEMANAGERNEEDPASS 2151 +#define IDS_ERRORMSG_SITEMANAGERNEEDUSER 2152 +#endif +#define IDS_ERRORMSG_TIMEOUT 22153 +#define IDS_ERRORMSG_UPLOADABORTED 22154 +#define IDS_ERRORMSG_UPLOADFAILED TOREMOTE_COPY_ERROR +#if 0 +#define IDS_ERRORMSG_URLSYNTAXINVALID 22156 +#define IDS_ERRORMSG_VIEWEDIT_CANTOPENFILE 2157 +#define IDS_ERRORMSG_VIEWEDIT_CANTSTARTPROGRAM 2158 +#define IDS_ERRORMSG_VIEWEDIT_NODEFPROG 2159 +#endif +#define IDS_ERRORMSG_SETFILEPOINTER 22160 +#define IDS_ERRORMSG_UNKNOWNSSLERROR 22161 +#define IDS_ERRORMSG_SSLCERTIFICATEERROR 2162 +#if 0 +#define IDS_COMMANDMSG_PREFIX 22200 +#define IDS_RESPONSEMSG_PREFIX 22300 +#define IDS_TRACEMSG_TRACE 22301 +#define IDS_QUESTION_BREAKCONNECTION 22400 +#define IDS_QUESTION_CANCELOPERATION 22401 +#define IDS_QUESTION_DELETEDIRS 22402 +#define IDS_QUESTION_DELETEFILE 22403 +#define IDS_QUESTION_DELETEFILES 22404 +#define IDS_QUESTION_DELETEFILESANDDIRS 2405 +#define IDS_QUESTION_FILEEXISTS 22406 +#define IDS_QUESTION_OPTIONS_FIRSTSTART 2407 +#define IDS_QUESTION_SFTP_CHANGEDHOSTKEY 2408 +#define IDS_QUESTION_SFTP_NEWHOSTKEY 22409 +#define IDS_QUESTION_SITEMANAGERDELETE 22410 +#define IDS_QUESTION_TRANSFER_USEPRIMARY 2411 +#define IDS_QUESTION_VIEWEDIT_MODIFIED 22412 +#define IDS_QUESTION_VIEWEDIT_MODIFIED_CAPTION 2413 +#define IDS_OPTIONS_EXPORTOK 22500 +#define IDS_OPTIONS_IDENTSYSTEM 22501 +#define IDS_OPTIONS_IDENTUSERID 22502 +#define IDS_OPTIONS_IMPORTFAILURE 22503 +#define IDS_OPTIONS_IMPORTOK 22504 +#define IDS_OPTIONS_KEEPALIVEBORDERDIFF 2505 +#define IDS_OPTIONS_KEEPALIVEBORDERHIGH 2506 +#define IDS_OPTIONS_KEEPALIVEBORDERLOW 22507 +#define IDS_OPTIONS_LIMITPORTRANGEDIFF 22508 +#define IDS_OPTIONS_LIMITPORTRANGEHIGH 22509 +#define IDS_OPTIONS_LIMITPORTRANGELOW 22510 +#define IDS_OPTIONS_NUMRETRIES 22511 +#define IDS_OPTIONS_PROXYHOST 22512 +#define IDS_OPTIONS_PROXYPORT 22513 +#define IDS_OPTIONS_RETRYDELAY 22514 +#define IDS_OPTIONS_TIMEOUT 22515 +#define IDS_GSSERROR_AUTHFAILED 22600 +#define IDS_GSSERROR_CANTLOADDLL 22601 +#define IDS_GSSERROR_CANTLOADGSSAPI 22602 +#define IDS_GSSERROR_CANTLOADKERBEROSAPI 2603 +#define IDS_INPUTDIALOGTITLE_CREATEDIR 22700 +#define IDS_INPUTDIALOGTEXT_CREATEDIR 22701 +#define IDS_INPUTDIALOGTITLE_FTPCOMMAND 2702 +#define IDS_INPUTDIALOGTEXT_FTPCOMMAND 22703 +#define IDS_INPUTDIALOGTITLE_INPUTPASSWORD 2704 +#define IDS_INPUTDIALOGTEXT_INPUTPASSWORD 2705 +#define IDS_INPUTDIALOGTITLE_RENAME 22706 +#define IDS_INPUTDIALOGTEXT_RENAME 22707 +#define IDS_INPUTDIALOGTITLE_KEYBOARDINTERACTIVE 2708 +#define IDS_INPUTDIALOGTITLE_INPUTUSER 22709 +#define IDS_INPUTDIALOGTEXT_INPUTUSER 22710 +#define IDS_OPTIONSPAGE_CONNECTION 22800 +#define IDS_OPTIONSPAGE_DEBUG 22801 +#define IDS_OPTIONSPAGE_DIRCACHE 22802 +#define IDS_OPTIONSPAGE_FIREWALL 22803 +#define IDS_OPTIONSPAGE_FTPPROXY 22804 +#define IDS_OPTIONSPAGE_GSS 22805 +#define IDS_OPTIONSPAGE_IDENT 22806 +#define IDS_OPTIONSPAGE_INTERFACE 22807 +#define IDS_OPTIONSPAGE_LANGUAGE 22808 +#define IDS_OPTIONSPAGE_LOCALVIEW 22809 +#define IDS_OPTIONSPAGE_MISC 22810 +#define IDS_OPTIONSPAGE_PROXY 22811 +#define IDS_OPTIONSPAGE_REMOTEVIEW 22812 +#define IDS_OPTIONSPAGE_SPEEDLIMIT 22813 +#define IDS_OPTIONSPAGE_TRANSFER 22814 +#define IDS_OPTIONSPAGE_TYPE 22815 +#define IDS_OPTIONSPAGE_VIEWEDIT 22816 +#define IDS_OPTIONSPAGE_SSH 22817 +#define IDS_OPTIONSPAGE_PANELAYOUT 22818 +#define IDS_OPTIONSPAGE_LOGGING 22819 +#define IDS_OPTIONSPAGE_TRANSFER2 22820 +#define IDS_OPTIONSPAGE_TRANSFER_COMPRESSION 22820 +#define IDP_SOCKETS_INIT_FAILED 3000 +#define IDS_BROWSEFORFOLDER 3001 +#define IDS_CERT_ERR_CERT_CHAIN_TOO_LONG 3002 +#define IDS_CERT_ERR_CERT_HAS_EXPIRED 3003 +#define IDS_CERT_ERR_CERT_NOT_YET_VALID 3004 +#define IDS_CERT_ERR_CERT_REJECTED 3005 +#define IDS_CERT_ERR_CERT_SIGNATURE_FAILURE 3006 +#define IDS_CERT_ERR_CERT_UNTRUSTED 3007 +#define IDS_CERT_ERR_DEPTH_ZERO_SELF_SIGNED_CERT 3008 +#define IDS_CERT_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD 3009 +#define IDS_CERT_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD 3010 +#define IDS_CERT_ERR_INVALID_CA 3011 +#define IDS_CERT_ERR_INVALID_PURPOSE 3012 +#define IDS_CERT_ERR_KEYUSAGE_NO_CERTSIGN 3013 +#define IDS_CERT_ERR_PATH_LENGTH_EXCEEDED 3014 +#define IDS_CERT_ERR_SELF_SIGNED_CERT_IN_CHAIN 3015 +#define IDS_CERT_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY 3016 +#define IDS_CERT_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE 3017 +#define IDS_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT 3018 +#define IDS_CERT_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY 3019 +#define IDS_CERT_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE 3020 +#define IDS_CERT_ERR_UNKNOWN 3021 +#define IDS_CERT_ERRDEPTH 3022 +#define IDS_CERT_OK 3023 +#define IDS_CHANGEDIALOG_DIRECTORY 3024 +#define IDS_CHANGEDIALOG_FILE 3025 +#define IDS_CHANGEDIALOG_MULTIPLEFILES 3026 +#define IDS_HEADER_DATE 3027 +#define IDS_HEADER_DIRECTION 3028 +#define IDS_HEADER_FILENAME 3029 +#define IDS_HEADER_FILESIZE 3030 +#define IDS_HEADER_FILETYPE 3031 +#define IDS_HEADER_HOST 3032 +#define IDS_HEADER_LASTMODIFIED 3033 +#define IDS_HEADER_LOCALNAME 3034 +#define IDS_HEADER_OWNERGROUP 3035 +#define IDS_HEADER_PERMISSIONS 3036 +#define IDS_HEADER_REMOTENAME 3037 +#define IDS_HEADER_SIZE 3038 +#define IDS_HEADER_STATUS 3039 +#define IDS_HEADER_TIME 3040 +#define IDS_MANUALTRANSFER_HOST 3041 +#define IDS_MANUALTRANSFER_LOCALFILE 3042 +#define IDS_MANUALTRANSFER_PORT 3043 +#define IDS_MANUALTRANSFER_REMOTEFILE 3044 +#define IDS_MANUALTRANSFER_USER 3045 +#define IDS_QUEUESTATUS_ABORTED 3046 +#define IDS_QUEUESTATUS_CRITICALERROR 3047 +#define IDS_QUEUESTATUS_ERROR 3048 +#define IDS_QUEUESTATUS_TOOMANYERTREIS 3049 +#define IDS_QUEUESTATUS_TRANSFERRING 3050 +#define IDS_QUICKCONN_HOST 3051 +#define IDS_QUICKCONN_PASS 3052 +#define IDS_QUICKCONN_PORT 3053 +#define IDS_QUICKCONN_QUICKCONN 3054 +#define IDS_QUICKCONN_USER 3055 +#define IDS_REMOTELIST_EMPTY 3056 +#define IDS_SITEMANAGER_NEWFOLDER 3057 +#define IDS_SITEMANAGER_NEWFOLDERWITHNUM 3058 +#define IDS_SITEMANAGER_NEWFTPSITE 3059 +#define IDS_SITEMANAGER_NEWFTPSITEWITHNUM 3060 +#define IDS_SITEMANAGER_TEXTFILES 3061 +#define IDS_SITEMANAGER_TREEROOT 3062 +#define IDS_SITEMANAGER_XMLFILES 3063 +#define IDS_SIZE_BYTES 3064 +#define IDS_SIZE_GB 3065 +#define IDS_SIZE_KBS 3066 +#define IDS_SIZE_MBS 3067 +#define IDS_STATUSBAR_ELAPSED 3068 +#define IDS_STATUSBAR_LEFT 3069 +#define IDS_TITLE_CONNECTED 3070 +#define IDS_TITLE_CONNECTING 3071 +#define IDS_TOOLBAR_SITEDROPDOWN_EMPTYFOLDER 3072 +#define IDS_VIEWLABEL_LOCAL 3073 +#define IDS_VIEWLABEL_REMOTE 3074 +#define IDS_DIRINFO_EMPTY 3075 +#define IDS_DIRINFO_FILES 3076 +#define IDS_DIRINFO_DIRSANDFILES 3077 +#define IDS_DIRINFO_FILESMIN 3078 +#define IDS_DIRINFO_DIRSANDFILESMIN 3079 +#define IDS_DIRINFO_SELECTED_FILES 3080 +#define IDS_DIRINFO_SELECTED_DIRS 3081 +#define IDS_DIRINFO_SELECTED_DIRSANDFILES 3082 +#define IDS_DIRINFO_SELECTED_FILESMIN 3083 +#define IDS_DIRINFO_SELECTED_DIRSANDFILESMIN 3084 +#define IDS_DIRINFO_FILE 3085 +#define IDS_DIRINFO_DIR 3086 +#define IDS_DIRINFO_DIRANDFILES 3087 +#define IDS_DIRINFO_FILEMIN 3088 +#define IDS_DIRINFO_DIRANDFILESMIN 3089 +#define IDS_DIRINFO_DIRANDFILE 3090 +#define IDS_DIRINFO_DIRSANDFILE 3091 +#define IDS_DIRINFO_DIRANDFILEMIN 3092 +#define IDS_DIRINFO_DIRSANDFILEMIN 3093 +#define IDS_DIRINFO_SELECTED_FILE 3094 +#define IDS_DIRINFO_SELECTED_DIR 3095 +#define IDS_DIRINFO_SELECTED_DIRANDFILES 3096 +#define IDS_DIRINFO_SELECTED_FILEMIN 3097 +#define IDS_DIRINFO_SELECTED_DIRANDFILESMIN 3098 +#define IDS_DIRINFO_SELECTED_DIRANDFILE 3099 +#define IDS_DIRINFO_SELECTED_DIRANDFILEMIN 3100 +#define IDS_DIRINFO_SELECTED_DIRSANDFILE 3101 +#define IDS_DIRINFO_SELECTED_DIRSANDFILEMIN 3102 +#define IDS_QUEUESTATUS_PAUSED 3103 +#define IDS_OPTIONSCAPTION_FONTNAME 3104 +#endif +#define IDS_PROXY_CONNECTED 3201 +#define IDS_CONTROL_CONNECTION 3202 +#define IDS_DATA_CONNECTION 3203 +#if 0 +#define ID_CANCELBUTTON 32768 +#define ID_COPYTOSITEMANAGER 32769 +#define ID_EDIT_EXPORTSETTINGS 32770 +#define ID_EDIT_IMPORTSETTINGS 32771 +#define ID_EDIT_SETTINGS 32772 +#define ID_FILE_CONNECTTODEFAULTSITE 32773 +#define ID_FILE_EXPORT_ASURLLIST 32774 +#define ID_FILE_EXPORT_WITHALLDETAILS 32775 +#define ID_FTPCONTEXT_ADDTOQUEUE 32776 +#define ID_FTPCONTEXT_ATTRIBUTES 32777 +#define ID_FTPCONTEXT_CREATEDIR 32778 +#define ID_FTPCONTEXT_DELETE 32779 +#define ID_FTPCONTEXT_DOWNLOAD 32780 +#define ID_FTPCONTEXT_DOWNLOADAS 32781 +#define ID_FTPCONTEXT_OPEN 32782 +#define ID_FTPCONTEXT_RENAME 32783 +#define ID_FTPCONTEXT_VIEWEDIT 32784 +#define ID_HELPMENU_CONTENTS 32785 +#define ID_HELPMENU_INDEX 32786 +#define ID_HELPMENU_SEARCH 32787 +#define ID_LOCALCONTEXT_ADDTOQUEUE 32788 +#define ID_LOCALCONTEXT_CREATEDIRECTORY 32789 +#define ID_LOCALCONTEXT_DELETE 32790 +#define ID_LOCALCONTEXT_OPEN 32791 +#define ID_LOCALCONTEXT_PROPERTIES 32792 +#define ID_LOCALCONTEXT_RENAME 32793 +#define ID_LOCALCONTEXT_UPLOAD 32794 +#define ID_LOCALCONTEXT_UPLOADAS 32795 +#define ID_LOCALCONTEXT_VIEWEDIT 32796 +#define ID_LOCALLISTSTYLE_ICON 32797 +#define ID_LOCALLISTSTYLE_LIST 32798 +#define ID_LOCALLISTSTYLE_REPORT 32799 +#define ID_LOCALLISTSTYLE_SMALLICON 32800 +#define ID_LOCALTREECONTEXT_ADDTOQUEUE 32801 +#define ID_LOCALTREECONTEXT_DELETE 32802 +#define ID_LOCALTREECONTEXT_RENAME 32803 +#define ID_LOCALTREECONTEXT_UPLOAD 32804 +#define ID_LOCALTREECONTEXT_UPLOADAS 32805 +#define ID_MENU_DEBUG 32806 +#define ID_MENU_DEBUG_DUMPDIRCACHE 32807 +#define ID_MENU_QUEUE_EXPORT 32808 +#define ID_MENU_QUEUE_IMPORT 32809 +#define ID_MENU_QUEUE_PROCESSNOW 32810 +#define ID_MENU_QUEUE_USEMULTIPLE 32811 +#define ID_MENU_SERVER_CHANGEPASS 32812 +#define ID_MENU_SERVER_COPYURLTOCLIPBOARD 32813 +#define ID_MENU_SERVER_ENTERRAWCOMMAND 32814 +#define ID_MENU_TRANSFER_MANUALTRANSFER 32815 +#define ID_MENU_VIEW_SHOWHIDDEN 32816 +#define ID_OUTPUTCONTEXT_CLEARALL 32817 +#define ID_OUTPUTCONTEXT_COPYTOCLIPBOARD 32818 +#define ID_OUTPUTCONTEXT_ENTERCOMMAND 32819 +#define ID_OVERWRITEMENU_ASK 32820 +#define ID_OVERWRITEMENU_OVERWRITE 32821 +#define ID_OVERWRITEMENU_OVERWRITEIFNEWER 32822 +#define ID_OVERWRITEMENU_RENAME 32823 +#define ID_OVERWRITEMENU_RESUME 32824 +#define ID_OVERWRITEMENU_SKIP 32825 +#define ID_PROCESS_QUEUE 32826 +#define ID_QUEUECONTEXT_MOVEDOWN 32827 +#define ID_QUEUECONTEXT_MOVETOBOTTOM 32828 +#define ID_QUEUECONTEXT_MOVETOTOP 32829 +#define ID_QUEUECONTEXT_MOVEUP 32830 +#define ID_QUEUECONTEXT_PROCESSQUEUE 32831 +#define ID_QUEUECONTEXT_REMOVEFROMQUEUE 32832 +#define ID_QUEUECONTEXT_RESETSTATUS 32833 +#define ID_QUICKCONNECTBAR_MENU_BYPASS 32834 +#define ID_QUICKCONNECTBAR_MENU_CLEAR 32835 +#define ID_QUICKCONNECTBAR_MENU_HISTORY1 32836 +#define ID_QUICKCONNECTBAR_MENU_HISTORY2 32837 +#define ID_QUICKCONNECTBAR_MENU_HISTORY3 32838 +#define ID_QUICKCONNECTBAR_MENU_HISTORY4 32839 +#define ID_QUICKCONNECTBAR_MENU_HISTORY5 32840 +#define ID_QUICKCONNECTBAR_MENU_HISTORY6 32841 +#define ID_QUICKCONNECTBAR_MENU_HISTORY7 32842 +#define ID_QUICKCONNECTBAR_MENU_HISTORY8 32843 +#define ID_QUICKCONNECTBAR_MENU_HISTORY9 32844 +#define ID_QUICKCONNECTBAR_MENU_HISTORY10 32845 +#define ID_REMOTELISTSTYLE_ICON 32846 +#define ID_REMOTELISTSTYLE_LIST 32847 +#define ID_REMOTELISTSTYLE_REPORT 32848 +#define ID_REMOTELISTSTYLE_SMALLICON 32849 +#define ID_SHOWQUEUE 32850 +#define ID_SHOWREMOTETREE 32851 +#define ID_SHOWTREE 32852 +#define ID_SITEMANAGER 32853 +#define ID_SITEMANAGER_FILE_EXPORT 32854 +#define ID_SITEMANAGER_FILE_EXPORT_ASURLLIST 32855 +#define ID_SITEMANAGER_FILE_EXPORT_WITHALLDETAILS 32856 +#define ID_SITEMANAGER_FILE_IMPORT 32857 +#define ID_SITEMANAGER_SAVEEXIT 32858 +#define ID_TOOLBAR_DISCONNECT 32859 +#define ID_TOOLBAR_RECONNECT 32860 +#define ID_TOOLBAR_REFRESH 32861 +#define ID_TRAY_EXIT 32862 +#define ID_TRAY_RESTORE 32863 +#define ID_TYPEMENU_ASCII 32864 +#define ID_TYPEMENU_BINARY 32865 +#define ID_TYPEMENU_DETECT 32866 +#define ID_VIEW_LOCALLISTVIEW_FILESIZE 32867 +#define ID_VIEW_LOCALLISTVIEW_FILETYPE 32868 +#define ID_VIEW_LOCALLISTVIEW_LASTMODIFIEDTIME 32869 +#define ID_VIEW_MESSAGELOG 32870 +#define ID_VIEW_QUICKCONNECT_BAR 32871 +#define ID_VIEW_REMOTELISTVIEW_DATE 32872 +#define ID_VIEW_REMOTELISTVIEW_FILESIZE 32873 +#define ID_VIEW_REMOTELISTVIEW_PERMISSIONS 32874 +#define ID_VIEW_REMOTELISTVIEW_TIME 32875 +#define ID_MENU_VIEW_LOCALLISTVIEW_STATUSBAR 32876 +#define ID_MENU_VIEW_REMOTELISTVIEW_STATUSBAR 32877 +#define ID_QUEUECONTEXT_ABORT 32878 +#define ID_QUEUECONTEXT_PAUSE 32879 +#define ID_QUEUECONTEXT_RESUME 32880 +#define ID_VIEW_REMOTELISTVIEW_OWNERGROUP 32881 +#define ID_MENU_DEBUG_CRASH 32882 +#define ID_VIEW_REMOTELISTVIEW_FILETYPE 32883 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_FILENAME 32890 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_FILESIZE 32891 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_FILETYPE 32892 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_DATETIME 32893 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_ASCENDING 32894 +#define ID_MENU_VIEW_REMOTELISTVIEW_SORTBY_DESCENDING 32895 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_FILENAME 32896 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_FILESIZE 32897 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_FILETYPE 32898 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_LASTMODIFIED 32899 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_ASCENDING 32900 +#define ID_MENU_VIEW_LOCALLISTVIEW_SORTBY_DESCENDING 32901 +#define ID_QUICKCONNECTBAR_MENU_EMPTYHISTORY 32902 +#define AFX_IDS_APP_TITLE 0xE000 +#define AFX_IDS_IDLEMESSAGE 0xE001 +#define ID_APP_ABOUT 0xE140 +#define ID_APP_EXIT 0xE141 +#define ID_HELP 0xE146 +#define ID_VIEW_TOOLBAR 0xE800 +#define ID_VIEW_STATUS_BAR 0xE801 +#define AFX_IDS_SCCLOSE 0xEF06 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 3106 +#define _APS_NEXT_COMMAND_VALUE 32903 +#define _APS_NEXT_CONTROL_VALUE 1291 +#define _APS_NEXT_SYMED_VALUE 3205 +#endif +#endif +#endif diff --git a/netbox/src/resource/TextsFileZilla.rc b/netbox/src/resource/TextsFileZilla.rc new file mode 100644 index 000000000..2c0b629d9 --- /dev/null +++ b/netbox/src/resource/TextsFileZilla.rc @@ -0,0 +1,90 @@ +#include "TextsFileZilla.h" + +#define _AFX_NO_APPMENU_RESOURCES +#define _AFX_NO_FILECMD_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_MAPI_RESOURCES +#define _AFX_NO_OCC_RESOURCES +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_CTL3D_RESOURCES +#define _AFX_NO_NEWTYPEDLG_RESOURCES +// #include "afxres.rc" + +STRINGTABLE +BEGIN + IDS_ERRORMSG_NAMEINUSE, "File already exists" + IDS_ERRORMSG_PROXY_AUTHNOLOGON, "Proxy requires authentication" + IDS_ERRORMSG_PROXY_AUTHTYPEUNKNOWN, "Required authtype reported by proxy server is unknown or not supported" + IDS_ERRORMSG_PROXY_CANTRESOLVEHOST, "Can't resolve host of proxy server" + IDS_ERRORMSG_PROXY_NOCONN, "Can't connect to proxy server" + IDS_ERRORMSG_PROXY_REQUESTFAILED, "Proxy request failed, can't connect through proxy server" +END + +STRINGTABLE +BEGIN + IDS_ERRORMSG_TIMEOUT, "Timeout detected." + IDS_ERRORMSG_UPLOADABORTED, "Upload aborted" +END + +STRINGTABLE +BEGIN + IDS_ERRORMSG_CANTOPENTRANSFERCHANNEL, "Transfer channel can't be opened. Reason: %s" + IDS_ERRORMSG_CANTRESOLVEHOST2, "Can't resolve host name" + IDS_ERRORMSG_CANTRESUME, "Resume command not supported by server, overwriting file." + IDS_ERRORMSG_CANTRESUME_FINISH, "Resume command not supported by server, but local and remote filesize are equal." + IDS_ERRORMSG_CANTSENDCOMMAND, "Unable to send command. Disconnected." + IDS_ERRORMSG_CERTREJECTED, "Peer certificate rejected" + IDS_ERRORMSG_DOWNLOADABORTED, "Download aborted" +END + +STRINGTABLE +BEGIN + IDS_ERRORMSG_CANTCREATEDUETOPORTRANGE, "Could not create socket in the specified port range." + IDS_ERRORMSG_CANTESTABLISHSSLCONNECTION, "Can't establish TLS connection" + IDS_ERRORMSG_CANTGETLIST, "Could not retrieve directory listing" + IDS_ERRORMSG_CANTINITSSL, "Can't initialize TLS library" +END + +STRINGTABLE +BEGIN + IDS_STATUSMSG_CONNECTED, "Connected" + IDS_STATUSMSG_CONNECTEDWITH, "Connected with %s. Waiting for welcome message..." + IDS_STATUSMSG_CONNECTEDWITHSSL, "Connected with %s, negotiating TLS connection..." + IDS_STATUSMSG_CONNECTING, "Connecting to %s ..." + IDS_STATUSMSG_DIRLISTSUCCESSFUL, "Directory listing successful" + IDS_STATUSMSG_DISCONNECTED, "Disconnected from server" + IDS_STATUSMSG_DOWNLOADSTART, "Starting download of %s" + IDS_STATUSMSG_DOWNLOADSUCCESSFUL, "Download successful" + IDS_STATUSMSG_FWCONNECT, "Trying to access %s through ftp proxy..." +END + +STRINGTABLE +BEGIN + IDS_STATUSMSG_RETRIEVINGDIRLIST, "Retrieving directory listing..." + IDS_STATUSMSG_SSLESTABLISHED, "TLS connection established. Waiting for welcome message..." + IDS_STATUSMSG_SSLESTABLISHEDTRANSFER, "TLS connection established" + IDS_STATUSMSG_UPLOADSTART, "Starting upload of %s" + IDS_STATUSMSG_UPLOADSUCCESSFUL, "Upload successful" +END + +STRINGTABLE +BEGIN + IDS_ERRORMSG_CANTGETLISTFILE, "Could not retrieve file information" + IDS_STATUSMSG_LISTFILESUCCESSFUL, "Retrieving file information successful" + IDS_STATUSMSG_RETRIEVINGLISTFILE, "Retrieving file information..." +END + +STRINGTABLE +BEGIN + IDS_ERRORMSG_SETFILEPOINTER, "Could not set file pointer" + IDS_ERRORMSG_UNKNOWNSSLERROR, "Unknown error in TLS layer" + IDS_ERRORMSG_SSLCERTIFICATEERROR, "Could not verify TLS certificate" +END + +STRINGTABLE +BEGIN + IDS_PROXY_CONNECTED, "Connection with proxy established, performing handshake..." + IDS_CONTROL_CONNECTION, "control connection" + IDS_DATA_CONNECTION, "data connection" +END diff --git a/netbox/src/resource/rtlconsts.h b/netbox/src/resource/rtlconsts.h new file mode 100644 index 000000000..5f7d8881c --- /dev/null +++ b/netbox/src/resource/rtlconsts.h @@ -0,0 +1,119 @@ +#pragma once + +//#define SAncestorNotFound 2000 +//#define SAssignError 2001 +//#define SBitsIndexError 2002 +//#define SBucketListLocked 2003 +//#define SCantWriteResourceStreamError 2004 +//#define SCharExpected 2005 +//#define SCheckSynchronizeError 2006 +//#define SClassNotFound 2007 +//#define SDelimiterQuoteCharError 2008 +//#define SDuplicateClass 2009 +//#define SDuplicateItem 2010 +//#define SDuplicateName 2011 +#define SDuplicateString 2012 +//#define SFCreateError 2013 +//#define SFixedColTooBig 2014 +//#define SFixedRowTooBig 2015 +//#define SFOpenError 2016 +//#define SGridTooLarge 2017 +//#define SIdentifierExpected 2018 +//#define SIndexOutOfRange 2019 +//#define SIniFileWriteError 2020 +//#define SInvalidActionCreation 2021 +//#define SInvalidActionEnumeration 2022 +//#define SInvalidActionRegistration 2023 +//#define SInvalidActionUnregistration 2024 +//#define SInvalidBinary 2025 +//#define SInvalidDate 2026 +//#define SInvalidDateTime 2027 +//#define SInvalidFileName 2028 +//#define SInvalidImage 2029 +//#define SInvalidInteger 2030 +//#define SInvalidMask 2031 +//#define SInvalidName 2032 +//#define SInvalidProperty 2033 +//#define SInvalidPropertyElement 2034 +//#define SInvalidPropertyPath 2035 +//#define SInvalidPropertyType 2036 +//#define SInvalidPropertyValue 2037 +//#define SInvalidRegType 2038 +//#define SInvalidString 2039 +//#define SInvalidStringGridOp 2040 +//#define SInvalidTime 2041 +//#define SItemNotFound 2042 +//#define SLineTooLong 2043 +//#define SListCapacityError 2044 +#define SListCountError 2045 +#define SListIndexError 2046 +//#define SMaskErr 2047 +//#define SMaskEditErr 2048 +#define SMemoryStreamError 2049 +//#define SNoComSupport 2050 +//#define SNotPrinting 2051 +//#define SNumberExpected 2052 +//#define SParseError 2053 +//#define SComponentNameTooLong 2054 +//#define SPropertyException 2055 +//#define SPrinting 2056 +#define SReadError 2057 +//#define SReadOnlyProperty 2058 +//#define SRegCreateFailed 2059 +//#define SRegGetDataFailed 2060 +//#define SRegisterError 2061 +//#define SRegSetDataFailed 2062 +//#define SResNotFound 2063 +//#define SSeekNotImplemented 2064 +#define SSortedListError 2065 +//#define SStringExpected 2066 +//#define SSymbolExpected 2067 +#define STimeEncodeError 2068 +//#define STooManyDeleted 2069 +//#define SUnknownGroup 2070 +//#define SUnknownProperty 2071 +#define SWriteError 2072 +//#define SStreamSetSize 2073 +//#define SThreadCreateError 2074 +//#define SThreadError 2075 + +//#define SInvalidDateDay 2076 +//#define SInvalidDateWeek 2077 +//#define SInvalidDateMonthWeek 2078 +//#define SInvalidDayOfWeekInMonth 2079 +//#define SInvalidJulianDate 2080 +//#define SMissingDateTimeField 2081 + +//#define SConvIncompatibleTypes2 2082 +//#define SConvIncompatibleTypes3 2083 +//#define SConvIncompatibleTypes4 2084 +//#define SConvUnknownType 2085 +//#define SConvDuplicateType 2086 +//#define SConvUnknownFamily 2087 +//#define SConvDuplicateFamily 2088 +//#define SConvUnknownDescription 2089 +//#define SConvIllegalType 2090 +//#define SConvIllegalFamily 2091 +//#define SConvFactorZero 2092 +//#define SConvStrParseError 2093 +//#define SFailedToCallConstructor 2094 + +//#define sWindowsSocketError 2095 +//#define sAsyncSocketError 2096 +//#define sNoAddress 2097 +//#define sCannotListenOnOpen 2098 +//#define sCannotCreateSocket 2099 +//#define sSocketAlreadyOpen 2100 +//#define sCantChangeWhileActive 2101 +//#define sSocketMustBeBlocking 2102 +//#define sSocketIOError 2103 +//#define sSocketRead 2104 +//#define sSocketWrite 2105 + +#define SNotImplemented 2106 +#define SOSError 2107 +#define SUnkOSError 2108 + +#define SDateEncodeError 2109 + +#define SCannotOpenClipboard 2110 diff --git a/netbox/src/tests/calculator.hpp b/netbox/src/tests/calculator.hpp new file mode 100644 index 000000000..02df0301e --- /dev/null +++ b/netbox/src/tests/calculator.hpp @@ -0,0 +1,14 @@ +#pragma once +/* +#include + +DL_NS_BLOCK((team) +( + DL_LIBRARY(calculator) + ( + (double, sum, (double,x)(double,y)) + (double, mul, (double,x)(double,y)) + (double, sqrt, (double,x)) + ) +)) +*/ diff --git a/netbox/src/tests/calculator_dll.cpp b/netbox/src/tests/calculator_dll.cpp new file mode 100644 index 000000000..bfe1bbf13 --- /dev/null +++ b/netbox/src/tests/calculator_dll.cpp @@ -0,0 +1,10 @@ +#include "calculator.hpp" + +struct calculator +{ + static double sum(double x, double y) { return x + y; } + static double mul(double x, double y) { return x * y; } + static double sqrt(double x) { return std::sqrt(x); } +}; + +DL_EXPORT(team::calculator, calculator) diff --git a/netbox/src/tests/testnetbox_01.cpp b/netbox/src/tests/testnetbox_01.cpp new file mode 100644 index 000000000..7422f81af --- /dev/null +++ b/netbox/src/tests/testnetbox_01.cpp @@ -0,0 +1,792 @@ +//------------------------------------------------------------------------------ +// testnetbox_01.cpp +// Тесты для NetBox +// testnetbox_01 --run_test=netbox/test1 --log_level=all 2>&1 | tee res.txt +//------------------------------------------------------------------------------ + +// #include "leak_detector.h" +// extern int GC_find_leak; + +#include +#include +#include +#include +#include +#include + +#include "boostdefines.hpp" +#define BOOST_TEST_MODULE "testnetbox_01" +#define BOOST_TEST_MAIN +#include +// #include + +#include + +#include "TestTexts.h" +#include "Common.h" +#include "FarPlugin.h" +#include "testutils.h" +#include "FastDelegate.h" +#include "CppProperties.h" + +using namespace boost::unit_test; + +//------------------------------------------------------------------------------ +// stub +TCustomFarPlugin * FarPlugin = NULL; +//------------------------------------------------------------------------------ + +void FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* ie_config) +{ + if (ie_config->lpszAutoConfigUrl) + GlobalFree(ie_config->lpszAutoConfigUrl); + if (ie_config->lpszProxy) + GlobalFree(ie_config->lpszProxy); + if (ie_config->lpszProxyBypass) + GlobalFree(ie_config->lpszProxyBypass); +} + +/******************************************************************************* + test suite +*******************************************************************************/ + +class base_fixture_t +{ +public: + base_fixture_t() + { + // BOOST_TEST_MESSAGE("base_fixture_t ctor"); + InitPlatformId(); + } + + virtual ~base_fixture_t() + { + } +public: +protected: +}; + +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE(netbox) + +BOOST_FIXTURE_TEST_CASE(test1, base_fixture_t) +{ + TList list; + BOOST_CHECK_EQUAL(0, list.GetCount()); + TObject obj1; + TObject obj2; + if (1) + { + list.Add(&obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + list.Add(&obj2); + BOOST_CHECK_EQUAL(2, list.GetCount()); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj2)); + } + list.Clear(); + if (1) + { + BOOST_CHECK_EQUAL(0, list.GetCount()); + list.Insert(0, &obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + list.Insert(0, &obj2); + BOOST_CHECK_EQUAL(2, list.GetCount()); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + } + if (1) + { + list.Delete(1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + BOOST_CHECK_EQUAL(-1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + BOOST_CHECK_EQUAL(1, list.Add(&obj1)); + list.Delete(0); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.Remove(&obj1)); + BOOST_CHECK_EQUAL(-1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.GetCount()); + } + if (1) + { + list.Add(&obj1); + list.Add(&obj2); + list.Extract(&obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + list.Add(&obj1); + BOOST_CHECK_EQUAL(2, list.GetCount()); + list.Move(0, 1); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj2)); + } +} + +BOOST_FIXTURE_TEST_CASE(test2, base_fixture_t) +{ + UnicodeString str; + if (1) + { + TStringList strings; + BOOST_CHECK_EQUAL(0, strings.GetCount()); + BOOST_CHECK_EQUAL(0, strings.Add(L"line 1")); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + str = strings.GetString(0); + // DEBUG_PRINTF(L"str = %s", str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 1"); + strings.SetString(0, L"line 0"); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + str = strings.GetString(0); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 0"); + strings.SetString(0, L"line 00"); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + BOOST_CHECK_EQUAL(W2MB(strings.GetString(0).c_str()), "line 00"); + strings.Add(L"line 11"); + BOOST_CHECK_EQUAL(2, strings.GetCount()); + BOOST_CHECK_EQUAL(W2MB(strings.GetString(1).c_str()), "line 11"); + strings.Delete(1); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + } + TStringList strings; + if (1) + { + BOOST_CHECK_EQUAL(0, strings.GetCount()); + strings.Add(L"line 1"); + str = strings.GetText(); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str()).c_str()); + // DEBUG_PRINTF(L"str = %s", BytesToHex(RawByteString(str.c_str(), str.Length()), true, L',').c_str()); + BOOST_TEST_MESSAGE("str = " << W2MB(BytesToHex(RawByteString(str.c_str(), str.Length()), true, L',').c_str()).c_str()); + BOOST_CHECK(_wcsicmp(str.c_str(), L"line 1\x0D\x0A") == 0); + } + if (1) + { + strings.Add(L"line 2"); + BOOST_CHECK_EQUAL(2, strings.GetCount()); + str = strings.GetText(); + BOOST_TEST_MESSAGE(L"str = " << str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "line 1\r\nline 2\r\n"); + strings.Insert(0, L"line 0"); + BOOST_CHECK_EQUAL(3, strings.GetCount()); + str = strings.GetText(); + BOOST_TEST_MESSAGE(L"str = " << str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "line 0\r\nline 1\r\nline 2\r\n"); + strings.SetObj(0, NULL); + UnicodeString str = strings.GetString(0); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 0"); + } + { + strings.SetString(0, L"line 12"); + BOOST_CHECK_EQUAL(W2MB(strings.GetString(0).c_str()), "line 12"); + } +} + +BOOST_FIXTURE_TEST_CASE(test3, base_fixture_t) +{ + UnicodeString Text = L"text text text text text1\ntext text text text text2\n"; + TStringList Lines; + Lines.SetText(Text); + BOOST_CHECK_EQUAL(2, Lines.GetCount()); + BOOST_TEST_MESSAGE("Lines 0 = " << W2MB(Lines.GetString(0).c_str())); + BOOST_TEST_MESSAGE("Lines 1 = " << W2MB(Lines.GetString(1).c_str())); + BOOST_CHECK_EQUAL("text text text text text1", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL("text text text text text2", W2MB(Lines.GetString(1).c_str()).c_str()); +} + +BOOST_FIXTURE_TEST_CASE(test4, base_fixture_t) +{ + UnicodeString Text = L"text, text text, text text1\ntext text text, text text2\n"; + TStringList Lines; + Lines.SetCommaText(Text); + BOOST_CHECK_EQUAL(5, Lines.GetCount()); + BOOST_CHECK_EQUAL(0, wcscmp(L"text", Lines.GetString(0).c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(L" text text", Lines.GetString(1).c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(L" text text1", Lines.GetString(2).c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(L"text text text", Lines.GetString(3).c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(L" text text2", Lines.GetString(4).c_str())); + UnicodeString Text2 = Lines.GetCommaText(); + BOOST_TEST_MESSAGE("Text2 = '" << W2MB(Text2.c_str()) << "'"); + BOOST_CHECK_EQUAL(0, wcscmp(L"\"text\",\" text text\",\" text text1\",\"text text text\",\" text text2\"", Text2.c_str())); +} + +BOOST_FIXTURE_TEST_CASE(test5, base_fixture_t) +{ + TStringList Lines; + TObject obj1; + Lines.InsertObject(0, L"line 1", &obj1); + BOOST_CHECK(&obj1 == Lines.GetObjject(0)); +} + +BOOST_FIXTURE_TEST_CASE(test6, base_fixture_t) +{ + TStringList Lines; + Lines.Add(L"bbb"); + Lines.Add(L"aaa"); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.Text.c_str()).c_str()); + { + Lines.SetSorted(true); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.Text.c_str()).c_str()); + BOOST_CHECK_EQUAL("aaa", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL(2, Lines.GetCount()); + } + { + Lines.SetSorted(false); + Lines.Add(L"Aaa"); + Lines.SetCaseSensitive(true); + Lines.SetSorted(true); + BOOST_CHECK_EQUAL(3, Lines.GetCount()); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.Text.c_str()).c_str()); + BOOST_CHECK_EQUAL("aaa", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL("Aaa", W2MB(Lines.GetString(1).c_str()).c_str()); + BOOST_CHECK_EQUAL("bbb", W2MB(Lines.GetString(2).c_str()).c_str()); + } +} + +BOOST_FIXTURE_TEST_CASE(test7, base_fixture_t) +{ + TStringList Lines; + { + Lines.Add(L"bbb"); + BOOST_TEST_MESSAGE("before try"); + try + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + BOOST_CHECK(1 == Lines.GetCount()); + } BOOST_SCOPE_EXIT_END + // throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + catch (...) + { + BOOST_TEST_MESSAGE("in catch(...) block"); + } + BOOST_TEST_MESSAGE("after try"); + Lines.Add(L"aaa"); + BOOST_CHECK(2 == Lines.GetCount()); + } + Lines.Clear(); + Lines.BeginUpdate(); + { + Lines.Add(L"bbb"); + BOOST_TEST_MESSAGE("before block"); + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + BOOST_CHECK(1 == Lines.GetCount()); + Lines.EndUpdate(); + } BOOST_SCOPE_EXIT_END + // throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + BOOST_TEST_MESSAGE("after block"); + Lines.Add(L"aaa"); + BOOST_CHECK(2 == Lines.GetCount()); + } + Lines.Clear(); + int cnt = 0; + TStringList * Lines1 = new TStringList(); + int cnt1 = 0; + TStringList * Lines2 = new TStringList(); + { + Lines.BeginUpdate(); + cnt++; + Lines1->BeginUpdate(); + cnt1++; + Lines2->Add(L"bbb"); + BOOST_TEST_MESSAGE("before block"); + try + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) (&Lines1) (&Lines2) (&cnt) (&cnt1) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + Lines.EndUpdate(); + cnt--; + Lines1->EndUpdate(); + cnt1--; + delete Lines1; + Lines1 = NULL; + delete Lines2; + Lines2 = NULL; + } BOOST_SCOPE_EXIT_END + BOOST_CHECK(1 == cnt); + BOOST_CHECK(1 == cnt1); + BOOST_CHECK(1 == Lines2->GetCount()); + throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + catch (const std::exception & ex) + { + BOOST_TEST_MESSAGE("in catch block"); + BOOST_CHECK(NULL == Lines1); + BOOST_CHECK(NULL == Lines2); + } + BOOST_TEST_MESSAGE("after block"); + BOOST_CHECK(0 == cnt); + BOOST_CHECK(0 == cnt1); + BOOST_CHECK(NULL == Lines1); + BOOST_CHECK(NULL == Lines2); + } +} + +BOOST_FIXTURE_TEST_CASE(test8, base_fixture_t) +{ + UnicodeString ProgramsFolder; + UnicodeString DefaultPuttyPathOnly = ::IncludeTrailingBackslash(ProgramsFolder) + L"PuTTY\\putty.exe"; + BOOST_CHECK(DefaultPuttyPathOnly == L"\\PuTTY\\putty.exe"); + BOOST_CHECK(L"" == ::ExcludeTrailingBackslash(::IncludeTrailingBackslash(ProgramsFolder))); +} + +BOOST_FIXTURE_TEST_CASE(test9, base_fixture_t) +{ + UnicodeString Folder = L"C:\\Program Files\\Putty"; + BOOST_TEST_MESSAGE("ExtractFileDir = " << W2MB(::ExtractFileDir(Folder).c_str()).c_str()); + BOOST_CHECK(L"C:\\Program Files\\" == ::ExtractFileDir(Folder)); + BOOST_CHECK(L"C:\\Program Files\\" == ::ExtractFilePath(Folder)); + BOOST_TEST_MESSAGE("GetCurrentDir = " << W2MB(::GetCurrentDir().c_str()).c_str()); + BOOST_CHECK(::GetCurrentDir().Length() > 0); + BOOST_CHECK(::DirectoryExists(::GetCurrentDir())); +} + +BOOST_FIXTURE_TEST_CASE(test10, base_fixture_t) +{ + TDateTime dt1(23, 58, 59, 102); + BOOST_TEST_MESSAGE("dt1 = " << dt1); + BOOST_CHECK(dt1 > 0.0); + unsigned short H, M, S, MS; + dt1.DecodeTime(H, M, S, MS); + BOOST_CHECK_EQUAL(H, 23); + BOOST_CHECK_EQUAL(M, 58); + BOOST_CHECK_EQUAL(S, 59); + BOOST_CHECK_EQUAL(MS, 102); +} + +BOOST_FIXTURE_TEST_CASE(test11, base_fixture_t) +{ + TDateTime dt1 = EncodeDateVerbose(2009, 12, 29); + BOOST_TEST_MESSAGE("dt1 = " << dt1); + // bg::date::ymd_type ymd(2009, 12, 29); + // BOOST_TEST_MESSAGE("ymd.year = " << ymd.year << ", ymd.month = " << ymd.month << ", ymd.day = " << ymd.day); + unsigned short Y, M, D; + dt1.DecodeDate(Y, M, D); + BOOST_TEST_MESSAGE("Y = " << Y << ", M = " << M << ", D = " << D); + BOOST_CHECK(Y == 2009); + BOOST_CHECK(M == 12); + BOOST_CHECK(D == 29); + int DOW = ::DayOfWeek(dt1); + BOOST_CHECK_EQUAL(3, DOW); +} + +BOOST_FIXTURE_TEST_CASE(test12, base_fixture_t) +{ + BOOST_TEST_MESSAGE("Is2000 = " << Is2000()); + BOOST_TEST_MESSAGE("IsWin7 = " << IsWin7()); + BOOST_TEST_MESSAGE("IsExactly2008R2 = " << IsExactly2008R2()); + TDateTime dt = ::EncodeDateVerbose(2009, 12, 29); + FILETIME ft = ::DateTimeToFileTime(dt, dstmWin); + BOOST_TEST_MESSAGE("ft.dwLowDateTime = " << ft.dwLowDateTime); + BOOST_TEST_MESSAGE("ft.dwHighDateTime = " << ft.dwHighDateTime); +} + +BOOST_FIXTURE_TEST_CASE(test13, base_fixture_t) +{ + UnicodeString str_value = ::IntToStr(1234); + BOOST_TEST_MESSAGE("str_value = " << W2MB(str_value.c_str())); + BOOST_CHECK(W2MB(str_value.c_str()) == "1234"); + int int_value = ::StrToInt(L"1234"); + BOOST_TEST_MESSAGE("int_value = " << int_value); + BOOST_CHECK(int_value == 1234); +} + +BOOST_FIXTURE_TEST_CASE(test14, base_fixture_t) +{ + TStringList Strings1; + TStringList Strings2; + Strings1.AddStrings(&Strings2); + BOOST_CHECK(0 == Strings1.GetCount()); + Strings2.Add(L"lalalla"); + Strings1.AddStrings(&Strings2); + BOOST_CHECK(1 == Strings1.GetCount()); + BOOST_CHECK(L"lalalla" == Strings1.GetString(0)); +} + +BOOST_FIXTURE_TEST_CASE(test15, base_fixture_t) +{ + UnicodeString res = ::IntToHex(10, 2); + BOOST_TEST_MESSAGE("res = " << W2MB(res.c_str())); + BOOST_CHECK(res == L"0A"); +} + +BOOST_FIXTURE_TEST_CASE(test16, base_fixture_t) +{ + { + UnicodeString Name1 = L"1"; + UnicodeString Name2 = L"2"; + int res = ::AnsiCompareIC(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + res = ::AnsiCompare(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + } + { + UnicodeString Name1 = L"abc"; + UnicodeString Name2 = L"ABC"; + int res = ::AnsiCompareIC(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res == 0); + res = ::AnsiCompare(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + } + { + UnicodeString Name1 = L"Unlimited"; + UnicodeString Name2 = L"Unlimited"; + BOOST_CHECK(::AnsiSameText(Name1, Name2)); + } +} + +BOOST_FIXTURE_TEST_CASE(test17, base_fixture_t) +{ + TStringList List1; + List1.SetText(L"123\n456"); + BOOST_CHECK(2 == List1.GetCount()); + BOOST_TEST_MESSAGE("List1.GetString(0] = " << W2MB(List1.GetString(0).c_str())); + BOOST_CHECK("123" == W2MB(List1.GetString(0).c_str())); + BOOST_TEST_MESSAGE("List1.GetString(1] = " << W2MB(List1.GetString(1).c_str())); + BOOST_CHECK("456" == W2MB(List1.GetString(1).c_str())); + List1.Move(0, 1); + BOOST_TEST_MESSAGE("List1.GetString(0] = " << W2MB(List1.GetString(0).c_str())); + BOOST_CHECK("456" == W2MB(List1.GetString(0).c_str())); + BOOST_TEST_MESSAGE("List1.GetString(1] = " << W2MB(List1.GetString(1).c_str())); + BOOST_CHECK("123" == W2MB(List1.GetString(1).c_str())); +} + +BOOST_FIXTURE_TEST_CASE(test18, base_fixture_t) +{ + { + UnicodeString Key = L"Interface"; + UnicodeString Res = ::CutToChar(Key, L'\\', false); + BOOST_CHECK(Key.IsEmpty()); + BOOST_CHECK("Interface" == W2MB(Res.c_str())); + } + { + UnicodeString Key = L"Interface\\SubKey"; + UnicodeString Res = ::CutToChar(Key, L'\\', false); + BOOST_CHECK("SubKey" == W2MB(Key.c_str())); + BOOST_CHECK("Interface" == W2MB(Res.c_str())); + } +} + +BOOST_FIXTURE_TEST_CASE(test19, base_fixture_t) +{ + TStringList Strings1; + Strings1.Add(L"Name1=Value1"); + BOOST_CHECK(0 == Strings1.IndexOfName(L"Name1")); +} + +BOOST_FIXTURE_TEST_CASE(test20, base_fixture_t) +{ + TDateTime DateTime = Now(); + unsigned short H, M, S, MS; + DateTime.DecodeTime(H, M, S, MS); + // UnicodeString str = ::FormatDateTime(L"HH:MM:SS", DateTime); + UnicodeString str = FORMAT("%02d:%02d:%02d", H, M, S); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + // BOOST_CHECK(str == L"20:20:20"); +} + +BOOST_FIXTURE_TEST_CASE(test21, base_fixture_t) +{ + UnicodeString str = ::FormatFloat(L"#,##0", 23.456); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + // BOOST_CHECK(str.c_str() == L"23.46"); + BOOST_CHECK("23.46" == W2MB(str.c_str())); +} + +BOOST_FIXTURE_TEST_CASE(test22, base_fixture_t) +{ + UnicodeString FileName = L"testfile"; + ::DeleteFile(FileName); + std::string str = "test string"; + { + unsigned int CreateAttrs = FILE_ATTRIBUTE_NORMAL; + HANDLE FileHandle = ::CreateFile(FileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, CreateAttrs, 0); + BOOST_CHECK(FileHandle != 0); + TStream * FileStream = new TSafeHandleStream(FileHandle); + TFileBuffer * BlockBuf = new TFileBuffer(); + // BlockBuf->SetSize(1024); + BlockBuf->SetPosition(0); + BlockBuf->Insert(0, str.c_str(), str.size()); + BOOST_TEST_MESSAGE("BlockBuf->GetSize = " << BlockBuf->GetSize()); + BOOST_CHECK(BlockBuf->GetSize() == str.size()); + BlockBuf->WriteToStream(FileStream, BlockBuf->GetSize()); + delete FileStream; FileStream = NULL; + delete BlockBuf; BlockBuf = NULL; + ::CloseHandle(FileHandle); + BOOST_TEST_MESSAGE("FileName1 = " << W2MB(FileName.c_str())); + BOOST_REQUIRE(::FileExists(FileName)); + } + { + BOOST_TEST_MESSAGE("FileName2 = " << W2MB(FileName.c_str())); + TSearchRec Rec; + BOOST_CHECK(FileSearchRec(FileName, Rec)); + } + { + HANDLE File = ::CreateFile( + FileName.c_str(), + GENERIC_READ, + 0, // FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, // OPEN_EXISTING, + 0, // FILE_ATTRIBUTE_NORMAL, // 0, + NULL); + DEBUG_PRINTF(L"File = %d", File); + TStream * FileStream = new TSafeHandleStream(File); + TFileBuffer * BlockBuf = new TFileBuffer(); + BlockBuf->ReadStream(FileStream, str.size(), true); + BOOST_TEST_MESSAGE("BlockBuf->GetSize = " << BlockBuf->GetSize()); + BOOST_CHECK(BlockBuf->GetSize() == str.size()); + delete FileStream; FileStream = NULL; + delete BlockBuf; BlockBuf = NULL; + ::CloseHandle(File); + } +} + +BOOST_FIXTURE_TEST_CASE(test23, base_fixture_t) +{ + UnicodeString Dir1 = L"subdir1"; + UnicodeString Dir2 = L"subdir1/subdir2"; + ::RemoveDir(Dir2); + ::RemoveDir(Dir1); + BOOST_TEST_MESSAGE("DirectoryExists(Dir2) = " << DirectoryExists(Dir2)); + BOOST_CHECK(!::DirectoryExists(Dir2)); + ::ForceDirectories(Dir2); + BOOST_CHECK(::DirectoryExists(Dir2)); + ::RemoveDir(Dir2); + ::ForceDirectories(Dir2); + BOOST_CHECK(::DirectoryExists(Dir2)); + BOOST_CHECK(::RecursiveDeleteFile(Dir1, false)); + BOOST_CHECK(!::DirectoryExists(Dir1)); +} + +BOOST_FIXTURE_TEST_CASE(test24, base_fixture_t) +{ + TDateTime now = Now(); + BOOST_TEST_MESSAGE("now = " << (double)now); + BOOST_CHECK(now > 0.0); +} + +BOOST_FIXTURE_TEST_CASE(test25, base_fixture_t) +{ +#if 0 + GC_find_leak = 1; + int * i = (int *)malloc(sizeof(int)); + CHECK_LEAKS(); +#endif +} + +//------------------------------------------------------------------------------ + +class CBaseClass +{ +protected: + char * m_name; +public: + CBaseClass(char * name) : m_name(name) {}; + void SimpleMemberFunction(int num, char * str) + { + printf("In SimpleMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str); + } + int SimpleMemberFunctionReturnsInt(int num, char * str) + { + printf("In SimpleMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str); return -1; + } + void ConstMemberFunction(int num, char * str) const + { + printf("In ConstMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str); + } + virtual void SimpleVirtualFunction(int num, char * str) + { + printf("In SimpleVirtualFunction in %s. Num=%d, str = %s\n", m_name, num, str); + } + static void StaticMemberFunction(int num, char * str) + { + printf("In StaticMemberFunction. Num=%d, str =%s\n", num, str); + } +}; + +BOOST_FIXTURE_TEST_CASE(test26, base_fixture_t) +{ + DEFINE_CALLBACK_TYPE2(TEvent, int, int, char *); + TEvent sig; + + CBaseClass a("Base A"); + sig = fastdelegate::MakeDelegate(&a, &CBaseClass::SimpleMemberFunctionReturnsInt); + int Result = sig(10, "abc"); + BOOST_TEST_MESSAGE("Result = " << Result); + BOOST_CHECK(Result == -1); +} +//------------------------------------------------------------------------------ +BOOST_FIXTURE_TEST_CASE(test27, base_fixture_t) +{ + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ProxyConfig = {0}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&ProxyConfig)) + { + DWORD Err = GetLastError(); + switch (Err) + { + case ERROR_FILE_NOT_FOUND: + BOOST_TEST_MESSAGE("The error is ERROR_FILE_NOT_FOUND"); + break; + case ERROR_WINHTTP_INTERNAL_ERROR: + BOOST_TEST_MESSAGE("ERROR_WINHTTP_INTERNAL_ERROR"); + break; + case ERROR_NOT_ENOUGH_MEMORY: + BOOST_TEST_MESSAGE("ERROR_NOT_ENOUGH_MEMORY"); + break; + default: + BOOST_TEST_MESSAGE("Look up error in header file."); + } + } + else + { + //no error so check the proxy settings and free any strings + BOOST_TEST_MESSAGE("AutoConfigDetect is: " << ProxyConfig.fAutoDetect); + if (NULL != ProxyConfig.lpszAutoConfigUrl) + { + BOOST_TEST_MESSAGE("AutoConfigURL is: " << W2MB(ProxyConfig.lpszAutoConfigUrl)); + } + if (NULL != ProxyConfig.lpszProxy) + { + BOOST_TEST_MESSAGE("AutoConfigProxy is: " << W2MB(ProxyConfig.lpszProxy)); + } + if (NULL != ProxyConfig.lpszProxyBypass) + { + BOOST_TEST_MESSAGE("AutoConfigBypass is: " << W2MB(ProxyConfig.lpszProxyBypass)); + } + FreeIEConfig(&ProxyConfig); + } +} +//------------------------------------------------------------------------------ +BOOST_FIXTURE_TEST_CASE(test28, base_fixture_t) +{ + DEBUG_PRINTF(L"1"); + if (1) + { + UnicodeString Buf; + UnicodeString DestFileName(L"FileName"); + TFileBuffer AsciiBuf; + AsciiBuf.SetSize(0x1000); + DEBUG_PRINTF(L"2"); + Buf.Clear(); + Buf.SetLength(MAX_PATH * 2); + DEBUG_PRINTF(L"AsciiBuf.GetSize = %lld", AsciiBuf.GetSize()); + swprintf_s(const_cast(Buf.c_str()), Buf.Length(), L"C%s %lld %s", + L"", + AsciiBuf.GetSize(), + DestFileName.c_str()); + DEBUG_PRINTF(L"3"); + BOOST_TEST_MESSAGE("Buf1 = " << AnsiString(Buf).c_str()); + DEBUG_PRINTF(L"Buf = %s", Buf.c_str()); + BOOST_CHECK(Buf.GetLength() > 0); + } + if (1) + { + UnicodeString Buf; + Buf.SetLength(20); + UnicodeString DestFileName(L"FileName"); + TFileBuffer AsciiBuf; + // swprintf_s causes error + swprintf(const_cast(Buf.c_str()), L"C%s %lld %s", + L"", + AsciiBuf.GetSize(), + DestFileName.c_str()); + BOOST_TEST_MESSAGE("Buf2 = " << AnsiString(Buf).c_str()); + BOOST_CHECK(AnsiString(Buf) == "C 0 FileName"); + BOOST_CHECK("C 0 FileName" == AnsiString(Buf)); + BOOST_CHECK(AnsiString(Buf) == AnsiString("C 0 FileName")); + BOOST_CHECK(Buf == L"C 0 FileName"); + BOOST_CHECK(L"C 0 FileName" == Buf); + } + DEBUG_PRINTF(L"4"); + if (1) + { + // Ctrl = T, Line = 1338899268 0 1338899268 0 + UnicodeString Line = L"1338899268 0 1338899268 0"; + unsigned long MTime, ATime; + if (swscanf(Line.c_str(), L"%ld %*d %ld %*d", &MTime, &ATime) != 2) + BOOST_FAIL("swscanf"); + BOOST_CHECK(MTime == 1338899268LU); + BOOST_CHECK(ATime == 1338899268LU); + } + DEBUG_PRINTF(L"5"); + if (1) + { + intptr_t errCode = 0xFF; + wchar_t codeNum[16]; + DEBUG_PRINTF(L"6"); + // swprintf_s(codeNum, sizeof(codeNum), L"[0x%08X]", errCode); // Causes AV x64 + swprintf(codeNum, L"[0x%08X]", errCode); + DEBUG_PRINTF(L"7"); + BOOST_TEST_MESSAGE("codeNum = " << AnsiString(codeNum).c_str()); + DEBUG_PRINTF(L"8"); + BOOST_CHECK(AnsiString(codeNum) == AnsiString("[0x000000FF]")); // Causes AV x64 + // BOOST_CHECK(AnsiString(codeNum) == AnsiString("")); + BOOST_CHECK(wcscmp(codeNum, L"[0x000000FF]") == 0); + } + DEBUG_PRINTF(L"9"); +} +//------------------------------------------------------------------------------ +#if 0 +class Foo +{ +public: + Foo(int j) { i=new int[j]; } + ~Foo() { delete i; } +private: + int* i; +}; + +class Bar: public Foo +{ +public: + Bar(int j): Foo(j) { i=new char[j]; } + ~Bar() { delete i; } +private: + char* i; +}; + + +BOOST_FIXTURE_TEST_CASE(test29, base_fixture_t) +{ + Foo* f=new Foo(100); + Foo* b=new Bar(200); + *f=*b; + delete f; + delete b; +} +#endif +//------------------------------------------------------------------------------ +BOOST_FIXTURE_TEST_CASE(test30, base_fixture_t) +{ + UnicodeString Instructions = L"Using keyboard authentication.\x0A\x0A\x0APlease enter your password."; + BOOST_TEST_MESSAGE("Instructions = " << AnsiString(Instructions).c_str()); + UnicodeString Instructions2 = ReplaceStrAll(Instructions, L"\x0D\x0A", L"\x01"); + Instructions2 = ReplaceStrAll(Instructions, L"\x0A\x0D", L"\x01"); + Instructions2 = ReplaceStrAll(Instructions, L"\x0A", L"\x01"); + Instructions2 = ReplaceStrAll(Instructions, L"\x0D", L"\x01"); + Instructions2 = ReplaceStrAll(Instructions2, L"\x01", L"\x0D\x0A"); + BOOST_TEST_MESSAGE("Instructions2 = " << AnsiString(Instructions2).c_str()); + BOOST_CHECK(wcscmp(Instructions2.c_str(), UnicodeString(L"Using keyboard authentication.\x0D\x0A\x0D\x0A\x0D\x0APlease enter your password.").c_str()) == 0); +} +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE_END() diff --git a/netbox/src/tests/testnetbox_02.cpp b/netbox/src/tests/testnetbox_02.cpp new file mode 100644 index 000000000..debaec049 --- /dev/null +++ b/netbox/src/tests/testnetbox_02.cpp @@ -0,0 +1,604 @@ +//------------------------------------------------------------------------------ +// testnetbox_02.cpp +// Тесты для NetBox +// testnetbox_02 --run_test=netbox/test1 --log_level=all 2>&1 | tee res.txt +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include + +#include "boostdefines.hpp" +#define BOOST_TEST_MODULE "testnetbox_02" +#define BOOST_TEST_MAIN +// #define BOOST_TEST_DYN_LINK +#include + +#include "winstuff.h" +#include "puttyexp.h" +#include "FarUtil.h" + +#include "testutils.h" +#include "TestTexts.h" +#include "Common.h" +#include "FileMasks.h" +#include "WinSCPPlugin.h" +#include "GUITools.h" +#include "GUIConfiguration.h" +#include "TextsCore.h" +#include "FileOperationProgress.h" +#include "HierarchicalStorage.h" +#include "CoreMain.h" + +using namespace boost::unit_test; + +/******************************************************************************* + test suite +*******************************************************************************/ + +class base_fixture_t : TObject +{ +public: + base_fixture_t() : + TObject(), + OnChangeNotifyEventTriggered(false), + ClickEventHandlerTriggered(false), + onStringListChangeTriggered(false) + { + // BOOST_TEST_MESSAGE("base_fixture_t ctor"); + FarPlugin = CreateStub(); + // CoreInitialize(); + } + + virtual ~base_fixture_t() + { + delete FarPlugin; + FarPlugin = NULL; + // CoreFinalize(); + } + + bool scp_test(std::string host, int port, std::string user, std::string password); +public: + void OnChangeNotifyEvent(TObject * Sender) + { + BOOST_TEST_MESSAGE("OnChangeNotifyEvent triggered"); + OnChangeNotifyEventTriggered = true; + } + void ClickEventHandler(TObject * Sender) + { + BOOST_TEST_MESSAGE("ClickEventHandler triggered"); + ClickEventHandlerTriggered = true; + } + void onStringListChange(TObject * Sender) + { + BOOST_TEST_MESSAGE("onStringListChange triggered"); + onStringListChangeTriggered = true; + } +protected: + bool OnChangeNotifyEventTriggered; + bool ClickEventHandlerTriggered; + bool onStringListChangeTriggered; +}; + +//------------------------------------------------------------------------------ + +bool base_fixture_t::scp_test(std::string host, int port, std::string user, std::string password) +{ + return false; +} + +//------------------------------------------------------------------------------ + +TCustomFarPlugin * CreateFarPlugin(HINSTANCE HInst); + +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE(netbox) + +BOOST_FIXTURE_TEST_CASE(test1, base_fixture_t) +{ + if (1) + { + UnicodeString Text = ::StringOfChar(' ', 4); + BOOST_CHECK_EQUAL(" ", W2MB(Text.c_str()).c_str()); + } + if (1) + { + UnicodeString Message = L"long long long long long long long long long text"; + TStringList MessageLines; + int MaxMessageWidth = 20; + FarWrapText(Message, &MessageLines, MaxMessageWidth); + BOOST_TEST_MESSAGE("MessageLines = " << W2MB(MessageLines.GetText().c_str())); + BOOST_CHECK_EQUAL(4, MessageLines.GetCount()); + BOOST_CHECK_EQUAL("long long long", W2MB(MessageLines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL("long long long", W2MB(MessageLines.GetString(1).c_str()).c_str()); + BOOST_CHECK_EQUAL("long long long", W2MB(MessageLines.GetString(2).c_str()).c_str()); + BOOST_CHECK_EQUAL("text", W2MB(MessageLines.GetString(3).c_str()).c_str()); + } +} + +class TClass1 : TObject +{ +public: + TClass1() : + OnChangeNotifyEventTriggered(false) + { + } + TNotifyEvent & GetOnChange() { return FOnChange; } + void SetOnChange(TNotifyEvent Event) { FOnChange = Event; } + virtual void Changed() + { + if (!FOnChange.empty()) + { + FOnChange(this); + OnChangeNotifyEventTriggered = true; + } + } + void Change(const UnicodeString & Str) + { + Changed(); + } + + bool OnChangeNotifyEventTriggered; +private: + TNotifyEvent FOnChange; +}; + +class TClass2; +DEFINE_CALLBACK_TYPE2(TClickEvent, void, TClass2 *, int); + +class TClass2 +{ + +public: + TClass2() : + OnClickTriggered(false), + m_OnClick(NULL) + { + } + + TClickEvent GetOnClick() const { return m_OnClick; } + void SetOnClick(TClickEvent onClick) + { + m_OnClick = onClick; + // DEBUG_PRINTF(L"m_OnClick.num_slots = %d", m_OnClick.num_slots()); + } + void Click() + { + if (m_OnClick) + m_OnClick(this, 1); + OnClickTriggered = true; + } + bool OnClickTriggered; +private: + TClickEvent m_OnClick; +}; + +class TClass3 +{ +public: + TClass3() : + ClickEventHandlerTriggered(false) + { + } + void ClickEventHandler(TClass2 * Sender, int Data) + { + BOOST_TEST_MESSAGE("TClass3: ClickEventHandler triggered"); + ClickEventHandlerTriggered = true; + } +public: + bool ClickEventHandlerTriggered; +}; + +BOOST_FIXTURE_TEST_CASE(test2, base_fixture_t) +{ + if (1) + { + TClass2 cl2; + BOOST_CHECK_EQUAL(false, cl2.OnClickTriggered); + cl2.Click(); + BOOST_CHECK_EQUAL(true, cl2.OnClickTriggered); + } + if (1) + { + TClass2 cl2; + TClass3 cl3; + cl2.SetOnClick(MAKE_CALLBACK(TClass3::ClickEventHandler, &cl3)); + BOOST_CHECK(!cl2.GetOnClick().empty()); + cl2.Click(); + BOOST_CHECK_EQUAL(true, cl2.OnClickTriggered); + BOOST_CHECK_EQUAL(true, cl3.ClickEventHandlerTriggered); + } +} + +BOOST_FIXTURE_TEST_CASE(test3, base_fixture_t) +{ + if (1) + { + TClass1 cl1; + BOOST_CHECK_EQUAL(false, cl1.OnChangeNotifyEventTriggered); + cl1.SetOnChange(MAKE_CALLBACK(base_fixture_t::OnChangeNotifyEvent, this)); + cl1.Change(L"line 1"); + BOOST_CHECK_EQUAL(true, cl1.OnChangeNotifyEventTriggered); + } +} + +BOOST_FIXTURE_TEST_CASE(test4, base_fixture_t) +{ + if (1) + { + TStringList strings; + strings.SetOnChange(MAKE_CALLBACK(base_fixture_t::onStringListChange, this)); + strings.Add(L"line 1"); + // BOOST_CHECK_EQUAL(true, OnChangeNotifyEventTriggered); + BOOST_CHECK_EQUAL(true, onStringListChangeTriggered); + } +} + +BOOST_FIXTURE_TEST_CASE(test5, base_fixture_t) +{ + if (1) + { + TFileOperationProgressType OperationProgress; + } +} + +BOOST_FIXTURE_TEST_CASE(test6, base_fixture_t) +{ + BOOST_CHECK_THROW(Error(SListIndexError, 0), ExtException); +} + +BOOST_FIXTURE_TEST_CASE(test7, base_fixture_t) +{ + TStringList Lines; + Lines.SetSorted(true); + if (1) + { + Lines.SetDuplicates(dupAccept); + Lines.Add(L"aaa"); + Lines.Add(L"aaa"); + Lines.Add(L"bbb"); + BOOST_CHECK(3 == Lines.GetCount()); + BOOST_CHECK(0 == Lines.IndexOf(L"aaa")); + BOOST_CHECK(2 == Lines.IndexOf(L"bbb")); + } + Lines.Clear(); + if (1) + { + Lines.SetDuplicates(dupIgnore); + Lines.Add(L"aaa"); + Lines.Add(L"aaa"); + Lines.Add(L"bbb"); + BOOST_CHECK(2 == Lines.GetCount()); + BOOST_CHECK(1 == Lines.IndexOf(L"bbb")); + } + Lines.Clear(); + if (1) + { + Lines.SetDuplicates(dupError); + Lines.Add(L"aaa"); + Lines.Add(L"bbb"); + BOOST_CHECK_THROW(Lines.Add(L"aaa"), std::exception); + } +} + +BOOST_FIXTURE_TEST_CASE(test8, base_fixture_t) +{ + UnicodeString RootKey = L"Software\\Michael Lukashov\\TestNetBox"; + TRegistryStorage Storage(RootKey); + Storage.SetAccessMode(smReadWrite); + BOOST_CHECK(Storage.OpenRootKey(true)); + UnicodeString SubKey = L"SubKey1"; + Storage.DeleteSubKey(SubKey); + BOOST_CHECK(!Storage.KeyExists(SubKey)); + BOOST_CHECK(Storage.OpenSubKey(SubKey, true)); + Storage.SetAccessMode(smReadWrite); + Storage.WriteInteger(L"IntVal", 1234); + // BOOST_TEST_MESSAGE("Storage.GetFailed = " << Storage.GetFailed()); + Storage.CloseSubKey(); + BOOST_CHECK(Storage.KeyExists(SubKey)); + BOOST_CHECK(Storage.OpenSubKey(SubKey, false)); + int res = Storage.ReadInteger(L"IntVal", -1); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(1234 == res); +} + +BOOST_FIXTURE_TEST_CASE(test9, base_fixture_t) +{ + UnicodeString path = L"C:\\test"; + AppendPathDelimiterW(path); + BOOST_CHECK(path == L"C:\\test\\"); +} + +BOOST_FIXTURE_TEST_CASE(test10, base_fixture_t) +{ + Config cfg; // = new Config(); + memset(&cfg, 0, sizeof(cfg)); + cfg.logtype = LGTYP_ASCII; + void * ctx = log_init(NULL, &cfg); + // strcpy(&ctx->currlogfilename.path, "putty.log"); + logfopen(ctx); + log_eventlog(ctx, "test2: start"); + + char buf[256]; + struct tm tm = ltime(); + time_t t = time(0); +#if 0 + char buf2[256]; + _snprintf(buf2, sizeof(buf2) - 1, "%04d.%02d.%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + strftime2(buf, sizeof(buf) - 1, "%Y.%m.%d %H:%M:%S", &tm); + BOOST_TEST_MESSAGE("buf = " << buf); // << ", sizeof(buf) = " << sizeof(buf)); + BOOST_TEST_MESSAGE("buf2 = " << buf2); + BOOST_CHECK(0 == strcmp(buf, buf2)); + log_eventlog(ctx, "test2: end"); + logfclose(ctx); + log_free(ctx); +#endif +} + +BOOST_FIXTURE_TEST_CASE(test11, base_fixture_t) +{ + // Тесты на ::FmtLoadStr FMTLOAD ::Format ::LoadStr ::LoadStrPart ::CutToChar ::TrimLeft ::TrimRight + { + UnicodeString str = FMTLOAD(CONST_TEST_STRING, L"lalala", 42); + // BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + // BOOST_TEST_MESSAGE("length = " << str.size()); + BOOST_CHECK(W2MB(str.c_str()) == "test string: \"lalala\" 42"); + } + { + UnicodeString str2 = FMTLOAD(CONST_TEST_STRING, L"lalala", 42); + // BOOST_TEST_MESSAGE("str2 = " << W2MB(str2.c_str())); + BOOST_CHECK(W2MB(str2.c_str()) == "test string: \"lalala\" 42"); + } + { + UnicodeString str2 = ::FORMAT("test: %s %d", L"lalala", 42); + BOOST_TEST_MESSAGE("str2 = " << W2MB(str2.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str2.c_str(), L"test: lalala 42")); + } + { + UnicodeString str3 = FORMAT("test: %s %d", L"lalala", 42); + BOOST_TEST_MESSAGE("str3 = " << W2MB(str3.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str3.c_str(), L"test: lalala 42")); + } + { + UnicodeString str = ::TrimLeft(L""); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"")); + } + { + UnicodeString str = ::TrimLeft(L"1"); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"1")); + } + { + UnicodeString str = ::TrimLeft(L" 1"); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"1")); + } + { + UnicodeString str = ::TrimRight(L""); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"")); + } + { + UnicodeString str = ::TrimRight(L"1"); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"1")); + } + { + UnicodeString str = ::TrimRight(L"1 "); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"1")); + } + { + // UnicodeString CutToChar(UnicodeString &Str, char Ch, bool Trim) + UnicodeString Str1 = L" part 1 | part 2 "; + UnicodeString str1 = ::CutToChar(Str1, '|', false); + BOOST_TEST_MESSAGE("str1 = '" << W2MB(str1.c_str()) << "'"); + BOOST_TEST_MESSAGE("Str1 = '" << W2MB(Str1.c_str()) << "'"); + // BOOST_TEST_MESSAGE("Str1 = '" << W2MB(Str1.c_str()) << "'"); + // DEBUG_PRINTF(L"str1 = \"%s\"", str1.c_str()); + BOOST_CHECK_EQUAL(0, wcscmp(str1.c_str(), L" part 1 ")); + + UnicodeString str2 = ::CutToChar(Str1, '|', true); + BOOST_TEST_MESSAGE("str2 = '" << W2MB(str2.c_str()) << "'"); + BOOST_TEST_MESSAGE("Str1 = '" << W2MB(Str1.c_str()) << "'"); + BOOST_CHECK_EQUAL(0, wcscmp(str2.c_str(), L" part 2")); + } + { + UnicodeString str = ::LoadStr(CONST_TEST_STRING); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"test string: \"%s\" %d")); + } + { + UnicodeString str = ::LoadStrPart(CONST_TEST_STRING2, 1); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"test string part 1")); + } + { + UnicodeString str = ::LoadStrPart(CONST_TEST_STRING2, 2); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + BOOST_CHECK_EQUAL(0, wcscmp(str.c_str(), L"part 2")); + } +} + +BOOST_FIXTURE_TEST_CASE(test12, base_fixture_t) +{ + std::string host = "localhost"; + int port = 2222; + std::string user = "testuser"; + std::string password = "testpassword"; + TEST_CASE_TODO(scp_test(host, port, user, password)); +} + +class TBaseClass1 +{ +public: + virtual ~TBaseClass1() + {} +}; + +class TDerivedClass1 : public TBaseClass1 +{ +}; + +class TBaseClass2 +{ +public: + virtual ~TBaseClass2() + {} +}; + +BOOST_FIXTURE_TEST_CASE(test13, base_fixture_t) +{ + TBaseClass1 E1; + TDerivedClass1 E2; + TBaseClass2 E3; + // typedef boost::is_base_of::type t1; +// BOOST_CHECK((::InheritsFrom(&E1))); +// BOOST_CHECK((::InheritsFrom(&E2))); + // BOOST_CHECK(!(::InheritsFrom(&E2))); + // BOOST_CHECK(!(::InheritsFrom(E1))); + // BOOST_CHECK(!(::InheritsFrom(E3))); +} + +BOOST_FIXTURE_TEST_CASE(test14, base_fixture_t) +{ + { + UnicodeString str = ::StringReplace(L"AA", L"A", L"B", TReplaceFlags() << rfReplaceAll); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "BB"); + } + { + UnicodeString str = ::AnsiReplaceStr(L"AA", L"A", L"B"); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "BB"); + } + { + UnicodeString str = L"ABC"; + BOOST_CHECK_EQUAL(::Pos(str, L"DEF"), 0); + BOOST_CHECK_EQUAL(::Pos(str, L"AB"), 1); + BOOST_CHECK_EQUAL(::Pos(str, L"BC"), 2); + BOOST_CHECK_EQUAL(::AnsiPos(str, 'D'), 0); + BOOST_CHECK_EQUAL(::AnsiPos(str, 'A'), 1); + BOOST_CHECK_EQUAL(::AnsiPos(str, 'B'), 2); + } + { + UnicodeString str = ::LowerCase(L"AA"); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "aa"); + } + { + UnicodeString str = ::UpperCase(L"aa"); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "AA"); + } + { + UnicodeString str = ::Trim(L" aa "); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "aa"); + } +} + +BOOST_FIXTURE_TEST_CASE(test15, base_fixture_t) +{ + if (1) + { + BOOST_CHECK_EQUAL(true, TFileMasks::IsMask(L"*.txt;*.log;*.exe,*.cmd|*.bat")); + // BOOST_CHECK_EQUAL(true, TFileMasks::IsAnyMask(L"*.*")); + TFileMasks m(L"*.txt;*.log"); + BOOST_CHECK_EQUAL(false, m.Matches(L"test.exe")); + } + { + TFileMasks m(L"*.txt;*.log"); + BOOST_CHECK_EQUAL(true, m.Matches(L"test.txt")); + } + if (1) + { + TFileMasks m(L"*.txt;*.log"); + BOOST_CHECK_EQUAL(true, m.Matches(L"test.log")); + + intptr_t Start, Length; + BOOST_CHECK_EQUAL(true, m.GetIsValid(Start, Length)); + m.SetMask(L"*.exe"); + BOOST_CHECK_EQUAL(true, m.Matches(L"test.exe")); + BOOST_CHECK_EQUAL(false, m.Matches(L"test.txt")); + BOOST_CHECK_EQUAL(false, m.Matches(L"test.log")); + } +} + +BOOST_FIXTURE_TEST_CASE(test16, base_fixture_t) +{ + if (1) + { + HINSTANCE HInst = GetModuleHandle(0); + TWinSCPPlugin * FarPlugin = new TWinSCPPlugin(HInst); + //DEBUG_PRINTF(L"FarPlugin = %x", FarPlugin); + BOOST_CHECK(FarPlugin != NULL); + // SAFE_DESTROY(FarPlugin); + delete FarPlugin; + // BOOST_CHECK(FarPlugin == NULL); + } +} + +BOOST_FIXTURE_TEST_CASE(test17, base_fixture_t) +{ + if (1) + { + HINSTANCE HInst = GetModuleHandle(0); + TCustomFarPlugin * FarPlugin = CreateFarPlugin(HInst); + //DEBUG_PRINTF(L"FarPlugin = %x", FarPlugin); + BOOST_CHECK(FarPlugin != NULL); + // SAFE_DESTROY(FarPlugin); + delete FarPlugin; + // BOOST_CHECK(FarPlugin == NULL); + } +} + +BOOST_FIXTURE_TEST_CASE(test18, base_fixture_t) +{ + TGUICopyParamType DefaultCopyParam; + TCopyParamType * CopyParam = new TCopyParamType(DefaultCopyParam); + CopyParam->SetTransferMode(tmAscii); + TCopyParamList CopyParamList; + // BOOST_TEST_MESSAGE("CopyParamList.GetCount() = " << CopyParamList.GetCount()); + CopyParamList.Add(LoadStr(COPY_PARAM_PRESET_ASCII), CopyParam, NULL); + // BOOST_TEST_MESSAGE("CopyParamList.GetCount() = " << CopyParamList.GetCount()); + CopyParam = new TCopyParamType(DefaultCopyParam); + CopyParam->SetTransferMode(tmAscii); + CopyParamList.Add(LoadStr(COPY_PARAM_PRESET_BINARY), CopyParam, NULL); + // BOOST_TEST_MESSAGE("CopyParamList.GetCount() = " << CopyParamList.GetCount()); +} + +BOOST_FIXTURE_TEST_CASE(test19, base_fixture_t) +{ + UnicodeString ProgramsFolder; + ::SpecialFolderLocation(CSIDL_PROGRAM_FILES, ProgramsFolder); + BOOST_TEST_MESSAGE("ProgramsFolder = " << W2MB(ProgramsFolder.c_str()).c_str()); + BOOST_CHECK(ProgramsFolder.Length() > 0); +} + +// BOOST_FIXTURE_TEST_CASE(test20, base_fixture_t) +// { +// random_ref(); +// random_unref(); +// } + +BOOST_FIXTURE_TEST_CASE(test21, base_fixture_t) +{ + BOOST_TEST_MESSAGE("RAND_MAX = " << RAND_MAX); + for (int i = 0; i < 10; i++) + { + BOOST_TEST_MESSAGE("rand() = " << rand()); + BOOST_TEST_MESSAGE("random(256) = " << random(256)); + } + UnicodeString enc = ::EncryptPassword(L"1234ABC", L"234556"); + BOOST_TEST_MESSAGE("enc = " << W2MB(enc.c_str()).c_str()); + UnicodeString dec = ::DecryptPassword(enc, L"234556"); + BOOST_TEST_MESSAGE("dec = " << W2MB(dec.c_str()).c_str()); + BOOST_CHECK(dec == L"1234ABC"); +} + +BOOST_FIXTURE_TEST_CASE(test22, base_fixture_t) +{ + // FarPlugin->RunTests(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/netbox/src/tests/testnetbox_02.rc b/netbox/src/tests/testnetbox_02.rc new file mode 100644 index 000000000..848f87b12 --- /dev/null +++ b/netbox/src/tests/testnetbox_02.rc @@ -0,0 +1,7 @@ +#include "TestTexts.h" + +STRINGTABLE +BEGIN + CONST_TEST_STRING, "test string: ""%s"" %d" + CONST_TEST_STRING2, "test string part 1|part 2" +END diff --git a/netbox/src/tests/testnetbox_03.cpp b/netbox/src/tests/testnetbox_03.cpp new file mode 100644 index 000000000..88c532a1d --- /dev/null +++ b/netbox/src/tests/testnetbox_03.cpp @@ -0,0 +1,645 @@ +//------------------------------------------------------------------------------ +// testnetbox_03.cpp +// Тесты для NetBox +// testnetbox_03 --run_test=netbox/test1 --log_level=all 2>&1 | tee res.txt +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include + +#include "boostdefines.hpp" +#define BOOST_TEST_MODULE "testnetbox_03" +#define BOOST_TEST_MAIN +// #define BOOST_TEST_DYN_LINK +#include + +#include + +#include "TestTexts.h" +#include "Common.h" +#include "FarPlugin.h" +#include "testutils.h" +#include "Bookmarks.h" + +using namespace boost::unit_test; + +//------------------------------------------------------------------------------ +// stub +// TCustomFarPlugin *FarPlugin = NULL; +//------------------------------------------------------------------------------ + +/******************************************************************************* + test suite +*******************************************************************************/ + +class base_fixture_t +{ +public: + base_fixture_t() + { + // BOOST_TEST_MESSAGE("base_fixture_t ctor"); + InitPlatformId(); + } + + virtual ~base_fixture_t() + { + } +public: +protected: +}; + +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE(netbox) + +BOOST_FIXTURE_TEST_CASE(test1, base_fixture_t) +{ + TList list; + BOOST_CHECK_EQUAL(0, list.GetCount()); + TObject obj1; + TObject obj2; + if (1) + { + list.Add(&obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + list.Add(&obj2); + BOOST_CHECK_EQUAL(2, list.GetCount()); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj2)); + } + list.Clear(); + if (1) + { + BOOST_CHECK_EQUAL(0, list.GetCount()); + list.Insert(0, &obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + list.Insert(0, &obj2); + BOOST_CHECK_EQUAL(2, list.GetCount()); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + } + if (1) + { + list.Delete(1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + BOOST_CHECK_EQUAL(-1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + BOOST_CHECK_EQUAL(1, list.Add(&obj1)); + list.Delete(0); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.Remove(&obj1)); + BOOST_CHECK_EQUAL(-1, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(0, list.GetCount()); + } + if (1) + { + list.Add(&obj1); + list.Add(&obj2); + list.Extract(&obj1); + BOOST_CHECK_EQUAL(1, list.GetCount()); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj2)); + list.Add(&obj1); + BOOST_CHECK_EQUAL(2, list.GetCount()); + list.Move(0, 1); + BOOST_CHECK_EQUAL(0, list.IndexOf(&obj1)); + BOOST_CHECK_EQUAL(1, list.IndexOf(&obj2)); + } +} + +BOOST_FIXTURE_TEST_CASE(test2, base_fixture_t) +{ + UnicodeString str; + if (1) + { + TStringList strings; + BOOST_CHECK_EQUAL(0, strings.GetCount()); + BOOST_CHECK_EQUAL(0, strings.Add(L"line 1")); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + str = strings.GetString(0); + // DEBUG_PRINTF(L"str = %s", str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 1"); + strings.SetString(0, L"line 0"); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + str = strings.GetString(0); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 0"); + strings.SetString(0, L"line 00"); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + BOOST_CHECK_EQUAL(W2MB(strings.GetString(0).c_str()), "line 00"); + strings.Add(L"line 11"); + BOOST_CHECK_EQUAL(2, strings.GetCount()); + BOOST_CHECK_EQUAL(W2MB(strings.GetString(1).c_str()), "line 11"); + strings.Delete(1); + BOOST_CHECK_EQUAL(1, strings.GetCount()); + } + TStringList strings; + if (1) + { + BOOST_CHECK_EQUAL(0, strings.GetCount()); + strings.Add(L"line 1"); + str = strings.GetText(); + DEBUG_PRINTF(L"str = '%s'", str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "line 1\r\n"); + } + if (1) + { + strings.Add(L"line 2"); + BOOST_CHECK_EQUAL(2, strings.GetCount()); + str = strings.GetText(); + // DEBUG_PRINTF(L"str = %s", str.c_str()); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "line 1\r\nline 2\r\n"); + strings.Insert(0, L"line 0"); + BOOST_CHECK_EQUAL(3, strings.GetCount()); + str = strings.GetText(); + BOOST_CHECK_EQUAL(W2MB(str.c_str()).c_str(), "line 0\r\nline 1\r\nline 2\r\n"); + strings.SetObj(0, NULL); + UnicodeString str = strings.GetString(0); + BOOST_CHECK_EQUAL(W2MB(str.c_str()), "line 0"); + } +} + +BOOST_FIXTURE_TEST_CASE(test3, base_fixture_t) +{ + UnicodeString Text = L"text text text text text1\ntext text text text text2\n"; + TStringList Lines; + Lines.SetText(Text); + BOOST_CHECK_EQUAL(2, Lines.GetCount()); + BOOST_TEST_MESSAGE("Lines 0 = " << W2MB(Lines.GetString(0).c_str())); + BOOST_TEST_MESSAGE("Lines 1 = " << W2MB(Lines.GetString(1).c_str())); + BOOST_CHECK_EQUAL("text text text text text1", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL("text text text text text2", W2MB(Lines.GetString(1).c_str()).c_str()); +} + +BOOST_FIXTURE_TEST_CASE(test4, base_fixture_t) +{ + UnicodeString Text = L"text, text text, text text1\ntext text text, text text2\n"; + TStringList Lines; + Lines.SetCommaText(Text); + BOOST_CHECK_EQUAL(5, Lines.GetCount()); + BOOST_CHECK_EQUAL("text", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL(" text text", W2MB(Lines.GetString(1).c_str()).c_str()); + BOOST_CHECK_EQUAL(" text text1", W2MB(Lines.GetString(2).c_str()).c_str()); + BOOST_CHECK_EQUAL("text text text", W2MB(Lines.GetString(3).c_str()).c_str()); + BOOST_CHECK_EQUAL(" text text2", W2MB(Lines.GetString(4).c_str()).c_str()); + UnicodeString Text2 = Lines.GetCommaText(); + BOOST_TEST_MESSAGE("Text2 = " << W2MB(Text2.c_str())); + BOOST_CHECK_EQUAL("\"text\",\" text text\",\" text text1\",\"text text text\",\" text text2\"", W2MB(Text2.c_str()).c_str()); +} + +BOOST_FIXTURE_TEST_CASE(test5, base_fixture_t) +{ + TStringList Lines; + TObject obj1; + Lines.InsertObject(0, L"line 1", &obj1); + BOOST_CHECK(&obj1 == Lines.GetObjject(0)); +} + +BOOST_FIXTURE_TEST_CASE(test6, base_fixture_t) +{ + TStringList Lines; + Lines.Add(L"bbb"); + Lines.Add(L"aaa"); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.GetText().c_str()).c_str()); + { + Lines.SetSorted(true); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.GetText().c_str()).c_str()); + BOOST_CHECK_EQUAL("aaa", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL(2, Lines.GetCount()); + } + { + Lines.SetSorted(false); + Lines.Add(L"Aaa"); + Lines.SetCaseSensitive(true); + Lines.SetSorted(true); + BOOST_CHECK_EQUAL(3, Lines.GetCount()); + // BOOST_TEST_MESSAGE("Lines = " << W2MB(Lines.GetText().c_str()).c_str()); + BOOST_CHECK_EQUAL("aaa", W2MB(Lines.GetString(0).c_str()).c_str()); + BOOST_CHECK_EQUAL("Aaa", W2MB(Lines.GetString(1).c_str()).c_str()); + BOOST_CHECK_EQUAL("bbb", W2MB(Lines.GetString(2).c_str()).c_str()); + } +} + +BOOST_FIXTURE_TEST_CASE(test7, base_fixture_t) +{ + TStringList Lines; + { + Lines.Add(L"bbb"); + BOOST_TEST_MESSAGE("before try"); + try + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + BOOST_CHECK(1 == Lines.GetCount()); + } BOOST_SCOPE_EXIT_END + // throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + catch (...) + { + BOOST_TEST_MESSAGE("in catch(...) block"); + } + BOOST_TEST_MESSAGE("after try"); + Lines.Add(L"aaa"); + BOOST_CHECK(2 == Lines.GetCount()); + } + Lines.Clear(); + Lines.BeginUpdate(); + { + Lines.Add(L"bbb"); + BOOST_TEST_MESSAGE("before block"); + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + BOOST_CHECK(1 == Lines.GetCount()); + Lines.EndUpdate(); + } BOOST_SCOPE_EXIT_END + // throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + BOOST_TEST_MESSAGE("after block"); + Lines.Add(L"aaa"); + BOOST_CHECK(2 == Lines.GetCount()); + } + Lines.Clear(); + int cnt = 0; + TStringList * Lines1 = new TStringList(); + int cnt1 = 0; + TStringList * Lines2 = new TStringList(); + { + Lines.BeginUpdate(); + cnt++; + Lines1->BeginUpdate(); + cnt1++; + Lines2->Add(L"bbb"); + BOOST_TEST_MESSAGE("before block"); + try + { + BOOST_TEST_MESSAGE("before BOOST_SCOPE_EXIT"); + BOOST_SCOPE_EXIT( (&Lines) (&Lines1) (&Lines2) (&cnt) (&cnt1) ) + { + BOOST_TEST_MESSAGE("in BOOST_SCOPE_EXIT"); + Lines.EndUpdate(); + cnt--; + Lines1->EndUpdate(); + cnt1--; + delete Lines1; + Lines1 = NULL; + delete Lines2; + Lines2 = NULL; + } BOOST_SCOPE_EXIT_END + BOOST_CHECK(1 == cnt); + BOOST_CHECK(1 == cnt1); + BOOST_CHECK(1 == Lines2->GetCount()); + throw std::exception(""); + BOOST_TEST_MESSAGE("after BOOST_SCOPE_EXIT_END"); + } + catch (const std::exception & ex) + { + BOOST_TEST_MESSAGE("in catch block"); + BOOST_CHECK(NULL == Lines1); + BOOST_CHECK(NULL == Lines2); + } + BOOST_TEST_MESSAGE("after block"); + BOOST_CHECK(0 == cnt); + BOOST_CHECK(0 == cnt1); + BOOST_CHECK(NULL == Lines1); + BOOST_CHECK(NULL == Lines2); + } +} + +BOOST_FIXTURE_TEST_CASE(test8, base_fixture_t) +{ + UnicodeString ProgramsFolder; + UnicodeString DefaultPuttyPathOnly = ::IncludeTrailingBackslash(ProgramsFolder) + L"PuTTY\\putty.exe"; + BOOST_CHECK(DefaultPuttyPathOnly == L"\\PuTTY\\putty.exe"); + BOOST_CHECK(L"" == ::ExcludeTrailingBackslash(::IncludeTrailingBackslash(ProgramsFolder))); +} + +BOOST_FIXTURE_TEST_CASE(test9, base_fixture_t) +{ + UnicodeString Folder = L"C:\\Program Files\\Putty"; + BOOST_TEST_MESSAGE("ExtractFileDir = " << W2MB(::ExtractFileDir(Folder).c_str()).c_str()); + BOOST_CHECK(L"C:\\Program Files\\" == ::ExtractFileDir(Folder)); + BOOST_CHECK(L"C:\\Program Files\\" == ::ExtractFilePath(Folder)); + BOOST_TEST_MESSAGE("GetCurrentDir = " << W2MB(::GetCurrentDir().c_str()).c_str()); + BOOST_CHECK(::GetCurrentDir().Length() > 0); + BOOST_CHECK(::DirectoryExists(::GetCurrentDir())); +} + +BOOST_FIXTURE_TEST_CASE(test10, base_fixture_t) +{ + TDateTime dt1(23, 58, 59, 102); + BOOST_TEST_MESSAGE("dt1 = " << dt1); + BOOST_CHECK(dt1 > 0.0); + unsigned short H, M, S, MS; + dt1.DecodeTime(H, M, S, MS); + BOOST_CHECK_EQUAL(H, 23); + BOOST_CHECK_EQUAL(M, 58); + BOOST_CHECK_EQUAL(S, 59); + BOOST_CHECK_EQUAL(MS, 102); +} + +BOOST_FIXTURE_TEST_CASE(test11, base_fixture_t) +{ + TDateTime dt1 = EncodeDateVerbose(2009, 12, 29); + BOOST_TEST_MESSAGE("dt1 = " << dt1); +#if 0 + bg::date::ymd_type ymd(2009, 12, 29); + BOOST_TEST_MESSAGE("ymd.year = " << ymd.year << ", ymd.month = " << ymd.month << ", ymd.day = " << ymd.day); + unsigned int Y, M, D; + dt1.DecodeDate(Y, M, D); + BOOST_TEST_MESSAGE("Y = " << Y << ", M = " << M << ", D = " << D); + BOOST_CHECK(Y == ymd.year); + BOOST_CHECK(M == ymd.month); + BOOST_CHECK(D == ymd.day); +#endif + int DOW = ::DayOfWeek(dt1); + BOOST_CHECK_EQUAL(3, DOW); +} + +BOOST_FIXTURE_TEST_CASE(test12, base_fixture_t) +{ + BOOST_TEST_MESSAGE("Is2000 = " << Is2000()); + BOOST_TEST_MESSAGE("IsWin7 = " << IsWin7()); + BOOST_TEST_MESSAGE("IsExactly2008R2 = " << IsExactly2008R2()); + TDateTime dt = ::EncodeDateVerbose(2009, 12, 29); + FILETIME ft = ::DateTimeToFileTime(dt, dstmWin); + BOOST_TEST_MESSAGE("ft.dwLowDateTime = " << ft.dwLowDateTime); + BOOST_TEST_MESSAGE("ft.dwHighDateTime = " << ft.dwHighDateTime); +} + +BOOST_FIXTURE_TEST_CASE(test13, base_fixture_t) +{ + UnicodeString str_value = ::IntToStr(1234); + BOOST_TEST_MESSAGE("str_value = " << W2MB(str_value.c_str())); + BOOST_CHECK(W2MB(str_value.c_str()) == "1234"); + int int_value = ::StrToInt(L"1234"); + BOOST_TEST_MESSAGE("int_value = " << int_value); + BOOST_CHECK(int_value == 1234); +} + +BOOST_FIXTURE_TEST_CASE(test14, base_fixture_t) +{ + TStringList Strings1; + TStringList Strings2; + Strings1.AddStrings(&Strings2); + BOOST_CHECK(0 == Strings1.GetCount()); + Strings2.Add(L"lalalla"); + Strings1.AddStrings(&Strings2); + BOOST_CHECK(1 == Strings1.GetCount()); + BOOST_CHECK(L"lalalla" == Strings1.GetString(0)); +} + +BOOST_FIXTURE_TEST_CASE(test15, base_fixture_t) +{ + UnicodeString res = ::IntToHex(10, 2); + BOOST_TEST_MESSAGE("res = " << W2MB(res.c_str())); + BOOST_CHECK(res == L"0A"); +} + +BOOST_FIXTURE_TEST_CASE(test16, base_fixture_t) +{ + { + UnicodeString Name1 = L"1"; + UnicodeString Name2 = L"2"; + int res = ::AnsiCompareIC(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + res = ::AnsiCompare(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + } + { + UnicodeString Name1 = L"abc"; + UnicodeString Name2 = L"ABC"; + int res = ::AnsiCompareIC(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res == 0); + res = ::AnsiCompare(Name1, Name2); + BOOST_TEST_MESSAGE("res = " << res); + BOOST_CHECK(res != 0); + } + { + UnicodeString Name1 = L"Unlimited"; + UnicodeString Name2 = L"Unlimited"; + BOOST_CHECK(::AnsiSameText(Name1, Name2)); + } +} + +BOOST_FIXTURE_TEST_CASE(test17, base_fixture_t) +{ + TStringList List1; + List1.SetText(L"123\n456"); + BOOST_CHECK(2 == List1.GetCount()); + BOOST_TEST_MESSAGE("List1.GetString(0) = " << W2MB(List1.GetString(0).c_str())); + BOOST_CHECK("123" == W2MB(List1.GetString(0).c_str())); + BOOST_TEST_MESSAGE("List1.GetString(1) = " << W2MB(List1.GetString(1).c_str())); + BOOST_CHECK("456" == W2MB(List1.GetString(1).c_str())); + List1.Move(0, 1); + BOOST_TEST_MESSAGE("List1.GetString(0) = " << W2MB(List1.GetString(0).c_str())); + BOOST_CHECK("456" == W2MB(List1.GetString(0).c_str())); + BOOST_TEST_MESSAGE("List1.GetString(1) = " << W2MB(List1.GetString(1).c_str())); + BOOST_CHECK("123" == W2MB(List1.GetString(1).c_str())); +} + +BOOST_FIXTURE_TEST_CASE(test18, base_fixture_t) +{ + { + UnicodeString Key = L"Interface"; + UnicodeString Res = ::CutToChar(Key, L'\\', false); + BOOST_CHECK(Key.IsEmpty()); + BOOST_CHECK("Interface" == W2MB(Res.c_str())); + } + { + UnicodeString Key = L"Interface\\SubKey"; + UnicodeString Res = ::CutToChar(Key, L'\\', false); + BOOST_CHECK("SubKey" == W2MB(Key.c_str())); + BOOST_CHECK("Interface" == W2MB(Res.c_str())); + } +} + +BOOST_FIXTURE_TEST_CASE(test19, base_fixture_t) +{ + TStringList Strings1; + Strings1.Add(L"Name1=Value1"); + BOOST_CHECK(0 == Strings1.IndexOfName(L"Name1")); +} + +BOOST_FIXTURE_TEST_CASE(test20, base_fixture_t) +{ + TDateTime DateTime = Now(); + unsigned short H, M, S, MS; + DateTime.DecodeTime(H, M, S, MS); + // UnicodeString str = ::FormatDateTime(L"HH:MM:SS", DateTime); + UnicodeString str = FORMAT("%02d:%02d:%02d", H, M, S); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + // BOOST_CHECK(str == L"20:20:20"); +} + +BOOST_FIXTURE_TEST_CASE(test21, base_fixture_t) +{ + UnicodeString str = ::FormatFloat(L"#,##0", 23.456); + BOOST_TEST_MESSAGE("str = " << W2MB(str.c_str())); + // BOOST_CHECK(str.c_str() == L"23.46"); + BOOST_CHECK("23.46" == W2MB(str.c_str())); +} + +BOOST_FIXTURE_TEST_CASE(test22, base_fixture_t) +{ + UnicodeString FileName = L"testfile"; + ::DeleteFile(FileName); + std::string str = "test string"; + { + unsigned int CreateAttrs = FILE_ATTRIBUTE_NORMAL; + HANDLE FileHandle = ::CreateFile(FileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, CreateAttrs, 0); + BOOST_CHECK(FileHandle != 0); + TStream * FileStream = new TSafeHandleStream(FileHandle); + TFileBuffer * BlockBuf = new TFileBuffer(); + // BlockBuf->SetSize(1024); + BlockBuf->SetPosition(0); + BlockBuf->Insert(0, str.c_str(), str.size()); + BOOST_TEST_MESSAGE("BlockBuf->GetSize = " << BlockBuf->GetSize()); + BOOST_CHECK(BlockBuf->GetSize() == str.size()); + BlockBuf->WriteToStream(FileStream, BlockBuf->GetSize()); + delete FileStream; FileStream = NULL; + delete BlockBuf; BlockBuf = NULL; + ::CloseHandle(FileHandle); + BOOST_TEST_MESSAGE("FileName1 = " << W2MB(FileName.c_str())); + BOOST_REQUIRE(::FileExists(FileName)); + } + { + BOOST_TEST_MESSAGE("FileName2 = " << W2MB(FileName.c_str())); + // WIN32_FIND_DATA Rec; + // BOOST_CHECK(FileSearchRec(FileName, Rec)); + } + { + HANDLE File = ::CreateFile( + FileName.c_str(), + GENERIC_READ, + 0, // FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, // OPEN_EXISTING, + 0, // FILE_ATTRIBUTE_NORMAL, // 0, + NULL); + DEBUG_PRINTF(L"File = %d", File); + TStream * FileStream = new TSafeHandleStream(File); + TFileBuffer * BlockBuf = new TFileBuffer(); + BlockBuf->ReadStream(FileStream, str.size(), true); + BOOST_TEST_MESSAGE("BlockBuf->GetSize = " << BlockBuf->GetSize()); + BOOST_CHECK(BlockBuf->GetSize() == str.size()); + delete FileStream; FileStream = NULL; + delete BlockBuf; BlockBuf = NULL; + ::CloseHandle(File); + } +} + +BOOST_FIXTURE_TEST_CASE(test23, base_fixture_t) +{ + UnicodeString Dir1 = L"subdir1"; + UnicodeString Dir2 = L"subdir1/subdir2"; + ::RemoveDir(Dir2); + ::RemoveDir(Dir1); + BOOST_TEST_MESSAGE("DirectoryExists(Dir2) = " << DirectoryExists(Dir2)); + BOOST_CHECK(!::DirectoryExists(Dir2)); + ::ForceDirectories(Dir2); + BOOST_CHECK(::DirectoryExists(Dir2)); + ::RemoveDir(Dir2); + ::ForceDirectories(Dir2); + BOOST_CHECK(::DirectoryExists(Dir2)); + BOOST_CHECK(::RecursiveDeleteFile(Dir1, false)); + BOOST_CHECK(!::DirectoryExists(Dir1)); +} + +BOOST_FIXTURE_TEST_CASE(test24, base_fixture_t) +{ + TDateTime now = Now(); + BOOST_TEST_MESSAGE("now = " << (double)now); + BOOST_CHECK(now > 0.0); +} + +BOOST_FIXTURE_TEST_CASE(test25, base_fixture_t) +{ +#if 0 + GC_find_leak = 1; + int * i = (int *)malloc(sizeof(int)); + CHECK_LEAKS(); +#endif +} + +BOOST_FIXTURE_TEST_CASE(test26, base_fixture_t) +{ + TBookmarks Bookmarks; +} + +//------------------------------------------------------------------------------ +class TestPropsClass +{ +private: + std::map FAssignments; + Property FKey; + int GetNumber() { return 42; } + void AddWeight(float value) { } + std::string GetKey() + { + // extra processing steps here + return FKey(); + } + void SetKey(std::string AKey) + { + // extra processing steps here + FKey = AKey; + } + std::string GetAssignment(std::string AKey) + { + // extra processing steps here + return FAssignments[Key]; + } + void SetAssignment(std::string Key, std::string Value) + { + // extra processing steps here + FAssignments[Key] = Value; + } +public: + TestPropsClass() + { + Number(this); + WeightedValue(this); + Key(this); + Assignments(this); + } + Property Name; + Property ID; + ROProperty Number; + WOProperty WeightedValue; + RWProperty Key; + IndexedProperty Assignments; +}; + +BOOST_FIXTURE_TEST_CASE(test27, base_fixture_t) +{ + TestPropsClass obj; + obj.Name = "Name"; + obj.WeightedValue = 1234; + obj.Key = "Key"; + obj.Assignments["Hours"] = "23"; + obj.Assignments["Minutes"] = "59"; + BOOST_TEST_MESSAGE("Name = " << obj.Name.get()); + BOOST_TEST_MESSAGE("Number = " << obj.Number.get()); + BOOST_TEST_MESSAGE("Key = " << obj.Key.get()); + // BOOST_TEST_MESSAGE("Assignments = " << obj.Assignments); + BOOST_TEST_MESSAGE("Hours = " << obj.Assignments["Hours"]); + BOOST_TEST_MESSAGE("Minutes = " << obj.Assignments["Minutes"]); +} + +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE_END() diff --git a/netbox/src/tests/testnetbox_04.cpp b/netbox/src/tests/testnetbox_04.cpp new file mode 100644 index 000000000..5c4f8d220 --- /dev/null +++ b/netbox/src/tests/testnetbox_04.cpp @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +// testnetbox_04.cpp +// testnetbox_04 --run_test=netbox/test1 --log_level=all 2>&1 | tee res.txt +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include + +#include "boostdefines.hpp" +#define BOOST_TEST_MODULE "testnetbox_04" +#define BOOST_TEST_MAIN +#include + +#include +#include + +#include "TestTexts.h" +#include "FarPlugin.h" +#include "testutils.h" + +#include "ne_session.h" +#include "ne_request.h" + +// #include "dl/include.hpp" + +#include "calculator.hpp" +#include "DynamicQueue.hpp" + +using namespace boost::unit_test; + +/******************************************************************************* + test suite +*******************************************************************************/ + +class base_fixture_t +{ +public: + base_fixture_t() + { + InitPlatformId(); + } + virtual ~base_fixture_t() + { + } + +public: +protected: + static int read_block(void * udata, const char * data, size_t len) + { + std::vector *vec = static_cast *>(udata); + for (unsigned int n = 0; n < len; ++n) + vec->push_back((unsigned char)data[n]); + } +}; + +//------------------------------------------------------------------------------ + +BOOST_AUTO_TEST_SUITE(netbox) + +BOOST_FIXTURE_TEST_CASE(test1, base_fixture_t) +{ +#if 0 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); + + ne_session * sess = ne_session_create("http", "farmanager.com", 80); + ne_request * req = ne_request_create(sess, "GET", "/svn/trunk/unicode_far/vbuild.m4"); + std::vector vec; + // ne_add_response_body_reader(req, ne_accept_2xx, read_block, &vec); + int rv = ne_request_dispatch(req); + BOOST_TEST_MESSAGE("rv = " << rv); + if (rv) + { + BOOST_FAIL("Request failed: " << ne_get_error(sess)); + } + BOOST_CHECK(rv == 0); + const ne_status * statstruct = ne_get_status(req); + BOOST_TEST_MESSAGE("statstruct->code = " << statstruct->code); + BOOST_TEST_MESSAGE("statstruct->reason_phrase = " << statstruct->reason_phrase); + if (vec.size() > 0) + { + BOOST_TEST_MESSAGE("response = " << (char *)&vec[0]); + } + ne_request_destroy(req); + ne_session_destroy(sess); + + WSACleanup(); +#endif +} + +/*BOOST_FIXTURE_TEST_CASE(test2, base_fixture_t) +{ + team::calculator calc("calculator_dll.dll"); + BOOST_TEST_MESSAGE("sum = " << calc.sum(10, 20)); + BOOST_TEST_MESSAGE("mul = " << calc.mul(10, 20)); + BOOST_TEST_MESSAGE("sqrt = " << calc.sqrt(25)); +}*/ + +BOOST_FIXTURE_TEST_CASE(test3, base_fixture_t) +{ + DynamicQueue q; + q.Reserve(10); + q.Put(1); + q.Put(2); + q.Put(3); + int val = q.Get(); + BOOST_TEST_MESSAGE("val = " << val); + BOOST_CHECK(val == 1); + val = q.Get(); + BOOST_TEST_MESSAGE("val = " << val); + BOOST_CHECK(val == 2); + val = q.Get(); + BOOST_TEST_MESSAGE("val = " << val); + BOOST_CHECK(val == 3); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/netbox/src/tests/testutils.h b/netbox/src/tests/testutils.h new file mode 100644 index 000000000..a2e2c6eeb --- /dev/null +++ b/netbox/src/tests/testutils.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +#include "boostdefines.hpp" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "FarPlugin.h" +#include "Cryptography.h" +#include "WinSCPSecurity.h" + +//------------------------------------------------------------------------------ + +#define TEST_CASE_TODO(exp) \ + std::cerr << "TODO: " << #exp << std::endl + +//------------------------------------------------------------------------------ +class TStubFarPlugin : public TCustomFarPlugin +{ +public: + explicit TStubFarPlugin() : + TCustomFarPlugin(GetModuleHandle(0)) + { + CryptographyInitialize(); + } + ~TStubFarPlugin() + { + CryptographyFinalize(); + } +protected: + virtual void GetPluginInfoEx(DWORD &Flags, + TStrings *DiskMenuStrings, TStrings *PluginMenuStrings, + TStrings *PluginConfigStrings, TStrings *CommandPrefixes) + { + DEBUG_PRINTF(L"call"); + } + virtual TCustomFarFileSystem * OpenPluginEx(intptr_t OpenFrom, intptr_t Item) + { + DEBUG_PRINTF(L"call"); + return NULL; + } + virtual bool ConfigureEx(intptr_t Item) + { + DEBUG_PRINTF(L"call"); + return false; + } + virtual intptr_t ProcessEditorEventEx(intptr_t Event, void *Param) + { + DEBUG_PRINTF(L"call"); + return -1; + } + virtual intptr_t ProcessEditorInputEx(const INPUT_RECORD *Rec) + { + DEBUG_PRINTF(L"call"); + return -1; + } +}; + +//------------------------------------------------------------------------------ + +static TCustomFarPlugin *CreateStub() +{ + return new TStubFarPlugin(); +} + +//------------------------------------------------------------------------------ + diff --git a/netbox/src/windows/GUIConfiguration.cpp b/netbox/src/windows/GUIConfiguration.cpp new file mode 100644 index 000000000..6370446d7 --- /dev/null +++ b/netbox/src/windows/GUIConfiguration.cpp @@ -0,0 +1,1260 @@ +#include +#pragma hdrstop +#include +#include "GUIConfiguration.h" +#include "GUITools.h" +#include +#include +#include +#include +#include +#ifndef __linux__ +#include +#endif + +const intptr_t ccLocal = ccUser; +const intptr_t ccShowResults = ccUser << 1; +const intptr_t ccCopyResults = ccUser << 2; +const intptr_t ccSet = 0x80000000; + +static const uintptr_t AdditionaLanguageMask = 0xFFFFFF00; +#define ADDITIONAL_LANGUAGE_PREFIX L"XX" + +TGUICopyParamType::TGUICopyParamType() + : TCopyParamType() +{ + GUIDefault(); +} + +TGUICopyParamType::TGUICopyParamType(const TCopyParamType & Source) + : TCopyParamType(Source) +{ + GUIDefault(); +} + +TGUICopyParamType::TGUICopyParamType(const TGUICopyParamType & Source) + : TCopyParamType(Source) +{ + GUIAssign(&Source); +} + +void TGUICopyParamType::Assign(const TCopyParamType * Source) +{ + TCopyParamType::Assign(Source); + + const TGUICopyParamType * GUISource; + GUISource = NB_STATIC_DOWNCAST_CONST(TGUICopyParamType, Source); + if (GUISource != nullptr) + { + GUIAssign(GUISource); + } +} + +void TGUICopyParamType::GUIAssign(const TGUICopyParamType * Source) +{ + SetQueue(Source->GetQueue()); + SetQueueNoConfirmation(Source->GetQueueNoConfirmation()); + SetQueueIndividually(Source->GetQueueIndividually()); +} + +void TGUICopyParamType::Default() +{ + GUIDefault(); +} + +void TGUICopyParamType::GUIDefault() +{ + TCopyParamType::Default(); + + SetQueue(false); + SetQueueNoConfirmation(true); + SetQueueIndividually(false); +} + +void TGUICopyParamType::Load(THierarchicalStorage * Storage) +{ + TCopyParamType::Load(Storage); + + SetQueue(Storage->ReadBool("Queue", GetQueue())); + SetQueueNoConfirmation(Storage->ReadBool("QueueNoConfirmation", GetQueueNoConfirmation())); + SetQueueIndividually(Storage->ReadBool("QueueIndividually", GetQueueIndividually())); +} + +void TGUICopyParamType::Save(THierarchicalStorage * Storage) +{ + TCopyParamType::Save(Storage); + + Storage->WriteBool("Queue", GetQueue()); + Storage->WriteBool("QueueNoConfirmation", GetQueueNoConfirmation()); + Storage->WriteBool("QueueIndividually", GetQueueIndividually()); +} + +TGUICopyParamType & TGUICopyParamType::operator =(const TCopyParamType & rhp) +{ + Assign(&rhp); + return *this; +} + +TGUICopyParamType & TGUICopyParamType::operator =(const TGUICopyParamType & rhp) +{ + Assign(&rhp); + return *this; +} + +void TCopyParamRuleData::Default() +{ + HostName.Clear(); + UserName.Clear(); + RemoteDirectory.Clear(); + LocalDirectory.Clear(); +} + +TCopyParamRule::TCopyParamRule() +{ +} + +TCopyParamRule::TCopyParamRule(const TCopyParamRuleData & Data) : + FData(Data) +{ +} + +TCopyParamRule::TCopyParamRule(const TCopyParamRule & Source) +{ + FData.HostName = Source.FData.HostName; + FData.UserName = Source.FData.UserName; + FData.RemoteDirectory = Source.FData.RemoteDirectory; + FData.LocalDirectory = Source.FData.LocalDirectory; +} + +#define C(Property) (Property == rhp.Property) +bool TCopyParamRule::operator==(const TCopyParamRule & rhp) const +{ + return + C(FData.HostName) && + C(FData.UserName) && + C(FData.RemoteDirectory) && + C(FData.LocalDirectory) && + true; +} +#undef C + +bool TCopyParamRule::Match(const UnicodeString & Mask, + const UnicodeString & Value, bool Path, bool Local) const +{ + bool Result; + if (Mask.IsEmpty()) + { + Result = true; + } + else + { + TFileMasks M(Mask); + if (Path) + { + Result = M.Matches(Value, Local, true); + } + else + { + Result = M.Matches(Value, false); + } + } + return Result; +} + +bool TCopyParamRule::Matches(const TCopyParamRuleData & Value) const +{ + return + Match(FData.HostName, Value.HostName, false) && + Match(FData.UserName, Value.UserName, false) && + Match(FData.RemoteDirectory, Value.RemoteDirectory, true, false) && + Match(FData.LocalDirectory, Value.LocalDirectory, true, true); +} + +void TCopyParamRule::Load(THierarchicalStorage * Storage) +{ + FData.HostName = Storage->ReadString("HostName", FData.HostName); + FData.UserName = Storage->ReadString("UserName", FData.UserName); + FData.RemoteDirectory = Storage->ReadString("RemoteDirectory", FData.RemoteDirectory); + FData.LocalDirectory = Storage->ReadString("LocalDirectory", FData.LocalDirectory); +} + +void TCopyParamRule::Save(THierarchicalStorage * Storage) const +{ + Storage->WriteString("HostName", FData.HostName); + Storage->WriteString("UserName", FData.UserName); + Storage->WriteString("RemoteDirectory", FData.RemoteDirectory); + Storage->WriteString("LocalDirectory", FData.LocalDirectory); +} + +bool TCopyParamRule::GetEmpty() const +{ + return + FData.HostName.IsEmpty() && + FData.UserName.IsEmpty() && + FData.RemoteDirectory.IsEmpty() && + FData.LocalDirectory.IsEmpty(); +} + +TCopyParamRule & TCopyParamRule::operator=(const TCopyParamRule & other) +{ + SetData(other.FData); + return *this; +} + +UnicodeString TCopyParamRule::GetInfoStr(const UnicodeString & Separator) const +{ + UnicodeString Result; + #define ADD(FMT, ELEM) \ + if (!FData.ELEM.IsEmpty()) \ + Result += (Result.IsEmpty() ? UnicodeString() : Separator) + FMTLOAD(FMT, FData.ELEM.c_str()); + ADD(COPY_RULE_HOSTNAME, HostName); + ADD(COPY_RULE_USERNAME, UserName); + ADD(COPY_RULE_REMOTE_DIR, RemoteDirectory); + ADD(COPY_RULE_LOCAL_DIR, LocalDirectory); + #undef ADD + return Result; +} + +void TCopyParamRule::SetData(const TCopyParamRuleData & Value) +{ + FData = Value; +} + +TCopyParamList::TCopyParamList() : + FRules(new TList()), + FCopyParams(new TList()), + FNames(new TStringList()), + FNameList(nullptr), + FModified(false) +{ +} + +TCopyParamList::TCopyParamList(const TCopyParamList & other) : + FRules(new TList()), + FCopyParams(new TList()), + FNames(new TStringList()), + FNameList(nullptr), + FModified(false) +{ + this->operator=(other); +} + +TCopyParamList::~TCopyParamList() +{ + Clear(); + SAFE_DESTROY(FCopyParams); + SAFE_DESTROY(FRules); + SAFE_DESTROY(FNames); + SAFE_DESTROY(FNameList); +} + +void TCopyParamList::Reset() +{ + SAFE_DESTROY(FNameList); + FModified = false; +} + +void TCopyParamList::Modify() +{ + SAFE_DESTROY(FNameList); + FModified = true; +} + +void TCopyParamList::ValidateName(const UnicodeString & Name) +{ + if (Name.LastDelimiter(CONST_INVALID_CHARS) > 0) + { + throw Exception(FMTLOAD(ITEM_NAME_INVALID, Name.c_str(), CONST_INVALID_CHARS)); + } +} + +TCopyParamList & TCopyParamList::operator=(const TCopyParamList & rhl) +{ + Clear(); + + for (intptr_t Index = 0; Index < rhl.GetCount(); ++Index) + { + TCopyParamType * CopyParam = new TCopyParamType(*rhl.GetCopyParam(Index)); + TCopyParamRule * Rule = nullptr; + if (rhl.GetRule(Index) != nullptr) + { + Rule = new TCopyParamRule(*rhl.GetRule(Index)); + } + Add(rhl.GetName(Index), CopyParam, Rule); + } + // there should be comparison of with the assigned list, but we rely on caller + // to do it instead (TGUIConfiguration::SetCopyParamList) + Modify(); + return *this; +} + +bool TCopyParamList::operator==(const TCopyParamList & rhl) const +{ + bool Result = (GetCount() == rhl.GetCount()); + if (Result) + { + intptr_t Index = 0; + while ((Index < GetCount()) && Result) + { + Result = (GetName(Index) == rhl.GetName(Index)) && + CompareItem(Index, rhl.GetCopyParam(Index), rhl.GetRule(Index)); + ++Index; + } + } + return Result; +} + +intptr_t TCopyParamList::IndexOfName(const UnicodeString & Name) const +{ + return FNames->IndexOf(Name.c_str()); +} + +bool TCopyParamList::CompareItem(intptr_t Index, + const TCopyParamType * CopyParam, const TCopyParamRule * Rule) const +{ + return + ((*GetCopyParam(Index)) == *CopyParam) && + ((GetRule(Index) == nullptr) ? + (Rule == nullptr) : + ((Rule != nullptr) && (*GetRule(Index)) == (*Rule))); +} + +void TCopyParamList::Clear() +{ + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + delete GetCopyParam(Index); + delete GetRule(Index); + } + FCopyParams->Clear(); + FRules->Clear(); + FNames->Clear(); +} + +void TCopyParamList::Add(const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule) +{ + Insert(GetCount(), Name, CopyParam, Rule); +} + +void TCopyParamList::Insert(intptr_t Index, const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule) +{ + DebugAssert(FNames->IndexOf(Name) < 0); + FNames->Insert(Index, Name); + DebugAssert(CopyParam != nullptr); + FCopyParams->Insert(Index, CopyParam); + FRules->Insert(Index, Rule); + Modify(); +} + +void TCopyParamList::Change(intptr_t Index, const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule) +{ + if ((Name != GetName(Index)) || !CompareItem(Index, CopyParam, Rule)) + { + FNames->SetString(Index, Name); + delete GetCopyParam(Index); + FCopyParams->SetItem(Index, CopyParam); + delete GetRule(Index); + FRules->SetItem(Index, Rule); + Modify(); + } + else + { + SAFE_DESTROY(CopyParam); + SAFE_DESTROY(Rule); + } +} + +void TCopyParamList::Move(intptr_t CurIndex, intptr_t NewIndex) +{ + if (CurIndex != NewIndex) + { + FNames->Move(CurIndex, NewIndex); + FCopyParams->Move(CurIndex, NewIndex); + FRules->Move(CurIndex, NewIndex); + Modify(); + } +} + +void TCopyParamList::Delete(intptr_t Index) +{ + DebugAssert((Index >= 0) && (Index < GetCount())); + FNames->Delete(Index); + delete GetCopyParam(Index); + FCopyParams->Delete(Index); + delete GetRule(Index); + FRules->Delete(Index); + Modify(); +} + +intptr_t TCopyParamList::Find(const TCopyParamRuleData & Value) const +{ + intptr_t Result = -1; + intptr_t Index = 0; + while ((Index < FRules->GetCount()) && (Result < 0)) + { + if (FRules->GetItem(Index) != nullptr) + { + if (GetRule(Index)->Matches(Value)) + { + Result = Index; + } + } + ++Index; + } + return Result; +} + +void TCopyParamList::Load(THierarchicalStorage * Storage, intptr_t ACount) +{ + for (intptr_t Index = 0; Index < ACount; ++Index) + { + UnicodeString Name = ::IntToStr(Index); + std::unique_ptr Rule; + std::unique_ptr CopyParam(new TCopyParamType()); + if (Storage->OpenSubKey(Name, false)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + Name = Storage->ReadString("Name", Name); + CopyParam->Load(Storage); + + if (Storage->ReadBool("HasRule", false)) + { + Rule.reset(new TCopyParamRule()); + Rule->Load(Storage); + } + } + + FCopyParams->Add(CopyParam.release()); + FRules->Add(Rule.release()); + FNames->Add(Name); + } + Reset(); +} + +void TCopyParamList::Save(THierarchicalStorage * Storage) const +{ + Storage->ClearSubKeys(); + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + if (Storage->OpenSubKey(::IntToStr(Index), true)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + const TCopyParamType * CopyParam = GetCopyParam(Index); + const TCopyParamRule * Rule = GetRule(Index); + + Storage->WriteString("Name", GetName(Index)); + CopyParam->Save(Storage); + Storage->WriteBool("HasRule", (Rule != nullptr)); + if (Rule != nullptr) + { + Rule->Save(Storage); + } + } + } +} + +const TCopyParamRule * TCopyParamList::GetRule(intptr_t Index) const +{ + return NB_STATIC_DOWNCAST(TCopyParamRule, FRules->GetItem(Index)); +} + +const TCopyParamType * TCopyParamList::GetCopyParam(intptr_t Index) const +{ + return NB_STATIC_DOWNCAST(TCopyParamType, FCopyParams->GetItem(Index)); +} + +UnicodeString TCopyParamList::GetName(intptr_t Index) const +{ + return FNames->GetString(Index); +} + +TStrings * TCopyParamList::GetNameList() const +{ + if (FNameList == nullptr) + { + FNameList = new TStringList(); + + for (intptr_t Index = 0; Index < GetCount(); ++Index) + { + FNameList->Add(FNames->GetString(Index)); + } + } + return FNameList; +} + +bool TCopyParamList::GetAnyRule() const +{ + bool Result = false; + intptr_t Index = 0; + while ((Index < GetCount()) && !Result) + { + Result = (GetRule(Index) != nullptr); + ++Index; + } + return Result; +} + +TGUIConfiguration::TGUIConfiguration() : TConfiguration(), + FLocale(0), + FLocales(CreateSortedStringList()), + FLastLocalesExts(L"*"), + FContinueOnError(false), + FConfirmCommandSession(false), + FPuttyPassword(false), + FTelnetForFtpInPutty(false), + FSynchronizeParams(0), + FSynchronizeOptions(0), + FSynchronizeModeAuto(0), + FSynchronizeMode(0), + FMaxWatchDirectories(0), + FQueueAutoPopup(false), + FSessionRememberPassword(false), + FQueueTransfersLimit(0), + FQueueKeepDoneItems(false), + FQueueKeepDoneItemsFor(0), + FBeepOnFinish(false), + FCopyParamList(new TCopyParamList()), + FCopyParamListDefaults(false), + FKeepUpToDateChangeDelay(0), + FSessionReopenAutoIdle(0) +{ + CoreSetResourceModule(0); +} + +TGUIConfiguration::~TGUIConfiguration() +{ + SAFE_DESTROY(FLocales); + SAFE_DESTROY(FCopyParamList); +} + +void TGUIConfiguration::Default() +{ + TConfiguration::Default(); + + // reset before call to DefaultLocalized() + FDefaultCopyParam.Default(); + + FCopyParamListDefaults = true; + DefaultLocalized(); + + FIgnoreCancelBeforeFinish = TDateTime(0, 0, 3, 0); + FContinueOnError = false; + FConfirmCommandSession = true; + FSynchronizeParams = TTerminal::spNoConfirmation | TTerminal::spPreviewChanges; + FSynchronizeModeAuto = -1; + FSynchronizeMode = TTerminal::smRemote; + FMaxWatchDirectories = 500; + FSynchronizeOptions = soRecurse | soSynchronizeAsk; + FQueueTransfersLimit = 2; + FQueueKeepDoneItems = true; + FQueueKeepDoneItemsFor = 15; + FQueueAutoPopup = true; + FSessionRememberPassword = false; + UnicodeString ProgramsFolder; + SpecialFolderLocation(CSIDL_PROGRAM_FILES, ProgramsFolder); + FDefaultPuttyPathOnly = ::IncludeTrailingBackslash(ProgramsFolder) + "PuTTY\\putty.exe"; + FDefaultPuttyPath = FormatCommand("%PROGRAMFILES%\\PuTTY\\putty.exe", L""); + FPuttyPath = FDefaultPuttyPath; + SetPSftpPath(FormatCommand("%PROGRAMFILES%\\PuTTY\\psftp.exe", L"")); + FPuttyPassword = false; + FTelnetForFtpInPutty = true; + FPuttySession = "WinSCP temporary session"; + FBeepOnFinish = false; + FBeepOnFinishAfter = TDateTime(0, 0, 30, 0); + FCopyParamCurrent.Clear(); + FKeepUpToDateChangeDelay = 500; + FChecksumAlg = "md5"; + FSessionReopenAutoIdle = 9000; + + FNewDirectoryProperties.Default(); + FNewDirectoryProperties.Rights = TRights::rfDefault | TRights::rfExec; +} + +void TGUIConfiguration::DefaultLocalized() +{ + if (FCopyParamListDefaults) + { + FCopyParamList->Clear(); + + // guard against "empty resource string" from obsolete translations + // (DefaultLocalized is called for the first time before detection of + // obsolete translations) + if (!LoadStr(COPY_PARAM_PRESET_ASCII).IsEmpty()) + { + TCopyParamType * CopyParam; + + CopyParam = new TCopyParamType(FDefaultCopyParam); + CopyParam->SetTransferMode(tmAscii); + FCopyParamList->Add(LoadStr(COPY_PARAM_PRESET_ASCII), CopyParam, nullptr); + + CopyParam = new TCopyParamType(FDefaultCopyParam); + CopyParam->SetTransferMode(tmBinary); + FCopyParamList->Add(LoadStr(COPY_PARAM_PRESET_BINARY), CopyParam, nullptr); + + CopyParam = new TCopyParamType(FDefaultCopyParam); + CopyParam->GetIncludeFileMask().SetMasks("|*.bak; *.tmp; ~$*; *.wbk; *~; #*; .#*"); + CopyParam->SetNewerOnly(true); + FCopyParamList->Add(LoadStr(COPY_PARAM_NEWER_ONLY), CopyParam, nullptr); + } + + FCopyParamList->Reset(); + } +} + +void TGUIConfiguration::UpdateStaticUsage() +{ + // TConfiguration::UpdateStaticUsage(); + // Usage->Set(L"CopyParamsCount", (FCopyParamListDefaults ? 0 : FCopyParamList->Count)); + // Usage->Set(L"CopyParamsCount", (FCopyParamListDefaults ? 0 : FCopyParamList->GetCount())); +} + +UnicodeString TGUIConfiguration::PropertyToKey(const UnicodeString & Property) +{ + // no longer useful + intptr_t P = Property.LastDelimiter(L".>"); + return Property.SubString(P + 1, Property.Length() - P); +} + +// duplicated from core\configuration.cpp +#undef BLOCK +#define BLOCK(KEY, CANCREATE, BLOCK) \ + if (Storage->OpenSubKey(KEY, CANCREATE, true)) \ + { SCOPE_EXIT { Storage->CloseSubKey(); }; { BLOCK } } +#undef REGCONFIG +#define REGCONFIG(CANCREATE) \ + BLOCK(L"Interface", CANCREATE, \ + KEY(Bool, ContinueOnError); \ + KEY(Bool, ConfirmCommandSession); \ + KEY(Integer, SynchronizeParams); \ + KEY(Integer, SynchronizeOptions); \ + KEY(Integer, SynchronizeModeAuto); \ + KEY(Integer, SynchronizeMode); \ + KEY(Integer, MaxWatchDirectories); \ + KEY(Integer, QueueTransfersLimit); \ + KEY(Integer, QueueKeepDoneItems); \ + KEY(Integer, QueueKeepDoneItemsFor); \ + KEY(Bool, QueueAutoPopup); \ + KEYEX(Bool, QueueRememberPassword, SessionRememberPassword); \ + KEY(String, PuttySession); \ + KEY(String, PuttyPath); \ + KEY(Bool, PuttyPassword); \ + KEY(Bool, TelnetForFtpInPutty); \ + KEY(DateTime, IgnoreCancelBeforeFinish); \ + KEY(Bool, BeepOnFinish); \ + KEY(DateTime, BeepOnFinishAfter); \ + KEY(Integer, KeepUpToDateChangeDelay); \ + KEY(String, ChecksumAlg); \ + KEY(Integer, SessionReopenAutoIdle); \ + ); \ + +void TGUIConfiguration::SaveData(THierarchicalStorage * Storage, bool All) +{ + TConfiguration::SaveData(Storage, All); + + // duplicated from core\configuration.cpp +#ifndef LASTELEM +#define LASTELEM(ELEM) \ + ELEM.SubString(ELEM.LastDelimiter(L".>")+1, ELEM.Length() - ELEM.LastDelimiter(L".>")) +#endif + #undef KEYEX + #define KEYEX(TYPE, NAME, VAR) Storage->Write ## TYPE(LASTELEM(UnicodeString(MB_TEXT(#NAME))), Get ## VAR()) + #undef KEY + #define KEY(TYPE, NAME) Storage->Write ## TYPE(PropertyToKey(MB_TEXT(#NAME)), Get ## NAME()) + REGCONFIG(true); + #undef KEY + #undef KEYEX + + if (Storage->OpenSubKey(L"Interface\\CopyParam", true, true)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + FDefaultCopyParam.Save(Storage); + + if (FCopyParamListDefaults) + { + DebugAssert(!FCopyParamList->GetModified()); + Storage->WriteInteger("CopyParamList", -1); + } + else if (All || FCopyParamList->GetModified()) + { + Storage->WriteInteger("CopyParamList", FCopyParamList->GetCount()); + FCopyParamList->Save(Storage); + } + } + + if (Storage->OpenSubKey(L"Interface\\NewDirectory", true, true)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + FNewDirectoryProperties.Save(Storage); + } +} + +void TGUIConfiguration::LoadData(THierarchicalStorage * Storage) +{ + TConfiguration::LoadData(Storage); + + // duplicated from core\configuration.cpp + #undef KEYEX + #define KEYEX(TYPE, NAME, VAR) Set ## VAR(Storage->Read ## TYPE(LASTELEM(UnicodeString(MB_TEXT(#NAME))), Get ## VAR())) + #undef KEY + #define KEY(TYPE, NAME) Set ## NAME(Storage->Read ## TYPE(PropertyToKey(MB_TEXT(#NAME)), Get ## NAME())) + REGCONFIG(false); + #undef KEY + #undef KEYEX + + if (Storage->OpenSubKey(L"Interface\\CopyParam", false, true)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + // must be loaded before eventual setting defaults for CopyParamList + FDefaultCopyParam.Load(Storage); + + intptr_t CopyParamListCount = Storage->ReadInteger("CopyParamList", -1); + FCopyParamListDefaults = ((int)CopyParamListCount <= 0); + if (!FCopyParamListDefaults) + { + FCopyParamList->Clear(); + FCopyParamList->Load(Storage, CopyParamListCount); + } + else if (FCopyParamList->GetModified()) + { + FCopyParamList->Clear(); + FCopyParamListDefaults = false; + } + FCopyParamList->Reset(); + } + + // Make it compatible with versions prior to 3.7.1 that have not saved PuttyPath + // with quotes. First check for absence of quotes. + // Add quotes either if the path is set to default putty path (even if it does + // not exists) or when the path points to existing file (so there are no parameters + // yet in the string). Note that FileExists may display error dialog, but as + // it should be called only for custom users path, let's expect that the user + // can take care of it. + if ((FPuttyPath.SubString(1, 1) != L"\"") && + (CompareFileName(::ExpandEnvironmentVariables(FPuttyPath), FDefaultPuttyPathOnly) || + ::FileExists(::ExpandEnvironmentVariables(FPuttyPath)))) + { + FPuttyPath = FormatCommand(FPuttyPath, L""); + } + + if (Storage->OpenSubKey(L"Interface\\NewDirectory", false, true)) + { + SCOPE_EXIT + { + Storage->CloseSubKey(); + }; + FNewDirectoryProperties.Load(Storage); + } +} + +void TGUIConfiguration::Saved() +{ + TConfiguration::Saved(); + + FCopyParamList->Reset(); +} + +HINSTANCE TGUIConfiguration::LoadNewResourceModule(LCID ALocale, + UnicodeString * AFileName) +{ + HINSTANCE NewInstance = 0; +#ifndef __linux__ + UnicodeString LibraryFileName; + bool Internal = (ALocale == InternalLocale()); + if (!Internal) + { + UnicodeString Module; + UnicodeString LocaleName; + + Module = ModuleFileName(); + if ((ALocale & AdditionaLanguageMask) != AdditionaLanguageMask) + { + LOCALESIGNATURE LocSig; + GetLocaleInfo(ALocale, LOCALE_SABBREVLANGNAME, reinterpret_cast(&LocSig), sizeof(LocSig) / sizeof(TCHAR)); + LocaleName = *reinterpret_cast(&LocSig); + DebugAssert(!LocaleName.IsEmpty()); + } + else + { + LocaleName = UnicodeString(ADDITIONAL_LANGUAGE_PREFIX) + + static_cast(ALocale & ~AdditionaLanguageMask); + } + + Module = ::ChangeFileExt(Module, UnicodeString(L".") + LocaleName); + // Look for a potential language/country translation + NewInstance = ::LoadLibraryEx(Module.c_str(), 0, LOAD_LIBRARY_AS_DATAFILE); + if (!NewInstance) + { + // Finally look for a language only translation + Module.SetLength(Module.Length() - 1); + NewInstance = ::LoadLibraryEx(Module.c_str(), 0, LOAD_LIBRARY_AS_DATAFILE); + if (NewInstance) + { + LibraryFileName = Module; + } + } + else + { + LibraryFileName = Module; + } + } + + if (!NewInstance && !Internal) + { + throw Exception(FMTLOAD(LOCALE_LOAD_ERROR, static_cast(ALocale))); + } + else + { + if (Internal) + { + ThrowNotImplemented(90); + NewInstance = 0; // FIXME HInstance; + } + } + + if (AFileName != nullptr) + { + *AFileName = LibraryFileName; + } +#endif + return NewInstance; +} + +LCID TGUIConfiguration::InternalLocale() const +{ + LCID Result; + if (GetTranslationCount(GetApplicationInfo()) > 0) + { + TTranslation Translation; + Translation = GetTranslation(GetApplicationInfo(), 0); + Result = MAKELANGID(PRIMARYLANGID(Translation.Language), SUBLANG_DEFAULT); + } + else + { + DebugAssert(false); + Result = 0; + } + return Result; +} + +LCID TGUIConfiguration::GetLocale() const +{ + if (!FLocale) + { + FLocale = InternalLocale(); + } + return FLocale; +} + +void TGUIConfiguration::SetLocale(LCID Value) +{ + if (GetLocale() != Value) + { + HINSTANCE Module = LoadNewResourceModule(Value); + if (Module != nullptr) + { + FLocale = Value; + // SetResourceModule(Module); + } + else + { + DebugAssert(false); + } + } +} + +void TGUIConfiguration::SetLocaleSafe(LCID Value) +{ + if (GetLocale() != Value) + { + HINSTANCE Module; + + try + { + Module = LoadNewResourceModule(Value); + } + catch (...) + { + // ignore any exception while loading locale + Module = nullptr; + } + + if (Module != nullptr) + { + FLocale = Value; + // SetResourceModule(Module); + } + } +} + +TStrings * TGUIConfiguration::GetLocales() +{ + ThrowNotImplemented(93); + UnicodeString LocalesExts; + std::unique_ptr Exts(CreateSortedStringList()); + + DWORD FindAttrs = faReadOnly | faArchive; + TSearchRecChecked SearchRec; + bool Found; + + Found = (FindFirst(::ChangeFileExt(ModuleFileName(), L".*"), + FindAttrs, SearchRec) == 0); + { + SCOPE_EXIT + { + FindClose(SearchRec); + }; + UnicodeString Ext; + while (Found) + { + Ext = ::ExtractFileExt(SearchRec.Name).UpperCase(); + if ((Ext.Length() >= 3) && (Ext != L".EXE") && (Ext != L".COM") && + (Ext != L".DLL") && (Ext != L".INI")) + { + Ext = Ext.SubString(2, Ext.Length() - 1); + LocalesExts += Ext; + Exts->Add(Ext); + } + Found = (FindNextChecked(SearchRec) == 0); + } + } + + if (FLastLocalesExts != LocalesExts) + { + FLastLocalesExts = LocalesExts; + FLocales->Clear(); + + /* // FIXME + TLanguages * Langs = nullptr; // FIXME LanguagesDEPF(); + int Ext, Index, Count; + wchar_t LocaleStr[255]; + LCID Locale; + + Count = Langs->GetCount(); + Index = -1; + while (Index < Count) + { + if (Index >= 0) + { + Locale = Langs->LocaleID[Index]; + Ext = Exts->IndexOf(Langs->Ext[Index]); + if (Ext < 0) + { + Ext = Exts->IndexOf(Langs->Ext[Index].SubString(1, 2)); + if (Ext >= 0) + { + Locale = MAKELANGID(PRIMARYLANGID(Locale), SUBLANG_DEFAULT); + } + } + + if (Ext >= 0) + { + Exts->SetObj(Ext, reinterpret_cast(Locale)); + } + else + { + Locale = 0; + } + } + else + { + Locale = InternalLocale(); + } + + if (Locale) + { + UnicodeString Name; + GetLocaleInfo(Locale, LOCALE_SENGLANGUAGE, + LocaleStr, sizeof(LocaleStr)); + Name = LocaleStr; + Name += " - "; + // LOCALE_SNATIVELANGNAME + GetLocaleInfo(Locale, LOCALE_SLANGUAGE, + LocaleStr, sizeof(LocaleStr)); + Name += LocaleStr; + FLocales->AddObject(Name, reinterpret_cast(Locale)); + } + ++Index; + } + */ + for (intptr_t Index = 0; Index < Exts->GetCount(); ++Index) + { + if ((Exts->GetObj(Index) == nullptr) && + (Exts->GetString(Index).Length() == 3) && + ::SameText(Exts->GetString(Index).SubString(1, 2), ADDITIONAL_LANGUAGE_PREFIX)) + { + UnicodeString LangName = GetFileFileInfoString(L"LangName", + ::ChangeFileExt(ModuleFileName(), UnicodeString(L".") + Exts->GetString(Index))); + if (!LangName.IsEmpty()) + { + FLocales->AddObject(LangName, reinterpret_cast(static_cast( + AdditionaLanguageMask + Exts->GetString(Index)[3]))); + } + } + } + } + + return FLocales; +} + +void TGUIConfiguration::SetDefaultCopyParam(const TGUICopyParamType & Value) +{ + FDefaultCopyParam.Assign(&Value); + Changed(); +} + +bool TGUIConfiguration::GetRememberPassword() const +{ + return GetSessionRememberPassword() || GetPuttyPassword(); +} + +const TCopyParamList * TGUIConfiguration::GetCopyParamList() +{ + return FCopyParamList; +} + +void TGUIConfiguration::SetCopyParamList(const TCopyParamList * Value) +{ + if (!(*FCopyParamList == *Value)) + { + *FCopyParamList = *Value; + FCopyParamListDefaults = false; + Changed(); + } +} + +intptr_t TGUIConfiguration::GetCopyParamIndex() const +{ + intptr_t Result; + if (FCopyParamCurrent.IsEmpty()) + { + Result = -1; + } + else + { + Result = FCopyParamList->IndexOfName(FCopyParamCurrent); + } + return Result; +} + +void TGUIConfiguration::SetCopyParamIndex(intptr_t Value) +{ + UnicodeString Name; + if (Value < 0) + { + Name.Clear(); + } + else + { + Name = FCopyParamList->GetName(Value); + } + SetCopyParamCurrent(Name); +} + +void TGUIConfiguration::SetCopyParamCurrent(const UnicodeString & Value) +{ + SET_CONFIG_PROPERTY(CopyParamCurrent); +} + +const TGUICopyParamType TGUIConfiguration::GetCurrentCopyParam() const +{ + return GetCopyParamPreset(GetCopyParamCurrent()); +} + +const TGUICopyParamType TGUIConfiguration::GetCopyParamPreset(const UnicodeString & Name) const +{ + TGUICopyParamType Result = FDefaultCopyParam; + if (!Name.IsEmpty()) + { + intptr_t Index = FCopyParamList->IndexOfName(Name); + DebugAssert(Index >= 0); + if (Index >= 0) + { + const TCopyParamType * Preset = FCopyParamList->GetCopyParam(Index); + DebugAssert(Preset != nullptr); + Result.Assign(Preset); // overwrite all but GUI options + // reset all options known not to be configurable per-preset + // kind of hack + Result.SetResumeSupport(FDefaultCopyParam.GetResumeSupport()); + Result.SetResumeThreshold(FDefaultCopyParam.GetResumeThreshold()); + Result.SetLocalInvalidChars(FDefaultCopyParam.GetLocalInvalidChars()); + } + } + return Result; +} + +bool TGUIConfiguration::GetHasCopyParamPreset(const UnicodeString & Name) const +{ + return Name.IsEmpty() || (FCopyParamList->IndexOfName(Name) >= 0); +} + +void TGUIConfiguration::SetNewDirectoryProperties( + const TRemoteProperties & Value) +{ + SET_CONFIG_PROPERTY(NewDirectoryProperties); +} + +void TGUIConfiguration::SetQueueTransfersLimit(intptr_t Value) +{ + SET_CONFIG_PROPERTY(QueueTransfersLimit); +} + +void TGUIConfiguration::SetQueueKeepDoneItems(bool Value) +{ + SET_CONFIG_PROPERTY(QueueKeepDoneItems); +} + +void TGUIConfiguration::SetQueueKeepDoneItemsFor(intptr_t Value) +{ + SET_CONFIG_PROPERTY(QueueKeepDoneItemsFor); +} + +TStoredSessionList * TGUIConfiguration::SelectPuttySessionsForImport( + TStoredSessionList * Sessions) +{ + std::unique_ptr ImportSessionList(new TStoredSessionList(true)); + ImportSessionList->SetDefaultSettings(Sessions->GetDefaultSettings()); + + std::unique_ptr Storage(new TRegistryStorage(GetPuttySessionsKey())); + Storage->SetForceAnsi(true); + if (Storage->OpenRootKey(false)) + { + ImportSessionList->Load(Storage.get(), false, true); + } + + TSessionData * PuttySessionData = + NB_STATIC_DOWNCAST(TSessionData, ImportSessionList->FindByName(GetPuttySession())); + if (PuttySessionData != nullptr) + { + ImportSessionList->Remove(PuttySessionData); + } + ImportSessionList->SelectSessionsToImport(Sessions, true); + + return ImportSessionList.release(); +} + +bool TGUIConfiguration::AnyPuttySessionForImport(TStoredSessionList * Sessions) +{ + try + { + std::unique_ptr Sesssions(SelectPuttySessionsForImport(Sessions)); + return (Sesssions->GetCount() > 0); + } + catch (...) + { + return false; + } +} + +TStoredSessionList * TGUIConfiguration::SelectFilezillaSessionsForImport( + TStoredSessionList * /*Sessions*/) +{ +/* + std::unique_ptr ImportSessionList(new TStoredSessionList(true)); + ImportSessionList->SetDefaultSettings(Sessions->GetDefaultSettings()); + + UnicodeString AppDataPath = GetShellFolderPath(CSIDL_APPDATA); + UnicodeString FilezillaSiteManagerFile = + IncludeTrailingBackslash(AppDataPath) + L"FileZilla\\sitemanager.xml"; + + if (::FileExists(FilezillaSiteManagerFile)) + { + ImportSessionList->ImportFromFilezilla(FilezillaSiteManagerFile); + + ImportSessionList->SelectSessionsToImport(Sessions, true); + } + + return ImportSessionList.release(); +*/ + return nullptr; +} + +bool TGUIConfiguration::AnyFilezillaSessionForImport(TStoredSessionList * Sessions) +{ + try + { + std::unique_ptr Sesssions(SelectFilezillaSessionsForImport(Sessions)); + return (Sesssions->GetCount() > 0); + } + catch (...) + { + return false; + } +} + +UnicodeString TGUIConfiguration::GetPuttyPath() const +{ + return FPuttyPath; +} + +void TGUIConfiguration::SetPuttyPath(const UnicodeString & Value) +{ + FPuttyPath = Value; +} + +UnicodeString TGUIConfiguration::GetDefaultPuttyPath() const +{ + return FDefaultPuttyPath; +} + +UnicodeString TGUIConfiguration::GetPSftpPath() const +{ + return FPSftpPath; +} + +void TGUIConfiguration::SetPSftpPath(const UnicodeString & Value) +{ + FPSftpPath = Value; +} + +UnicodeString TGUIConfiguration::GetPuttySession() const +{ + return FPuttySession; +} + +void TGUIConfiguration::SetPuttySession(const UnicodeString & Value) +{ + FPuttySession = Value; +} + +UnicodeString TGUIConfiguration::GetCopyParamCurrent() const +{ + return FCopyParamCurrent; +} + +UnicodeString TGUIConfiguration::GetChecksumAlg() const +{ + return FChecksumAlg; +} + +void TGUIConfiguration::SetChecksumAlg(const UnicodeString & Value) +{ + FChecksumAlg = Value; +} + +TGUIConfiguration * GetGUIConfiguration() +{ + return NB_STATIC_DOWNCAST(TGUIConfiguration, GetConfiguration()); +} + +NB_IMPLEMENT_CLASS(TGUICopyParamType, NB_GET_CLASS_INFO(TCopyParamType), nullptr) +NB_IMPLEMENT_CLASS(TGUIConfiguration, NB_GET_CLASS_INFO(TConfiguration), nullptr) +NB_IMPLEMENT_CLASS(TCopyParamRule, NB_GET_CLASS_INFO(TObject), nullptr) + diff --git a/netbox/src/windows/GUIConfiguration.h b/netbox/src/windows/GUIConfiguration.h new file mode 100644 index 000000000..3a6cdced4 --- /dev/null +++ b/netbox/src/windows/GUIConfiguration.h @@ -0,0 +1,394 @@ +#pragma once + +#include "Configuration.h" +#include "CopyParam.h" + +#define CONST_INVALID_CHARS L"/\\[]" + +class TGUIConfiguration; +class TStoredSessionList; + +enum TLogView +{ + lvNone, + lvWindow, + pvPanel +}; + +enum TInterface +{ + ifCommander, + ifExplorer +}; + +extern const intptr_t ccLocal; +extern const intptr_t ccShowResults; +extern const intptr_t ccCopyResults; +extern const intptr_t ccSet; + +const int soRecurse = 0x01; +const int soSynchronize = 0x02; +const int soSynchronizeAsk = 0x04; +const int soContinueOnError = 0x08; + +class TGUICopyParamType : public TCopyParamType +{ +NB_DECLARE_CLASS(TGUICopyParamType) +public: + TGUICopyParamType(); + TGUICopyParamType(const TCopyParamType & Source); + explicit TGUICopyParamType(const TGUICopyParamType & Source); + virtual ~TGUICopyParamType() {} + + void Load(THierarchicalStorage * Storage); + void Save(THierarchicalStorage * Storage); + + virtual void Default(); + virtual void Assign(const TCopyParamType * Source); + TGUICopyParamType & operator =(const TGUICopyParamType & rhp); + TGUICopyParamType & operator =(const TCopyParamType & rhp); + +/* + __property bool Queue = { read = FQueue, write = FQueue }; + __property bool QueueNoConfirmation = { read = FQueueNoConfirmation, write = FQueueNoConfirmation }; + __property bool QueueIndividually = { read = FQueueIndividually, write = FQueueIndividually }; +*/ + bool GetQueue() const { return FQueue; } + void SetQueue(bool Value) { FQueue = Value; } + bool GetQueueNoConfirmation() const { return FQueueNoConfirmation; } + void SetQueueNoConfirmation(bool Value) { FQueueNoConfirmation = Value; } + bool GetQueueIndividually() const { return FQueueIndividually; } + void SetQueueIndividually(bool Value) { FQueueIndividually = Value; } + +protected: + void GUIDefault(); + void GUIAssign(const TGUICopyParamType * Source); + +private: + bool FQueue; + bool FQueueNoConfirmation; + bool FQueueIndividually; +}; + +struct TCopyParamRuleData : public TObject +{ + UnicodeString HostName; + UnicodeString UserName; + UnicodeString RemoteDirectory; + UnicodeString LocalDirectory; + + void Default(); +}; + +class TCopyParamRule : public TObject +{ +NB_DECLARE_CLASS(TCopyParamRule) +public: + explicit TCopyParamRule(); + explicit TCopyParamRule(const TCopyParamRuleData & Data); + explicit TCopyParamRule(const TCopyParamRule & Source); + + bool Matches(const TCopyParamRuleData & Value) const; + void Load(THierarchicalStorage * Storage); + void Save(THierarchicalStorage * Storage) const; + + UnicodeString GetInfoStr(const UnicodeString & Separator) const; + + bool operator ==(const TCopyParamRule & rhp) const; + + TCopyParamRuleData GetData() const { return FData; } + void SetData(const TCopyParamRuleData & Value); + bool GetEmpty() const; + +public: + TCopyParamRule & operator=(const TCopyParamRule & other); + +private: + TCopyParamRuleData FData; + + inline bool Match(const UnicodeString & Mask, + const UnicodeString & Value, bool Path, bool Local = true) const; +}; + +class TCopyParamList : public TObject +{ +friend class TGUIConfiguration; +public: + explicit TCopyParamList(); + explicit TCopyParamList(const TCopyParamList & other); + + virtual ~TCopyParamList(); + intptr_t Find(const TCopyParamRuleData & Value) const; + + void Load(THierarchicalStorage * Storage, intptr_t Count); + void Save(THierarchicalStorage * Storage) const; + + static void ValidateName(const UnicodeString & Name); + + TCopyParamList & operator=(const TCopyParamList & rhl); + bool operator==(const TCopyParamList & rhl) const; + + void Clear(); + void Add(const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule); + void Insert(intptr_t Index, const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule); + void Change(intptr_t Index, const UnicodeString & Name, + TCopyParamType * CopyParam, TCopyParamRule * Rule); + void Move(intptr_t CurIndex, intptr_t NewIndex); + void Delete(intptr_t Index); + intptr_t IndexOfName(const UnicodeString & Name) const; + +/* + __property int Count = { read = GetCount }; + __property UnicodeString Names[int Index] = { read = GetName }; + __property const TCopyParamRule * Rules[int Index] = { read = GetRule }; + __property const TCopyParamType * CopyParams[int Index] = { read = GetCopyParam }; + __property bool Modified = { read = FModified }; + __property TStrings * NameList = { read = GetNameList }; + __property bool AnyRule = { read = GetAnyRule }; +*/ + intptr_t GetCount() const { return FCopyParams ? FCopyParams->GetCount() : 0; } + UnicodeString GetName(intptr_t Index) const; + const TCopyParamRule * GetRule(intptr_t Index) const; + const TCopyParamType * GetCopyParam(intptr_t Index) const; + bool GetModified() const { return FModified; } + TStrings * GetNameList() const; + bool GetAnyRule() const; + +private: + TList * FRules; + TList * FCopyParams; + TStrings * FNames; + mutable TStrings * FNameList; + bool FModified; + + void Reset(); + void Modify(); + bool CompareItem(intptr_t Index, const TCopyParamType * CopyParam, + const TCopyParamRule * Rule) const; +}; + +class TGUIConfiguration : public TConfiguration +{ +NB_DISABLE_COPY(TGUIConfiguration) +NB_DECLARE_CLASS(TGUIConfiguration) +public: + virtual void SaveData(THierarchicalStorage * Storage, bool All); + virtual void LoadData(THierarchicalStorage * Storage); + virtual LCID GetLocale() const; + LCID GetLocaleSafe() const { return GetLocale(); } + void SetLocale(LCID Value); + void SetLocaleSafe(LCID Value); + virtual HINSTANCE LoadNewResourceModule(LCID Locale, + UnicodeString * AFileName = nullptr); + HANDLE GetResourceModule(); + // virtual void SetResourceModule(HINSTANCE Instance); + TStrings * GetLocales(); + LCID InternalLocale() const; + void FreeResourceModule(HANDLE Instance); + void SetDefaultCopyParam(const TGUICopyParamType & Value); + virtual bool GetRememberPassword() const; + const TCopyParamList * GetCopyParamList(); + void SetCopyParamList(const TCopyParamList * Value); + static UnicodeString PropertyToKey(const UnicodeString & Property); + virtual void DefaultLocalized(); + intptr_t GetCopyParamIndex() const; + const TGUICopyParamType GetCurrentCopyParam() const; + const TGUICopyParamType GetCopyParamPreset(const UnicodeString & Name) const; + bool GetHasCopyParamPreset(const UnicodeString & Name) const; + void SetCopyParamIndex(intptr_t Value); + void SetCopyParamCurrent(const UnicodeString & Value); + void SetNewDirectoryProperties(const TRemoteProperties & Value); + virtual void Saved(); + void SetQueueTransfersLimit(intptr_t Value); + void SetQueueKeepDoneItems(bool Value); + void SetQueueKeepDoneItemsFor(intptr_t Value); + +public: + explicit TGUIConfiguration(); + virtual ~TGUIConfiguration(); + virtual void Default(); + virtual void UpdateStaticUsage(); + + HANDLE ChangeResourceModule(HANDLE Instance); + TStoredSessionList * SelectPuttySessionsForImport(TStoredSessionList * Sessions); + bool AnyPuttySessionForImport(TStoredSessionList * Sessions); + TStoredSessionList * SelectFilezillaSessionsForImport(TStoredSessionList * Sessions); + bool AnyFilezillaSessionForImport(TStoredSessionList * Sessions); + + bool GetContinueOnError() const { return FContinueOnError; } + void SetContinueOnError(bool Value) { FContinueOnError = Value; } + bool GetConfirmCommandSession() const { return FConfirmCommandSession; } + void SetConfirmCommandSession(bool Value) { FConfirmCommandSession = Value; } + intptr_t GetSynchronizeParams() const { return FSynchronizeParams; } + void SetSynchronizeParams(intptr_t Value) { FSynchronizeParams = Value; } + intptr_t GetSynchronizeOptions() const { return FSynchronizeOptions; } + void SetSynchronizeOptions(intptr_t Value) { FSynchronizeOptions = Value; } + intptr_t GetSynchronizeModeAuto() const { return FSynchronizeModeAuto; } + void SetSynchronizeModeAuto(intptr_t Value) { FSynchronizeModeAuto = Value; } + intptr_t GetSynchronizeMode() const { return FSynchronizeMode; } + void SetSynchronizeMode(intptr_t Value) { FSynchronizeMode = Value; } + intptr_t GetMaxWatchDirectories() const { return FMaxWatchDirectories; } + void SetMaxWatchDirectories(intptr_t Value) { FMaxWatchDirectories = Value; } + intptr_t GetQueueTransfersLimit() const { return FQueueTransfersLimit; } + bool GetQueueKeepDoneItems() const { return FQueueKeepDoneItems; } + intptr_t GetQueueKeepDoneItemsFor() const { return FQueueKeepDoneItemsFor; } + bool GetQueueAutoPopup() const { return FQueueAutoPopup; } + void SetQueueAutoPopup(bool Value) { FQueueAutoPopup = Value; } + bool GetSessionRememberPassword() const { return FSessionRememberPassword; } + void SetSessionRememberPassword(bool Value) { FSessionRememberPassword = Value; } + UnicodeString GetPuttyPath() const; + void SetPuttyPath(const UnicodeString & Value); + UnicodeString GetDefaultPuttyPath() const; + UnicodeString GetPSftpPath() const; + void SetPSftpPath(const UnicodeString & Value); + bool GetPuttyPassword() const { return FPuttyPassword; } + void SetPuttyPassword(bool Value) { FPuttyPassword = Value; } + bool GetTelnetForFtpInPutty() const { return FTelnetForFtpInPutty; } + void SetTelnetForFtpInPutty(bool Value) { FTelnetForFtpInPutty = Value; } + UnicodeString GetPuttySession() const; + void SetPuttySession(const UnicodeString & Value); + TDateTime GetIgnoreCancelBeforeFinish() const { return FIgnoreCancelBeforeFinish; } + void SetIgnoreCancelBeforeFinish(const TDateTime & Value) { FIgnoreCancelBeforeFinish = Value; } + TGUICopyParamType & GetDefaultCopyParam() { return FDefaultCopyParam; } + bool GetBeepOnFinish() const { return FBeepOnFinish; } + void SetBeepOnFinish(bool Value) { FBeepOnFinish = Value; } + TDateTime GetBeepOnFinishAfter() const { return FBeepOnFinishAfter; } + void SetBeepOnFinishAfter(const TDateTime & Value) { FBeepOnFinishAfter = Value; } + UnicodeString GetCopyParamCurrent() const; + const TRemoteProperties & GetNewDirectoryProperties() const { return FNewDirectoryProperties; } + intptr_t GetKeepUpToDateChangeDelay() const { return FKeepUpToDateChangeDelay; } + void SetKeepUpToDateChangeDelay(intptr_t Value) { FKeepUpToDateChangeDelay = Value; } + UnicodeString GetChecksumAlg() const; + void SetChecksumAlg(const UnicodeString & Value); + intptr_t GetSessionReopenAutoIdle() const { return FSessionReopenAutoIdle; } + void SetSessionReopenAutoIdle(intptr_t Value) { FSessionReopenAutoIdle = Value; } + +protected: + mutable LCID FLocale; + +private: + TStrings * FLocales; + UnicodeString FLastLocalesExts; + bool FContinueOnError; + bool FConfirmCommandSession; + UnicodeString FPuttyPath; + UnicodeString FPSftpPath; + bool FPuttyPassword; + bool FTelnetForFtpInPutty; + UnicodeString FPuttySession; + intptr_t FSynchronizeParams; + intptr_t FSynchronizeOptions; + intptr_t FSynchronizeModeAuto; + intptr_t FSynchronizeMode; + intptr_t FMaxWatchDirectories; + TDateTime FIgnoreCancelBeforeFinish; + bool FQueueAutoPopup; + bool FSessionRememberPassword; + intptr_t FQueueTransfersLimit; + bool FQueueKeepDoneItems; + intptr_t FQueueKeepDoneItemsFor; + TGUICopyParamType FDefaultCopyParam; + bool FBeepOnFinish; + TDateTime FBeepOnFinishAfter; + UnicodeString FDefaultPuttyPathOnly; + UnicodeString FDefaultPuttyPath; + TCopyParamList * FCopyParamList; + bool FCopyParamListDefaults; + UnicodeString FCopyParamCurrent; + TRemoteProperties FNewDirectoryProperties; + intptr_t FKeepUpToDateChangeDelay; + UnicodeString FChecksumAlg; + intptr_t FSessionReopenAutoIdle; +/* +protected: + LCID FLocale; + + virtual void __fastcall SaveData(THierarchicalStorage * Storage, bool All); + virtual void __fastcall LoadData(THierarchicalStorage * Storage); + virtual LCID __fastcall GetLocale(); + void __fastcall SetLocale(LCID value); + void __fastcall SetLocaleSafe(LCID value); + virtual HINSTANCE __fastcall LoadNewResourceModule(LCID Locale, + UnicodeString * FileName = nullptr); + HANDLE __fastcall GetResourceModule(); + virtual void __fastcall SetResourceModule(HINSTANCE Instance); + TStrings * __fastcall GetLocales(); + LCID __fastcall InternalLocale(); + void __fastcall FreeResourceModule(HANDLE Instance); + void __fastcall SetDefaultCopyParam(const TGUICopyParamType & value); + virtual bool __fastcall GetRememberPassword(); + const TCopyParamList * __fastcall GetCopyParamList(); + void __fastcall SetCopyParamList(const TCopyParamList * value); + virtual void __fastcall DefaultLocalized(); + int __fastcall GetCopyParamIndex(); + TGUICopyParamType __fastcall GetCurrentCopyParam(); + TGUICopyParamType __fastcall GetCopyParamPreset(UnicodeString Name); + bool __fastcall GetHasCopyParamPreset(UnicodeString Name); + void __fastcall SetCopyParamIndex(int value); + void __fastcall SetCopyParamCurrent(UnicodeString value); + void __fastcall SetNewDirectoryProperties(const TRemoteProperties & value); + virtual void __fastcall Saved(); + void __fastcall SetQueueTransfersLimit(int value); + void __fastcall SetQueueKeepDoneItems(bool value); + void __fastcall SetQueueKeepDoneItemsFor(int value); + void __fastcall SetLocaleInternal(LCID value, bool Safe); + void __fastcall SetInitialLocale(LCID value); + +public: + __fastcall TGUIConfiguration(); + virtual __fastcall ~TGUIConfiguration(); + virtual void __fastcall Default(); + virtual void __fastcall UpdateStaticUsage(); + + HANDLE __fastcall ChangeResourceModule(HANDLE Instance); + TStoredSessionList * __fastcall SelectPuttySessionsForImport(TStoredSessionList * Sessions, UnicodeString & Error); + bool __fastcall AnyPuttySessionForImport(TStoredSessionList * Sessions); + TStoredSessionList * __fastcall SelectFilezillaSessionsForImport( + TStoredSessionList * Sessions, UnicodeString & Error); + bool __fastcall AnyFilezillaSessionForImport(TStoredSessionList * Sessions); + void __fastcall DetectScalingType(); + + __property bool ContinueOnError = { read = FContinueOnError, write = FContinueOnError }; + __property bool ConfirmCommandSession = { read = FConfirmCommandSession, write = FConfirmCommandSession }; + __property int SynchronizeParams = { read = FSynchronizeParams, write = FSynchronizeParams }; + __property int SynchronizeOptions = { read = FSynchronizeOptions, write = FSynchronizeOptions }; + __property int SynchronizeModeAuto = { read = FSynchronizeModeAuto, write = FSynchronizeModeAuto }; + __property int SynchronizeMode = { read = FSynchronizeMode, write = FSynchronizeMode }; + __property int MaxWatchDirectories = { read = FMaxWatchDirectories, write = FMaxWatchDirectories }; + __property int QueueTransfersLimit = { read = FQueueTransfersLimit, write = SetQueueTransfersLimit }; + __property bool QueueKeepDoneItems = { read = FQueueKeepDoneItems, write = SetQueueKeepDoneItems }; + __property int QueueKeepDoneItemsFor = { read = FQueueKeepDoneItemsFor, write = SetQueueKeepDoneItemsFor }; + __property bool QueueAutoPopup = { read = FQueueAutoPopup, write = FQueueAutoPopup }; + __property bool SessionRememberPassword = { read = FSessionRememberPassword, write = FSessionRememberPassword }; + __property LCID Locale = { read = GetLocale, write = SetLocale }; + __property LCID LocaleSafe = { read = GetLocale, write = SetLocaleSafe }; + __property TStrings * Locales = { read = GetLocales }; + __property UnicodeString PuttyPath = { read = FPuttyPath, write = FPuttyPath }; + __property UnicodeString DefaultPuttyPath = { read = FDefaultPuttyPath }; + __property UnicodeString PSftpPath = { read = FPSftpPath, write = FPSftpPath }; + __property bool PuttyPassword = { read = FPuttyPassword, write = FPuttyPassword }; + __property bool TelnetForFtpInPutty = { read = FTelnetForFtpInPutty, write = FTelnetForFtpInPutty }; + __property UnicodeString PuttySession = { read = FPuttySession, write = FPuttySession }; + __property TDateTime IgnoreCancelBeforeFinish = { read = FIgnoreCancelBeforeFinish, write = FIgnoreCancelBeforeFinish }; + __property TGUICopyParamType DefaultCopyParam = { read = FDefaultCopyParam, write = SetDefaultCopyParam }; + __property bool BeepOnFinish = { read = FBeepOnFinish, write = FBeepOnFinish }; + __property TDateTime BeepOnFinishAfter = { read = FBeepOnFinishAfter, write = FBeepOnFinishAfter }; + __property const TCopyParamList * CopyParamList = { read = GetCopyParamList, write = SetCopyParamList }; + __property UnicodeString CopyParamCurrent = { read = FCopyParamCurrent, write = SetCopyParamCurrent }; + __property int CopyParamIndex = { read = GetCopyParamIndex, write = SetCopyParamIndex }; + __property TGUICopyParamType CurrentCopyParam = { read = GetCurrentCopyParam }; + __property TGUICopyParamType CopyParamPreset[UnicodeString Name] = { read = GetCopyParamPreset }; + __property bool HasCopyParamPreset[UnicodeString Name] = { read = GetHasCopyParamPreset }; + __property TRemoteProperties NewDirectoryProperties = { read = FNewDirectoryProperties, write = SetNewDirectoryProperties }; + __property int KeepUpToDateChangeDelay = { read = FKeepUpToDateChangeDelay, write = FKeepUpToDateChangeDelay }; + __property UnicodeString ChecksumAlg = { read = FChecksumAlg, write = FChecksumAlg }; + __property int SessionReopenAutoIdle = { read = FSessionReopenAutoIdle, write = FSessionReopenAutoIdle }; + __property bool CanApplyLocaleImmediately = { read = FCanApplyLocaleImmediately, write = FCanApplyLocaleImmediately }; + __property LCID AppliedLocale = { read = FAppliedLocale }; +*/ +}; + +TGUIConfiguration * GetGUIConfiguration(); + diff --git a/netbox/src/windows/GUITools.cpp b/netbox/src/windows/GUITools.cpp new file mode 100644 index 000000000..71c2a54a7 --- /dev/null +++ b/netbox/src/windows/GUITools.cpp @@ -0,0 +1,530 @@ +#include +#pragma hdrstop + +#ifndef __linux__ +#include +#include +#endif +#include + +#include "GUITools.h" +#include "GUIConfiguration.h" +#include +#include +#include +#include + +#if 0 +template +void ValidateMaskEditT(const UnicodeString & Mask, TEditControl * Edit, int ForceDirectoryMasks) +{ + DebugAssert(Edit != nullptr); + TFileMasks Masks(ForceDirectoryMasks); + try + { + Masks = Mask; + } + catch (EFileMasksException & E) + { + ShowExtendedException(&E); + Edit->SetFocus(); + // This does not work for TEdit and TMemo (descendants of TCustomEdit) anymore, + // as it re-selects whole text on exception in TCustomEdit.CMExit +// Edit->SelStart = E.ErrorStart - 1; +// Edit->SelLength = E.ErrorLen; + Abort(); + } +} + +void ValidateMaskEdit(TFarComboBox * Edit) +{ + ValidateMaskEditT(Edit->GetText(), Edit, -1); +} + +void ValidateMaskEdit(TFarEdit * Edit) +{ + ValidateMaskEditT(Edit->GetText(), Edit, -1); +} +#endif + +bool FindFile(UnicodeString & APath) +{ + bool Result = ::FileExists(APath); + if (!Result) + { + intptr_t Len = ::GetEnvironmentVariable(L"PATH", nullptr, 0); + if (Len > 0) + { + UnicodeString Paths; + Paths.SetLength(Len - 1); + ::GetEnvironmentVariable(L"PATH", reinterpret_cast(const_cast(Paths.c_str())), static_cast(Len)); + + UnicodeString NewPath = ::FileSearch(base::ExtractFileName(APath, true), Paths); + Result = !NewPath.IsEmpty(); + if (Result) + { + APath = NewPath; + } + } + } + return Result; +} + +bool FileExistsEx(const UnicodeString & APath) +{ + UnicodeString LocalPath = APath; + return FindFile(LocalPath); +} + +void OpenSessionInPutty(const UnicodeString & PuttyPath, + TSessionData * SessionData) +{ + UnicodeString Program, Params, Dir; + SplitCommand(PuttyPath, Program, Params, Dir); + Program = ::ExpandEnvironmentVariables(Program); + if (FindFile(Program)) + { + Params = ::ExpandEnvironmentVariables(Params); + UnicodeString Password = GetGUIConfiguration()->GetPuttyPassword() ? SessionData->GetPassword() : UnicodeString(); + UnicodeString Psw = Password; + UnicodeString SessionName; + std::unique_ptr Storage(new TRegistryStorage(GetConfiguration()->GetPuttySessionsKey())); + Storage->SetAccessMode(smReadWrite); + // make it compatible with putty + Storage->SetMungeStringValues(false); + Storage->SetForceAnsi(true); + if (Storage->OpenRootKey(true)) + { + if (Storage->KeyExists(SessionData->GetStorageKey())) + { + SessionName = SessionData->GetSessionName(); + } + else + { + std::unique_ptr SourceStorage(new TRegistryStorage(GetConfiguration()->GetPuttySessionsKey())); + SourceStorage->SetMungeStringValues(false); + SourceStorage->SetForceAnsi(true); + if (SourceStorage->OpenSubKey(StoredSessions->GetDefaultSettings()->GetName(), false) && + Storage->OpenSubKey(GetGUIConfiguration()->GetPuttySession(), true)) + { + Storage->Copy(SourceStorage.get()); + Storage->CloseSubKey(); + } + + std::unique_ptr ExportData(new TSessionData(L"")); + ExportData->Assign(SessionData); + ExportData->SetModified(true); + ExportData->SetName(GetGUIConfiguration()->GetPuttySession()); + ExportData->SetPassword(L""); + + if (SessionData->GetFSProtocol() == fsFTP) + { + if (GetGUIConfiguration()->GetTelnetForFtpInPutty()) + { + ExportData->SetPuttyProtocol(PuttyTelnetProtocol); + ExportData->SetPortNumber(TelnetPortNumber); + // PuTTY does not allow -pw for telnet + Psw.Clear(); + } + else + { + ExportData->SetPuttyProtocol(PuttySshProtocol); + ExportData->SetPortNumber(SshPortNumber); + } + } + + ExportData->Save(Storage.get(), true); + SessionName = GetGUIConfiguration()->GetPuttySession(); + } + } + + if (!Params.IsEmpty()) + { + Params += L" "; + } + if (!Psw.IsEmpty()) + { + Params += FORMAT(L"-pw %s ", EscapePuttyCommandParam(Psw).c_str()); + } + //Params += FORMAT(L"-load %s", EscapePuttyCommandParam(SessionName).c_str()); + Params += FORMAT(L"-l %s ", EscapePuttyCommandParam(SessionData->GetUserNameExpanded()).c_str()); + Params += FORMAT(L"-P %d ", SessionData->GetPortNumber()); + Params += FORMAT(L"%s ", EscapePuttyCommandParam(SessionData->GetHostNameExpanded()).c_str()); + + if (!ExecuteShell(Program, Params)) + { + throw Exception(FMTLOAD(EXECUTE_APP_ERROR, Program.c_str())); + } + } + else + { + throw Exception(FMTLOAD(FILE_NOT_FOUND, Program.c_str())); + } +} + +bool FindTool(const UnicodeString & Name, UnicodeString & APath) +{ + UnicodeString AppPath = ::IncludeTrailingBackslash(::ExtractFilePath(GetConfiguration()->ModuleFileName())); + APath = AppPath + Name; + bool Result = true; + if (!::FileExists(APath)) + { + APath = AppPath + "PuTTY\\" + Name; + if (!::FileExists(APath)) + { + APath = Name; + if (!FindFile(APath)) + { + Result = false; + } + } + } + return Result; +} + +bool ExecuteShell(const UnicodeString & APath, const UnicodeString & Params) +{ +#ifndef __linux__ + return ((intptr_t)::ShellExecute(nullptr, L"open", const_cast(APath.data()), + const_cast(Params.data()), nullptr, SW_SHOWNORMAL) > 32); +#else + return false; +#endif +} + +bool ExecuteShell(const UnicodeString & APath, const UnicodeString & Params, + HANDLE & Handle) +{ +#ifndef __linux__ + TShellExecuteInfoW ExecuteInfo; + ClearStruct(ExecuteInfo); + ExecuteInfo.cbSize = sizeof(ExecuteInfo); + ExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ExecuteInfo.hwnd = reinterpret_cast(::GetModuleHandle(0)); + ExecuteInfo.lpFile = const_cast(APath.data()); + ExecuteInfo.lpParameters = const_cast(Params.data()); + ExecuteInfo.nShow = SW_SHOW; + + bool Result = (::ShellExecuteEx(&ExecuteInfo) != 0); + if (Result) + { + Handle = ExecuteInfo.hProcess; + } + return Result; +#else + return false; +#endif +} + +bool ExecuteShellAndWait(HINSTANCE /*Handle*/, const UnicodeString & APath, + const UnicodeString & Params, TProcessMessagesEvent ProcessMessages) +{ +#ifndef __linux__ + TShellExecuteInfoW ExecuteInfo; + ClearStruct(ExecuteInfo); + ExecuteInfo.cbSize = sizeof(ExecuteInfo); + ExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ExecuteInfo.hwnd = reinterpret_cast(::GetModuleHandle(0)); + ExecuteInfo.lpFile = const_cast(APath.data()); + ExecuteInfo.lpParameters = const_cast(Params.data()); + ExecuteInfo.nShow = SW_SHOW; + + bool Result = (::ShellExecuteEx(&ExecuteInfo) != 0); + if (Result) + { + if (ProcessMessages != nullptr) + { + uint32_t WaitResult; + do + { + WaitResult = ::WaitForSingleObject(ExecuteInfo.hProcess, 200); + if (WaitResult == WAIT_FAILED) + { + throw Exception(LoadStr(DOCUMENT_WAIT_ERROR)); + } + ProcessMessages(); + } + while (WaitResult == WAIT_TIMEOUT); + } + else + { + ::WaitForSingleObject(ExecuteInfo.hProcess, INFINITE); + } + } + return Result; +#else + return false; +#endif +} + +bool ExecuteShellAndWait(HINSTANCE Handle, const UnicodeString & Command, + TProcessMessagesEvent ProcessMessages) +{ + UnicodeString Program, Params, Dir; + SplitCommand(Command, Program, Params, Dir); + return ExecuteShellAndWait(Handle, Program, Params, ProcessMessages); +} + +bool SpecialFolderLocation(int PathID, UnicodeString & APath) +{ +#ifndef __linux__ + LPITEMIDLIST Pidl; + wchar_t Buf[MAX_PATH]; + if (::SHGetSpecialFolderLocation(nullptr, PathID, &Pidl) == NO_ERROR && + ::SHGetPathFromIDList(Pidl, Buf)) + { + APath = UnicodeString(Buf); + return true; + } +#endif + return false; +} + +UnicodeString GetPersonalFolder() +{ + UnicodeString Result; + SpecialFolderLocation(CSIDL_PERSONAL, Result); + + if (IsWine()) + { + UnicodeString WineHostHome; + int Len1 = ::GetEnvironmentVariable(L"WINE_HOST_HOME", nullptr, 0); + if (Len1 > 0) + { + WineHostHome.SetLength(Len1 - 1); + ::GetEnvironmentVariable(L"WINE_HOST_HOME", const_cast(WineHostHome.c_str()), Len1); + } + if (!WineHostHome.IsEmpty()) + { + UnicodeString WineHome = L"Z:" + core::ToUnixPath(WineHostHome); + if (::DirectoryExists(WineHome)) + { + Result = WineHome; + } + } + else + { + // Should we use WinAPI GetUserName() instead? + UnicodeString UserName; + int Len2 = ::GetEnvironmentVariable(L"USERNAME", nullptr, 0); + if (Len2 > 0) + { + UserName.SetLength(Len2 - 1); + ::GetEnvironmentVariable(L"USERNAME", const_cast(UserName.c_str()), Len2); + } + if (!UserName.IsEmpty()) + { + UnicodeString WineHome = L"Z:\\home\\" + UserName; + if (::DirectoryExists(WineHome)) + { + Result = WineHome; + } + } + } + } + return Result; +} + +UnicodeString ItemsFormatString(const UnicodeString & SingleItemFormat, + const UnicodeString & MultiItemsFormat, intptr_t Count, const UnicodeString & FirstItem) +{ + UnicodeString Result; + if (Count == 1) + { + Result = FORMAT(SingleItemFormat.c_str(), FirstItem.c_str()); + } + else + { + Result = FORMAT(MultiItemsFormat.c_str(), Count); + } + return Result; +} + +UnicodeString ItemsFormatString(const UnicodeString & SingleItemFormat, + const UnicodeString & MultiItemsFormat, const TStrings * Items) +{ + return ItemsFormatString(SingleItemFormat, MultiItemsFormat, + Items->GetCount(), (Items->GetCount() > 0 ? Items->GetString(0) : UnicodeString())); +} + +UnicodeString FileNameFormatString(const UnicodeString & SingleFileFormat, + const UnicodeString & MultiFilesFormat, const TStrings * AFiles, bool Remote) +{ + DebugAssert(AFiles != nullptr); + UnicodeString Item; + if (AFiles->GetCount() > 0) + { + Item = Remote ? base::UnixExtractFileName(AFiles->GetString(0)) : + base::ExtractFileName(AFiles->GetString(0), true); + } + return ItemsFormatString(SingleFileFormat, MultiFilesFormat, + AFiles->GetCount(), Item); +} + +UnicodeString UniqTempDir(const UnicodeString & BaseDir, const UnicodeString & Identity, + bool Mask) +{ + UnicodeString TempDir; + do + { + TempDir = BaseDir.IsEmpty() ? GetSystemTemporaryDirectory() : BaseDir; + TempDir = ::IncludeTrailingBackslash(TempDir) + Identity; + if (Mask) + { + TempDir += L"?????"; + } + else + { +#if defined(__BORLANDC__) + TempDir += ::IncludeTrailingBackslash(FormatDateTime(L"nnzzz", Now())); +#else + TDateTime dt = Now(); + uint16_t H, M, S, MS; + dt.DecodeTime(H, M, S, MS); + TempDir += ::IncludeTrailingBackslash(FORMAT(L"%02d%03d", M, MS)); +#endif + } + } + while (!Mask && ::DirectoryExists(TempDir)); + + return TempDir; +} + +bool DeleteDirectory(const UnicodeString & ADirName) +{ + TSearchRecChecked SearchRec; + bool retval = true; + if (::FindFirst(ADirName + L"\\*", faAnyFile, SearchRec) == 0) // VCL Function + { + if (FLAGSET(SearchRec.Attr, faDirectory)) + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + retval = DeleteDirectory(ADirName + L"\\" + SearchRec.Name); + } + else + { + retval = ::RemoveFile(ADirName + L"\\" + SearchRec.Name); + } + + if (retval) + { + while (FindNextChecked(SearchRec) == 0) + { // VCL Function + if (FLAGSET(SearchRec.Attr, faDirectory)) + { + if ((SearchRec.Name != THISDIRECTORY) && (SearchRec.Name != PARENTDIRECTORY)) + retval = DeleteDirectory(ADirName + L"\\" + SearchRec.Name); + } + else + { + retval = ::RemoveFile(ADirName + L"\\" + SearchRec.Name); + } + + if (!retval) + { + break; + } + } + } + } + FindClose(SearchRec); + if (retval) + { + retval = ::RemoveDir(ADirName); // VCL function + } + return retval; +} + +UnicodeString FormatDateTimeSpan(const UnicodeString & /*TimeFormat*/, const TDateTime & DateTime) +{ + UnicodeString Result; + if (static_cast(DateTime) > 0) + { + Result = ::IntToStr(static_cast((double)DateTime)) + L", "; + } + // days are decremented, because when there are to many of them, + // "integer overflow" error occurs +#if defined(__BORLANDC__) + Result += FormatDateTime(TimeFormat, DateTime - int(DateTime)); +#else + TDateTime dt(DateTime - static_cast(DateTime)); + uint16_t H, M, S, MS; + dt.DecodeTime(H, M, S, MS); + Result += FORMAT(L"%02d:%02d:%02d", H, M, S); +#endif + return Result; +} + +TLocalCustomCommand::TLocalCustomCommand() +{ +} + +TLocalCustomCommand::TLocalCustomCommand(const TCustomCommandData & Data, + const UnicodeString & RemotePath, const UnicodeString & LocalPath) : + TFileCustomCommand(Data, RemotePath) +{ + FLocalPath = LocalPath; +} + +TLocalCustomCommand::TLocalCustomCommand(const TCustomCommandData & Data, + const UnicodeString & RemotePath, const UnicodeString & LocalPath, + const UnicodeString & AFileName, const UnicodeString & LocalFileName, const UnicodeString & FileList) : + TFileCustomCommand(Data, RemotePath, AFileName, FileList) +{ + FLocalPath = LocalPath; + FLocalFileName = LocalFileName; +} + +intptr_t TLocalCustomCommand::PatternLen(const UnicodeString & Command, intptr_t Index) const +{ + intptr_t Len = 0; + if (Command[Index + 1] == L'^') + { + Len = 3; + } + else + { + Len = TFileCustomCommand::PatternLen(Command, Index); + } + return Len; +} + +bool TLocalCustomCommand::PatternReplacement( + intptr_t Index, const UnicodeString & Pattern, UnicodeString & Replacement, bool & Delimit) const +{ + bool Result = false; + if (Pattern == L"!\\") + { + // When used as "!\" in an argument to PowerShell, the trailing \ would escpae the ", + // so we exclude it + Replacement = ::ExcludeTrailingBackslash(FLocalPath); + Result = true; + } + else if (Pattern == L"!^!") + { + Replacement = FLocalFileName; + Result = true; + } + else + { + Result = TFileCustomCommand::PatternReplacement(Index, Pattern, Replacement, Delimit); + } + return Result; +} + +void TLocalCustomCommand::DelimitReplacement( + UnicodeString & /*Replacement*/, wchar_t /*Quote*/) +{ + // never delimit local commands +} + +bool TLocalCustomCommand::HasLocalFileName(const UnicodeString & Command) const +{ + return FindPattern(Command, L'^'); +} + +bool TLocalCustomCommand::IsFileCommand(const UnicodeString & Command) const +{ + return TFileCustomCommand::IsFileCommand(Command) || HasLocalFileName(Command); +} + diff --git a/netbox/src/windows/GUITools.h b/netbox/src/windows/GUITools.h new file mode 100644 index 000000000..c94559841 --- /dev/null +++ b/netbox/src/windows/GUITools.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +// from shlobj.h +#define CSIDL_DESKTOP 0x0000 // +#define CSIDL_SENDTO 0x0009 // \SendTo +#define CSIDL_DESKTOPDIRECTORY 0x0010 // \Desktop +#define CSIDL_COMMON_DESKTOPDIRECTORY 0x0019 // All Users\Desktop +#define CSIDL_APPDATA 0x001a // \Application Data +#define CSIDL_PROGRAM_FILES 0x0026 // C:\Program Files +#define CSIDL_PERSONAL 0x0005 // My Documents + +#include + +class TSessionData; + +DEFINE_CALLBACK_TYPE0(TProcessMessagesEvent, void); + +bool FindFile(UnicodeString & APath); +bool FindTool(const UnicodeString & Name, UnicodeString & APath); +bool FileExistsEx(const UnicodeString & APath); +bool ExecuteShell(const UnicodeString & APath, const UnicodeString & Params); +bool ExecuteShell(const UnicodeString & APath, const UnicodeString & Params, + HANDLE & Handle); +bool ExecuteShellAndWait(HINSTANCE Handle, const UnicodeString & APath, + const UnicodeString & Params, TProcessMessagesEvent ProcessMessages); +bool ExecuteShellAndWait(HINSTANCE Handle, const UnicodeString & Command, + TProcessMessagesEvent ProcessMessages); +void OpenSessionInPutty(const UnicodeString & PuttyPath, + TSessionData * SessionData); +bool SpecialFolderLocation(int PathID, UnicodeString & APath); +UnicodeString GetPersonalFolder(); +UnicodeString ItemsFormatString(const UnicodeString & SingleItemFormat, + const UnicodeString & MultiItemsFormat, intptr_t Count, const UnicodeString & FirstItem); +UnicodeString ItemsFormatString(const UnicodeString & SingleItemFormat, + const UnicodeString & MultiItemsFormat, const TStrings * Items); +UnicodeString FileNameFormatString(const UnicodeString & SingleFileFormat, + const UnicodeString & MultiFileFormat, const TStrings * AFiles, bool Remote); +UnicodeString UniqTempDir(const UnicodeString & BaseDir, + const UnicodeString & Identity, bool Mask = false); +bool DeleteDirectory(const UnicodeString & ADirName); +UnicodeString FormatDateTimeSpan(const UnicodeString & TimeFormat, const TDateTime & DateTime); + +class TLocalCustomCommand : public TFileCustomCommand +{ +public: + TLocalCustomCommand(); + explicit TLocalCustomCommand( + const TCustomCommandData & Data, const UnicodeString & RemotePath, const UnicodeString & LocalPath); + explicit TLocalCustomCommand( + const TCustomCommandData & Data, const UnicodeString & RemotePath, const UnicodeString & LocalPath, + const UnicodeString & AFileName, const UnicodeString & LocalFileName, + const UnicodeString & FileList); + virtual ~TLocalCustomCommand() {} + + virtual bool IsFileCommand(const UnicodeString & Command) const; + bool HasLocalFileName(const UnicodeString & Command) const; + +protected: + virtual intptr_t PatternLen(const UnicodeString & Command, intptr_t Index) const; + virtual bool PatternReplacement(intptr_t Index, const UnicodeString & Pattern, + UnicodeString & Replacement, bool & Delimit) const; + virtual void DelimitReplacement(UnicodeString & Replacement, wchar_t Quote); + +private: + UnicodeString FLocalPath; + UnicodeString FLocalFileName; +}; + +#if 0 +void ValidateMaskEdit(TFarComboBox * Edit); +void ValidateMaskEdit(TFarEdit * Edit); +#endif + +#define PageantTool L"pageant.exe" +#define PuttygenTool L"puttygen.exe" + diff --git a/netbox/src/windows/ProgParams.cpp b/netbox/src/windows/ProgParams.cpp new file mode 100644 index 000000000..ec6898796 --- /dev/null +++ b/netbox/src/windows/ProgParams.cpp @@ -0,0 +1,62 @@ +#include +#pragma hdrstop + +#include +#include "ProgParams.h" + +// unique_ptr-like class +class TProgramParamsOwner : public TObject +{ +NB_DISABLE_COPY(TProgramParamsOwner) +public: + TProgramParamsOwner() : + FProgramParams(nullptr) + { + } + + ~TProgramParamsOwner() + { + SAFE_DESTROY(FProgramParams); + } + + TProgramParams * Get() + { + if (FProgramParams == nullptr) + { + FProgramParams = new TProgramParams(); + } + return FProgramParams; + } + +private: + TProgramParams * FProgramParams; +}; + +// TProgramParamsOwner ProgramParamsOwner; + +// TProgramParams * TProgramParams::Instance() +// { + // return ProgramParamsOwner.Get(); +// } + +TProgramParams::TProgramParams() +{ + Init(L""); +} + +TProgramParams::TProgramParams(const UnicodeString & CmdLine) +{ + Init(CmdLine); +} + +void TProgramParams::Init(const UnicodeString & CmdLine) +{ + UnicodeString CommandLine = CmdLine; + + UnicodeString Param; + CutToken(CommandLine, Param); + while (CutToken(CommandLine, Param)) + { + Add(Param); + } +} diff --git a/netbox/src/windows/ProgParams.h b/netbox/src/windows/ProgParams.h new file mode 100644 index 000000000..cd3878fde --- /dev/null +++ b/netbox/src/windows/ProgParams.h @@ -0,0 +1,17 @@ +#pragma once +#define ProgParamsH + +#include + +class TProgramParams : public TOptions +{ +public: + // static TProgramParams * Instance(); + + explicit TProgramParams(); + explicit TProgramParams(const UnicodeString & CmdLine); + +private: + void Init(const UnicodeString & CmdLine); +}; + diff --git a/netbox/src/windows/SynchronizeController.cpp b/netbox/src/windows/SynchronizeController.cpp new file mode 100644 index 000000000..a0957b69e --- /dev/null +++ b/netbox/src/windows/SynchronizeController.cpp @@ -0,0 +1,267 @@ +#include +#pragma hdrstop + +#include +#include +#include +#include +#include +#include "GUIConfiguration.h" +#include "CoreMain.h" +#include "TextsCore.h" +#include "SynchronizeController.h" + +TSynchronizeController::TSynchronizeController( + TSynchronizeEvent AOnSynchronize, TSynchronizeInvalidEvent AOnSynchronizeInvalid, + TSynchronizeTooManyDirectoriesEvent AOnTooManyDirectories) : + FOnSynchronize(AOnSynchronize), + FOptions(nullptr), + FOnSynchronizeThreads(nullptr), + FSynchronizeMonitor(nullptr), + FSynchronizeAbort(nullptr), + FOnSynchronizeInvalid(AOnSynchronizeInvalid), + FOnTooManyDirectories(AOnTooManyDirectories), + FSynchronizeLog(nullptr) +{ + FSynchronizeParams.Params = 0; + FSynchronizeParams.Options = 0; +} + +TSynchronizeController::~TSynchronizeController() +{ + DebugAssert(FSynchronizeMonitor == nullptr); +} + +void TSynchronizeController::StartStop(TObject * /*Sender*/, + bool Start, const TSynchronizeParamType & Params, const TCopyParamType & CopyParam, + TSynchronizeOptions * Options, + TSynchronizeAbortEvent OnAbort, TSynchronizeThreadsEvent /*OnSynchronizeThreads*/, + TSynchronizeLogEvent OnSynchronizeLog) +{ + if (Start) + { + // Configuration->GetUsage()->Inc(L"KeepUpToDates"); + + try + { + DebugAssert(OnSynchronizeLog != nullptr); + FSynchronizeLog = OnSynchronizeLog; + + FOptions = Options; + if (FLAGSET(Params.Options, soSynchronize) && + (FOnSynchronize != nullptr)) + { + FOnSynchronize(this, Params.LocalDirectory, + Params.RemoteDirectory, CopyParam, + Params, nullptr, FOptions, /*Full=*/true); + } + + FCopyParam = CopyParam; + FSynchronizeParams = Params; + + DebugAssert(OnAbort); + FSynchronizeAbort = OnAbort; + + if (FLAGSET(FSynchronizeParams.Options, soRecurse)) + { + SynchronizeLog(slScan, + FMTLOAD(SYNCHRONIZE_SCAN, FSynchronizeParams.LocalDirectory.c_str())); + } + ThrowNotImplemented(256); + /* + // FIXME + FSynchronizeMonitor = new TDiscMonitor(NB_STATIC_DOWNCAST(TComponent, Sender)); + FSynchronizeMonitor->SubTree = false; + TMonitorFilters Filters; + Filters << moFilename << moLastWrite; + if (FLAGSET(FSynchronizeParams.Options, soRecurse)) + { + Filters << moDirName; + } + FSynchronizeMonitor->Filters = Filters; + FSynchronizeMonitor->MaxDirectories = 0; + FSynchronizeMonitor->ChangeDelay = GetGUIConfiguration()->GetKeepUpToDateChangeDelay(); + FSynchronizeMonitor->OnTooManyDirectories = SynchronizeTooManyDirectories; + FSynchronizeMonitor->OnDirectoriesChange = SynchronizeDirectoriesChange; + FSynchronizeMonitor->OnFilter = SynchronizeFilter; + FSynchronizeMonitor->AddDirectory(FSynchronizeParams.LocalDirectory, + FLAGSET(FSynchronizeParams.Options, soRecurse)); + FSynchronizeMonitor->OnChange = SynchronizeChange; + FSynchronizeMonitor->OnInvalid = SynchronizeInvalid; + FSynchronizeMonitor->OnSynchronize = OnSynchronizeThreads; + // get count before open to avoid thread issues + int Directories = FSynchronizeMonitor->Directories->Count; + FSynchronizeMonitor->Open(); + SynchronizeLog(slStart, FMTLOAD(SYNCHRONIZE_START, Directories)); + */ + } + catch (...) + { + // FIXME SAFE_DESTROY(FSynchronizeMonitor); + ThrowNotImplemented(257); + throw; + } + } + else + { + FOptions = nullptr; + // SAFE_DESTROY(FSynchronizeMonitor); + } +} + +void TSynchronizeController::SynchronizeChange( + TObject * /*Sender*/, const UnicodeString & Directory, bool & SubdirsChanged) +{ + try + { + UnicodeString RemoteDirectory; + UnicodeString RootLocalDirectory; + RootLocalDirectory = ::IncludeTrailingBackslash(FSynchronizeParams.LocalDirectory); + RemoteDirectory = core::UnixIncludeTrailingBackslash(FSynchronizeParams.RemoteDirectory); + + UnicodeString LocalDirectory = ::IncludeTrailingBackslash(Directory); + + DebugAssert(LocalDirectory.SubString(1, RootLocalDirectory.Length()) == + RootLocalDirectory); + RemoteDirectory = RemoteDirectory + + core::ToUnixPath(LocalDirectory.SubString(RootLocalDirectory.Length() + 1, + LocalDirectory.Length() - RootLocalDirectory.Length())); + + SynchronizeLog(slChange, FMTLOAD(SYNCHRONIZE_CHANGE, + ::ExcludeTrailingBackslash(LocalDirectory).c_str())); + + if (FOnSynchronize != nullptr) + { + // this is completely wrong as the options structure + // can contain non-root specific options in future + TSynchronizeOptions * Options = + ((LocalDirectory == RootLocalDirectory) ? FOptions : nullptr); + TSynchronizeChecklist * Checklist = nullptr; + FOnSynchronize(this, LocalDirectory, RemoteDirectory, FCopyParam, + FSynchronizeParams, &Checklist, Options, false); + if (Checklist != nullptr) + { + std::unique_ptr ChecklistPtr(Checklist); + (void)ChecklistPtr; + if (FLAGSET(FSynchronizeParams.Options, soRecurse)) + { + SubdirsChanged = false; + DebugAssert(Checklist != nullptr); + for (intptr_t Index = 0; Index < Checklist->GetCount(); ++Index) + { + const TChecklistItem * Item = Checklist->GetItem(Index); + // note that there may be action saDeleteRemote even if nothing has changed + // so this is sub-optimal + if (Item->IsDirectory) + { + if ((Item->Action == saUploadNew) || + (Item->Action == saDeleteRemote)) + { + SubdirsChanged = true; + break; + } + else + { + DebugAssert(false); + } + } + } + } + else + { + SubdirsChanged = false; + } + } + } + } + catch (Exception & E) + { + SynchronizeAbort(NB_STATIC_DOWNCAST(EFatal, &E) != nullptr); + } +} + +void TSynchronizeController::SynchronizeAbort(bool Close) +{ + if (FSynchronizeMonitor != nullptr) + { + // FIXME FSynchronizeMonitor->Close(); + ThrowNotImplemented(258); + } + DebugAssert(FSynchronizeAbort); + FSynchronizeAbort(nullptr, Close); +} + +void TSynchronizeController::LogOperation(TSynchronizeOperation Operation, + const UnicodeString & AFileName) +{ + TSynchronizeLogEntry Entry; + UnicodeString Message; + switch (Operation) + { + case soDelete: + Entry = slDelete; + Message = FMTLOAD(SYNCHRONIZE_DELETED, AFileName.c_str()); + break; + + default: + DebugAssert(false); + // fallthru + + case soUpload: + Entry = slUpload; + Message = FMTLOAD(SYNCHRONIZE_UPLOADED, AFileName.c_str()); + break; + } + SynchronizeLog(Entry, Message); +} + +void TSynchronizeController::SynchronizeLog(TSynchronizeLogEntry Entry, + const UnicodeString & Message) +{ + if (FSynchronizeLog != nullptr) + { + FSynchronizeLog(this, Entry, Message); + } +} + +void TSynchronizeController::SynchronizeFilter(TObject * /*Sender*/, + const UnicodeString & DirectoryName, bool & Add) +{ + if ((FOptions != nullptr) && (FOptions->Filter != nullptr)) + { + if (::IncludeTrailingBackslash(::ExtractFilePath(DirectoryName)) == + ::IncludeTrailingBackslash(FSynchronizeParams.LocalDirectory)) + { + intptr_t FoundIndex; + Add = FOptions->Filter->Find(base::ExtractFileName(DirectoryName, /*Unix=*/true), FoundIndex); + } + } + TFileMasks::TParams MaskParams; // size/time does not matter for directories + Add = Add && FCopyParam.AllowTransfer(DirectoryName, osLocal, /*Directory=*/true, MaskParams); +} + +void TSynchronizeController::SynchronizeInvalid( + TObject * /*Sender*/, const UnicodeString & Directory, const UnicodeString & ErrorStr) +{ + if (FOnSynchronizeInvalid != nullptr) + { + FOnSynchronizeInvalid(this, Directory, ErrorStr); + } + + SynchronizeAbort(false); +} + +void TSynchronizeController::SynchronizeTooManyDirectories( + TObject * /*Sender*/, intptr_t & MaxDirectories) +{ + if (FOnTooManyDirectories != nullptr) + { + FOnTooManyDirectories(this, MaxDirectories); + } +} + +void TSynchronizeController::SynchronizeDirectoriesChange( + TObject * /*Sender*/, intptr_t Directories) +{ + SynchronizeLog(slDirChange, FMTLOAD(SYNCHRONIZE_START, Directories)); +} diff --git a/netbox/src/windows/SynchronizeController.h b/netbox/src/windows/SynchronizeController.h new file mode 100644 index 000000000..3ba680c99 --- /dev/null +++ b/netbox/src/windows/SynchronizeController.h @@ -0,0 +1,98 @@ +#pragma once + +#include + +struct TSynchronizeParamType : public TObject +{ + UnicodeString LocalDirectory; + UnicodeString RemoteDirectory; + intptr_t Params; + intptr_t Options; +}; + +class TSynchronizeController; +struct TSynchronizeOptions; +class TSynchronizeChecklist; +DEFINE_CALLBACK_TYPE2(TSynchronizeAbortEvent, void, + TObject * /*Sender*/, bool /*Close*/); +DEFINE_CALLBACK_TYPE2(TSynchronizeThreadsEvent, void, + TObject * /*Sender*/, TThreadMethod /*Method*/); +enum TSynchronizeLogEntry +{ + slScan, + slStart, + slChange, + slUpload, + slDelete, + slDirChange +}; + +DEFINE_CALLBACK_TYPE3(TSynchronizeLogEvent, void, + TSynchronizeController * /*Controller*/, TSynchronizeLogEntry /*Entry*/, + const UnicodeString & /*Message*/); +DEFINE_CALLBACK_TYPE8(TSynchronizeStartStopEvent, void, + TObject * /*Sender*/, bool /*Start*/, const TSynchronizeParamType & /*Params*/, + const TCopyParamType & /*CopyParam*/, TSynchronizeOptions * /*Options*/, + TSynchronizeAbortEvent /*OnAbort*/, TSynchronizeThreadsEvent /*OnSynchronizeThreads*/, + TSynchronizeLogEvent /*OnSynchronizeLog*/); +DEFINE_CALLBACK_TYPE8(TSynchronizeEvent, void, + TSynchronizeController * /*Sender*/, const UnicodeString & /*LocalDirectory*/, + const UnicodeString & /*RemoteDirectory*/, const TCopyParamType & /*CopyParam*/, + const TSynchronizeParamType & /*Params*/, TSynchronizeChecklist ** /*Checklist*/, + TSynchronizeOptions * /*Options*/, bool /*Full*/); +DEFINE_CALLBACK_TYPE3(TSynchronizeInvalidEvent, void, + TSynchronizeController * /*Sender*/, const UnicodeString & /*Directory*/, + const UnicodeString & /*ErrorStr*/); +DEFINE_CALLBACK_TYPE2(TSynchronizeTooManyDirectoriesEvent, void, + TSynchronizeController * /*Sender*/, intptr_t & /*MaxDirectories*/); + +namespace Discmon { +class TDiscMonitor; +} + +enum TSynchronizeOperation +{ + soUpload, + soDelete +}; + +class TSynchronizeController : public TObject +{ +NB_DISABLE_COPY(TSynchronizeController) +public: + explicit TSynchronizeController(TSynchronizeEvent AOnSynchronize, + TSynchronizeInvalidEvent AOnSynchronizeInvalid, + TSynchronizeTooManyDirectoriesEvent AOnTooManyDirectories); + virtual ~TSynchronizeController(); + + void StartStop(TObject * Sender, bool Start, + const TSynchronizeParamType & Params, const TCopyParamType & CopyParam, + TSynchronizeOptions * Options, + TSynchronizeAbortEvent OnAbort, TSynchronizeThreadsEvent OnSynchronizeThreads, + TSynchronizeLogEvent OnSynchronizeLog); + void LogOperation(TSynchronizeOperation Operation, const UnicodeString & AFileName); + +private: + TSynchronizeEvent FOnSynchronize; + TSynchronizeParamType FSynchronizeParams; + TSynchronizeOptions * FOptions; + TSynchronizeThreadsEvent FOnSynchronizeThreads; + Discmon::TDiscMonitor * FSynchronizeMonitor; + TSynchronizeAbortEvent FSynchronizeAbort; + TSynchronizeInvalidEvent FOnSynchronizeInvalid; + TSynchronizeTooManyDirectoriesEvent FOnTooManyDirectories; + TSynchronizeLogEvent FSynchronizeLog; + TCopyParamType FCopyParam; + + void SynchronizeChange(TObject * Sender, const UnicodeString & Directory, + bool & SubdirsChanged); + void SynchronizeAbort(bool Close); + void SynchronizeLog(TSynchronizeLogEntry Entry, const UnicodeString & Message); + void SynchronizeInvalid(TObject * Sender, const UnicodeString & Directory, + const UnicodeString & ErrorStr); + void SynchronizeFilter(TObject * Sender, const UnicodeString & DirectoryName, + bool & Add); + void SynchronizeTooManyDirectories(TObject * Sender, intptr_t & MaxDirectories); + void SynchronizeDirectoriesChange(TObject * Sender, intptr_t Directories); +}; + diff --git a/netbox/src/windows/Tools.cpp b/netbox/src/windows/Tools.cpp new file mode 100644 index 000000000..4d0a3c10a --- /dev/null +++ b/netbox/src/windows/Tools.cpp @@ -0,0 +1,397 @@ +#include +#pragma hdrstop + +#ifndef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "WinInterface.h" +#include "GUITools.h" +#include "PuttyTools.h" +#include "Tools.h" + +TOptions * GetGlobalOptions() +{ + return nullptr; // TProgramParams::Instance(); +} + +bool SaveDialog(const UnicodeString & ATitle, const UnicodeString & Filter, + const UnicodeString & ADefaultExt, UnicodeString & AFileName) +{ + bool Result = false; + DebugUsedParam(Filter); + DebugUsedParam(ADefaultExt); + + Result = InputDialog(ATitle, L""/*LoadStr(LOGIN_PRIVATE_KEY)*/, AFileName, L"", nullptr, true, nullptr, true); + return Result; +} + +#if 0 +// implemented in FarInterface.cpp +void CopyToClipboard(const UnicodeString & Text) +{ + HANDLE Data; + void * DataPtr; + + if (OpenClipboard(0)) + { + try__finally + { + SCOPE_EXIT + { + CloseClipboard(); + }; + size_t Size = (Text.Length() + 1) * sizeof(wchar_t); + Data = GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, Size); + try + { + SCOPE_EXIT + { + GlobalUnlock(Data); + }; + DataPtr = GlobalLock(Data); + try__finally + { + SCOPE_EXIT + { + GlobalUnlock(Data); + }; + memcpy(DataPtr, Text.c_str(), Size); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, Data); + } + __finally + { + GlobalUnlock(Data); + }; + } + catch (...) + { + GlobalFree(Data); + throw; + } + } + __finally + { + CloseClipboard(); + }; + } + else + { + throw Exception(SCannotOpenClipboard); + } +} +#endif + +void CopyToClipboard(TStrings * Strings) +{ + if (Strings->GetCount() > 0) + { + CopyToClipboard(StringsToText(Strings)); + } +} + +bool IsWin64() +{ + static int Result = -1; +#ifndef __linux__ + if (Result < 0) + { + Result = 0; + BOOL Wow64Process = FALSE; + if (::IsWow64Process(::GetCurrentProcess(), &Wow64Process)) + { + if (Wow64Process) + { + Result = 1; + } + } + } +#endif + return (Result > 0); +} + +static void ConvertKey(UnicodeString & FileName, TKeyType Type) +{ + UnicodeString Passphrase; + + UnicodeString Comment; + if (IsKeyEncrypted(Type, FileName, Comment)) + { + if (!InputDialog( + LoadStr(PASSPHRASE_TITLE), + FORMAT(LoadStr(PROMPT_KEY_PASSPHRASE).c_str(), Comment.c_str()), + Passphrase, HELP_NONE, nullptr, false, nullptr, false)) + { + Abort(); + } + } + + TPrivateKey * PrivateKey = LoadKey(Type, FileName, Passphrase); + + try__finally + { + SCOPE_EXIT + { + FreeKey(PrivateKey); + }; + FileName = ChangeFileExt(FileName, ".ppk", L'\\'); + + if (!SaveDialog(LoadStr(CONVERTKEY_SAVE_TITLE), LoadStr(CONVERTKEY_SAVE_FILTER), L"ppk", FileName)) + { + Abort(); + } + + SaveKey(ktSSH2, FileName, Passphrase, PrivateKey); + + MessageDialog(MainInstructions(FMTLOAD(CONVERTKEY_SAVED, FileName.c_str())), qtInformation, qaOK); + } + __finally + { + FreeKey(PrivateKey); + }; +} + +static bool DoVerifyKey( + const UnicodeString & AFileName, bool TypeOnly, TSshProt SshProt, bool Convert) +{ + bool Result = true; + if (!AFileName.Trim().IsEmpty()) + { + UnicodeString FileName = ::ExpandEnvironmentVariables(AFileName); + TKeyType Type = GetKeyType(FileName); + // reason _wfopen failed + int Error = errno; + UnicodeString Message; + UnicodeString HelpKeyword; // = HELP_LOGIN_KEY_TYPE; + UnicodeString PuttygenPath; + std::unique_ptr MoreMessages; + switch (Type) + { + case ktOpenSSHPEM: + case ktOpenSSHNew: + case ktSSHCom: + { + UnicodeString TypeName = (Type == ktOpenSSHAuto) ? L"OpenSSH SSH-2" : L"ssh.com SSH-2"; + Message = FMTLOAD(KEY_TYPE_UNSUPPORTED2, AFileName.c_str(), TypeName.c_str()); + + if (Convert) + { + // Configuration->Usage->Inc(L"PrivateKeyConvertSuggestionsNative"); + UnicodeString ConvertMessage = FMTLOAD(KEY_TYPE_CONVERT3, TypeName.c_str(), RemoveMainInstructionsTag(Message).c_str()); + Message = UnicodeString(); + if (MoreMessageDialog(ConvertMessage, nullptr, qtConfirmation, qaOK | qaCancel, HelpKeyword) == qaOK) + { + ConvertKey(FileName, Type); + // Configuration->Usage->Inc(L"PrivateKeyConverted"); + Result = true; + } + else + { + Abort(); + } + } + else + { + HelpKeyword = ""; // HELP_KEY_TYPE_UNSUPPORTED; + } + } + break; + + case ktSSH1: + case ktSSH2: + // on file select do not check for SSH version as user may + // intend to change it only after he/she selects key file + if (!TypeOnly) + { + if ((Type == ktSSH1) != + ((SshProt == ssh1only) || (SshProt == ssh1deprecated))) + { + Message = + MainInstructions( + FMTLOAD(KEY_TYPE_DIFFERENT_SSH, + AFileName.c_str(), (Type == ktSSH1 ? L"SSH-1" : L"PuTTY SSH-2"))); + } + } + break; + + case ktSSH1Public: + case ktSSH2PublicRFC4716: + case ktSSH2PublicOpenSSH: + // noop + // Do not even bother checking SSH protocol version + Result = true; + break; + + case ktUnopenable: + Message = MainInstructions(FMTLOAD(KEY_TYPE_UNOPENABLE, AFileName.c_str())); + if (Error != ERROR_SUCCESS) + { + MoreMessages.reset(TextToStringList(SysErrorMessageForError(Error))); + } + break; + + default: + DebugFail(); + // fallthru + case ktUnknown: + Message = MainInstructions(FMTLOAD(KEY_TYPE_UNKNOWN2, AFileName.c_str())); + break; + } + + if (!Message.IsEmpty()) + { + // Configuration->Usage->Inc(L"PrivateKeySelectErrors"); + if (MoreMessageDialog(Message, MoreMessages.get(), qtWarning, qaIgnore | qaAbort, HelpKeyword) == qaAbort) + { + Abort(); + } + Result = true; + } + } + return Result; +} + +bool VerifyAndConvertKey(const UnicodeString & AFileName, bool TypeOnly) +{ + return DoVerifyKey(AFileName, TypeOnly, TSshProt(0), true); +} + +bool VerifyKey(const UnicodeString & AFileName, bool TypeOnly) +{ + return DoVerifyKey(AFileName, TypeOnly, TSshProt(0), false); +} + +void VerifyKeyIncludingVersion(const UnicodeString & AFileName, TSshProt SshProt) +{ + DoVerifyKey(AFileName, false, SshProt, false); +} + +void VerifyCertificate(const UnicodeString & AFileName) +{ + if (!AFileName.Trim().IsEmpty()) + { + try + { + CheckCertificate(AFileName); + } + catch (Exception & E) + { + if (ExceptionMessageDialog(&E, qtWarning, L"", qaIgnore | qaAbort) == qaAbort) + { + Abort(); + } + } + } +} + +#ifndef __linux__ +// Code from http://gentoo.osuosl.org/distfiles/cl331.zip/io/ + +// this was moved to global scope in past in some attempt to fix crashes, +// not sure it really helped +WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IEProxyInfo; + +static bool GetProxyUrlFromIE(UnicodeString & Proxy) +{ + bool Result = false; + memset(&IEProxyInfo, 0, sizeof(IEProxyInfo)); + if (WinHttpGetIEProxyConfigForCurrentUser(&IEProxyInfo)) + { + if (IEProxyInfo.lpszProxy != NULL) + { + UnicodeString IEProxy = IEProxyInfo.lpszProxy; + Proxy = L""; + while (Proxy.IsEmpty() && !IEProxy.IsEmpty()) + { + UnicodeString Str = CutToChar(IEProxy, L';', true); + if (Str.Pos(L"=") == 0) + { + Proxy = Str; + } + else + { + UnicodeString Protocol = CutToChar(Str, L'=', true); + if (SameText(Protocol, L"http")) + { + Proxy = Str; + } + } + } + + GlobalFree(IEProxyInfo.lpszProxy); + Result = true; + } + if (IEProxyInfo.lpszAutoConfigUrl != NULL) + { + GlobalFree(IEProxyInfo.lpszAutoConfigUrl); + } + if (IEProxyInfo.lpszProxyBypass != NULL) + { + GlobalFree(IEProxyInfo.lpszProxyBypass); + } + } + return Result; +} +#endif + +bool AutodetectProxy(UnicodeString & AHostName, intptr_t & APortNumber) +{ + bool Result = false; +#ifndef __linux__ + /* First we try for proxy info direct from the registry if + it's available. */ + UnicodeString Proxy; + WINHTTP_PROXY_INFO ProxyInfo; + memset(&ProxyInfo, 0, sizeof(ProxyInfo)); + if (WinHttpGetDefaultProxyConfiguration(&ProxyInfo)) + { + if (ProxyInfo.lpszProxy != NULL) + { + Proxy = ProxyInfo.lpszProxy; + GlobalFree(ProxyInfo.lpszProxy); + Result = true; + } + if (ProxyInfo.lpszProxyBypass != NULL) + { + GlobalFree(ProxyInfo.lpszProxyBypass); + } + } + + /* The next fallback is to get the proxy info from MSIE. This is also + usually much quicker than WinHttpGetProxyForUrl(), although sometimes + it seems to fall back to that, based on the longish delay involved. + Another issue with this is that it won't work in a service process + that isn't impersonating an interactive user (since there isn't a + current user), but in that case we just fall back to + WinHttpGetProxyForUrl() */ + if (!Result) + { + Result = GetProxyUrlFromIE(Proxy); + } + + if (Result) + { + if (Proxy.Trim().IsEmpty()) + { + Result = false; + } + else + { + AHostName = CutToChar(Proxy, L':', true); + APortNumber = StrToIntDef(Proxy, ProxyPortNumber); + } + } + + // We can also use WinHttpGetProxyForUrl, but it is lengthy + // See the source address of the code for example +#endif + return Result; +} diff --git a/netbox/src/windows/Tools.h b/netbox/src/windows/Tools.h new file mode 100644 index 000000000..e548fba22 --- /dev/null +++ b/netbox/src/windows/Tools.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +bool SaveDialog(const UnicodeString & ATitle, const UnicodeString & Filter, + const UnicodeString & ADefaultExt, UnicodeString & AFileName); + +bool AutodetectProxy(UnicodeString & AHostName, intptr_t & APortNumber); +bool IsWin64(); +void CopyToClipboard(const UnicodeString & AText); +void CopyToClipboard(TStrings * Strings); + +bool VerifyAndConvertKey(const UnicodeString & AFileName, bool TypeOnly); +bool VerifyKey(const UnicodeString & AFileName, bool TypeOnly); +void VerifyKeyIncludingVersion(const UnicodeString & AFileName, TSshProt SshProt); +void VerifyCertificate(const UnicodeString & AFileName); diff --git a/netbox/src/windows/WinInterface.cpp b/netbox/src/windows/WinInterface.cpp new file mode 100644 index 000000000..575999794 --- /dev/null +++ b/netbox/src/windows/WinInterface.cpp @@ -0,0 +1,1313 @@ +#include +#include + +static bool IsPositiveAnswer(uintptr_t Answer) +{ + return (Answer == qaYes) || (Answer == qaOK) || (Answer == qaYesToAll); +} + +TMessageParams::TMessageParams(uintptr_t AParams) : + Aliases(nullptr), + AliasesCount(0), + Flags(0), + Params(0), + Timer(0), + TimerEvent(nullptr), + TimerAnswers(0), + Timeout(0), + TimeoutAnswer(0), + NeverAskAgainAnswer(0), + NeverAskAgainCheckedInitially(false), + AllowHelp(false) +{ +} + +void TMessageParams::Assign(const TMessageParams * AParams) +{ + Aliases = AParams->Aliases; + AliasesCount = AParams->AliasesCount; + Flags = AParams->Flags; + Params = AParams->Params; + Timer = AParams->Timer; + TimerEvent = AParams->TimerEvent; + TimerMessage = AParams->TimerMessage; + TimerAnswers = AParams->TimerAnswers; + Timeout = AParams->Timeout; + TimeoutAnswer = AParams->TimeoutAnswer; +} + +inline void TMessageParams::Reset() +{ + Params = 0; + Aliases = NULL; + AliasesCount = 0; + Timer = 0; + TimerEvent = NULL; + TimerMessage = L""; + TimerAnswers = 0; + TimerQueryType = static_cast(-1); + Timeout = 0; + TimeoutAnswer = 0; + NeverAskAgainTitle = L""; + NeverAskAgainAnswer = 0; + NeverAskAgainCheckedInitially = false; + AllowHelp = true; + ImageName = L""; + MoreMessagesUrl = L""; + MoreMessagesSize = TSize(); + CustomCaption = L""; +} + +#if 0 +static void NeverAskAgainCheckClick(void * /*Data*/, TObject * Sender) +{ + TFarCheckBox * CheckBox = NB_STATIC_DOWNCAST(TFarCheckBox, Sender); + DebugAssert(CheckBox != nullptr); + TFarDialog * Dialog = NB_STATIC_DOWNCAST(TFarDialog, CheckBox->GetOwner()); + DebugAssert(Dialog != nullptr); + + uintptr_t PositiveAnswer = 0; + + if (CheckBox->GetChecked()) + { + if (CheckBox->GetTag() > 0) + { + PositiveAnswer = CheckBox->GetTag(); + } + else + { + for (int ii = 0; ii < Dialog->GetControlCount(); ii++) + { + TFarButton * Button = NB_STATIC_DOWNCAST(TFarButton, Dialog->GetControl(ii)); + if (Button != nullptr) + { + if (IsPositiveAnswer(Button->GetModalResult())) + { + PositiveAnswer = Button->GetModalResult(); + break; + } + } + } + } + + DebugAssert(PositiveAnswer != 0); + } + + for (int ii = 0; ii < Dialog->GetControlCount(); ii++) + { + TFarButton * Button = NB_STATIC_DOWNCAST(TFarButton, Dialog->GetControl(ii)); + if (Button != nullptr) + { + if ((Button->GetModalResult() != 0) && (Button->GetModalResult() != static_cast(qaCancel))) + { + Button->SetEnabled(!CheckBox->GetChecked() || (Button->GetModalResult() == static_cast(PositiveAnswer))); + } + +#if 0 + if (Button->DropDownMenu != nullptr) + { + for (int iii = 0; iii < Button->DropDownMenu->Items->Count; iii++) + { + TMenuItem * Item = Button->DropDownMenu->Items->Items[iii]; + Item->Enabled = Item->Default || !CheckBox->Checked; + } + } +#endif + } + } +} +#endif + +#if 0 +static TFarCheckBox * FindNeverAskAgainCheck(TFarDialog * Dialog) +{ + return nullptr; // DebugNotNull(NB_STATIC_DOWNCAST(TFarCheckBox, Dialog->FindComponent(L"NeverAskAgainCheck"))); +} + +TFarDialog * CreateMessageDialogEx(const UnicodeString & Msg, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, const UnicodeString & HelpKeyword, + const TMessageParams * Params, TFarButton *& TimeoutButton) +{ + TMsgDlgType DlgType; + switch (Type) + { + case qtConfirmation: DlgType = mtConfirmation; break; + case qtInformation: DlgType = mtInformation; break; + case qtError: DlgType = mtError; break; + case qtWarning: DlgType = mtWarning; break; + default: DebugFail(); + } + + uintptr_t TimeoutAnswer = (Params != nullptr) ? Params->TimeoutAnswer : 0; + + uintptr_t ActualAnswers = Answers; + /*if ((Params == nullptr) || Params->AllowHelp) + { + Answers = Answers | qaHelp; + }*/ + + if (IsInternalErrorHelpKeyword(HelpKeyword)) + { + Answers = Answers | qaReport; + } + + if ((MoreMessages != nullptr) && (MoreMessages->GetCount() == 0)) + { + MoreMessages = nullptr; + } + + UnicodeString ImageName; + UnicodeString MoreMessagesUrl; + + TSize MoreMessagesSize; + UnicodeString CustomCaption; + if (Params != nullptr) + { + ImageName = Params->ImageName; + MoreMessagesUrl = Params->MoreMessagesUrl; + MoreMessagesSize = Params->MoreMessagesSize; + CustomCaption = Params->CustomCaption; + } + + const TQueryButtonAlias * Aliases = (Params != nullptr) ? Params->Aliases : nullptr; + uintptr_t AliasesCount = (Params != nullptr) ? Params->AliasesCount : 0; + + UnicodeString NeverAskAgainCaption; + bool HasNeverAskAgain = (Params != nullptr) && FLAGSET(Params->Params, mpNeverAskAgainCheck); + if (HasNeverAskAgain) + { + NeverAskAgainCaption = + !Params->NeverAskAgainTitle.IsEmpty() ? + (UnicodeString)Params->NeverAskAgainTitle : + // qaOK | qaIgnore is used, when custom "non-answer" button is required + LoadStr(((ActualAnswers == qaOK) || (ActualAnswers == (qaOK | qaIgnore))) ? + MSG_CHECK_NEVER_SHOW_AGAIN : MSG_CHECK_NEVER_ASK_AGAIN); + } + + TFarDialog * Dialog = CreateMoreMessageDialog(Msg, MoreMessages, DlgType, Answers, + Aliases, AliasesCount, TimeoutAnswer, &TimeoutButton, ImageName, NeverAskAgainCaption, + MoreMessagesUrl, MoreMessagesSize, CustomCaption); + + try + { + if (HasNeverAskAgain && DebugAlwaysTrue(Params != nullptr)) + { + TFarCheckBox * NeverAskAgainCheck = FindNeverAskAgainCheck(Dialog); + NeverAskAgainCheck->SetChecked(Params->NeverAskAgainCheckedInitially; + if (Params->NeverAskAgainAnswer > 0) + { + NeverAskAgainCheck->Tag = Params->NeverAskAgainAnswer; + } + TNotifyEvent OnClick; + ((TMethod*)&OnClick)->Code = NeverAskAgainCheckClick; + NeverAskAgainCheck->OnClick = OnClick; + } + + Dialog->HelpKeyword = HelpKeyword; + if (FLAGSET(Answers, qaHelp)) + { + Dialog->BorderIcons = Dialog->BorderIcons << biHelp; + } + ResetSystemSettings(Dialog); + } + catch (...) + { + delete Dialog; + throw; + } + return Dialog; +} + +uintptr_t ExecuteMessageDialog(TForm * Dialog, uintptr_t Answers, const TMessageParams * Params) +{ + FlashOnBackground(); + uintptr_t Answer = Dialog->ShowModal(); + // mrCancel is returned always when X button is pressed, despite + // no Cancel button was on the dialog. Find valid "cancel" answer. + // mrNone is retuned when Windows session is closing (log off) + if ((Answer == mrCancel) || (Answer == mrNone)) + { + Answer = CancelAnswer(Answers); + } + + if ((Params != nullptr) && (Params->Params & mpNeverAskAgainCheck)) + { + TCheckBox * NeverAskAgainCheck = FindNeverAskAgainCheck(Dialog); + + if (NeverAskAgainCheck->Checked) + { + bool PositiveAnswer = + (Params->NeverAskAgainAnswer > 0) ? + (Answer == Params->NeverAskAgainAnswer) : + IsPositiveAnswer(Answer); + if (PositiveAnswer) + { + Answer = qaNeverAskAgain; + } + } + } + + return Answer; +} + +class TMessageTimer : public TTimer +{ +public: + TQueryParamsTimerEvent Event; + TForm * Dialog; + + TMessageTimer(TComponent * AOwner); + +protected: + void DoTimer(TObject * Sender); +}; + +TMessageTimer::TMessageTimer(TComponent * AOwner) : TTimer(AOwner) +{ + Event = nullptr; + OnTimer = DoTimer; + Dialog = nullptr; +} + +void TMessageTimer::DoTimer(TObject * /*Sender*/) +{ + if (Event != nullptr) + { + uintptr_t Result = 0; + Event(Result); + if (Result != 0) + { + Dialog->ModalResult = Result; + } + } +} + +class TMessageTimeout : public TTimer +{ +public: + TMessageTimeout(TComponent * AOwner, uintptr_t Timeout, + TButton * Button); + + void MouseMove(); + void Cancel(); + +protected: + uintptr_t FOrigTimeout; + uintptr_t FTimeout; + TButton * FButton; + UnicodeString FOrigCaption; + TPoint FOrigCursorPos; + + void DoTimer(TObject * Sender); + void UpdateButton(); +}; + +TMessageTimeout::TMessageTimeout(TComponent * AOwner, + uintptr_t Timeout, TButton * Button) : + TTimer(AOwner), FOrigTimeout(Timeout), FTimeout(Timeout), FButton(Button) +{ + OnTimer = DoTimer; + Interval = MSecsPerSec; + FOrigCaption = FButton->Caption; + FOrigCursorPos = Mouse->CursorPos; + UpdateButton(); +} + +void TMessageTimeout::MouseMove() +{ + TPoint CursorPos = Mouse->CursorPos; + int Delta = std::max(std::abs(FOrigCursorPos.X - CursorPos.X), std::abs(FOrigCursorPos.Y - CursorPos.Y)); + + int Threshold = 8; + if (DebugAlwaysTrue(FButton != nullptr)) + { + Threshold = ScaleByTextHeight(FButton, Threshold); + } + + if (Delta > Threshold) + { + FOrigCursorPos = CursorPos; + const uintptr_t SuspendTime = 30 * MSecsPerSec; + FTimeout = std::max(FOrigTimeout, SuspendTime); + UpdateButton(); + } +} + +void TMessageTimeout::Cancel() +{ + Enabled = false; + UpdateButton(); +} + +void TMessageTimeout::UpdateButton() +{ + DebugAssert(FButton != nullptr); + FButton->Caption = + !Enabled ? FOrigCaption : FMTLOAD(TIMEOUT_BUTTON, FOrigCaption.c_str(), int(FTimeout / MSecsPerSec)); +} + +void TMessageTimeout::DoTimer(TObject * /*Sender*/) +{ + if (FTimeout <= Interval) + { + DebugAssert(FButton != nullptr); + TForm * Dialog = dynamic_cast(FButton->Parent); + DebugAssert(Dialog != nullptr); + + Dialog->ModalResult = FButton->ModalResult; + } + else + { + FTimeout -= Interval; + UpdateButton(); + } +} +//--------------------------------------------------------------------- +class TPublicControl : public TControl +{ +friend void MenuPopup(TObject * Sender, const TPoint & MousePos, bool & Handled); +friend void SetTimeoutEvents(TControl * Control, TMessageTimeout * Timeout); +}; +//--------------------------------------------------------------------- +class TPublicWinControl : public TWinControl +{ +friend void SetTimeoutEvents(TControl * Control, TMessageTimeout * Timeout); +}; + +static void MessageDialogMouseMove(void * Data, TObject * /*Sender*/, + TShiftState /*Shift*/, int /*X*/, int /*Y*/) +{ + DebugAssert(Data != nullptr); + TMessageTimeout * Timeout = static_cast(Data); + Timeout->MouseMove(); +} + +static void MessageDialogMouseDown(void * Data, TObject * /*Sender*/, + TMouseButton /*Button*/, TShiftState /*Shift*/, int /*X*/, int /*Y*/) +{ + DebugAssert(Data != nullptr); + TMessageTimeout * Timeout = static_cast(Data); + Timeout->Cancel(); +} + +static void MessageDialogKeyDownUp(void * Data, TObject * /*Sender*/, + Word & /*Key*/, TShiftState /*Shift*/) +{ + DebugAssert(Data != nullptr); + TMessageTimeout * Timeout = static_cast(Data); + Timeout->Cancel(); +} + +void SetTimeoutEvents(TControl * Control, TMessageTimeout * Timeout) +{ + TPublicControl * PublicControl = reinterpret_cast(Control); + DebugAssert(PublicControl->OnMouseMove == nullptr); + PublicControl->OnMouseMove = MakeMethod(Timeout, MessageDialogMouseMove); + DebugAssert(PublicControl->OnMouseDown == nullptr); + PublicControl->OnMouseDown = MakeMethod(Timeout, MessageDialogMouseDown); + + TWinControl * WinControl = dynamic_cast(Control); + if (WinControl != nullptr) + { + TPublicWinControl * PublicWinControl = reinterpret_cast(Control); + DebugAssert(PublicWinControl->OnKeyDown == nullptr); + PublicWinControl->OnKeyDown = MakeMethod(Timeout, MessageDialogKeyDownUp); + DebugAssert(PublicWinControl->OnKeyUp == nullptr); + PublicWinControl->OnKeyUp = MakeMethod(Timeout, MessageDialogKeyDownUp); + + for (int Index = 0; Index < WinControl->ControlCount; Index++) + { + SetTimeoutEvents(WinControl->Controls[Index], Timeout); + } + } +} + +// Merge with CreateMessageDialogEx +TForm * CreateMoreMessageDialogEx(const UnicodeString Message, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, UnicodeString HelpKeyword, const TMessageParams * Params) +{ + std::unique_ptr Dialog; + UnicodeString AMessage = Message; + TMessageTimer * Timer = nullptr; + + if ((Params != nullptr) && (Params->Timer > 0)) + { + Timer = new TMessageTimer(Application); + Timer->Interval = Params->Timer; + Timer->Event = Params->TimerEvent; + if (Params->TimerAnswers > 0) + { + Answers = Params->TimerAnswers; + } + if (Params->TimerQueryType >= 0) + { + Type = Params->TimerQueryType; + } + if (!Params->TimerMessage.IsEmpty()) + { + AMessage = Params->TimerMessage; + } + Timer->Name = L"MessageTimer"; + } + + TButton * TimeoutButton = nullptr; + Dialog.reset( + CreateMessageDialogEx( + AMessage, MoreMessages, Type, Answers, HelpKeyword, Params, TimeoutButton)); + + if (Timer != nullptr) + { + Timer->Dialog = Dialog.get(); + Dialog->InsertComponent(Timer); + } + + if (Params != nullptr) + { + if (Params->Timeout > 0) + { + TMessageTimeout * Timeout = new TMessageTimeout(Application, Params->Timeout, TimeoutButton); + SetTimeoutEvents(Dialog.get(), Timeout); + Timeout->Name = L"MessageTimeout"; + Dialog->InsertComponent(Timeout); + } + } + + return Dialog.release(); +} + +uintptr_t MoreMessageDialog(const UnicodeString & Message, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ + std::unique_ptr Dialog(CreateMoreMessageDialogEx(Message, MoreMessages, Type, Answers, HelpKeyword, Params)); + uintptr_t Result = ExecuteMessageDialog(Dialog.get(), Answers, Params); + return Result; +} + +uintptr_t MessageDialog(const UnicodeString & Msg, TQueryType Type, + uintptr_t Answers, const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ + return MoreMessageDialog(Msg, nullptr, Type, Answers, HelpKeyword, Params); +} + +uintptr_t SimpleErrorDialog(const UnicodeString & Msg, const UnicodeString & MoreMessages) +{ + uintptr_t Result; + TStrings * More = nullptr; + try + { + if (!MoreMessages.IsEmpty()) + { + More = TextToStringList(MoreMessages); + } + Result = MoreMessageDialog(Msg, More, qtError, qaOK, HELP_NONE); + } + __finally + { + delete More; + } + return Result; +} +#endif + +#if 0 +static TStrings * StackInfoListToStrings( + TJclStackInfoList * StackInfoList) +{ + std::unique_ptr StackTrace(new TStringList()); + StackInfoList->AddToStrings(StackTrace.get(), true, false, true, true); + for (int Index = 0; Index < StackTrace->Count; Index++) + { + UnicodeString Frame = StackTrace->Strings[Index]; + // get rid of declarations "flags" that are included in .map + Frame = ReplaceStr(Frame, L"", L""); + Frame = ReplaceStr(Frame, L"__linkproc__ ", L""); + if (DebugAlwaysTrue(!Frame.IsEmpty() && (Frame[1] == L'('))) + { + int Start = Frame.Pos(L"["); + int End = Frame.Pos(L"]"); + if (DebugAlwaysTrue((Start > 1) && (End > Start) && (Frame[Start - 1] == L' '))) + { + // remove absolute address + Frame.Delete(Start - 1, End - Start + 2); + } + } + StackTrace->Strings[Index] = Frame; + } + return StackTrace.release(); +} +#endif + +static TCriticalSection StackTraceCriticalSection; +typedef std::map TStackTraceMap; +static TStackTraceMap StackTraceMap; + +bool AppendExceptionStackTraceAndForget(TStrings *& MoreMessages) +{ + bool Result = false; + + TGuard Guard(StackTraceCriticalSection); + + DWORD Id = ::GetCurrentThreadId(); + TStackTraceMap::iterator Iterator = StackTraceMap.find(Id); + if (Iterator != StackTraceMap.end()) + { + std::unique_ptr OwnedMoreMessages; + if (MoreMessages == nullptr) + { + OwnedMoreMessages.reset(new TStringList()); + MoreMessages = OwnedMoreMessages.get(); + Result = true; + } + if (!MoreMessages->GetText().IsEmpty()) + { + MoreMessages->SetText(MoreMessages->GetText() + "\n"); + } + MoreMessages->SetText(MoreMessages->GetText() + LoadStr(MSG_STACK_TRACE) + "\n"); + MoreMessages->AddStrings(Iterator->second); + + delete Iterator->second; + StackTraceMap.erase(Id); + + OwnedMoreMessages.release(); + } + return Result; +} + +uintptr_t ExceptionMessageDialog(Exception * E, TQueryType Type, + const UnicodeString & MessageFormat, uintptr_t Answers, const UnicodeString & HelpKeyword, + const TMessageParams * Params) +{ +#if 0 + TStrings * MoreMessages = nullptr; + ExtException * EE = dynamic_cast(E); + if (EE != nullptr) + { + MoreMessages = EE->MoreMessages; + } + + UnicodeString Message; + // this is always called from within ExceptionMessage check, + // so it should never fail here + DebugCheck(ExceptionMessageFormatted(E, Message)); + + HelpKeyword = ""; // MergeHelpKeyword(HelpKeyword, GetExceptionHelpKeyword(E)); + + std::unique_ptr OwnedMoreMessages; + if (AppendExceptionStackTraceAndForget(MoreMessages)) + { + OwnedMoreMessages.reset(MoreMessages); + } + + return MoreMessageDialog( + FORMAT(UnicodeString(MessageFormat.IsEmpty() ? UnicodeString(L"%s") : MessageFormat).c_str(), Message.c_str()), + MoreMessages, Type, Answers, HelpKeyword, Params); +#endif + ThrowNotImplemented(3018); + return 0; +} + +uintptr_t FatalExceptionMessageDialog(Exception * E, TQueryType Type, + int SessionReopenTimeout, const UnicodeString & MessageFormat, uintptr_t Answers, + const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ +#if 0 + DebugAssert(FLAGCLEAR(Answers, qaRetry)); + Answers |= qaRetry; + + TQueryButtonAlias Aliases[1]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(RECONNECT_BUTTON); + + TMessageParams AParams; + if (Params != nullptr) + { + AParams = *Params; + } + DebugAssert(AParams.Timeout == 0); + // the condition is de facto excess + if (SessionReopenTimeout > 0) + { + AParams.Timeout = SessionReopenTimeout; + AParams.TimeoutAnswer = qaRetry; + } + DebugAssert(AParams.Aliases == nullptr); + AParams.Aliases = Aliases; + AParams.AliasesCount = LENOF(Aliases); + + return ExceptionMessageDialog(E, Type, MessageFormat, Answers, HelpKeyword, &AParams); +#endif + ThrowNotImplemented(3017); + return 0; +} + +static void DoExceptNotify(TObject * ExceptObj, void * ExceptAddr, + bool OSException, void * BaseOfStack) +{ +#if 0 + if (ExceptObj != nullptr) + { + Exception * E = dynamic_cast(ExceptObj); + if ((E != nullptr) && IsInternalException(E)) // optimization + { + DoExceptionStackTrace(ExceptObj, ExceptAddr, OSException, BaseOfStack); + + TJclStackInfoList * StackInfoList = JclLastExceptStackList(); + + if (DebugAlwaysTrue(StackInfoList != nullptr)) + { + std::unique_ptr StackTrace(StackInfoListToStrings(StackInfoList)); + + DWORD ThreadID = GetCurrentThreadId(); + + TGuard Guard(StackTraceCriticalSection.get()); + + TStackTraceMap::iterator Iterator = StackTraceMap.find(ThreadID); + if (Iterator != StackTraceMap.end()) + { + Iterator->second->Add(L""); + Iterator->second->AddStrings(StackTrace.get()); + } + else + { + StackTraceMap.insert(std::make_pair(ThreadID, StackTrace.release())); + } + + // this chains so that JclLastExceptStackList() returns nullptr the next time + // for the current thread + delete StackInfoList; + } + } + } +#endif + ThrowNotImplemented(3016); +} + +void * BusyStart() +{ + void * Token = nullptr; // reinterpret_cast(Screen->Cursor); +// Screen->Cursor = crHourGlass; + return Token; +} + +void BusyEnd(void * Token) +{ +// Screen->Cursor = reinterpret_cast(Token); +} + + +static uint64_t MainThread = 0; +static TDateTime LastGUIUpdate(0.0); +static double GUIUpdateIntervalFrac = static_cast(OneSecond / 1000 * GUIUpdateInterval); // 1/5 sec +static bool NoGUI = false; + +void SetNoGUI() +{ + NoGUI = true; +} + +bool ProcessGUI(bool Force) +{ + DebugAssert(MainThread != 0); + bool Result = false; + if (MainThread == ::GetCurrentThreadId() && !NoGUI) + { + TDateTime N = Now(); + if (Force || + (double(N) - double(LastGUIUpdate) > GUIUpdateIntervalFrac)) + { + LastGUIUpdate = N; + TODO("GetGlobalFunctions()->ProcessMessages()"); +// Application->ProcessMessages(); + Result = true; + } + } + return Result; +} + +#if 0 +void CopyParamListButton(TButton * Button) +{ + if (!SupportsSplitButton()) + { + MenuButton(Button); + } +} + +const int cpiDefault = -1; +const int cpiConfigure = -2; +const int cpiCustom = -3; +const int cpiSaveSettings = -4; + +void CopyParamListPopup(TRect Rect, TPopupMenu * Menu, + const TCopyParamType & Param, UnicodeString Preset, TNotifyEvent OnClick, + int Options, int CopyParamAttrs, bool SaveSettings) +{ + Menu->Items->Clear(); + + TMenuItem * CustomizeItem = nullptr; + TMenuItem * Item; + + if (FLAGSET(Options, cplCustomize)) + { + Item = new TMenuItem(Menu); + Item->Caption = LoadStr(COPY_PARAM_CUSTOM); + Item->Tag = cpiCustom; + Item->Default = FLAGSET(Options, cplCustomizeDefault); + Item->OnClick = OnClick; + Menu->Items->Add(Item); + CustomizeItem = Item; + } + + if (FLAGSET(Options, cplSaveSettings)) + { + Item = new TMenuItem(Menu); + Item->Caption = LoadStr(COPY_PARAM_SAVE_SETTINGS); + Item->Tag = cpiSaveSettings; + Item->Checked = SaveSettings; + Item->OnClick = OnClick; + Menu->Items->Add(Item); + } + + Item = new TMenuItem(Menu); + Item->Caption = LoadStr(COPY_PARAM_PRESET_HEADER); + Item->Visible = false; + Item->Enabled = false; + Menu->Items->Add(Item); + + bool AnyChecked = false; + Item = new TMenuItem(Menu); + Item->Caption = LoadStr(COPY_PARAM_DEFAULT); + Item->Tag = cpiDefault; + Item->Checked = + Preset.IsEmpty() && (GUIConfiguration->CopyParamPreset[L""] == Param); + AnyChecked = AnyChecked || Item->Checked; + Item->OnClick = OnClick; + Menu->Items->Add(Item); + + TCopyParamType DefaultParam; + const TCopyParamList * CopyParamList = GUIConfiguration->CopyParamList; + for (int i = 0; i < CopyParamList->Count; i++) + { + UnicodeString Name = CopyParamList->Names[i]; + TCopyParamType AParam = GUIConfiguration->CopyParamPreset[Name]; + if (AParam.AnyUsableCopyParam(CopyParamAttrs) || + // This makes "Binary" preset visible, + // as long as we care about transfer mode + ((AParam == DefaultParam) && + FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly) && + FLAGCLEAR(CopyParamAttrs, cpaNoTransferMode))) + { + Item = new TMenuItem(Menu); + Item->Caption = Name; + Item->Tag = i; + Item->Checked = + (Preset == Name) && (AParam == Param); + AnyChecked = AnyChecked || Item->Checked; + Item->OnClick = OnClick; + Menu->Items->Add(Item); + } + } + + if (CustomizeItem != nullptr) + { + CustomizeItem->Checked = !AnyChecked; + } + + Item = new TMenuItem(Menu); + Item->Caption = L"-"; + Menu->Items->Add(Item); + + Item = new TMenuItem(Menu); + Item->Caption = LoadStr(COPY_PARAM_CONFIGURE); + Item->Tag = cpiConfigure; + Item->OnClick = OnClick; + Menu->Items->Add(Item); + + MenuPopup(Menu, Rect, nullptr); +} + +bool CopyParamListPopupClick(TObject * Sender, + TCopyParamType & Param, UnicodeString & Preset, int CopyParamAttrs, + bool * SaveSettings) +{ + TComponent * Item = dynamic_cast(Sender); + DebugAssert(Item != nullptr); + DebugAssert((Item->Tag >= cpiSaveSettings) && (Item->Tag < GUIConfiguration->CopyParamList->Count)); + + bool Result; + if (Item->Tag == cpiConfigure) + { + bool MatchedPreset = (GUIConfiguration->CopyParamPreset[Preset] == Param); + DoPreferencesDialog(pmPresets); + Result = (MatchedPreset && GUIConfiguration->HasCopyParamPreset[Preset]); + if (Result) + { + // For cast, see a comment below + Param = TCopyParamType(GUIConfiguration->CopyParamPreset[Preset]); + } + } + else if (Item->Tag == cpiCustom) + { + Result = DoCopyParamCustomDialog(Param, CopyParamAttrs); + } + else if (Item->Tag == cpiSaveSettings) + { + if (DebugAlwaysTrue(SaveSettings != nullptr)) + { + *SaveSettings = !*SaveSettings; + } + Result = false; + } + else + { + Preset = (Item->Tag >= 0) ? + GUIConfiguration->CopyParamList->Names[Item->Tag] : UnicodeString(); + // The cast strips away the "queue" properties of the TGUICopyParamType + // that are not configurable in presets + Param = TCopyParamType(GUIConfiguration->CopyParamPreset[Preset]); + Result = true; + } + return Result; +} + +TWinInteractiveCustomCommand::TWinInteractiveCustomCommand( + TCustomCommand * ChildCustomCommand, const UnicodeString CustomCommandName) : + TInteractiveCustomCommand(ChildCustomCommand) +{ + FCustomCommandName = StripHotkey(CustomCommandName); +} + +void TWinInteractiveCustomCommand::Prompt( + const UnicodeString & Prompt, UnicodeString & Value) +{ + UnicodeString APrompt = Prompt; + if (APrompt.IsEmpty()) + { + APrompt = FMTLOAD(CUSTOM_COMMANDS_PARAM_PROMPT, FCustomCommandName.c_str()); + } + std::unique_ptr History(CloneStrings(CustomWinConfiguration->History[L"CustomCommandParam"])); + if (InputDialog(FMTLOAD(CUSTOM_COMMANDS_PARAM_TITLE, FCustomCommandName.c_str()), + APrompt, Value, HELP_CUSTOM_COMMAND_PARAM, History.get())) + { + CustomWinConfiguration->History[L"CustomCommandParam"] = History.get(); + } + else + { + Abort(); + } +} + +void TWinInteractiveCustomCommand::Execute( + const UnicodeString & Command, UnicodeString & Value) +{ + // inspired by + // http://forum.codecall.net/topic/72472-execute-a-console-program-and-capture-its-output/ + HANDLE StdOutOutput; + HANDLE StdOutInput; + HANDLE StdInOutput; + HANDLE StdInInput; + SECURITY_ATTRIBUTES SecurityAttributes; + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = nullptr; + SecurityAttributes.bInheritHandle = TRUE; + try + { + if (!CreatePipe(&StdOutOutput, &StdOutInput, &SecurityAttributes, 0)) + { + throw Exception(FMTLOAD(SHELL_PATTERN_ERROR, Command.c_str(), L"out")); + } + else if (!CreatePipe(&StdInOutput, &StdInInput, &SecurityAttributes, 0)) + { + throw Exception(FMTLOAD(SHELL_PATTERN_ERROR, Command.c_str(), L"in")); + } + else + { + STARTUPINFO StartupInfo; + PROCESS_INFORMATION ProcessInformation; + + FillMemory(&StartupInfo, sizeof(StartupInfo), 0); + StartupInfo.cb = sizeof(StartupInfo); + StartupInfo.wShowWindow = SW_HIDE; + StartupInfo.hStdInput = StdInOutput; + StartupInfo.hStdOutput = StdOutInput; + StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + + if (!CreateProcess(nullptr, Command.c_str(), &SecurityAttributes, &SecurityAttributes, + TRUE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &StartupInfo, &ProcessInformation)) + { + throw Exception(FMTLOAD(SHELL_PATTERN_ERROR, Command.c_str(), L"process")); + } + else + { + try + { + // wait until the console program terminated + bool Running = true; + while (Running) + { + switch (WaitForSingleObject(ProcessInformation.hProcess, 200)) + { + case WAIT_TIMEOUT: + Application->ProcessMessages(); + break; + + case WAIT_OBJECT_0: + Running = false; + break; + + default: + throw Exception(FMTLOAD(SHELL_PATTERN_ERROR, Command.c_str(), L"wait")); + } + } + + char Buffer[1024]; + unsigned long Read; + while (PeekNamedPipe(StdOutOutput, nullptr, 0, nullptr, &Read, nullptr) && + (Read > 0)) + + { + if (!ReadFile(StdOutOutput, &Buffer, Read, &Read, nullptr)) + { + throw Exception(FMTLOAD(SHELL_PATTERN_ERROR, Command.c_str(), L"read")); + } + else if (Read > 0) + { + Value += AnsiToString(Buffer, Read); + } + } + + // trim trailing cr/lf + Value = TrimRight(Value); + } + __finally + { + CloseHandle(ProcessInformation.hProcess); + CloseHandle(ProcessInformation.hThread); + } + } + } + } + __finally + { + if (StdOutOutput != INVALID_HANDLE_VALUE) + { + CloseHandle(StdOutOutput); + } + if (StdOutInput != INVALID_HANDLE_VALUE) + { + CloseHandle(StdOutInput); + } + if (StdInOutput != INVALID_HANDLE_VALUE) + { + CloseHandle(StdInOutput); + } + if (StdInInput != INVALID_HANDLE_VALUE) + { + CloseHandle(StdInInput); + } + } +} + +void MenuPopup(TPopupMenu * Menu, TButton * Button) +{ + MenuPopup(Menu, CalculatePopupRect(Button), Button); +} + +void MenuPopup(TObject * Sender, const TPoint & MousePos, bool & Handled) +{ + TControl * Control = dynamic_cast(Sender); + DebugAssert(Control != nullptr); + TPoint Point; + if ((MousePos.x == -1) && (MousePos.y == -1)) + { + Point = Control->ClientToScreen(TPoint(0, 0)); + } + else + { + Point = Control->ClientToScreen(MousePos); + } + TPopupMenu * PopupMenu = (reinterpret_cast(Control))->PopupMenu; + DebugAssert(PopupMenu != nullptr); + TRect Rect(Point, Point); + MenuPopup(PopupMenu, Rect, Control); + Handled = true; +} + +TComponent * GetPopupComponent(TObject * Sender) +{ + TComponent * Item = dynamic_cast(Sender); + DebugAssert(Item != nullptr); + TPopupMenu * PopupMenu = dynamic_cast(Item->Owner); + DebugAssert(PopupMenu != nullptr); + DebugAssert(PopupMenu->PopupComponent != nullptr); + return PopupMenu->PopupComponent; +} + +void MenuButton(TButton * Button) +{ + Button->Images = GlyphsModule->ButtonImages; + Button->ImageIndex = 0; + Button->DisabledImageIndex = 1; + Button->ImageAlignment = iaRight; +} + +TRect CalculatePopupRect(TButton * Button) +{ + TPoint UpPoint = Button->ClientToScreen(TPoint(0, 0)); + TPoint DownPoint = Button->ClientToScreen(TPoint(Button->Width, Button->Height)); + TRect Rect(UpPoint, DownPoint); + // With themes enabled, button are rendered 1 pixel smaller than their actual size + int Offset = UseThemes() ? -1 : 0; + Rect.Inflate(Offset, Offset); + return Rect; +} + +TRect CalculatePopupRect(TControl * Control, TPoint MousePos) +{ + MousePos = Control->ClientToScreen(MousePos); + TRect Rect(MousePos, MousePos); + return Rect; +} + +void FixButtonImage(TButton * Button) +{ + // with themes enabled, button image is by default drawn too high + if (UseThemes()) + { + Button->ImageMargins->Top = 1; + } +} + +void CenterButtonImage(TButton * Button) +{ + // with themes disabled, the text seems to be drawn over the icon, + // so that the padding spaces hide away most of the icon + if (UseThemes()) + { + Button->ImageAlignment = iaCenter; + int ImageWidth = Button->Images->Width; + + std::unique_ptr Canvas(new TControlCanvas()); + Canvas->Control = Button; + + UnicodeString Caption = Button->Caption; + UnicodeString Padding; + while (Canvas->TextWidth(Padding) < ImageWidth) + { + Padding += L" "; + } + if (Button->IsRightToLeft()) + { + Caption = Caption + Padding; + } + else + { + Caption = Padding + Caption; + } + Button->Caption = Caption; + + int CaptionWidth = Canvas->TextWidth(Caption); + // The margins seem to extend the area over which the image is centered, + // so we have to set it to a double of desired padding. + // The original formula is - 2 * ((CaptionWidth / 2) - (ImageWidth / 2) + ScaleByTextHeight(Button, 2)) + // the one below is equivalent, but with reduced rouding. + // Without the change, the rouding caused the space between icon and caption too + // small on 200% zoom. + // Note that (CaptionWidth / 2) - (ImageWidth / 2) + // is approximatelly same as half of caption width before padding. + Button->ImageMargins->Left = -(CaptionWidth - ImageWidth + ScaleByTextHeight(Button, 4)); + } + else + { + // at least do not draw it so near to the edge + Button->ImageMargins->Left = 1; + } +} + +int AdjustLocaleFlag(const UnicodeString & S, TLocaleFlagOverride LocaleFlagOverride, bool Recommended, int On, int Off) +{ + int Result = !S.IsEmpty() && StrToInt(S); + switch (LocaleFlagOverride) + { + default: + case lfoLanguageIfRecommended: + if (!Recommended) + { + Result = Off; + } + break; + + case lfoLanguage: + // noop = as configured in locale + break; + + case lfoAlways: + Result = On; + break; + + case lfoNever: + Result = Off; + break; + } + return Result; +} + +void SetGlobalMinimizeHandler(TCustomForm * /*Form*/, TNotifyEvent OnMinimize) +{ + if (GlobalOnMinimize == nullptr) + { + GlobalOnMinimize = OnMinimize; + } +} + +void ClearGlobalMinimizeHandler(TNotifyEvent OnMinimize) +{ + if (GlobalOnMinimize == OnMinimize) + { + GlobalOnMinimize = nullptr; + } +} + +void CallGlobalMinimizeHandler(TObject * Sender) +{ + Configuration->Usage->Inc(L"OperationMinimizations"); + if (DebugAlwaysTrue(GlobalOnMinimize != nullptr)) + { + GlobalOnMinimize(Sender); + } +} + +static void DoApplicationMinimizeRestore(bool Minimize) +{ + // WORKAROUND + // When main window is hidden (command-line operation), + // we do not want it to be shown by TApplication.Restore, + // so we temporarily detach it from an application. + // Probably not really necessary for minimizing phase, + // but we do it for consistency anyway. + TForm * MainForm = Application->MainForm; + bool RestoreMainForm = false; + if (DebugAlwaysTrue(MainForm != nullptr) && + !MainForm->Visible) + { + SetAppMainForm(nullptr); + RestoreMainForm = true; + } + try + { + if (Minimize) + { + Application->Minimize(); + } + else + { + Application->Restore(); + } + } + __finally + { + if (RestoreMainForm) + { + SetAppMainForm(MainForm); + } + } +} + +void ApplicationMinimize() +{ + DoApplicationMinimizeRestore(true); +} + +void ApplicationRestore() +{ + DoApplicationMinimizeRestore(false); +} + +bool IsApplicationMinimized() +{ + // VCL help recommends handling Application->OnMinimize/OnRestore + // for tracking state, but OnRestore is actually not called + // (OnMinimize is), when app is minimized from e.g. Progress window + bool AppMinimized = IsIconic(Application->Handle); + bool MainFormMinimized = IsIconic(Application->MainFormHandle); + return AppMinimized || MainFormMinimized; +} + +bool HandleMinimizeSysCommand(TMessage & Message) +{ + TWMSysCommand & SysCommand = reinterpret_cast(Message); + uintptr_t Cmd = (SysCommand.CmdType & 0xFFF0); + bool Result = (Cmd == SC_MINIMIZE); + if (Result) + { + ApplicationMinimize(); + SysCommand.Result = 1; + } + return Result; +} +#endif + +void WinInitialize() +{ +// if (JclHookExceptions()) +// { +// JclStackTrackingOptions << stAllModules; +// JclAddExceptNotifier(DoExceptNotify, npFirstChain); +// } +#ifndef __linux__ + SetErrorMode(SEM_FAILCRITICALERRORS); +#endif +// OnApiPath = ::ApiPath; + MainThread = ::GetCurrentThreadId(); + +} + + +void WinFinalize() +{ +// JclRemoveExceptNotifier(DoExceptNotify); +} + +bool InputDialog(const UnicodeString & ACaption, + const UnicodeString & APrompt, UnicodeString & Value, const UnicodeString & HelpKeyword, + TStrings * History, bool PathInput, + TInputDialogInitializeEvent OnInitialize, bool Echo) +{ + bool Result = GetGlobalFunctions()->InputDialog(ACaption, APrompt, Value, HelpKeyword, + History, PathInput, OnInitialize, Echo); + return Result; +} + +uintptr_t MessageDialog(const UnicodeString & Msg, TQueryType Type, + uintptr_t Answers, const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ + DebugUsedParam(HelpKeyword); + uintptr_t Result = GetGlobalFunctions()->MoreMessageDialog(Msg, nullptr, Type, Answers, Params); + return Result; +} + +uintptr_t MessageDialog(intptr_t Ident, TQueryType Type, + uintptr_t Answers, const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ + DebugUsedParam(HelpKeyword); + UnicodeString Msg = LoadStr(Ident); + uintptr_t Result = GetGlobalFunctions()->MoreMessageDialog(Msg, nullptr, Type, Answers, Params); + return Result; +} + +uintptr_t SimpleErrorDialog(const UnicodeString & Msg, const UnicodeString & MoreMessages) +{ + uintptr_t Answers = qaOK; + uintptr_t Result = GetGlobalFunctions()->MoreMessageDialog(Msg, nullptr, qtError, Answers, nullptr); + return Result; +} + +uintptr_t MoreMessageDialog(const UnicodeString & Message, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, + const UnicodeString & HelpKeyword, const TMessageParams * Params) +{ + DebugUsedParam(HelpKeyword); + uintptr_t Result = GetGlobalFunctions()->MoreMessageDialog(Message, MoreMessages, Type, Answers, Params); + return Result; +} + diff --git a/netbox/src/windows/WinInterface.h b/netbox/src/windows/WinInterface.h new file mode 100644 index 000000000..c6908bc0f --- /dev/null +++ b/netbox/src/windows/WinInterface.h @@ -0,0 +1,538 @@ + +#pragma once + +#include +//#include +#include +#include +#include +#include + +#ifdef LOCALINTERFACE +#include +#endif + +#define SITE_ICON 1 +#define SITE_FOLDER_ICON 2 +#define WORKSPACE_ICON 3 + +class TStoredSessionList; +class TConfiguration; +class TTerminal; + +const int mpNeverAskAgainCheck = 0x01; +const int mpAllowContinueOnError = 0x02; + +#define UPLOAD_IF_ANY_SWITCH L"UploadIfAny" +#define UPLOAD_SWITCH L"Upload" +#define JUMPLIST_SWITCH L"JumpList" +#define DESKTOP_SWITCH L"Desktop" +#define SEND_TO_HOOK_SWITCH L"SendToHook" +#define UNSAFE_SWITCH L"Unsafe" +#define NEWINSTANCE_SWICH L"NewInstance" +#define KEYGEN_SWITCH L"KeyGen" +#define KEYGEN_OUTPUT_SWITCH L"Output" +#define KEYGEN_COMMENT_SWITCH L"Comment" +#define KEYGEN_CHANGE_PASSPHRASE_SWITCH L"ChangePassphrase" +#define LOG_SWITCH L"Log" +#define INI_SWITCH L"Ini" + +typedef int TSize; + +struct TMessageParams : public TObject +{ +NB_DISABLE_COPY(TMessageParams) +public: +// TMessageParams(); + explicit TMessageParams(uintptr_t AParams = 0); + void Assign(const TMessageParams * AParams); + + const TQueryButtonAlias * Aliases; + uintptr_t AliasesCount; + uintptr_t Flags; + uintptr_t Params; + uintptr_t Timer; + TQueryParamsTimerEvent TimerEvent; + UnicodeString TimerMessage; + uintptr_t TimerAnswers; + TQueryType TimerQueryType; + uintptr_t Timeout; + uintptr_t TimeoutAnswer; + UnicodeString NeverAskAgainTitle; + uintptr_t NeverAskAgainAnswer; + bool NeverAskAgainCheckedInitially; + bool AllowHelp; + UnicodeString ImageName; + UnicodeString MoreMessagesUrl; + TSize MoreMessagesSize; + UnicodeString CustomCaption; + +private: + inline void Reset(); +}; + +class TCustomScpExplorerForm; +TCustomScpExplorerForm * CreateScpExplorer(); + +void ConfigureInterface(); + +void DoProductLicense(); + +extern const UnicodeString AppName; + +void SetOnForeground(bool OnForeground); +void FlashOnBackground(); + +void ShowExtendedExceptionEx(TTerminal * Terminal, Exception * E); +//void FormHelp(TCustomForm * Form); +void SearchHelp(const UnicodeString & Message); +void MessageWithNoHelp(const UnicodeString & Message); + +class TProgramParams; +bool CheckSafe(TProgramParams * Params); +bool CheckXmlLogParam(TProgramParams * Params); + +#if 0 +UnicodeString GetToolbarsLayoutStr(TComponent * OwnerComponent); +void LoadToolbarsLayoutStr(TComponent * OwnerComponent, UnicodeString LayoutStr); + +namespace Tb2item { class TTBCustomItem; } +void AddMenuSeparator(Tb2item::TTBCustomItem * Menu); +void AddMenuLabel(Tb2item::TTBCustomItem * Menu, const UnicodeString & Label); +#endif + +// windows\WinHelp.cpp +void InitializeWinHelp(); +void FinalizeWinHelp(); + +// windows\WinInterface.cpp +uintptr_t MessageDialog(const UnicodeString & Msg, TQueryType Type, + uintptr_t Answers, const UnicodeString & HelpKeyword = HELP_NONE, const TMessageParams * Params = nullptr); +uintptr_t MessageDialog(intptr_t Ident, TQueryType Type, + uintptr_t Answers, const UnicodeString & HelpKeyword = HELP_NONE, const TMessageParams * Params = nullptr); +uintptr_t SimpleErrorDialog(const UnicodeString & Msg, const UnicodeString & MoreMessages = L""); + +uintptr_t MoreMessageDialog(const UnicodeString & Message, + TStrings * MoreMessages, TQueryType Type, uintptr_t Answers, + const UnicodeString & HelpKeyword, const TMessageParams * Params = nullptr); + +uintptr_t ExceptionMessageDialog(Exception * E, TQueryType Type, + const UnicodeString & MessageFormat = L"", uintptr_t Answers = qaOK, + const UnicodeString & HelpKeyword = HELP_NONE, const TMessageParams * Params = nullptr); +uintptr_t FatalExceptionMessageDialog(Exception * E, TQueryType Type, + intptr_t SessionReopenTimeout, const UnicodeString & MessageFormat = L"", uintptr_t Answers = qaOK, + const UnicodeString & HelpKeyword = HELP_NONE, const TMessageParams * Params = nullptr); + +// forms\Custom.cpp +TSessionData * DoSaveSession(TSessionData * SessionData, + TSessionData * OriginalSession, bool ForceDialog, + TStrings * AdditionalFolders); +void SessionNameValidate(const UnicodeString & Text, + const UnicodeString & OriginalName); +bool DoSaveWorkspaceDialog(UnicodeString & WorkspaceName, + bool * SavePasswords, bool NotRecommendedSavingPasswords, + bool & CreateShortcut, bool & EnableAutoSave); +class TShortCuts; +bool DoShortCutDialog(TShortCut & ShortCut, + const TShortCuts & ShortCuts, UnicodeString HelpKeyword); + +// windows\UserInterface.cpp +bool DoMasterPasswordDialog(); +bool DoChangeMasterPasswordDialog(UnicodeString & NewPassword); + +// windows\WinMain.cpp +int Execute(); +void GetLoginData(UnicodeString SessionName, TOptions * Options, + TObjectList * DataList, UnicodeString & DownloadFile, bool NeedSession); + +bool InputDialog(const UnicodeString & ACaption, + const UnicodeString & APrompt, UnicodeString & Value, const UnicodeString & HelpKeyword = HELP_NONE, + TStrings * History = nullptr, bool PathInput = false, + TInputDialogInitializeEvent OnInitialize = nullptr, bool Echo = true); + +// forms\About.cpp +struct TRegistration +{ + bool Registered; + UnicodeString Subject; + int Licenses; + UnicodeString ProductId; + bool NeverExpires; + TDateTime Expiration; + bool EduLicense; + TNotifyEvent OnRegistrationLink; +}; +void DoAboutDialog(TConfiguration * Configuration, + bool AllowLicense, TRegistration * Registration); +void DoAboutDialog(TConfiguration * Configuration); + +// forms\Cleanup.cpp +bool DoCleanupDialog(TStoredSessionList *SessionList, + TConfiguration *Configuration); + +// forms\Console.cpp +void DoConsoleDialog(TTerminal * Terminal, + const UnicodeString & Command = L"", const TStrings * Log = nullptr); + +// forms\Copy.cpp +const int coTemp = 0x001; +const int coDisableQueue = 0x002; +const int coDisableDirectory = 0x008; // not used anymore +const int coDoNotShowAgain = 0x020; +const int coDisableSaveSettings = 0x040; // not used anymore +const int coDoNotUsePresets = 0x080; +const int coAllowRemoteTransfer = 0x100; +const int coNoQueue = 0x200; +const int coNoQueueIndividually = 0x400; +const int coShortCutHint = 0x800; +const int cooDoNotShowAgain = 0x01; +const int cooRemoteTransfer = 0x02; +const int cooSaveSettings = 0x04; + +const int coTempTransfer = 0x08; +const int coDisableNewerOnly = 0x10; + +bool DoCopyDialog(bool ToRemote, + bool Move, TStrings * FileList, UnicodeString & TargetDirectory, + TGUICopyParamType * Params, int Options, int CopyParamAttrs, + int * OutputOptions); + +// forms\CreateDirectory.cpp +bool DoCreateDirectoryDialog(UnicodeString & Directory, + TRemoteProperties * Properties, int AllowedChanges, bool & SaveSettings); + +// forms\ImportSessions.cpp +bool DoImportSessionsDialog(TList * Imported); + +// forms\License.cpp +enum TLicense { lcNoLicense = -1, lcWinScp, lcExpat }; +void DoLicenseDialog(TLicense License); + +bool DoLoginDialog(TStoredSessionList * SessionList, TList * DataList); + + // forms\SiteAdvanced.cpp +bool DoSiteAdvancedDialog(TSessionData * SessionData); + +// forms\OpenDirectory.cpp +enum TOpenDirectoryMode { odBrowse, odAddBookmark }; +bool DoOpenDirectoryDialog(TOpenDirectoryMode Mode, TOperationSide Side, + UnicodeString & Directory, TStrings * Directories, TTerminal * Terminal, + bool AllowSwitch); + +// forms\LocatinoProfiles.cpp +bool LocationProfilesDialog(TOpenDirectoryMode Mode, + TOperationSide Side, UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TStrings * LocalDirectories, TStrings * RemoteDirectories, TTerminal * Terminal); + +// forms\Preferences.cpp +enum TPreferencesMode { pmDefault, pmEditor, pmCustomCommands, + pmQueue, pmLogging, pmUpdates, pmPresets, pmEditors, pmCommander, + pmEditorInternal }; +struct TCopyParamRuleData; +struct TPreferencesDialogData +{ + TCopyParamRuleData * CopyParamRuleData; +}; +bool DoPreferencesDialog(TPreferencesMode APreferencesMode, + TPreferencesDialogData * DialogData = nullptr); + +// forms\CustomCommand.cpp +class TCustomCommandList; +class TCustomCommandType; +class TShortCuts; +enum TCustomCommandsMode { ccmAdd, ccmEdit, ccmAdHoc }; +const int ccoDisableRemote = 0x01; +//typedef void (__closure *TCustomCommandValidate) +// (const TCustomCommandType & Command); +DEFINE_CALLBACK_TYPE1(TCustomCommandValidateEvent, void, + const TCustomCommandType & /*Command*/); +bool DoCustomCommandDialog(TCustomCommandType & Command, + const TCustomCommandList * CustomCommandList, + TCustomCommandsMode Mode, int Options, TCustomCommandValidateEvent OnValidate, + const TShortCuts * ShortCuts); + +// forms\CopyParamPreset.cpp +class TCopyParamList; +enum TCopyParamPresetMode { cpmAdd, cpmAddCurrent, cpmEdit, cpmDuplicate }; +bool DoCopyParamPresetDialog(TCopyParamList * CopyParamList, + int & Index, TCopyParamPresetMode Mode, TCopyParamRuleData * CurrentRuleData, + const TCopyParamType & DefaultCopyParams); + +// forms\CopyParamCsutom.cpp +bool DoCopyParamCustomDialog(TCopyParamType & CopyParam, + int CopyParamAttrs); + +// forms\Properties.cpp +class TRemoteProperties; +class TRemoteTokenList; +struct TCalculateSizeStats; +const int cpMode = 0x01; +const int cpOwner = 0x02; +const int cpGroup = 0x04; +//typedef void (__closure *TCalculateSizeEvent) +// (TStrings * FileList, __int64 & Size, TCalculateSizeStats & Stats, +// bool & Close); +DEFINE_CALLBACK_TYPE4(TCalculateSizeEvent, void, + TStrings * /*FileList*/, int64_t & /*Size*/, TCalculateSizeStats & /*Stats*/, + bool & /*Close*/); +//typedef void (__closure *TCalculatedChecksumCallbackEvent)( +// const UnicodeString & FileName, const UnicodeString & Alg, const UnicodeString & Hash); +DEFINE_CALLBACK_TYPE3(TCalculatedChecksumCallbackEvent, void, + const UnicodeString & /*FileName*/, const UnicodeString & /*Alg*/, const UnicodeString & /*Hash*/); +//typedef void (__closure *TCalculateChecksumEvent) +// (const UnicodeString & Alg, TStrings * FileList, +// TCalculatedChecksumCallbackEvent OnCalculatedChecksum, bool & Close); +DEFINE_CALLBACK_TYPE4(TCalculateChecksumEvent, void, + const UnicodeString & /*Alg*/, TStrings * /*FileList*/, + TCalculatedChecksumCallbackEvent /*OnCalculatedChecksum*/, bool & /*Close*/); +bool DoPropertiesDialog(TStrings * FileList, + const UnicodeString & Directory, const TRemoteTokenList * GroupList, + const TRemoteTokenList * UserList, TStrings * ChecksumAlgs, + TRemoteProperties * Properties, + int AllowedChanges, bool UserGroupByID, TCalculateSizeEvent OnCalculateSize, + TCalculateChecksumEvent OnCalculateChecksum); + +bool DoRemoteMoveDialog(bool Multi, UnicodeString & Target, UnicodeString & FileMask); +enum TDirectRemoteCopy { drcDisallow, drcAllow, drcConfirmCommandSession }; +bool DoRemoteCopyDialog(TStrings * Sessions, TStrings * Directories, + TDirectRemoteCopy AllowDirectCopy, bool Multi, void *& Session, + UnicodeString & Target, UnicodeString & FileMask, bool & DirectCopy); + +// forms\SelectMask.cpp +#ifdef CustomdirviewHPP +bool DoSelectMaskDialog(TCustomDirView * Parent, bool Select, + TFileFilter * Filter, TConfiguration * Configuration); +bool DoFilterMaskDialog(TCustomDirView * Parent, + TFileFilter * Filter); +#endif + +// forms\EditMask.cpp +bool DoEditMaskDialog(TFileMasks & Mask); + +const int spDelete = 0x01; +const int spNoConfirmation = 0x02; +const int spExistingOnly = 0x04; +const int spPreviewChanges = 0x40; // not used by core +const int spTimestamp = 0x100; +const int spNotByTime = 0x200; +const int spBySize = 0x400; +const int spSelectedOnly = 0x800; +const int spMirror = 0x1000; + +// forms\Synchronize.cpp +const int soDoNotUsePresets = 0x01; +const int soNoMinimize = 0x02; +const int soAllowSelectedOnly = 0x04; +//typedef void (__closure *TGetSynchronizeOptionsEvent) +// (int Params, TSynchronizeOptions & Options); +DEFINE_CALLBACK_TYPE2(TGetSynchronizeOptionsEvent, void, + intptr_t /*Params*/, TSynchronizeOptions & /*Options*/); +//typedef void (__closure *TFeedSynchronizeError) +// (const UnicodeString & Message, TStrings * MoreMessages, TQueryType Type, +// const UnicodeString & HelpKeyword); +DEFINE_CALLBACK_TYPE4(TFeedSynchronizeErrorEvent, void, + const UnicodeString & /*Message*/, TStrings * /*MoreMessages*/, TQueryType /*Type*/, + const UnicodeString & /*HelpKeyword*/); +bool DoSynchronizeDialog(TSynchronizeParamType & Params, + const TCopyParamType * CopyParams, TSynchronizeStartStopEvent OnStartStop, + bool & SaveSettings, int Options, int CopyParamAttrs, + TGetSynchronizeOptionsEvent OnGetOptions, + TFeedSynchronizeErrorEvent & OnFeedSynchronizeError, + bool Start); + +// forms\FullSynchronize.cpp +struct TUsableCopyParamAttrs; +enum TSynchronizeMode { smRemote, smLocal, smBoth }; +const int fsoDisableTimestamp = 0x01; +const int fsoDoNotUsePresets = 0x02; +const int fsoAllowSelectedOnly = 0x04; +bool DoFullSynchronizeDialog(TSynchronizeMode & Mode, intptr_t & Params, + UnicodeString & LocalDirectory, UnicodeString & RemoteDirectory, + TCopyParamType * CopyParams, bool & SaveSettings, bool & SaveMode, + intptr_t Options, const TUsableCopyParamAttrs & CopyParamAttrs); + +// forms\SynchronizeChecklist.cpp +class TSynchronizeChecklist; +//typedef void (__closure *TCustomCommandMenuEvent) +// (TAction * Action, TStrings * LocalFileList, TStrings * RemoteFileList); +DEFINE_CALLBACK_TYPE3(TCustomCommandMenuEvent, void, + void * /*Action*/, TStrings * /*LocalFileList*/, TStrings * /*RemoteFileList*/); +bool DoSynchronizeChecklistDialog(TSynchronizeChecklist * Checklist, + TSynchronizeMode Mode, intptr_t Params, + const UnicodeString & LocalDirectory, const UnicodeString & RemoteDirectory, + TCustomCommandMenuEvent OnCustomCommandMenu); + +// forms\Editor.cpp +//typedef void (__closure *TFileClosedEvent) +// (TObject * Sender, bool Forced); +#if 0 +DEFINE_CALLBACK_TYPE2(TFileClosedEvent, void, + TObject * /*Sender* /, bool /*Forced*/); +DEFINE_CALLBACK_TYPE2(TAnyModifiedEvent, void, + TObject * /*Sender* /, bool & /*Modified*/); +TForm * ShowEditorForm(const UnicodeString FileName, TCustomForm * ParentForm, + TNotifyEvent OnFileChanged, TNotifyEvent OnFileReload, TFileClosedEvent OnClose, + TNotifyEvent OnSaveAll, TAnyModifiedEvent OnAnyModified, + const UnicodeString Caption, bool StandaloneEditor, TColor Color); +void ReconfigureEditorForm(TForm * Form); +void EditorFormFileUploadComplete(TForm * Form); +void EditorFormFileSave(TForm * Form); +bool IsEditorFormModified(TForm * Form); +#endif + +bool DoSymlinkDialog(UnicodeString & FileName, UnicodeString & PointTo, + TOperationSide Side, bool & SymbolicLink, bool Edit, bool AllowSymbolic); + +// forms\FileSystemInfo.cpp +struct TSpaceAvailable; +struct TFileSystemInfo; +struct TSessionInfo; +//typedef void (__closure *TGetSpaceAvailable) +// (const UnicodeString Path, TSpaceAvailable & ASpaceAvailable, bool & Close); +DEFINE_CALLBACK_TYPE3(TGetSpaceAvailableEvent, void, + const UnicodeString & /*Path*/, TSpaceAvailable & /*ASpaceAvailable*/, bool & /*Close*/); +void DoFileSystemInfoDialog( + const TSessionInfo & SessionInfo, const TFileSystemInfo & FileSystemInfo, + const UnicodeString & SpaceAvailablePath, TGetSpaceAvailableEvent OnGetSpaceAvailable); + +//moved to FarInterface.h +#if 0 +// forms\MessageDlg.cpp +void AnswerNameAndCaption( + uintptr_t Answer, UnicodeString & Name, UnicodeString & Caption); +TFarDialog * CreateMoreMessageDialog(const UnicodeString & Msg, + TStrings * MoreMessages, TMsgDlgType DlgType, uintptr_t Answers, + const TQueryButtonAlias * Aliases, uintptr_t AliasesCount, + uintptr_t TimeoutAnswer, TFarButton ** TimeoutButton, + const UnicodeString & ImageName, const UnicodeString & NeverAskAgainCaption, + const UnicodeString & MoreMessagesUrl, TSize MoreMessagesSize, + const UnicodeString & CustomCaption); +TFarDialog * CreateMoreMessageDialogEx(const UnicodeString & Message, TStrings * MoreMessages, + TQueryType Type, uintptr_t Answers, UnicodeString HelpKeyword, const TMessageParams * Params); +uintptr_t ExecuteMessageDialog(TFarDialog * Dialog, uintptr_t Answers, const TMessageParams * Params); +void InsertPanelToMessageDialog(TFarDialog * Form, TPanel * Panel); +void NavigateMessageDialogToUrl(TFarDialog * Form, const UnicodeString & Url); +#endif + +// windows\Console.cpp +enum TConsoleMode { cmNone, cmScripting, cmHelp, cmBatchSettings, cmKeyGen }; +int Console(TConsoleMode Mode); + +// forms\EditorPreferences.cpp +enum TEditorPreferencesMode { epmAdd, epmEdit, epmAdHoc }; +class TEditorData; +bool DoEditorPreferencesDialog(TEditorData * Editor, + bool & Remember, TEditorPreferencesMode Mode, bool MayRemote); + +// forms\Find.cpp +//typedef void (__closure *TFindEvent) +// (UnicodeString Directory, const TFileMasks & FileMask, +// TFileFoundEvent OnFileFound, TFindingFileEvent OnFindingFile); +DEFINE_CALLBACK_TYPE4(TFindEvent, void, + const UnicodeString & /*Directory*/, const TFileMasks & /*FileMask*/, + TFileFoundEvent /*OnFileFound*/, TFindingFileEvent /*OnFindingFile*/); +bool DoFileFindDialog(UnicodeString Directory, + TFindEvent OnFind, UnicodeString & Path); + +// forms\GenerateUrl.cpp +void DoGenerateUrlDialog(TSessionData * Data, TStrings * Paths); + +#if 0 +void CopyParamListButton(TButton * Button); +const int cplNone = 0x00; +const int cplCustomize = 0x01; +const int cplCustomizeDefault = 0x02; +const int cplSaveSettings = 0x04; +void CopyParamListPopup(TRect R, TPopupMenu * Menu, + const TCopyParamType & Param, UnicodeString Preset, TNotifyEvent OnClick, + int Options, int CopyParamAttrs, bool SaveSettings = false); +bool CopyParamListPopupClick(TObject * Sender, + TCopyParamType & Param, UnicodeString & Preset, int CopyParamAttrs, + bool * SaveSettings = nullptr); + +void MenuPopup(TPopupMenu * Menu, TRect Rect, TComponent * PopupComponent); +void MenuPopup(TPopupMenu * Menu, TButton * Button); +void MenuPopup(TObject * Sender, const TPoint & MousePos, bool & Handled); +void MenuButton(TButton * Button); +TComponent * GetPopupComponent(TObject * Sender); +TRect CalculatePopupRect(TButton * Button); +TRect CalculatePopupRect(TControl * Control, TPoint MousePos); + +typedef void (__closure *TColorChangeEvent) + (TColor Color); +TPopupMenu * CreateSessionColorPopupMenu(TColor Color, + TColorChangeEvent OnColorChange); +void CreateSessionColorMenu(TComponent * AOwner, TColor Color, + TColorChangeEvent OnColorChange); +void CreateEditorBackgroundColorMenu(TComponent * AOwner, TColor Color, + TColorChangeEvent OnColorChange); +TPopupMenu * CreateColorPopupMenu(TColor Color, + TColorChangeEvent OnColorChange); + +void FixButtonImage(TButton * Button); +void CenterButtonImage(TButton * Button); + +void UpgradeSpeedButton(TSpeedButton * Button); + +int AdjustLocaleFlag(const UnicodeString & S, TLocaleFlagOverride LocaleFlagOverride, bool Recommended, int On, int Off); + +void SetGlobalMinimizeHandler(TCustomForm * Form, TNotifyEvent OnMinimize); +void ClearGlobalMinimizeHandler(TNotifyEvent OnMinimize); +void CallGlobalMinimizeHandler(TObject * Sender); +bool IsApplicationMinimized(); +void ApplicationMinimize(); +void ApplicationRestore(); +bool HandleMinimizeSysCommand(TMessage & Message); +void WinInitialize(); +void WinFinalize(); +#endif + +void ShowNotification(TTerminal * Terminal, const UnicodeString & Str, + TQueryType Type); +#if 0 +void InitializeShortCutCombo(TComboBox * ComboBox, + const TShortCuts & ShortCuts); +void SetShortCutCombo(TComboBox * ComboBox, TShortCut Value); +TShortCut GetShortCutCombo(TComboBox * ComboBox); +bool IsCustomShortCut(TShortCut ShortCut); + +class TAnimationsModule; +TAnimationsModule * GetAnimationsModule(); +void ReleaseAnimationsModule(); +#endif +#ifdef _DEBUG +void ForceTracing(); +#endif + +#define HIDDEN_WINDOW_NAME L"WinSCPHiddenWindow2" + +struct TCopyDataMessage +{ + enum { CommandCanCommandLine, CommandCommandLine }; + static const uintptr_t Version1 = 1; + + uintptr_t Version; + uintptr_t Command; + + union + { + wchar_t CommandLine[10240]; + }; +}; + +class TWinInteractiveCustomCommand : public TInteractiveCustomCommand +{ +public: + TWinInteractiveCustomCommand(TCustomCommand * ChildCustomCommand, + const UnicodeString & CustomCommandName); + +protected: + virtual void Prompt(const UnicodeString & Prompt, + UnicodeString & Value); + virtual void Execute(const UnicodeString & Command, + UnicodeString & Value); + +private: + UnicodeString FCustomCommandName; +}; +